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; private baseDate: dayjs.Dayjs; constructor(private config: ITimeFormatConfig, baseDate?: Date) { this.timezone = config.timezone; // Allow setting a fixed base date for demo/testing purposes this.baseDate = baseDate ? dayjs(baseDate) : dayjs(); } /** * Set a fixed base date (useful for demos with static mock data) */ setBaseDate(date: Date): void { this.baseDate = dayjs(date); } /** * Get the current base date (either fixed or today) */ getBaseDate(): Date { return this.baseDate.toDate(); } 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 = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week'); return Array.from({ length: days }, (_, i) => monday.add(i, 'day').format('YYYY-MM-DD') ); } /** * Get dates for specific weekdays within a week * @param offset - Week offset from base date (0 = current week) * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday) * @returns Array of date strings in YYYY-MM-DD format */ getWorkWeekDates(offset: number, workDays: number[]): string[] { const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week'); return workDays.map(isoDay => { // ISO: 1=Monday, 7=Sunday → days from Monday: 0-6 const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1; return monday.add(daysFromMonday, '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 } }