/** * DateService - Unified date/time service using date-fns * Handles all date operations, timezone conversions, and formatting */ import { format, parse, addMinutes, differenceInMinutes, startOfDay, endOfDay, setHours, setMinutes as setMins, getHours, getMinutes, parseISO, isValid, addDays, startOfWeek, endOfWeek, addWeeks, isSameDay } from 'date-fns'; import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz'; export class DateService { private timezone: string; constructor(timezone: string = 'Europe/Copenhagen') { this.timezone = timezone; } // ============================================ // CORE CONVERSIONS // ============================================ /** * Convert local date to UTC ISO string * @param localDate - Date in local timezone * @returns ISO string in UTC (with 'Z' suffix) */ public toUTC(localDate: Date): string { return fromZonedTime(localDate, this.timezone).toISOString(); } /** * Convert UTC ISO string to local date * @param utcString - ISO string in UTC * @returns Date in local timezone */ public fromUTC(utcString: string): Date { return toZonedTime(parseISO(utcString), this.timezone); } // ============================================ // FORMATTING // ============================================ /** * Format time as HH:mm or HH:mm:ss * @param date - Date to format * @param showSeconds - Include seconds in output * @returns Formatted time string */ public formatTime(date: Date, showSeconds = false): string { const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm'; return format(date, pattern); } /** * Format time range as "HH:mm - HH:mm" * @param start - Start date * @param end - End date * @returns Formatted time range */ public formatTimeRange(start: Date, end: Date): string { return `${this.formatTime(start)} - ${this.formatTime(end)}`; } /** * Format date and time in technical format: yyyy-MM-dd HH:mm:ss * @param date - Date to format * @returns Technical datetime string */ public formatTechnicalDateTime(date: Date): string { return format(date, 'yyyy-MM-dd HH:mm:ss'); } /** * Format date as yyyy-MM-dd * @param date - Date to format * @returns ISO date string */ public formatDate(date: Date): string { return format(date, 'yyyy-MM-dd'); } /** * Format date as ISO string (same as formatDate for compatibility) * @param date - Date to format * @returns ISO date string */ public formatISODate(date: Date): string { return this.formatDate(date); } // ============================================ // TIME CALCULATIONS // ============================================ /** * Convert time string (HH:mm or HH:mm:ss) to total minutes since midnight * @param timeString - Time in format HH:mm or HH:mm:ss * @returns Total minutes since midnight */ public timeToMinutes(timeString: string): number { const parts = timeString.split(':').map(Number); const hours = parts[0] || 0; const minutes = parts[1] || 0; return hours * 60 + minutes; } /** * Convert total minutes since midnight to time string HH:mm * @param totalMinutes - Minutes since midnight * @returns Time string in format HH:mm */ public minutesToTime(totalMinutes: number): string { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; const date = setMins(setHours(new Date(), hours), minutes); return format(date, 'HH:mm'); } /** * Format time from total minutes (alias for minutesToTime) * @param totalMinutes - Minutes since midnight * @returns Time string in format HH:mm */ public formatTimeFromMinutes(totalMinutes: number): string { return this.minutesToTime(totalMinutes); } /** * Get minutes since midnight for a given date * @param date - Date to calculate from * @returns Minutes since midnight */ public getMinutesSinceMidnight(date: Date): number { return getHours(date) * 60 + getMinutes(date); } /** * Calculate duration in minutes between two dates * @param start - Start date or ISO string * @param end - End date or ISO string * @returns Duration in minutes */ public getDurationMinutes(start: Date | string, end: Date | string): number { const startDate = typeof start === 'string' ? parseISO(start) : start; const endDate = typeof end === 'string' ? parseISO(end) : end; return differenceInMinutes(endDate, startDate); } // ============================================ // WEEK OPERATIONS // ============================================ /** * Get start and end of week (Monday to Sunday) * @param date - Reference date * @returns Object with start and end dates */ public getWeekBounds(date: Date): { start: Date; end: Date } { return { start: startOfWeek(date, { weekStartsOn: 1 }), // Monday end: endOfWeek(date, { weekStartsOn: 1 }) // Sunday }; } /** * Add weeks to a date * @param date - Base date * @param weeks - Number of weeks to add (can be negative) * @returns New date */ public addWeeks(date: Date, weeks: number): Date { return addWeeks(date, weeks); } // ============================================ // GRID HELPERS // ============================================ /** * Create a date at a specific time (minutes since midnight) * @param baseDate - Base date (date component) * @param totalMinutes - Minutes since midnight * @returns New date with specified time */ public createDateAtTime(baseDate: Date, totalMinutes: number): Date { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return setMins(setHours(startOfDay(baseDate), hours), minutes); } /** * Snap date to nearest interval * @param date - Date to snap * @param intervalMinutes - Snap interval in minutes * @returns Snapped date */ public snapToInterval(date: Date, intervalMinutes: number): Date { const minutes = this.getMinutesSinceMidnight(date); const snappedMinutes = Math.round(minutes / intervalMinutes) * intervalMinutes; return this.createDateAtTime(date, snappedMinutes); } // ============================================ // UTILITY METHODS // ============================================ /** * Check if two dates are the same day * @param date1 - First date * @param date2 - Second date * @returns True if same day */ public isSameDay(date1: Date, date2: Date): boolean { return isSameDay(date1, date2); } /** * Get start of day * @param date - Date * @returns Start of day (00:00:00) */ public startOfDay(date: Date): Date { return startOfDay(date); } /** * Get end of day * @param date - Date * @returns End of day (23:59:59.999) */ public endOfDay(date: Date): Date { return endOfDay(date); } /** * Add days to a date * @param date - Base date * @param days - Number of days to add (can be negative) * @returns New date */ public addDays(date: Date, days: number): Date { return addDays(date, days); } /** * Add minutes to a date * @param date - Base date * @param minutes - Number of minutes to add (can be negative) * @returns New date */ public addMinutes(date: Date, minutes: number): Date { return addMinutes(date, minutes); } /** * Parse ISO string to date * @param isoString - ISO date string * @returns Parsed date */ public parseISO(isoString: string): Date { return parseISO(isoString); } /** * Check if date is valid * @param date - Date to check * @returns True if valid */ public isValid(date: Date): boolean { return isValid(date); } }