// Plain "HH:MM" (24-hour) wall-clock time-of-day helpers for a // ScheduledTodo's optional time due. Deliberately just a string, not a // Date -- there's no timezone or calendar date attached until it's paired // with one occurrence's date key (see isTimeDuePast). This is local wall // time, not UTC -- unlike lib/dates.ts, which is calendar-date math the // server and client must agree on byte-for-byte. const TIME_DUE_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; export function isValidTimeDue(value: string): boolean { return TIME_DUE_REGEX.test(value); } /** "17:05" -> "5:05 PM", for display next to a scheduled occurrence. */ export function formatTimeDue(hhmm: string): string { const [hours, minutes] = hhmm.split(":").map(Number); const period = hours < 12 ? "AM" : "PM"; const hour12 = hours % 12 === 0 ? 12 : hours % 12; return `${hour12}:${String(minutes).padStart(2, "0")} ${period}`; } /** * True once the viewer's local wall clock has passed `hhmm` on the local * calendar day named by `dateKey` ("YYYY-MM-DD") -- used to promote * today's own occurrence into Overdue as soon as its time due passes, * without waiting for the date to roll over. Always evaluated client-side: * the server doesn't know the viewer's timezone, so it only buckets by * date (see lib/scheduled-todos.ts) and leaves same-day time comparisons * to the browser's own clock. `now` defaults to the real clock but is * injectable for tests. */ export function isTimeDuePast(dateKey: string, hhmm: string, now: Date = new Date()): boolean { const due = new Date(`${dateKey}T${hhmm}:00`); // no "Z" -- local time return now.getTime() > due.getTime(); } // The "Remind me" dropdown's choices -- lead time before `timeDue` to fire // a notification (see use-scheduled-notifications.ts). `minutesBefore: // null` is "Don't remind me" -- a to-do can have a time due with no // reminder at all, distinct from "When due" (0 minutes early). export const REMIND_OPTIONS: { value: string; label: string; minutesBefore: number | null }[] = [ { value: "none", label: "Don't remind me", minutesBefore: null }, { value: "0", label: "When due", minutesBefore: 0 }, { value: "5", label: "5 minutes before", minutesBefore: 5 }, { value: "10", label: "10 minutes before", minutesBefore: 10 }, { value: "15", label: "15 minutes before", minutesBefore: 15 }, { value: "30", label: "30 minutes before", minutesBefore: 30 }, { value: "60", label: "1 hour before", minutesBefore: 60 }, { value: "120", label: "2 hours before", minutesBefore: 120 }, { value: "1440", label: "1 day before", minutesBefore: 1440 }, ]; /** True once the viewer's local wall clock has reached `minutesBefore` * minutes ahead of `hhmm` on `dateKey` -- i.e. it's time to fire the * reminder. Distinct from isTimeDuePast: a reminder can (and typically * does) fire before the to-do is actually due. */ export function isReminderDue( dateKey: string, hhmm: string, minutesBefore: number, now: Date = new Date() ): boolean { const due = new Date(`${dateKey}T${hhmm}:00`); // no "Z" -- local time return now.getTime() >= due.getTime() - minutesBefore * 60_000; }