66 lines
3.1 KiB
TypeScript
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;
|
|
}
|