Calendar/src/utils/TimeFormatter.ts

223 lines
7.2 KiB
TypeScript
Raw Normal View History

/**
* 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<TimeFormatSettings>): 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}`;
}
}