fix(domain): add month range validation to parse and monthOf
Shape-valid but out-of-range months (e.g., month 13 or 00) previously normalized silently through ordinal arithmetic, resulting in wrong months downstream. Now throw on invalid month values (< 1 or > 12), with the offending value in the error message. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bab6fc5f3e
commit
9af581e69c
@@ -66,3 +66,31 @@ test("monthsOfYear returns twelve padded months", () => {
|
||||
test("yearOf parses the year", () => {
|
||||
expect(yearOf("2026-09")).toBe(2026);
|
||||
});
|
||||
|
||||
describe("month range validation", () => {
|
||||
test("yearOf rejects out-of-range month 13", () => {
|
||||
expect(() => yearOf("2026-13")).toThrow(/2026-13/);
|
||||
});
|
||||
|
||||
test("yearOf rejects out-of-range month 00", () => {
|
||||
expect(() => yearOf("2026-00")).toThrow(/2026-00/);
|
||||
});
|
||||
|
||||
test("monthOf rejects out-of-range month 13 from IsoDate", () => {
|
||||
expect(() => monthOf("2026-13-01")).toThrow(/2026-13-01/);
|
||||
});
|
||||
|
||||
test("monthOf rejects out-of-range month 00 from IsoDate", () => {
|
||||
expect(() => monthOf("2026-00-15")).toThrow(/2026-00-15/);
|
||||
});
|
||||
|
||||
test("valid boundary month 01 works", () => {
|
||||
expect(yearOf("2026-01")).toBe(2026);
|
||||
expect(monthOf("2026-01-01")).toBe("2026-01");
|
||||
});
|
||||
|
||||
test("valid boundary month 12 works", () => {
|
||||
expect(yearOf("2026-12")).toBe(2026);
|
||||
expect(monthOf("2026-12-31")).toBe("2026-12");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { IsoDate, MonthId } from "./types.ts";
|
||||
function parse(id: MonthId): { year: number; month: number } {
|
||||
const match = /^(\d{4})-(\d{2})$/.exec(id);
|
||||
if (!match) throw new Error(`Invalid MonthId: ${id}`);
|
||||
return { year: Number(match[1]), month: Number(match[2]) };
|
||||
const year = Number(match[1]);
|
||||
const month = Number(match[2]);
|
||||
if (month < 1 || month > 12) throw new Error(`Invalid MonthId: ${id}`);
|
||||
return { year, month };
|
||||
}
|
||||
|
||||
function format(year: number, month: number): MonthId {
|
||||
@@ -23,6 +26,8 @@ function fromOrdinal(ordinal: number): MonthId {
|
||||
export function monthOf(date: IsoDate): MonthId {
|
||||
const match = /^(\d{4})-(\d{2})/.exec(date);
|
||||
if (!match) throw new Error(`Invalid IsoDate: ${date}`);
|
||||
const month = Number(match[2]);
|
||||
if (month < 1 || month > 12) throw new Error(`Invalid IsoDate: ${date}`);
|
||||
return `${match[1]}-${match[2]}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user