/** * TimeFormatter - Centralized time formatting with timezone support * * Handles conversion from UTC/Zulu time to configured timezone (default: Europe/Copenhagen) * Supports both 12-hour and 24-hour format configuration * * All events in the system are stored in UTC and must be converted to local timezone */ export interface TimeFormatSettings { timezone: string; use24HourFormat: boolean; locale: string; dateFormat: 'locale' | 'technical'; showSeconds: boolean; } export class TimeFormatter { private static settings: TimeFormatSettings = { timezone: 'Europe/Copenhagen', // Default to Denmark use24HourFormat: true, // 24-hour format standard in Denmark locale: 'da-DK', // Danish locale dateFormat: 'technical', // Use technical format yyyy-mm-dd hh:mm:ss showSeconds: false // Don't show seconds by default }; /** * Configure time formatting settings */ static configure(settings: Partial): void { TimeFormatter.settings = { ...TimeFormatter.settings, ...settings }; } /** * Get current time format settings */ static getSettings(): TimeFormatSettings { return { ...TimeFormatter.settings }; } /** * Convert UTC date to configured timezone * @param utcDate - Date in UTC (or assumed to be UTC) * @returns Date object adjusted to configured timezone */ static convertToLocalTime(utcDate: Date): Date { // Create a new date to avoid mutating the original const localDate = new Date(utcDate); // If the date doesn't have timezone info, treat it as UTC // This handles cases where mock data doesn't have 'Z' suffix if (!utcDate.toISOString().endsWith('Z') && utcDate.getTimezoneOffset() === new Date().getTimezoneOffset()) { // Adjust for the fact that we're treating local time as UTC localDate.setMinutes(localDate.getMinutes() + localDate.getTimezoneOffset()); } return localDate; } /** * Get timezone offset for configured timezone * @param date - Reference date for calculating offset (handles DST) * @returns Offset in minutes */ static getTimezoneOffset(date: Date = new Date()): number { const utc = new Date(date.getTime() + (date.getTimezoneOffset() * 60000)); const targetTime = new Date(utc.toLocaleString('en-US', { timeZone: TimeFormatter.settings.timezone })); return (targetTime.getTime() - utc.getTime()) / 60000; } /** * Format time in 12-hour format * @param date - Date to format * @returns Formatted time string (e.g., "9:00 AM") */ static format12Hour(date: Date): string { const localDate = TimeFormatter.convertToLocalTime(date); return localDate.toLocaleTimeString(TimeFormatter.settings.locale, { timeZone: TimeFormatter.settings.timezone, hour: 'numeric', minute: '2-digit', hour12: true }); } /** * Format time in 24-hour format * @param date - Date to format * @returns Formatted time string (e.g., "09:00") */ static format24Hour(date: Date): string { const localDate = TimeFormatter.convertToLocalTime(date); // Always use colon separator, not locale-specific formatting let hours = String(localDate.getHours()).padStart(2, '0'); let minutes = String(localDate.getMinutes()).padStart(2, '0'); return `${hours}:${minutes}`; } /** * Format time according to current configuration * @param date - Date to format * @returns Formatted time string */ static formatTime(date: Date): string { return TimeFormatter.settings.use24HourFormat ? TimeFormatter.format24Hour(date) : TimeFormatter.format12Hour(date); } /** * Format time from total minutes since midnight * @param totalMinutes - Minutes since midnight (e.g., 540 for 9:00 AM) * @returns Formatted time string */ static formatTimeFromMinutes(totalMinutes: number): string { const hours = Math.floor(totalMinutes / 60) % 24; const minutes = totalMinutes % 60; // Create a date object for today with the specified time const date = new Date(); date.setHours(hours, minutes, 0, 0); return TimeFormatter.formatTime(date); } /** * Format date and time together * @param date - Date to format * @returns Formatted date and time string */ static formatDateTime(date: Date): string { const localDate = TimeFormatter.convertToLocalTime(date); const dateStr = localDate.toLocaleDateString(TimeFormatter.settings.locale, { timeZone: TimeFormatter.settings.timezone, year: 'numeric', month: '2-digit', day: '2-digit' }); const timeStr = TimeFormatter.formatTime(date); return `${dateStr} ${timeStr}`; } /** * Format time range (start - end) * @param startDate - Start date * @param endDate - End date * @returns Formatted time range string (e.g., "09:00 - 10:30") */ static formatTimeRange(startDate: Date, endDate: Date): string { const startTime = TimeFormatter.formatTime(startDate); const endTime = TimeFormatter.formatTime(endDate); return `${startTime} - ${endTime}`; } /** * Check if current timezone observes daylight saving time * @param date - Reference date * @returns True if DST is active */ static isDaylightSavingTime(date: Date = new Date()): boolean { const january = new Date(date.getFullYear(), 0, 1); const july = new Date(date.getFullYear(), 6, 1); const janOffset = TimeFormatter.getTimezoneOffset(january); const julOffset = TimeFormatter.getTimezoneOffset(july); return Math.max(janOffset, julOffset) !== TimeFormatter.getTimezoneOffset(date); } /** * Get timezone abbreviation (e.g., "CET", "CEST") * @param date - Reference date * @returns Timezone abbreviation */ static getTimezoneAbbreviation(date: Date = new Date()): string { const localDate = TimeFormatter.convertToLocalTime(date); return localDate.toLocaleTimeString('en-US', { timeZone: TimeFormatter.settings.timezone, timeZoneName: 'short' }).split(' ').pop() || ''; } /** * Format date in technical format: yyyy-mm-dd */ static formatDateTechnical(date: Date): string { let year = date.getFullYear(); let month = String(date.getMonth() + 1).padStart(2, '0'); let day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * Format time in technical format: hh:mm or hh:mm:ss */ static formatTimeTechnical(date: Date, includeSeconds: boolean = false): string { let hours = String(date.getHours()).padStart(2, '0'); let minutes = String(date.getMinutes()).padStart(2, '0'); if (includeSeconds) { let seconds = String(date.getSeconds()).padStart(2, '0'); return `${hours}:${minutes}:${seconds}`; } return `${hours}:${minutes}`; } /** * Format date and time in technical format: yyyy-mm-dd hh:mm:ss */ static formatDateTimeTechnical(date: Date): string { let localDate = TimeFormatter.convertToLocalTime(date); let dateStr = TimeFormatter.formatDateTechnical(localDate); let timeStr = TimeFormatter.formatTimeTechnical(localDate, TimeFormatter.settings.showSeconds); return `${dateStr} ${timeStr}`; } }