import { describe, it, expect } from 'vitest'; import { DateService } from '../../src/utils/DateService'; describe('DateService - Edge Cases', () => { const dateService = new DateService('Europe/Copenhagen'); describe('Leap Year Handling', () => { it('should handle February 29 in leap year (2024)', () => { const leapDate = new Date(2024, 1, 29); // Feb 29, 2024 expect(dateService.isValid(leapDate)).toBe(true); expect(leapDate.getMonth()).toBe(1); // February expect(leapDate.getDate()).toBe(29); }); it('should reject February 29 in non-leap year (2023)', () => { const invalidDate = new Date(2023, 1, 29); // Tries Feb 29, 2023 // JavaScript auto-corrects to March 1 expect(invalidDate.getMonth()).toBe(2); // March expect(invalidDate.getDate()).toBe(1); }); it('should handle February 28 in non-leap year', () => { const validDate = new Date(2023, 1, 28); // Feb 28, 2023 expect(dateService.isValid(validDate)).toBe(true); expect(validDate.getMonth()).toBe(1); // February expect(validDate.getDate()).toBe(28); }); it('should correctly add 1 year to Feb 29 (leap year)', () => { const leapDate = new Date(2024, 1, 29); const nextYear = dateService.addDays(leapDate, 365); // 2025 is not leap year // Should be Feb 28, 2025 (or March 1 depending on implementation) expect(nextYear.getFullYear()).toBe(2025); expect(nextYear.getMonth()).toBeGreaterThanOrEqual(1); // Feb or March }); it('should validate leap year dates with isWithinBounds', () => { const leapDate2024 = new Date(2024, 1, 29); const leapDate2000 = new Date(2000, 1, 29); expect(dateService.isWithinBounds(leapDate2024)).toBe(true); expect(dateService.isWithinBounds(leapDate2000)).toBe(true); }); }); describe('ISO Week 53 Handling', () => { it('should correctly identify week 53 in 2020 (has week 53)', () => { const dec31_2020 = new Date(2020, 11, 31); // Dec 31, 2020 const weekNum = dateService.getWeekNumber(dec31_2020); expect(weekNum).toBe(53); }); it('should correctly identify week 53 in 2026 (has week 53)', () => { const dec31_2026 = new Date(2026, 11, 31); // Dec 31, 2026 const weekNum = dateService.getWeekNumber(dec31_2026); expect(weekNum).toBe(53); }); it('should NOT have week 53 in 2021 (goes to week 52)', () => { const dec31_2021 = new Date(2021, 11, 31); // Dec 31, 2021 const weekNum = dateService.getWeekNumber(dec31_2021); expect(weekNum).toBe(52); }); it('should handle transition from week 53 to week 1', () => { const lastDayOf2020 = new Date(2020, 11, 31); // Week 53 const firstDayOf2021 = dateService.addDays(lastDayOf2020, 1); expect(dateService.getWeekNumber(lastDayOf2020)).toBe(53); expect(dateService.getWeekNumber(firstDayOf2021)).toBe(53); // Still week 53! // Monday after should be week 1 const firstMonday2021 = new Date(2021, 0, 4); expect(dateService.getWeekNumber(firstMonday2021)).toBe(1); }); it('should get correct week bounds for week 53', () => { const dec31_2020 = new Date(2020, 11, 31); const weekBounds = dateService.getWeekBounds(dec31_2020); // Week 53 of 2020 starts on Monday Dec 28, 2020 expect(weekBounds.start.getDate()).toBe(28); expect(weekBounds.start.getMonth()).toBe(11); // December // Ends on Sunday Jan 3, 2021 expect(weekBounds.end.getDate()).toBe(3); expect(weekBounds.end.getMonth()).toBe(0); // January expect(weekBounds.end.getFullYear()).toBe(2021); }); }); describe('Month Boundary Edge Cases', () => { it('should correctly add months across year boundary', () => { const nov2024 = new Date(2024, 10, 15); // Nov 15, 2024 const feb2025 = dateService.addMonths(nov2024, 3); expect(feb2025.getFullYear()).toBe(2025); expect(feb2025.getMonth()).toBe(1); // February expect(feb2025.getDate()).toBe(15); }); it('should handle month-end overflow (Jan 31 + 1 month)', () => { const jan31 = new Date(2024, 0, 31); const result = dateService.addMonths(jan31, 1); // date-fns addMonths handles this gracefully expect(result.getMonth()).toBe(1); // February expect(result.getFullYear()).toBe(2024); // Will be Feb 29 (leap year) or last day of Feb }); it('should handle adding negative months', () => { const mar2024 = new Date(2024, 2, 15); // March 15, 2024 const dec2023 = dateService.addMonths(mar2024, -3); expect(dec2023.getFullYear()).toBe(2023); expect(dec2023.getMonth()).toBe(11); // December expect(dec2023.getDate()).toBe(15); }); }); describe('Year Boundary Edge Cases', () => { it('should handle year transition (Dec 31 -> Jan 1)', () => { const dec31 = new Date(2024, 11, 31); const jan1 = dateService.addDays(dec31, 1); expect(jan1.getFullYear()).toBe(2025); expect(jan1.getMonth()).toBe(0); // January expect(jan1.getDate()).toBe(1); }); it('should handle reverse year transition (Jan 1 -> Dec 31)', () => { const jan1 = new Date(2024, 0, 1); const dec31 = dateService.addDays(jan1, -1); expect(dec31.getFullYear()).toBe(2023); expect(dec31.getMonth()).toBe(11); // December expect(dec31.getDate()).toBe(31); }); it('should correctly calculate week bounds at year boundary', () => { const jan1_2024 = new Date(2024, 0, 1); const weekBounds = dateService.getWeekBounds(jan1_2024); // Jan 1, 2024 is a Monday (week 1) expect(weekBounds.start.getDate()).toBe(1); expect(weekBounds.start.getMonth()).toBe(0); expect(weekBounds.start.getFullYear()).toBe(2024); }); }); describe('DST Transition Edge Cases', () => { it('should handle spring DST transition (CET -> CEST)', () => { // Last Sunday of March 2024: March 31, 02:00 -> 03:00 const beforeDST = new Date(2024, 2, 31, 1, 30); // 01:30 CET const afterDST = new Date(2024, 2, 31, 3, 30); // 03:30 CEST expect(dateService.isValid(beforeDST)).toBe(true); expect(dateService.isValid(afterDST)).toBe(true); // The hour 02:00-03:00 doesn't exist! const nonExistentTime = new Date(2024, 2, 31, 2, 30); // JavaScript auto-adjusts this expect(nonExistentTime.getHours()).not.toBe(2); }); it('should handle fall DST transition (CEST -> CET)', () => { // Last Sunday of October 2024: October 27, 03:00 -> 02:00 const beforeDST = new Date(2024, 9, 27, 2, 30, 0, 0); const afterDST = new Date(2024, 9, 27, 3, 30, 0, 0); expect(dateService.isValid(beforeDST)).toBe(true); expect(dateService.isValid(afterDST)).toBe(true); // 02:00-03:00 exists TWICE (ambiguous hour) // This is handled by timezone-aware libraries }); it('should calculate duration correctly across DST', () => { // Event spanning DST transition const start = new Date(2024, 2, 31, 1, 0); // Before DST const end = new Date(2024, 2, 31, 4, 0); // After DST const duration = dateService.getDurationMinutes(start, end); // Clock time: 3 hours, but actual duration: 2 hours (due to DST) // date-fns should handle this correctly expect(duration).toBeGreaterThan(0); }); }); describe('Extreme Date Values', () => { it('should reject dates before 1900', () => { const oldDate = new Date(1899, 11, 31); expect(dateService.isWithinBounds(oldDate)).toBe(false); }); it('should reject dates after 2100', () => { const futureDate = new Date(2101, 0, 1); expect(dateService.isWithinBounds(futureDate)).toBe(false); }); it('should accept boundary dates (1900 and 2100)', () => { const minDate = new Date(1900, 0, 1); const maxDate = new Date(2100, 11, 31); expect(dateService.isWithinBounds(minDate)).toBe(true); expect(dateService.isWithinBounds(maxDate)).toBe(true); }); it('should validate invalid Date objects', () => { const invalidDate = new Date('invalid'); expect(dateService.isValid(invalidDate)).toBe(false); expect(dateService.isWithinBounds(invalidDate)).toBe(false); }); }); });