import { addDays, berlinDateTime, berlinDayOf, berlinToday, dayToDate, daysBetween, } from './dates'; describe('dates', () => { it('berlinToday at 2026-03-28T23:30Z is 2026-03-29', () => { expect(berlinToday(new Date('2026-03-28T23:30:00Z'))).toBe('2026-03-29'); }); it('the 04:30 Berlin job time is the same calendar day', () => { expect(berlinToday(new Date('2026-10-13T02:30:00Z'))).toBe('2026-10-13'); }); it('berlinToday defaults to the current instant', () => { expect(berlinToday()).toMatch(/^\d{4}-\d{2}-\d{2}$/); }); it('berlinDayOf uses the Berlin calendar day, not the UTC one', () => { expect(berlinDayOf(new Date('2026-12-31T23:30:00Z'))).toBe('2027-01-01'); expect(berlinDayOf(new Date('2026-06-30T21:59:00Z'))).toBe('2026-06-30'); expect(berlinDayOf(new Date('2026-06-30T22:00:00Z'))).toBe('2026-07-01'); }); it('berlinDateTime 2026-03-29 06:30 is 04:30Z on the day DST starts', () => { expect(berlinDateTime('2026-03-29', '06:30').toISOString()).toBe( '2026-03-29T04:30:00.000Z', ); }); it('berlinDateTime 2026-10-25 06:30 is 05:30Z after DST ends', () => { expect(berlinDateTime('2026-10-25', '06:30').toISOString()).toBe( '2026-10-25T05:30:00.000Z', ); }); it('berlinDateTime uses winter and summer offsets on ordinary days', () => { expect(berlinDateTime('2026-01-15', '06:30').toISOString()).toBe( '2026-01-15T05:30:00.000Z', ); expect(berlinDateTime('2026-07-15', '06:30').toISOString()).toBe( '2026-07-15T04:30:00.000Z', ); }); it('berlinDateTime handles midnight and the evening before a DST change', () => { expect(berlinDateTime('2026-03-29', '00:00').toISOString()).toBe( '2026-03-28T23:00:00.000Z', ); expect(berlinDateTime('2026-10-25', '00:00').toISOString()).toBe( '2026-10-24T22:00:00.000Z', ); expect(berlinDateTime('2026-03-28', '21:00').toISOString()).toBe( '2026-03-28T20:00:00.000Z', ); }); it('berlinDateTime rejects malformed input', () => { expect(() => berlinDateTime('2026-3-29', '06:30')).toThrow(); expect(() => berlinDateTime('2026-03-29', '6:30')).toThrow(); expect(() => berlinDateTime('2026-03-29', '25:00')).toThrow(); }); it('dayToDate is midnight UTC', () => { expect(dayToDate('2026-05-05').toISOString()).toBe( '2026-05-05T00:00:00.000Z', ); }); it('dayToDate rejects impossible days', () => { expect(() => dayToDate('2026-02-30')).toThrow(); expect(() => dayToDate('2026-13-01')).toThrow(); expect(() => dayToDate('2026-04-31')).toThrow(); expect(dayToDate('2024-02-29').toISOString()).toBe( '2024-02-29T00:00:00.000Z', ); }); it('addDays crosses month ends', () => { expect(addDays('2026-02-27', 2)).toBe('2026-03-01'); }); it('addDays goes backwards and over year ends', () => { expect(addDays('2026-01-01', -1)).toBe('2025-12-31'); expect(addDays('2026-12-31', 1)).toBe('2027-01-01'); }); it('daysBetween counts calendar days', () => { expect(daysBetween('2026-05-01', '2026-05-09')).toBe(8); expect(daysBetween('2026-05-09', '2026-05-01')).toBe(-8); }); it('daysBetween counts whole days across a DST change', () => { expect(daysBetween('2026-03-28', '2026-03-30')).toBe(2); }); });