Files
publisher-dashboard/packages/common/src/format-date.ts
igm 2baf10b0cd Replace String() calls with .toString()/.toLocaleString() per ast-grep rule
- Add formatError() helper in CLI to safely handle unknown error types
- Add uniqueTestId() helper for generating unique test identifiers
- Replace String(id) with id.toString() for database ID conversions
- Replace String(n) with n.toLocaleString() for user-facing number formatting
- Fix TypeScript errors in test files (undefined checks, unused variables)
- Update lint commands to include ast-grep scanning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 15:02:46 +08:00

129 lines
3.3 KiB
TypeScript

/**
* Date formatting utilities for consistent display across the app.
* Works in all JavaScript environments (browser, Node.js, Bun, etc.)
*/
type DateInput = string | Date;
/**
* Safely convert a date input to a Date object.
*/
function toDate(date: DateInput): Date {
return typeof date === "string" ? new Date(date) : date;
}
/**
* Calculate the difference in days between two dates.
*/
function daysDiff(from: Date, to: Date): number {
const diffMs = to.getTime() - from.getTime();
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
}
/**
* Format a date for display in tables and lists.
* @example formatDate("2024-01-15") // "Jan 15, 2024"
*/
export function formatDate(date: DateInput): string {
const d = toDate(date);
return d.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}
/**
* Format a date with time for detailed views.
* @example formatDateTime("2024-01-15T15:30:00") // "Jan 15, 2024, 3:30 PM"
*/
export function formatDateTime(date: DateInput): string {
const d = toDate(date);
return d.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
/**
* Format a date in long form.
* @example formatLongDate("2024-01-15") // "January 15, 2024"
*/
export function formatLongDate(date: DateInput): string {
const d = toDate(date);
return d.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
/**
* Options for relative date formatting.
*/
export interface FormatRelativeDateOptions {
/**
* Reference date to compare against. Defaults to current date.
*/
now?: Date;
}
/**
* Format a date as a relative time string.
* @example
* formatRelativeDate("2024-01-15") // "Today" (if today is Jan 15)
* formatRelativeDate("2024-01-14") // "Yesterday" (if today is Jan 15)
* formatRelativeDate("2024-01-10") // "5 days ago" (if today is Jan 15)
* formatRelativeDate("2024-01-01") // "2 weeks ago" (if today is Jan 15)
* formatRelativeDate("2023-06-15") // "Jun 15, 2023" (older dates)
*/
export function formatRelativeDate(
date: DateInput,
options?: FormatRelativeDateOptions,
): string {
const d = toDate(date);
const now = options?.now ?? new Date();
const diffDays = daysDiff(d, now);
if (diffDays === 0) {
return "Today";
}
if (diffDays === 1) {
return "Yesterday";
}
if (diffDays < 7) {
return `${diffDays.toLocaleString()} days ago`;
}
if (diffDays < 30) {
const weeks = Math.floor(diffDays / 7);
return weeks === 1 ? "1 week ago" : `${weeks.toLocaleString()} weeks ago`;
}
// For older dates, show the actual date
return d.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
});
}
/**
* Format a date as a relative time string, with "Never" for null values.
* Useful for displaying "last used" timestamps.
* @example
* formatRelativeTime("2024-01-15") // "Today"
* formatRelativeTime(null) // "Never"
*/
export function formatRelativeTime(
date: DateInput | null | undefined,
options?: FormatRelativeDateOptions,
): string {
if (date === null || date === undefined) {
return "Never";
}
return formatRelativeDate(date, options);
}