Adds DateService using date-fns-tz for robust time zone conversions and date manipulations. Refactors DateCalculator and TimeFormatter to utilize the DateService, centralizing date logic and ensuring consistent time zone handling throughout the application. Improves event dragging by updating time displays and data attributes, handling cross-midnight events correctly.
222 lines
No EOL
7.2 KiB
TypeScript
222 lines
No EOL
7.2 KiB
TypeScript
/**
|
|
* TimeFormatter - Centralized time formatting with timezone support
|
|
* Now uses DateService internally for all date/time operations
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
import { DateService } from './DateService';
|
|
|
|
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
|
|
};
|
|
|
|
private static dateService: DateService = new DateService('Europe/Copenhagen');
|
|
|
|
/**
|
|
* Configure time formatting settings
|
|
*/
|
|
static configure(settings: Partial<TimeFormatSettings>): void {
|
|
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
|
|
// Update DateService with new timezone
|
|
TimeFormatter.dateService = new DateService(TimeFormatter.settings.timezone);
|
|
}
|
|
|
|
/**
|
|
* Get current time format settings
|
|
*/
|
|
static getSettings(): TimeFormatSettings {
|
|
return { ...TimeFormatter.settings };
|
|
}
|
|
|
|
/**
|
|
* Convert UTC date to configured timezone
|
|
* @param utcDate - Date in UTC (or ISO string)
|
|
* @returns Date object adjusted to configured timezone
|
|
*/
|
|
static convertToLocalTime(utcDate: Date | string): Date {
|
|
if (typeof utcDate === 'string') {
|
|
return TimeFormatter.dateService.fromUTC(utcDate);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
/**
|
|
* 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 using DateService
|
|
* @param date - Date to format
|
|
* @returns Formatted time string (e.g., "09:00")
|
|
*/
|
|
static format24Hour(date: Date): string {
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
return TimeFormatter.dateService.formatTime(localDate, TimeFormatter.settings.showSeconds);
|
|
}
|
|
|
|
/**
|
|
* 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 using DateService
|
|
* @param totalMinutes - Minutes since midnight (e.g., 540 for 9:00 AM)
|
|
* @returns Formatted time string
|
|
*/
|
|
static formatTimeFromMinutes(totalMinutes: number): string {
|
|
return TimeFormatter.dateService.formatTimeFromMinutes(totalMinutes);
|
|
}
|
|
|
|
/**
|
|
* 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) using DateService
|
|
* @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 localStart = TimeFormatter.convertToLocalTime(startDate);
|
|
const localEnd = TimeFormatter.convertToLocalTime(endDate);
|
|
return TimeFormatter.dateService.formatTimeRange(localStart, localEnd);
|
|
}
|
|
|
|
/**
|
|
* 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 using DateService
|
|
*/
|
|
static formatDateTechnical(date: Date): string {
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
return TimeFormatter.dateService.formatDate(localDate);
|
|
}
|
|
|
|
/**
|
|
* Format time in technical format: hh:mm or hh:mm:ss using DateService
|
|
*/
|
|
static formatTimeTechnical(date: Date, includeSeconds: boolean = false): string {
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
return TimeFormatter.dateService.formatTime(localDate, includeSeconds);
|
|
}
|
|
|
|
/**
|
|
* Format date and time in technical format: yyyy-mm-dd hh:mm:ss using DateService
|
|
*/
|
|
static formatDateTimeTechnical(date: Date): string {
|
|
const localDate = TimeFormatter.convertToLocalTime(date);
|
|
return TimeFormatter.dateService.formatTechnicalDateTime(localDate);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
} |