Skip to article
Documentation

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;
}