import type { TextValue } from "./page.js"; /** Serialize Notion-as-Code date values for Apps property payloads. */ export function date(startDate: string, endDate?: string): TextValue { const start_date = parseIsoDate(startDate); const end_date = endDate === undefined ? undefined : parseIsoDate(endDate); if (end_date !== undefined && end_date < start_date) { throw new Error("Date range end must be on or after start."); } const value = end_date === undefined ? { type: "date", start_date } : { type: "daterange", start_date, end_date }; return [["‣", [["d", value]]]]; } /** Serialize Notion-as-Code datetime values for Apps property payloads. */ export function datetime(value: { start: string; end?: string; timeZone?: string }): TextValue { const timeZone = value.timeZone ?? "UTC"; try { new Intl.DateTimeFormat("en-US", { timeZone }).format(); } catch { throw new Error("Invalid time zone: " + timeZone); } const start = parseIsoDatetime(value.start); const end = value.end === undefined ? undefined : parseIsoDatetime(value.end); if (end !== undefined && end.date + "T" + end.time < start.date + "T" + start.time) { throw new Error("Datetime range end must be on or after start."); } const dateValue = end === undefined ? { type: "datetime", start_date: start.date, start_time: start.time, time_zone: timeZone, } : { type: "datetimerange", start_date: start.date, start_time: start.time, end_date: end.date, end_time: end.time, time_zone: timeZone, }; return [["‣", [["d", dateValue]]]]; } /** Validate a calendar date before serializing it. */ function parseIsoDate(value: string): string { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); if (match === null) { throw new Error("Invalid ISO 8601 date: " + value + ". Expected format like 2024-01-15"); } if (!isValidCalendarDate(match) || Number.isNaN(new Date(value).getTime())) { throw new Error("Invalid date: " + value); } return value; } /** Validate and split an ISO datetime before serializing it. */ function parseIsoDatetime(value: string): { date: string; time: string } { const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(value); if (match === null) { throw new Error( "Invalid ISO 8601 datetime: " + value + ". Expected format like 2024-01-15T10:30 or 2024-01-15T10:30:00Z", ); } if ( !isValidCalendarDate(match) || Number(match[4]) > 23 || Number(match[5]) > 59 || Number.isNaN(new Date(value).getTime()) ) { throw new Error("Invalid datetime: " + value); } return { date: value.slice(0, 10), time: value.slice(11, 16) }; } /** Verify that parsed date components form a real calendar date. */ function isValidCalendarDate(match: RegExpExecArray): boolean { const year = Number(match[1] ?? ""); const month = Number(match[2] ?? ""); const day = Number(match[3] ?? ""); const date = new Date(Date.UTC(year, month - 1, day)); return ( date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day ); }