Files
ApiCore/ApiCore-Backend/src/common/dates.spec.ts
T
Nick SzaunigandClaude Opus 5.5 d99a697739 feat: T4 waves 1-2 - rule configs, schema, suggestions core, season, rule forms
- Rule configs with typed parsers and registry; Berlin day helpers
- Schema: T4 enums, Suggestion table, feed vocabulary (feedStatus as
  ColonyCondition with legacy text mapping), split queen source,
  varroa sample size; migration harness for data migrations
- SuggestionsService: propose/refresh/resolve/obsolete/expire,
  accept/dismiss/reopen incl. bulk, lock order, concurrency e2e
- SeasonService: phenology-based segments, inspection/varroa due dates,
  queen age; TimeEventsStep with JobPartialFailure
- Frontend: per-rule settings forms with visible value sources

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-14 13:42:32 +02:00

101 lines
3.2 KiB
TypeScript

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);
});
});