// Shared date-formatting and date-validation helpers. Previously // scattered across workspace/journal/paths.ts, journal/indexFile.ts, // and workspace/tool-trace/writeSearch.ts. // `toUtcIsoDate` lives in `@mulmoclaude/common` (#2480) and is re-exported // here so the host import sites keep their surface, mirroring files/safe.ts. import { toUtcIsoDate } from "@mulmoclaude/common"; export { toUtcIsoDate }; /** * YYYY-MM-DD in the LOCAL timezone. Used for journal daily paths * and human-facing date labels — "what did I do on 2026-04-11" * is a wall-clock question, not a UTC question. */ export function toLocalIsoDate(input: Date | number): string { const dateValue = typeof input === "number" ? new Date(input) : input; const year = dateValue.getFullYear(); const month = String(dateValue.getMonth() + 1).padStart(2, "0"); const day = String(dateValue.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } /** * Trim an ISO timestamp string to its YYYY-MM-DD date prefix. * Example: "2026-04-11T08:30:00Z" → "2026-04-11". */ export function isoDateOnly(iso: string): string { return iso.slice(0, 10); } /** * Strict validation of a YYYY-MM-DD string without regex — * checks length, separator positions, and numeric segments. * Does NOT validate month/day ranges (Feb 30 passes); that's the * caller's or LLM's responsibility. */ export function isValidIsoDate(input: string): boolean { if (input.length !== 10) return false; if (input[4] !== "-" || input[7] !== "-") return false; return isNumeric(input.slice(0, 4)) && isNumeric(input.slice(5, 7)) && isNumeric(input.slice(8, 10)); } /** * Split a `YYYY-MM-DD` string into its three segments, or `null` when it * isn't one. Path builders need all three present — a missing segment would * otherwise land in the tree as `undefined/undefined.md`. */ export function splitIsoDate(input: string): { year: string; month: string; day: string } | null { if (!isValidIsoDate(input)) return null; const [year, month, day] = input.split("-"); if (year === undefined || month === undefined || day === undefined) return null; return { year, month, day }; } function isNumeric(input: string): boolean { if (input.length === 0) return false; for (let index = 0; index < input.length; index++) { const code = input.charCodeAt(index); if (code < 48 || code > 57) return false; } return true; }