feat: refactor date formatting and timezone utilities for improved consistency and functionality
Some checks failed
Frontend CI / lint (push) Failing after 19s
Frontend CI / build (push) Has been skipped

This commit is contained in:
JSC
2025-08-15 23:43:17 +02:00
parent cd654b8777
commit 9cfa1f6a28
9 changed files with 148 additions and 45 deletions

115
src/utils/format-date.ts Normal file
View File

@@ -0,0 +1,115 @@
import { formatDistanceToNow } from 'date-fns';
/**
* Parse and optionally convert a date string to a Date object with timezone handling
* @param dateString - The date string to parse
* @param isUTC - Whether to convert from UTC to local timezone (default: true)
* @returns Processed Date object or null if invalid
*/
function parseAndConvertDate(dateString: string, isUTC: boolean = true): Date | null {
try {
// If isUTC is true and the date string doesn't have timezone info, treat it as UTC
let dateToProcess = dateString;
if (isUTC && !dateString.endsWith('Z') && !dateString.includes('+') && !dateString.includes('-', 10)) {
dateToProcess = `${dateString}Z`;
}
const date = new Date(dateToProcess);
if (isNaN(date.getTime())) {
return null;
}
if (!isUTC) {
return date;
}
// Get timezone from localStorage, default to Europe/Paris
const timezone = localStorage.getItem('timezone') || 'Europe/Paris';
// Format the date in the target timezone
const formatter = new Intl.DateTimeFormat('fr-FR', {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
const parts = formatter.formatToParts(date);
const partsObj = parts.reduce((acc, part) => {
acc[part.type] = part.value;
return acc;
}, {} as Record<string, string>);
// Create new date object in the target timezone
return new Date(
parseInt(partsObj.year),
parseInt(partsObj.month) - 1, // Month is 0-indexed
parseInt(partsObj.day),
parseInt(partsObj.hour),
parseInt(partsObj.minute),
parseInt(partsObj.second)
);
} catch (error) {
console.error('Error parsing date:', error);
return null;
}
}
/**
* Format a date string to DD/MM/YYYY HH:MM:SS or DD/MM/YYYY
* @param dateString - The date string to format
* @param withTime - Whether to include time in the output (default: true)
* @param isUTC - Whether to convert from UTC to local timezone (default: true)
* @returns Formatted date string
*/
export function formatDate(
dateString: string,
withTime: boolean = true,
isUTC: boolean = true
): string {
const date = parseAndConvertDate(dateString, isUTC);
if (!date) {
return 'Invalid Date';
}
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear().toString();
const dateFormatted = `${day}/${month}/${year}`;
if (!withTime) {
return dateFormatted;
}
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${dateFormatted} ${hours}:${minutes}:${seconds}`;
}
/**
* Format a date string to show distance to now (e.g., "2 hours ago", "3 days ago")
* @param dateString - The date string to format
* @param isUTC - Whether to convert from UTC to local timezone (default: true)
* @returns Formatted distance string (e.g., "2 hours ago")
*/
export function formatDateDistanceToNow(
dateString: string,
isUTC: boolean = true
): string {
const date = parseAndConvertDate(dateString, isUTC);
if (!date) {
return 'Invalid Date';
}
return formatDistanceToNow(date, { addSuffix: true });
}

14
src/utils/locale.ts Normal file
View File

@@ -0,0 +1,14 @@
// Get supported timezones, fallback to basic list if Intl.supportedValuesOf is not available
export const getSupportedTimezones = (): string[] => {
try {
if (typeof Intl !== 'undefined' && 'supportedValuesOf' in Intl) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (Intl as any).supportedValuesOf('timeZone')
}
} catch {
console.warn('Intl.supportedValuesOf not available, using fallback timezones')
}
// Fallback timezone list
return ['America/New_York', 'Europe/Paris']
}