src/features/reports/sample-month.ts
Linked repository file from this build.
import { isCalendarMonth } from "@/programs/snap-negative/input-limits";
export const SAMPLE_MONTH_LABELS = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
] as const;
function centuryYear(year: number) {
return year < 100 ? 2000 + year : year;
}
function formatMonth(year: number, month: number) {
const value = `${centuryYear(year)}-${String(month).padStart(2, "0")}`;
return isCalendarMonth(value) ? value : null;
}
/** Read YYYY-MM from typed variants such as 2026-5, 5/2026, 26-5 and 26-05. */
export function parseSampleMonth(raw: string) {
const value = raw.trim();
if (!value) return null;
const yearMonth = value.match(/^(\d{4})[-/.](\d{1,2})$/);
if (yearMonth) return formatMonth(Number(yearMonth[1]), Number(yearMonth[2]));
const monthYear = value.match(/^(\d{1,2})[-/.](\d{4})$/);
if (monthYear) return formatMonth(Number(monthYear[2]), Number(monthYear[1]));
const compact = value.match(/^(\d{4})(\d{2})$/);
if (compact) return formatMonth(Number(compact[1]), Number(compact[2]));
const short = value.match(/^(\d{1,2})[-/.](\d{1,2})$/);
if (!short) return null;
const first = Number(short[1]);
const second = Number(short[2]);
if (first > 12) return formatMonth(first, second);
if (second > 12) return formatMonth(second, first);
return short[1].length === 2 ? formatMonth(first, second) : formatMonth(second, first);
}
/**
* True when more digits would not change the parsed month.
* `2026-5` stays incomplete so a later `0` can still become October.
*/
export function isCompleteMonthInput(raw: string) {
const value = raw.trim();
if (!value) return true;
return (
/^\d{4}[-/.]\d{2}$/.test(value) ||
/^\d{1,2}[-/.]\d{4}$/.test(value) ||
/^\d{2}[-/.]\d{2}$/.test(value) ||
/^\d{6}$/.test(value)
);
}
export function monthFieldYear(value: string, fallback = new Date().getFullYear()) {
const parsed = parseSampleMonth(value);
if (parsed) return Number(parsed.slice(0, 4));
return fallback;
}