Files
TREK/shared/src/datetime/datetime-normalize.spec.ts
T
Maurice 016fdeec64 fix(booking-import): read a printed 12-hour clock instead of slicing it (#2094)
splitIso reached the time by taking characters 11..16, so "2026-06-11T01:11 PM"
became 01:11. The hour survived and the half of the day did not, which is why
AM and 24-hour times in the same document imported correctly. The arrival then
sorted before the departure and the router rolled it onto the next day, so a
three-hour hop was rendered as an overnight.

The client has done this properly since #1725; this is the server half, which
was never pulled across. normalizeLocalDateTime rewrites a value only when it
actually finds a meridiem, so a date-only value, a value carrying an offset and
anything the parser does not know come back byte for byte.

The mapper has only two exits for a date, toIsoString and splitIso, so this
covers all eight types and every emitted field at once. It also stops
sameConnection comparing a NaN timestamp, which silently merged unrelated legs
into one booking.
2026-08-28 14:25:08 +02:00

62 lines
2.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { parseMeridiemClock, splitLocalDateTime, normalizeLocalDateTime } from './datetime-normalize';
describe('parseMeridiemClock', () => {
it('resolves the half of the day', () => {
expect(parseMeridiemClock('01:11 pm')).toBe('13:11');
expect(parseMeridiemClock('09:51 am')).toBe('09:51');
expect(parseMeridiemClock('3 PM')).toBe('15:00');
expect(parseMeridiemClock('02:30:00 p.m.')).toBe('14:30');
});
it('keeps noon at 12 and midnight at 00', () => {
expect(parseMeridiemClock('12:05 pm')).toBe('12:05');
expect(parseMeridiemClock('12:05 am')).toBe('00:05');
});
it('returns null when there is no meridiem to resolve', () => {
expect(parseMeridiemClock('13:11')).toBeNull();
expect(parseMeridiemClock('')).toBeNull();
expect(parseMeridiemClock(null)).toBeNull();
// 13 with a meridiem is not a 12-hour clock, so it is not ours to rewrite.
expect(parseMeridiemClock('13:11 pm')).toBeNull();
});
});
describe('splitLocalDateTime', () => {
it('reads a printed 12-hour clock instead of slicing it', () => {
// The defect this pins: 'T02:30 PM'.slice(11, 16) is '02:30'. The hour
// survives and the booking lands twelve hours early (#2094).
expect(splitLocalDateTime('2026-06-11T02:30 PM')).toEqual({ date: '2026-06-11', time: '14:30' });
});
it('leaves a 24-hour value exactly as it is', () => {
expect(splitLocalDateTime('2026-06-11T13:11:00')).toEqual({ date: '2026-06-11', time: '13:11' });
expect(splitLocalDateTime('2026-06-11')).toEqual({ date: '2026-06-11', time: null });
});
it('has no date to give when the value does not start with one', () => {
expect(splitLocalDateTime('Aug 23 2025 10:00')).toEqual({ date: null, time: null });
expect(splitLocalDateTime(undefined)).toEqual({ date: null, time: null });
});
});
describe('normalizeLocalDateTime', () => {
it('rewrites only the clock, and keeps the seconds it was given', () => {
expect(normalizeLocalDateTime('2026-06-11T02:30 PM')).toBe('2026-06-11T14:30:00');
expect(normalizeLocalDateTime('2026-06-11T02:30:45 pm')).toBe('2026-06-11T14:30:45');
});
it('touches nothing that has no meridiem on it', () => {
// Strictly additive: a date-only value stays date-only (the calendar
// branches on whether the string contains a 'T' and would turn an all-day
// event into a midnight point), an offset survives, and an unreadable
// shape keeps whatever it meant before.
expect(normalizeLocalDateTime('2026-06-11')).toBe('2026-06-11');
expect(normalizeLocalDateTime('2026-06-11T13:11:00')).toBe('2026-06-11T13:11:00');
expect(normalizeLocalDateTime('2026-06-11T13:11:00+02:00')).toBe('2026-06-11T13:11:00+02:00');
expect(normalizeLocalDateTime('Aug 23 2025 10:00')).toBe('Aug 23 2025 10:00');
expect(normalizeLocalDateTime('')).toBe('');
});
});