feat: Add timezone architecture for consistent date/time handling

- Create dateUtils.ts with helpers for UTC conversion and timezone display
- Add TimezoneSerializerMixin to include business_timezone in API responses
- Update GeneralSettings timezone dropdown with IANA identifiers
- Apply timezone mixin to Event, TimeBlock, and field mobile serializers
- Document timezone architecture in CLAUDE.md

All times stored in UTC, converted for display based on business timezone.
If business_timezone is null, uses user's local timezone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-07 19:39:36 -05:00
parent 897a336d0b
commit f4332153f4
10 changed files with 783 additions and 43 deletions

116
CLAUDE.md
View File

@@ -61,6 +61,122 @@ docker compose -f docker-compose.local.yml exec django python manage.py <command
| `frontend/src/api/client.ts` | Axios API client |
| `frontend/src/types.ts` | TypeScript interfaces |
| `frontend/src/i18n/locales/en.json` | Translations |
| `frontend/src/utils/dateUtils.ts` | Date formatting utilities |
## Timezone Architecture (CRITICAL)
All date/time handling follows this architecture to ensure consistency across timezones.
### Core Principles
1. **Database**: All times stored in UTC
2. **API Communication**: Always use UTC (both directions)
3. **API Responses**: Include `business_timezone` field
4. **Frontend Display**: Convert UTC based on `business_timezone`
- If `business_timezone` is set → display in that timezone
- If `business_timezone` is null/blank → display in user's local timezone
### Data Flow
```
FRONTEND (User in Eastern Time selects "Dec 8, 2:00 PM")
Convert to UTC: "2024-12-08T19:00:00Z"
Send to API (always UTC)
DATABASE (stores UTC)
API RESPONSE:
{
"start_time": "2024-12-08T19:00:00Z", // Always UTC
"business_timezone": "America/Denver" // IANA timezone (or null for local)
}
FRONTEND CONVERTS:
- If business_timezone set: UTC → Mountain Time → "Dec 8, 12:00 PM MST"
- If business_timezone null: UTC → User local → "Dec 8, 2:00 PM EST"
```
### Frontend Helper Functions
Located in `frontend/src/utils/dateUtils.ts`:
```typescript
import {
toUTC,
fromUTC,
formatForDisplay,
formatDateForDisplay,
getDisplayTimezone,
} from '../utils/dateUtils';
// SENDING TO API - Always convert to UTC
const apiPayload = {
start_time: toUTC(selectedDateTime), // "2024-12-08T19:00:00Z"
};
// RECEIVING FROM API - Convert for display
const displayTime = formatForDisplay(
response.start_time, // UTC from API
response.business_timezone // "America/Denver" or null
);
// Result: "Dec 8, 2024 12:00 PM" (in business or local timezone)
// DATE-ONLY fields (time blocks)
const displayDate = formatDateForDisplay(
response.start_date,
response.business_timezone
);
```
### API Response Requirements
All endpoints returning date/time data MUST include:
```python
# In serializers or views
{
"start_time": "2024-12-08T19:00:00Z",
"business_timezone": business.timezone, # "America/Denver" or None
}
```
### Backend Serializer Mixin
Use `TimezoneSerializerMixin` from `core/mixins.py` to automatically add the timezone field:
```python
from core.mixins import TimezoneSerializerMixin
class EventSerializer(TimezoneSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Event
fields = [
'id', 'start_time', 'end_time',
# ... other fields ...
'business_timezone', # Provided by mixin
]
read_only_fields = ['business_timezone']
```
The mixin automatically retrieves the timezone from the tenant context.
- Returns the IANA timezone string if set (e.g., "America/Denver")
- Returns `null` if not set (frontend uses user's local timezone)
### Common Mistakes to Avoid
```typescript
// BAD - Uses browser local time, not UTC
date.toISOString().split('T')[0]
// BAD - Doesn't respect business timezone setting
new Date(utcString).toLocaleString()
// GOOD - Use helper functions
toUTC(date) // For API requests
formatForDisplay(utcString, businessTimezone) // For displaying
```
## Key Django Apps