import { describe, it, expect } from 'vitest'; import { DateService } from '../../src/utils/DateService'; describe('DateService - Midnight Crossing & Multi-Day Events', () => { const dateService = new DateService('Europe/Copenhagen'); describe('Midnight Crossing Events', () => { it('should handle event starting before midnight and ending after', () => { const start = new Date(2024, 0, 15, 23, 30); // Jan 15, 23:30 const end = new Date(2024, 0, 16, 1, 30); // Jan 16, 01:30 expect(dateService.isMultiDay(start, end)).toBe(true); expect(dateService.isSameDay(start, end)).toBe(false); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(120); // 2 hours }); it('should calculate duration correctly across midnight', () => { const start = new Date(2024, 0, 15, 22, 0); // 22:00 const end = new Date(2024, 0, 16, 2, 0); // 02:00 next day const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(240); // 4 hours }); it('should handle event ending exactly at midnight', () => { const start = new Date(2024, 0, 15, 20, 0); // 20:00 const end = new Date(2024, 0, 16, 0, 0); // 00:00 (midnight) expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(240); // 4 hours }); it('should handle event starting exactly at midnight', () => { const start = new Date(2024, 0, 15, 0, 0); // 00:00 (midnight) const end = new Date(2024, 0, 15, 3, 0); // 03:00 same day expect(dateService.isMultiDay(start, end)).toBe(false); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(180); // 3 hours }); it('should create date at specific time correctly across midnight', () => { const baseDate = new Date(2024, 0, 15); // 1440 minutes = 24:00 = midnight next day const midnightNextDay = dateService.createDateAtTime(baseDate, 1440); expect(midnightNextDay.getDate()).toBe(16); expect(midnightNextDay.getHours()).toBe(0); expect(midnightNextDay.getMinutes()).toBe(0); // 1500 minutes = 25:00 = 01:00 next day const oneAmNextDay = dateService.createDateAtTime(baseDate, 1500); expect(oneAmNextDay.getDate()).toBe(16); expect(oneAmNextDay.getHours()).toBe(1); expect(oneAmNextDay.getMinutes()).toBe(0); }); }); describe('Multi-Day Events', () => { it('should detect 2-day event', () => { const start = new Date(2024, 0, 15, 10, 0); const end = new Date(2024, 0, 16, 14, 0); expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(28 * 60); // 28 hours }); it('should detect 3-day event', () => { const start = new Date(2024, 0, 15, 9, 0); const end = new Date(2024, 0, 17, 17, 0); expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(56 * 60); // 56 hours }); it('should detect week-long event', () => { const start = new Date(2024, 0, 15, 0, 0); const end = new Date(2024, 0, 22, 0, 0); expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(7 * 24 * 60); // 7 days }); it('should handle month-spanning multi-day event', () => { const start = new Date(2024, 0, 30, 12, 0); // Jan 30 const end = new Date(2024, 1, 2, 12, 0); // Feb 2 expect(dateService.isMultiDay(start, end)).toBe(true); expect(start.getMonth()).toBe(0); // January expect(end.getMonth()).toBe(1); // February const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(3 * 24 * 60); // 3 days }); it('should handle year-spanning multi-day event', () => { const start = new Date(2024, 11, 30, 10, 0); // Dec 30, 2024 const end = new Date(2025, 0, 2, 10, 0); // Jan 2, 2025 expect(dateService.isMultiDay(start, end)).toBe(true); expect(start.getFullYear()).toBe(2024); expect(end.getFullYear()).toBe(2025); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(3 * 24 * 60); // 3 days }); }); describe('Timezone Boundary Events', () => { it('should handle UTC to local timezone conversion across midnight', () => { // Event in UTC that crosses date boundary in local timezone const utcStart = '2024-01-15T23:00:00Z'; // 23:00 UTC const utcEnd = '2024-01-16T01:00:00Z'; // 01:00 UTC next day const localStart = dateService.fromUTC(utcStart); const localEnd = dateService.fromUTC(utcEnd); // Copenhagen is UTC+1 (or UTC+2 in summer) // So 23:00 UTC = 00:00 or 01:00 local (midnight crossing) expect(localStart.getDate()).toBeGreaterThanOrEqual(15); expect(localEnd.getDate()).toBeGreaterThanOrEqual(16); const duration = dateService.getDurationMinutes(localStart, localEnd); expect(duration).toBe(120); // 2 hours }); it('should preserve duration when converting UTC to local', () => { const utcStart = '2024-06-15T10:00:00Z'; const utcEnd = '2024-06-15T18:00:00Z'; const localStart = dateService.fromUTC(utcStart); const localEnd = dateService.fromUTC(utcEnd); const utcDuration = 8 * 60; // 8 hours const localDuration = dateService.getDurationMinutes(localStart, localEnd); expect(localDuration).toBe(utcDuration); }); it('should handle all-day events (00:00 to 00:00 next day)', () => { const start = new Date(2024, 0, 15, 0, 0, 0); const end = new Date(2024, 0, 16, 0, 0, 0); expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(24 * 60); // 24 hours }); it('should handle multi-day all-day events', () => { const start = new Date(2024, 0, 15, 0, 0, 0); const end = new Date(2024, 0, 18, 0, 0, 0); // 3-day event expect(dateService.isMultiDay(start, end)).toBe(true); const duration = dateService.getDurationMinutes(start, end); expect(duration).toBe(3 * 24 * 60); // 72 hours }); }); describe('Edge Cases with Minutes Since Midnight', () => { it('should calculate minutes since midnight correctly at day boundary', () => { const midnight = new Date(2024, 0, 15, 0, 0); const beforeMidnight = new Date(2024, 0, 14, 23, 59); const afterMidnight = new Date(2024, 0, 15, 0, 1); expect(dateService.getMinutesSinceMidnight(midnight)).toBe(0); expect(dateService.getMinutesSinceMidnight(beforeMidnight)).toBe(23 * 60 + 59); expect(dateService.getMinutesSinceMidnight(afterMidnight)).toBe(1); }); it('should handle createDateAtTime with overflow minutes (>1440)', () => { const baseDate = new Date(2024, 0, 15); // 1500 minutes = 25 hours = next day at 01:00 const result = dateService.createDateAtTime(baseDate, 1500); expect(result.getDate()).toBe(16); // Next day expect(result.getHours()).toBe(1); expect(result.getMinutes()).toBe(0); }); it('should handle createDateAtTime with large overflow (48+ hours)', () => { const baseDate = new Date(2024, 0, 15); // 2880 minutes = 48 hours = 2 days later const result = dateService.createDateAtTime(baseDate, 2880); expect(result.getDate()).toBe(17); // 2 days later expect(result.getHours()).toBe(0); expect(result.getMinutes()).toBe(0); }); }); describe('Same Day vs Multi-Day Detection', () => { it('should correctly identify same-day events', () => { const start = new Date(2024, 0, 15, 8, 0); const end = new Date(2024, 0, 15, 17, 0); expect(dateService.isSameDay(start, end)).toBe(true); expect(dateService.isMultiDay(start, end)).toBe(false); }); it('should correctly identify multi-day events', () => { const start = new Date(2024, 0, 15, 23, 0); const end = new Date(2024, 0, 16, 1, 0); expect(dateService.isSameDay(start, end)).toBe(false); expect(dateService.isMultiDay(start, end)).toBe(true); }); it('should handle ISO string inputs for multi-day detection', () => { const startISO = '2024-01-15T23:00:00Z'; const endISO = '2024-01-16T01:00:00Z'; // Convert UTC strings to local timezone first const startLocal = dateService.fromUTC(startISO); const endLocal = dateService.fromUTC(endISO); const result = dateService.isMultiDay(startLocal, endLocal); // 23:00 UTC = 00:00 CET (next day) in Copenhagen // So this IS a multi-day event in local time expect(result).toBe(true); }); it('should handle mixed Date and string inputs', () => { const startDate = new Date(2024, 0, 15, 10, 0); const endISO = '2024-01-16T10:00:00Z'; const result = dateService.isMultiDay(startDate, endISO); expect(typeof result).toBe('boolean'); }); }); });