- 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]>
101 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|