Keep school dates in the school's timezone
The container runs on UTC and the school does not: at 00:30 in Erfurt the process clock still says yesterday, so anything deriving "today" from a Date would prepare the wrong school day twice a night. core/dates.ts holds calendar dates as plain YYYY-MM-DD strings, which is what a school day is — today in Europe/Berlin, whole-day arithmetic anchored at noon UTC so no daylight-saving change can shift a date, the German weekday and day formats, and the compact form the timetable API takes. isCalendarDate round-trips rather than only matching a shape: Date.parse turns 2026-02-30 into March 2nd instead of refusing it, so a shape check alone would let a caller read a different day than it asked for. germanDate moves here from mcp/prompts.ts, its only previous home, so the timezone is stated in one place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
test/dates.test.ts
Normal file
66
test/dates.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import {
|
||||
addDays,
|
||||
compactDate,
|
||||
daysBetween,
|
||||
germanDate,
|
||||
germanDay,
|
||||
germanWeekday,
|
||||
isCalendarDate,
|
||||
schoolToday,
|
||||
} from '../src/core/dates.ts';
|
||||
|
||||
describe('schoolToday', () => {
|
||||
it('is the school\'s date, not the container\'s', () => {
|
||||
// 23:30 UTC in January is already tomorrow in Berlin (UTC+1) — the case
|
||||
// that would make a nightly briefing prepare the wrong day.
|
||||
assert.equal(schoolToday(new Date('2026-01-15T23:30:00Z')), '2026-01-16');
|
||||
// And in summer the offset is two hours.
|
||||
assert.equal(schoolToday(new Date('2026-07-15T22:30:00Z')), '2026-07-16');
|
||||
assert.equal(schoolToday(new Date('2026-07-15T21:30:00Z')), '2026-07-15');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDays and daysBetween', () => {
|
||||
it('walks calendar dates across a daylight-saving change', () => {
|
||||
// Clocks go forward in the EU on 2026-03-29, so a day of 23 hours lies
|
||||
// inside this range.
|
||||
assert.equal(addDays('2026-03-28', 2), '2026-03-30');
|
||||
assert.equal(daysBetween('2026-03-28', '2026-03-30'), 2);
|
||||
// And back again in October.
|
||||
assert.equal(addDays('2026-10-24', 2), '2026-10-26');
|
||||
});
|
||||
|
||||
it('crosses months and years', () => {
|
||||
assert.equal(addDays('2026-12-31', 1), '2027-01-01');
|
||||
assert.equal(addDays('2026-01-01', -1), '2025-12-31');
|
||||
assert.equal(addDays('2028-02-28', 1), '2028-02-29');
|
||||
assert.equal(daysBetween('2026-09-21', '2026-09-18'), -3);
|
||||
});
|
||||
|
||||
it('refuses something that is not a calendar date', () => {
|
||||
assert.throws(() => addDays('21.09.2026', 1), /YYYY-MM-DD/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calendar date helpers', () => {
|
||||
it('recognises real dates only', () => {
|
||||
assert.equal(isCalendarDate('2026-09-21'), true);
|
||||
assert.equal(isCalendarDate('2026-13-01'), false);
|
||||
assert.equal(isCalendarDate('2026-02-30'), false);
|
||||
assert.equal(isCalendarDate('2026-9-21'), false);
|
||||
assert.equal(isCalendarDate('heute'), false);
|
||||
});
|
||||
|
||||
it('formats the way a German timetable reads', () => {
|
||||
assert.equal(germanWeekday('2026-09-21'), 'Montag');
|
||||
assert.equal(germanWeekday('2026-09-17'), 'Donnerstag');
|
||||
assert.equal(germanDay('2026-09-21'), '21.09.2026');
|
||||
assert.equal(compactDate('2026-09-21'), 20_260_921);
|
||||
});
|
||||
|
||||
it('gives a moment the school\'s day, with its weekday', () => {
|
||||
assert.equal(germanDate(new Date('2026-09-14T23:30:00Z')), 'Dienstag, 15.09.2026');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { argumentText, examPrompt, germanDate, resolveTarget, summaryPrompt, type Target } from '../src/mcp/prompts.ts';
|
||||
import { germanDate } from '../src/core/dates.ts';
|
||||
import { argumentText, examPrompt, resolveTarget, summaryPrompt, type Target } from '../src/mcp/prompts.ts';
|
||||
import { ProtocolError } from '../src/mcp/tools/result.ts';
|
||||
|
||||
const CANDIDATES: Target[] = [
|
||||
@@ -151,3 +152,4 @@ describe('germanDate', () => {
|
||||
assert.equal(germanDate(new Date('2026-09-14T23:30:00Z')), 'Dienstag, 15.09.2026');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user