Organize/lib/time-of-day.ts

66 lines
3.1 KiB
TypeScript

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