51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
/**
|
||
* Date/time formatting for the Today tab.
|
||
*
|
||
* Calendar dates ("YYYY-MM-DD") are parsed by their components and formatted
|
||
* in UTC, so the printed day never shifts with the viewer's offset — unlike
|
||
* `new Date("YYYY-MM-DD")`, which is UTC midnight and reads as the previous
|
||
* day in negative-offset zones.
|
||
*/
|
||
|
||
const LOCALE = "en-US";
|
||
|
||
function calendarDate(iso: string): Date {
|
||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
||
if (!match) throw new Error(`Invalid calendar date: ${iso}`);
|
||
const [, year, month, day] = match;
|
||
return new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
|
||
}
|
||
|
||
function format(date: Date, options: Intl.DateTimeFormatOptions): string {
|
||
// Newer ICU puts a narrow no-break space before AM/PM; normalise it.
|
||
return new Intl.DateTimeFormat(LOCALE, options)
|
||
.format(date)
|
||
.replace(/ /g, " ");
|
||
}
|
||
|
||
/** "Wednesday, September 23" */
|
||
export function formatLongDate(iso: string): string {
|
||
const date = calendarDate(iso);
|
||
const weekday = format(date, { weekday: "long", timeZone: "UTC" });
|
||
const monthDay = format(date, { month: "long", day: "numeric", timeZone: "UTC" });
|
||
return `${weekday}, ${monthDay}`;
|
||
}
|
||
|
||
/** "Sep 23" */
|
||
export function formatShortDate(iso: string): string {
|
||
return format(calendarDate(iso), { month: "short", day: "numeric", timeZone: "UTC" });
|
||
}
|
||
|
||
/** "09:00" -> "9:00 AM" (a wall-clock time, no zone conversion). */
|
||
export function formatDueTime(hhmm: string): string {
|
||
const match = /^(\d{2}):(\d{2})/.exec(hhmm);
|
||
if (!match) return hhmm;
|
||
const date = new Date(Date.UTC(2000, 0, 1, Number(match[1]), Number(match[2])));
|
||
return format(date, { hour: "numeric", minute: "2-digit", timeZone: "UTC" });
|
||
}
|
||
|
||
/** A UTC ISO timestamp shown as the viewer's local time, "7:04 AM". */
|
||
export function formatClockTime(isoTimestamp: string): string {
|
||
return format(new Date(isoTimestamp), { hour: "numeric", minute: "2-digit" });
|
||
}
|