import { DateTime } from "luxon"; import { DEFAULT_TIMEZONE } from "@cmbot/shared"; export type Quick = "now" | "tomorrow_9am" | "next_mon_9am"; export function quickToDate(quick: Quick, timezone: string = DEFAULT_TIMEZONE): Date { const now = DateTime.now().setZone(timezone); switch (quick) { case "now": // Add 30s so pg-boss has time to schedule + the system has time to dispatch return now.plus({ seconds: 30 }).toJSDate(); case "tomorrow_9am": return now.plus({ days: 1 }).set({ hour: 9, minute: 0, second: 0, millisecond: 0 }).toJSDate(); case "next_mon_9am": { const dow = now.weekday; // 1 = Mon const daysUntilMon = ((1 - dow + 7) % 7) || 7; return now.plus({ days: daysUntilMon }).set({ hour: 9, minute: 0, second: 0, millisecond: 0 }).toJSDate(); } } } /** * Build a Date from a day-offset (days from today, in the operator's timezone), * an hour (0-23) and a minute (0-59). Returns the JS Date or null if the * resulting time is in the past. */ export function buildCustomDate( dayOffset: number, hour: number, minute: number, timezone: string = DEFAULT_TIMEZONE, ): { ok: true; date: Date } | { ok: false; reason: string } { const target = DateTime.now() .setZone(timezone) .plus({ days: dayOffset }) .set({ hour, minute, second: 0, millisecond: 0 }); if (!target.isValid) { return { ok: false, reason: "Invalid date" }; } const jsDate = target.toJSDate(); if (jsDate.getTime() <= Date.now()) { return { ok: false, reason: "Time is in the past" }; } return { ok: true, date: jsDate }; } export function formatCustomDay(dayOffset: number, timezone: string = DEFAULT_TIMEZONE): string { const dt = DateTime.now().setZone(timezone).plus({ days: dayOffset }); if (dayOffset === 0) return `Today (${dt.toFormat("EEE dd MMM")})`; if (dayOffset === 1) return `Tomorrow (${dt.toFormat("EEE dd MMM")})`; return dt.toFormat("EEE dd MMM"); } export type ParseResult = { ok: true; date: Date } | { ok: false; reason: string }; const FORMATS = [ "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm", "dd/MM/yyyy HH:mm", "dd-MM-yyyy HH:mm", ]; export function parseFreeText(input: string, timezone: string = DEFAULT_TIMEZONE): ParseResult { const trimmed = input.trim(); for (const fmt of FORMATS) { const dt = DateTime.fromFormat(trimmed, fmt, { zone: timezone }); if (dt.isValid) { const jsDate = dt.toJSDate(); if (jsDate.getTime() <= Date.now()) { return { ok: false, reason: "Time is in the past" }; } return { ok: true, date: jsDate }; } } return { ok: false, reason: "Couldn't parse — try YYYY-MM-DD HH:MM (e.g. 2026-05-15 09:00)" }; }