feat(sim): calendar

This commit is contained in:
Victor Bodinaud committed 2026-10-08 12:27:52 +02:00
1 parent 13192ee0bc
commit b6054086c3
2 files changed
+98

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
export interface GameDate {
year: number;
month: number; // 1-12
day: number;
}
const MS_PER_DAY = 86_400_000;
export const START_DATE: GameDate = { year: 1990, month: 9, day: 28 };
export const EURO_DATE: GameDate = { year: 2002, month: 1, day: 1 };
function toUTC(d: GameDate): number {
return Date.UTC(d.year, d.month - 1, d.day);
}
function fromUTC(ms: number): GameDate {
const js = new Date(ms);
return { year: js.getUTCFullYear(), month: js.getUTCMonth() + 1, day: js.getUTCDate() };
}
export function addDays(d: GameDate, n: number): GameDate {
return fromUTC(toUTC(d) + n * MS_PER_DAY);
}
export function daysBetween(a: GameDate, b: GameDate): number {
return Math.round((toUTC(b) - toUTC(a)) / MS_PER_DAY);
}
export function compareDates(a: GameDate, b: GameDate): number {
return toUTC(a) - toUTC(b);
}
export function isSunday(d: GameDate): boolean {
return new Date(toUTC(d)).getUTCDay() === 0;
}
export function isLastDayOfMonth(d: GameDate): boolean {
return addDays(d, 1).month !== d.month;
}
export function isEuroEra(d: GameDate): boolean {
return compareDates(d, EURO_DATE) >= 0;
}
export function dateToISO(d: GameDate): string {
const mm = String(d.month).padStart(2, '0');
const dd = String(d.day).padStart(2, '0');
return `${d.year}-${mm}-${dd}`;
}
export function parseISO(s: string): GameDate {
const [year, month, day] = s.split('-').map(Number);
return { year, month, day };
}
+44
View File
@@ -0,0 +1,44 @@
import { describe, it, expect } from 'vitest';
import {
addDays, compareDates, dateToISO, daysBetween, isEuroEra,
isLastDayOfMonth, isSunday, parseISO, START_DATE,
} from '../../src/sim/calendar';
describe('calendar', () => {
it('starts on the European Game Boy launch (Friday 28/09/1990)', () => {
expect(START_DATE).toEqual({ year: 1990, month: 9, day: 28 });
expect(isSunday(START_DATE)).toBe(false);
});
it('detects Sundays', () => {
expect(isSunday({ year: 1990, month: 9, day: 30 })).toBe(true);
expect(isSunday({ year: 1990, month: 9, day: 29 })).toBe(false);
});
it('adds days across month and year boundaries', () => {
expect(addDays({ year: 1990, month: 12, day: 31 }, 1)).toEqual({ year: 1991, month: 1, day: 1 });
expect(addDays({ year: 1990, month: 9, day: 28 }, 3)).toEqual({ year: 1990, month: 10, day: 1 });
});
it('detects the last day of a month, including leap years', () => {
expect(isLastDayOfMonth({ year: 1992, month: 2, day: 29 })).toBe(true);
expect(isLastDayOfMonth({ year: 1992, month: 2, day: 28 })).toBe(false);
expect(isLastDayOfMonth({ year: 1990, month: 9, day: 30 })).toBe(true);
});
it('switches to the euro on 01/01/2002', () => {
expect(isEuroEra({ year: 2001, month: 12, day: 31 })).toBe(false);
expect(isEuroEra({ year: 2002, month: 1, day: 1 })).toBe(true);
});
it('computes days between dates', () => {
expect(daysBetween({ year: 1990, month: 9, day: 28 }, { year: 1990, month: 10, day: 28 })).toBe(30);
expect(daysBetween({ year: 1990, month: 10, day: 28 }, { year: 1990, month: 9, day: 28 })).toBe(-30);
});
it('round-trips ISO strings and compares dates', () => {
expect(dateToISO(parseISO('1990-09-28'))).toBe('1990-09-28');
expect(compareDates(parseISO('1990-09-28'), parseISO('1991-01-01'))).toBeLessThan(0);
expect(compareDates(parseISO('1991-01-01'), parseISO('1991-01-01'))).toBe(0);
});
});