Files
igny8/frontend/src/utils/date.ts
2026-01-12 23:25:47 +00:00

123 lines
3.9 KiB
TypeScript

/**
* Global Date Formatting Utility
* Formats dates to relative time strings (today, yesterday, etc.)
* Usage: formatRelativeDate('2025-01-15') or formatRelativeDate(new Date())
*/
export function formatRelativeDate(dateString: string | Date): string {
if (!dateString) {
return 'Today';
}
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
// Check if date is valid
if (isNaN(date.getTime())) {
return 'Today';
}
const now = new Date();
// Set time to midnight for both dates to compare days only
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const diffTime = today.getTime() - dateOnly.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
// Handle future dates (negative diffDays)
if (diffDays < 0) {
const futureDays = Math.abs(diffDays);
if (futureDays === 1) {
return 'Tomorrow';
} else if (futureDays < 30) {
return `in ${futureDays} days`;
} else if (futureDays < 365) {
const months = Math.floor(futureDays / 30);
const remainingDays = futureDays % 30;
if (remainingDays === 0) {
return `in ${months} month${months > 1 ? 's' : ''}`;
} else {
return `in ${months} month${months > 1 ? 's' : ''} ${remainingDays} day${remainingDays > 1 ? 's' : ''}`;
}
} else {
const years = Math.floor(futureDays / 365);
const remainingMonths = Math.floor((futureDays % 365) / 30);
if (remainingMonths === 0) {
return `in ${years} year${years > 1 ? 's' : ''}`;
} else {
return `in ${years} year${years > 1 ? 's' : ''} ${remainingMonths} month${remainingMonths > 1 ? 's' : ''}`;
}
}
}
// Handle past dates (positive diffDays)
if (diffDays === 0) {
return 'Today';
} else if (diffDays === 1) {
return 'Yesterday';
} else if (diffDays < 30) {
return `${diffDays} days ago`;
} else if (diffDays < 365) {
const months = Math.floor(diffDays / 30);
const remainingDays = diffDays % 30;
if (remainingDays === 0) {
return `${months} month${months > 1 ? 's' : ''} ago`;
} else {
return `${months} month${months > 1 ? 's' : ''} ${remainingDays} day${remainingDays > 1 ? 's' : ''} ago`;
}
} else {
const years = Math.floor(diffDays / 365);
const remainingMonths = Math.floor((diffDays % 365) / 30);
if (remainingMonths === 0) {
return `${years} year${years > 1 ? 's' : ''} ago`;
} else {
return `${years} year${years > 1 ? 's' : ''} ${remainingMonths} month${remainingMonths > 1 ? 's' : ''} ago`;
}
}
}
/**
* Format date to a standard display format
* @param dateString - ISO date string or Date object
* @param options - Intl.DateTimeFormat options
* @returns Formatted date string (e.g., "Jan 7, 2026")
*/
export function formatDate(
dateString: string | Date | null | undefined,
options: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' }
): string {
if (!dateString) return '-';
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
if (isNaN(date.getTime())) return '-';
return date.toLocaleDateString('en-US', options);
}
/**
* Format date and time to a standard display format
* @param dateString - ISO date string or Date object
* @returns Formatted date and time string (e.g., "Jan 7, 2026, 3:30 PM")
*/
export function formatDateTime(
dateString: string | Date | null | undefined
): string {
if (!dateString) return '-';
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
if (isNaN(date.getTime())) return '-';
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}