Centralizes time formatting with timezone support
Addresses inconsistent time formatting and lack of timezone handling throughout the application by introducing a `TimeFormatter` utility. This class centralizes time formatting logic, providing timezone conversion (defaults to Europe/Copenhagen) and support for both 12-hour and 24-hour formats, configurable via `CalendarConfig`. It also updates event rendering to utilize the new `TimeFormatter` for consistent time displays.
This commit is contained in:
parent
c07d83d86f
commit
8b96376d1f
7 changed files with 489 additions and 199 deletions
0
docs/implementation-todo.md
Normal file
0
docs/implementation-todo.md
Normal file
216
docs/timeformatter-specification.md
Normal file
216
docs/timeformatter-specification.md
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
# TimeFormatter Specification
|
||||
|
||||
## Problem
|
||||
- Alle events i systemet/mock JSON er i Zulu tid (UTC)
|
||||
- Nuværende formatTime() metoder håndterer ikke timezone konvertering
|
||||
- Ingen support for 12/24 timers format baseret på configuration
|
||||
- Duplikeret formattering logik flere steder
|
||||
|
||||
## Løsning: Centraliseret TimeFormatter
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **Timezone Support**
|
||||
- Konverter fra UTC/Zulu til brugerens lokale timezone
|
||||
- Respekter browser timezone settings
|
||||
- Håndter sommertid korrekt
|
||||
|
||||
2. **12/24 Timer Format**
|
||||
- Læs format præference fra CalendarConfig
|
||||
- Support både 12-timer (AM/PM) og 24-timer format
|
||||
- Gør det konfigurerbart per bruger
|
||||
|
||||
3. **Centralisering**
|
||||
- Én enkelt kilde til al tidsformattering
|
||||
- Konsistent formattering gennem hele applikationen
|
||||
- Nem at teste og vedligeholde
|
||||
|
||||
### Proposed Implementation
|
||||
|
||||
```typescript
|
||||
// src/utils/TimeFormatter.ts
|
||||
export class TimeFormatter {
|
||||
private static use24HourFormat: boolean = false;
|
||||
private static userTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
/**
|
||||
* Initialize formatter with user preferences
|
||||
*/
|
||||
static initialize(config: { use24Hour: boolean; timezone?: string }) {
|
||||
this.use24HourFormat = config.use24Hour;
|
||||
if (config.timezone) {
|
||||
this.userTimezone = config.timezone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format UTC/Zulu time to local time with correct format
|
||||
* @param input - UTC Date, ISO string, or minutes from midnight
|
||||
* @returns Formatted time string in user's preferred format
|
||||
*/
|
||||
static formatTime(input: Date | string | number): string {
|
||||
let date: Date;
|
||||
|
||||
if (typeof input === 'number') {
|
||||
// Minutes from midnight - create date for today
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setMinutes(input);
|
||||
date = today;
|
||||
} else if (typeof input === 'string') {
|
||||
// ISO string - parse as UTC
|
||||
date = new Date(input);
|
||||
} else {
|
||||
date = input;
|
||||
}
|
||||
|
||||
// Convert to local timezone
|
||||
const localDate = this.convertToLocalTime(date);
|
||||
|
||||
// Format based on user preference
|
||||
if (this.use24HourFormat) {
|
||||
return this.format24Hour(localDate);
|
||||
} else {
|
||||
return this.format12Hour(localDate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UTC date to local timezone
|
||||
*/
|
||||
private static convertToLocalTime(utcDate: Date): Date {
|
||||
// Use Intl.DateTimeFormat for proper timezone conversion
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: this.userTimezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(utcDate);
|
||||
const dateParts: any = {};
|
||||
|
||||
parts.forEach(part => {
|
||||
dateParts[part.type] = part.value;
|
||||
});
|
||||
|
||||
return new Date(
|
||||
parseInt(dateParts.year),
|
||||
parseInt(dateParts.month) - 1,
|
||||
parseInt(dateParts.day),
|
||||
parseInt(dateParts.hour),
|
||||
parseInt(dateParts.minute),
|
||||
parseInt(dateParts.second)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in 24-hour format (HH:mm)
|
||||
*/
|
||||
private static format24Hour(date: Date): string {
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in 12-hour format (h:mm AM/PM)
|
||||
*/
|
||||
private static format12Hour(date: Date): string {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const period = hours >= 12 ? 'PM' : 'AM';
|
||||
const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
|
||||
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date and time together
|
||||
*/
|
||||
static formatDateTime(date: Date | string): string {
|
||||
const localDate = typeof date === 'string' ? new Date(date) : date;
|
||||
const convertedDate = this.convertToLocalTime(localDate);
|
||||
|
||||
const dateStr = convertedDate.toLocaleDateString();
|
||||
const timeStr = this.formatTime(convertedDate);
|
||||
|
||||
return `${dateStr} ${timeStr}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offset in hours
|
||||
*/
|
||||
static getTimezoneOffset(): number {
|
||||
return new Date().getTimezoneOffset() / -60;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Integration
|
||||
|
||||
```typescript
|
||||
// src/core/CalendarConfig.ts
|
||||
export interface TimeFormatSettings {
|
||||
use24HourFormat: boolean;
|
||||
timezone?: string; // Optional override, defaults to browser timezone
|
||||
}
|
||||
|
||||
// Add to CalendarConfig
|
||||
getTimeFormatSettings(): TimeFormatSettings {
|
||||
return {
|
||||
use24HourFormat: this.config.use24HourFormat ?? false,
|
||||
timezone: this.config.timezone // undefined = use browser default
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```typescript
|
||||
// Initialize on app start
|
||||
TimeFormatter.initialize({
|
||||
use24Hour: calendarConfig.getTimeFormatSettings().use24HourFormat,
|
||||
timezone: calendarConfig.getTimeFormatSettings().timezone
|
||||
});
|
||||
|
||||
// Format UTC event time to local
|
||||
const utcEventTime = "2024-01-15T14:30:00Z"; // 2:30 PM UTC
|
||||
const localTime = TimeFormatter.formatTime(utcEventTime);
|
||||
// Result (Copenhagen, 24h): "15:30"
|
||||
// Result (Copenhagen, 12h): "3:30 PM"
|
||||
// Result (New York, 12h): "9:30 AM"
|
||||
|
||||
// Format minutes from midnight
|
||||
const minutes = 570; // 9:30 AM
|
||||
const formatted = TimeFormatter.formatTime(minutes);
|
||||
// Result (24h): "09:30"
|
||||
// Result (12h): "9:30 AM"
|
||||
```
|
||||
|
||||
### Testing Considerations
|
||||
|
||||
1. Test timezone conversions:
|
||||
- UTC to Copenhagen (UTC+1/+2)
|
||||
- UTC to New York (UTC-5/-4)
|
||||
- UTC to Tokyo (UTC+9)
|
||||
|
||||
2. Test daylight saving transitions
|
||||
|
||||
3. Test 12/24 hour format switching
|
||||
|
||||
4. Test edge cases:
|
||||
- Midnight (00:00 / 12:00 AM)
|
||||
- Noon (12:00 / 12:00 PM)
|
||||
- Events spanning multiple days
|
||||
|
||||
### Migration Plan
|
||||
|
||||
1. Implement TimeFormatter class
|
||||
2. Add configuration options to CalendarConfig
|
||||
3. Replace all existing formatTime() calls
|
||||
4. Update mock data loader to handle UTC properly
|
||||
5. Test thoroughly with different timezones
|
||||
Loading…
Add table
Add a link
Reference in a new issue