98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
/** Returns the ISO date string (YYYY-MM-DD) of the Monday for the week containing `date`. */
|
||
export function getWeekStart(date: Date): string {
|
||
const d = new Date(date);
|
||
const day = d.getDay(); // 0=Sun, 1=Mon, ...
|
||
const diff = day === 0 ? -6 : 1 - day;
|
||
d.setDate(d.getDate() + diff);
|
||
return formatDate(d);
|
||
}
|
||
|
||
/** Adds `n` weeks to a week-start date string and returns the new week-start. */
|
||
export function addWeeks(weekStart: string, n: number): string {
|
||
const d = parseDate(weekStart);
|
||
d.setDate(d.getDate() + n * 7);
|
||
return formatDate(d);
|
||
}
|
||
|
||
/** Adds `n` days to a date string and returns the new date. */
|
||
export function addDays(dateStr: string, n: number): string {
|
||
const d = parseDate(dateStr);
|
||
d.setDate(d.getDate() + n);
|
||
return formatDate(d);
|
||
}
|
||
|
||
/** Returns an array of 7 ISO date strings for the week starting at `weekStart` (Monday). */
|
||
export function getWeekDays(weekStart: string): string[] {
|
||
const d = parseDate(weekStart);
|
||
const days: string[] = [];
|
||
for (let i = 0; i < 7; i++) {
|
||
const day = new Date(d);
|
||
day.setDate(d.getDate() + i);
|
||
days.push(formatDate(day));
|
||
}
|
||
return days;
|
||
}
|
||
|
||
/** Returns a human-readable heading like "Monday, April 7th". */
|
||
export function formatDayHeading(dateStr: string): string {
|
||
const d = parseDate(dateStr);
|
||
const weekday = d.toLocaleDateString(undefined, { weekday: "long" });
|
||
const month = d.toLocaleDateString(undefined, { month: "long" });
|
||
const day = d.getDate();
|
||
return `${weekday}, ${month} ${ordinal(day)}`;
|
||
}
|
||
|
||
/** Returns a short day label like "Mon" and day number like "7". */
|
||
export function formatShortDay(dateStr: string): { label: string; day: number } {
|
||
const d = parseDate(dateStr);
|
||
return {
|
||
label: d.toLocaleDateString(undefined, { weekday: "short" }),
|
||
day: d.getDate(),
|
||
};
|
||
}
|
||
|
||
/** Returns the month + year label like "April 2026". */
|
||
export function formatMonthYear(dateStr: string): string {
|
||
const d = parseDate(dateStr);
|
||
return d.toLocaleDateString(undefined, { month: "long", year: "numeric" });
|
||
}
|
||
|
||
/** Compact range for a Mon–Sun week, e.g. "Apr 6 – 12, 2026" or spanning two months. */
|
||
export function formatWeekRangeLabel(weekStart: string): string {
|
||
const start = parseDate(weekStart);
|
||
const end = new Date(start);
|
||
end.setDate(end.getDate() + 6);
|
||
const sameMonth =
|
||
start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear();
|
||
if (sameMonth) {
|
||
const month = start.toLocaleDateString(undefined, { month: "short" });
|
||
return `${month} ${start.getDate()} – ${end.getDate()}, ${start.getFullYear()}`;
|
||
}
|
||
return `${start.toLocaleDateString(undefined, { month: "short", day: "numeric" })} – ${end.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })}`;
|
||
}
|
||
|
||
/** Checks if a date string is today. */
|
||
export function isToday(dateStr: string): boolean {
|
||
return dateStr === formatDate(new Date());
|
||
}
|
||
|
||
/** Formats a Date to YYYY-MM-DD. */
|
||
function formatDate(d: Date): string {
|
||
const year = d.getFullYear();
|
||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||
const day = String(d.getDate()).padStart(2, "0");
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
|
||
/** Parses a YYYY-MM-DD string to a local Date. */
|
||
function parseDate(dateStr: string): Date {
|
||
const parts = dateStr.split("-").map(Number);
|
||
return new Date(parts[0]!, parts[1]! - 1, parts[2]!);
|
||
}
|
||
|
||
function ordinal(n: number): string {
|
||
const s = ["th", "st", "nd", "rd"] as const;
|
||
const v = n % 100;
|
||
return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]);
|
||
}
|