import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import isoWeek from 'dayjs/plugin/isoWeek'; import { ITimeFormatConfig } from './ITimeFormatConfig'; // Enable dayjs plugins dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(isoWeek); export class DateService { private timezone: string; constructor(private config: ITimeFormatConfig) { this.timezone = config.timezone; } parseISO(isoString: string): Date { return dayjs(isoString).toDate(); } getDayName(date: Date, format: 'short' | 'long' = 'short'): string { return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date); } getWeekDates(offset = 0, days = 7): string[] { const monday = dayjs().startOf('week').add(1, 'day').add(offset, 'week'); return Array.from({ length: days }, (_, i) => monday.add(i, 'day').format('YYYY-MM-DD') ); } // ============================================ // FORMATTING // ============================================ formatTime(date: Date, showSeconds = false): string { const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm'; return dayjs(date).format(pattern); } formatTimeRange(start: Date, end: Date): string { return `${this.formatTime(start)} - ${this.formatTime(end)}`; } formatDate(date: Date): string { return dayjs(date).format('YYYY-MM-DD'); } getDateKey(date: Date): string { return this.formatDate(date); } // ============================================ // COLUMN KEY // ============================================ /** * Build a uniform columnKey from grouping segments * Handles any combination of date, resource, team, etc. * * @example * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09" * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001" */ buildColumnKey(segments: Record): string { // Always put date first if present, then other segments alphabetically const date = segments.date; const others = Object.entries(segments) .filter(([k]) => k !== 'date') .sort(([a], [b]) => a.localeCompare(b)) .map(([, v]) => v); return date ? [date, ...others].join(':') : others.join(':'); } /** * Parse a columnKey back into segments * Assumes format: "date:resource:..." or just "date" */ parseColumnKey(columnKey: string): { date: string; resource?: string } { const parts = columnKey.split(':'); return { date: parts[0], resource: parts[1] }; } /** * Extract dateKey from columnKey (first segment) */ getDateFromColumnKey(columnKey: string): string { return columnKey.split(':')[0]; } // ============================================ // TIME CALCULATIONS // ============================================ timeToMinutes(timeString: string): number { const parts = timeString.split(':').map(Number); const hours = parts[0] || 0; const minutes = parts[1] || 0; return hours * 60 + minutes; } minutesToTime(totalMinutes: number): string { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return dayjs().hour(hours).minute(minutes).format('HH:mm'); } getMinutesSinceMidnight(date: Date): number { const d = dayjs(date); return d.hour() * 60 + d.minute(); } // ============================================ // UTC CONVERSIONS // ============================================ toUTC(localDate: Date): string { return dayjs.tz(localDate, this.timezone).utc().toISOString(); } fromUTC(utcString: string): Date { return dayjs.utc(utcString).tz(this.timezone).toDate(); } // ============================================ // DATE CREATION // ============================================ createDateAtTime(baseDate: Date | string, timeString: string): Date { const totalMinutes = this.timeToMinutes(timeString); const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate(); } getISOWeekDay(date: Date | string): number { return dayjs(date).isoWeekday(); // 1=Monday, 7=Sunday } }