2025-09-12 22:21:56 +02:00
|
|
|
/**
|
|
|
|
|
* TimeFormatter - Centralized time formatting with timezone support
|
2025-10-03 16:47:42 +02:00
|
|
|
* Now uses DateService internally for all date/time operations
|
2025-09-12 22:21:56 +02:00
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-03 16:47:42 +02:00
|
|
|
import { DateService } from './DateService';
|
|
|
|
|
|
2025-09-12 22:21:56 +02:00
|
|
|
export interface TimeFormatSettings {
|
|
|
|
|
timezone: string;
|
|
|
|
|
use24HourFormat: boolean;
|
|
|
|
|
locale: string;
|
2025-10-03 16:05:22 +02:00
|
|
|
dateFormat: 'locale' | 'technical';
|
|
|
|
|
showSeconds: boolean;
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class TimeFormatter {
|
|
|
|
|
private static settings: TimeFormatSettings = {
|
|
|
|
|
timezone: 'Europe/Copenhagen', // Default to Denmark
|
|
|
|
|
use24HourFormat: true, // 24-hour format standard in Denmark
|
2025-10-03 16:05:22 +02:00
|
|
|
locale: 'da-DK', // Danish locale
|
|
|
|
|
dateFormat: 'technical', // Use technical format yyyy-mm-dd hh:mm:ss
|
|
|
|
|
showSeconds: false // Don't show seconds by default
|
2025-09-12 22:21:56 +02:00
|
|
|
};
|
|
|
|
|
|
2025-10-03 16:47:42 +02:00
|
|
|
private static dateService: DateService = new DateService('Europe/Copenhagen');
|
|
|
|
|
|
2025-09-12 22:21:56 +02:00
|
|
|
/**
|
|
|
|
|
* Configure time formatting settings
|
|
|
|
|
*/
|
|
|
|
|
static configure(settings: Partial<TimeFormatSettings>): void {
|
|
|
|
|
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
|
2025-10-03 16:47:42 +02:00
|
|
|
// Update DateService with new timezone
|
|
|
|
|
TimeFormatter.dateService = new DateService(TimeFormatter.settings.timezone);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current time format settings
|
|
|
|
|
*/
|
|
|
|
|
static getSettings(): TimeFormatSettings {
|
|
|
|
|
return { ...TimeFormatter.settings };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert UTC date to configured timezone
|
2025-10-03 16:47:42 +02:00
|
|
|
* @param utcDate - Date in UTC (or ISO string)
|
2025-09-12 22:21:56 +02:00
|
|
|
* @returns Date object adjusted to configured timezone
|
|
|
|
|
*/
|
2025-10-03 16:47:42 +02:00
|
|
|
static convertToLocalTime(utcDate: Date | string): Date {
|
|
|
|
|
if (typeof utcDate === 'string') {
|
|
|
|
|
return TimeFormatter.dateService.fromUTC(utcDate);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-03 16:47:42 +02:00
|
|
|
// If it's already a Date object, convert to UTC string first, then back to local
|
|
|
|
|
const utcString = utcDate.toISOString();
|
|
|
|
|
return TimeFormatter.dateService.fromUTC(utcString);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format time in 24-hour format using DateService
|
2025-09-12 22:21:56 +02:00
|
|
|
* @param date - Date to format
|
|
|
|
|
* @returns Formatted time string (e.g., "09:00")
|
|
|
|
|
*/
|
|
|
|
|
static format24Hour(date: Date): string {
|
|
|
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
2025-10-03 16:47:42 +02:00
|
|
|
return TimeFormatter.dateService.formatTime(localDate, TimeFormatter.settings.showSeconds);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format time from total minutes since midnight using DateService
|
2025-09-12 22:21:56 +02:00
|
|
|
* @param totalMinutes - Minutes since midnight (e.g., 540 for 9:00 AM)
|
|
|
|
|
* @returns Formatted time string
|
|
|
|
|
*/
|
|
|
|
|
static formatTimeFromMinutes(totalMinutes: number): string {
|
2025-10-03 16:47:42 +02:00
|
|
|
return TimeFormatter.dateService.formatTimeFromMinutes(totalMinutes);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format time range (start - end) using DateService
|
2025-09-12 22:21:56 +02:00
|
|
|
* @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 {
|
2025-10-03 16:47:42 +02:00
|
|
|
const localStart = TimeFormatter.convertToLocalTime(startDate);
|
|
|
|
|
const localEnd = TimeFormatter.convertToLocalTime(endDate);
|
|
|
|
|
return TimeFormatter.dateService.formatTimeRange(localStart, localEnd);
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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() || '';
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 16:05:22 +02:00
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format date in technical format: yyyy-mm-dd using DateService
|
2025-10-03 16:05:22 +02:00
|
|
|
*/
|
|
|
|
|
static formatDateTechnical(date: Date): string {
|
2025-10-03 16:47:42 +02:00
|
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
|
|
|
return TimeFormatter.dateService.formatDate(localDate);
|
2025-10-03 16:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format time in technical format: hh:mm or hh:mm:ss using DateService
|
2025-10-03 16:05:22 +02:00
|
|
|
*/
|
|
|
|
|
static formatTimeTechnical(date: Date, includeSeconds: boolean = false): string {
|
2025-10-03 16:47:42 +02:00
|
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
|
|
|
return TimeFormatter.dateService.formatTime(localDate, includeSeconds);
|
2025-10-03 16:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-03 16:47:42 +02:00
|
|
|
* Format date and time in technical format: yyyy-mm-dd hh:mm:ss using DateService
|
2025-10-03 16:05:22 +02:00
|
|
|
*/
|
|
|
|
|
static formatDateTimeTechnical(date: Date): string {
|
2025-10-03 16:47:42 +02:00
|
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
|
|
|
return TimeFormatter.dateService.formatTechnicalDateTime(localDate);
|
2025-10-03 16:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-03 16:47:42 +02:00
|
|
|
/**
|
|
|
|
|
* Convert local date to UTC ISO string using DateService
|
|
|
|
|
* @param localDate - Date in local timezone
|
|
|
|
|
* @returns ISO string in UTC (with 'Z' suffix)
|
|
|
|
|
*/
|
|
|
|
|
static toUTC(localDate: Date): string {
|
|
|
|
|
return TimeFormatter.dateService.toUTC(localDate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert UTC ISO string to local date using DateService
|
|
|
|
|
* @param utcString - ISO string in UTC
|
|
|
|
|
* @returns Date in local timezone
|
|
|
|
|
*/
|
|
|
|
|
static fromUTC(utcString: string): Date {
|
|
|
|
|
return TimeFormatter.dateService.fromUTC(utcString);
|
|
|
|
|
}
|
2025-09-12 22:21:56 +02:00
|
|
|
}
|