import { parseISO, parse, format, isValid } from 'date-fns'; import { formatInTimeZone } from 'date-fns-tz'; import { Timezone } from './timezone'; import { setDefaultDateFnLocale } from '@/i18n'; setDefaultDateFnLocale(); const DATE_FORMAT_TINY = 'dd MMM yy'; const DATE_FORMAT_TINY_PLACEHOLDER = '00 000 00'; const DATE_FORMAT_SHORT = 'dd MMM yyyy'; const DATE_FORMAT_SHORT_PLACEHOLDER = '00 000 0000'; const DATE_FORMAT_MEDIUM = 'dd MMM yy h:mma'; const DATE_FORMAT_MEDIUM_PLACEHOLDER = '00 000 00 00:0000'; const DATE_FORMAT_LONG = 'dd MMM yyyy h:mma'; const DATE_FORMAT_LONG_PLACEHOLDER = '00 000 0000 00:0000'; const DATE_FORMAT_RSS = 'EEE, dd LLL yyyy HH:mm:ss xx'; const DATE_FORMAT_RSS_PLACEHOLDER = '000, 00 000 0000 00:00:00 00'; const DATE_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss'; export const VALIDATION_EXAMPLE_POSTGRES = '2025-01-03T21:00:44.000Z'; export const VALIDATION_EXAMPLE_POSTGRES_NAIVE = '2025-01-03 16:00:44'; type AmbiguousTimestamp = number | string; type Length = 'tiny' | 'short' | 'medium' | 'long' | 'rss'; export const formatDate = ({ date, length = 'long', timezone, hideTime, showPlaceholder, }: { date: Date, length?: Length, timezone?: Timezone, hideTime?: boolean, showPlaceholder?: boolean, }) => { let formatString = !hideTime ? DATE_FORMAT_LONG : DATE_FORMAT_SHORT; let placeholderString = !hideTime ? DATE_FORMAT_LONG_PLACEHOLDER : DATE_FORMAT_SHORT_PLACEHOLDER; switch (length) { case 'rss': formatString = DATE_FORMAT_RSS; placeholderString = DATE_FORMAT_RSS_PLACEHOLDER; break; case 'tiny': formatString = DATE_FORMAT_TINY; placeholderString = DATE_FORMAT_TINY_PLACEHOLDER; break; case 'short': formatString = DATE_FORMAT_SHORT; placeholderString = DATE_FORMAT_SHORT_PLACEHOLDER; break; case 'medium': formatString = !hideTime ? DATE_FORMAT_MEDIUM : DATE_FORMAT_SHORT; placeholderString = !hideTime ? DATE_FORMAT_MEDIUM_PLACEHOLDER : DATE_FORMAT_SHORT_PLACEHOLDER; break; } return showPlaceholder ? placeholderString : timezone ? formatInTimeZone(date, timezone, formatString) : format(date, formatString); }; export const formatDateFromPostgresString = ( date: string, length?: Length, ) => formatDate({ date: parse(date, DATE_FORMAT_POSTGRES, new Date()), length, }); export const formatDateForPostgres = (date: Date) => date.toISOString().replace( /(\d{4}):(\d{2}):(\d{2}) (\d{2}:\d{2}:\d{2})/, '$1-$2-$3 $4', ); const dateFromTimestamp = (timestamp?: AmbiguousTimestamp): Date => { const date = typeof timestamp === 'number' ? new Date(timestamp * 1000) : typeof timestamp === 'string' ? /.+Z/i.test(timestamp) ? new Date(timestamp) : new Date(`${timestamp}Z`) : timestamp; return isValid(date) ? date as Date : new Date(); }; const createNaiveDateWithOffset = ( timestamp?: AmbiguousTimestamp, offset = '+00:00', ) => { const date = dateFromTimestamp(timestamp); const dateString = `${date.toISOString()}`.replace(/\.[\d]+Z/, offset); return parseISO(dateString); }; // Run on the server, when there are date/timestamp/offset inputs export const convertTimestampWithOffsetToPostgresString = ( timestamp?: AmbiguousTimestamp, offset?: string, ) => formatDateForPostgres(createNaiveDateWithOffset(timestamp, offset)); export const convertTimestampToNaivePostgresString = ( timestamp?: AmbiguousTimestamp, ) => dateFromTimestamp(timestamp) .toISOString().replace( /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(.[\d]+Z)*/, '$1 $2', ); // Run in browser to generate local date time strings export const generateLocalPostgresString = () => formatDateForPostgres(new Date()); export const generateLocalNaivePostgresString = () => format(new Date(), DATE_FORMAT_POSTGRES); // Form validation to prevent Postgres runtime errors // POSTGRES: 2025-01-03T21:00:44.000Z export const validatePostgresDateString = (date = ''): boolean => /^(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}:\d{2}(.[\d]+)*Z$/.test(date); export const validationMessagePostgresDateString = (date = '') => validatePostgresDateString(date) ? undefined : `Invalid format (${VALIDATION_EXAMPLE_POSTGRES})`; // NAIVE: 2025-01-03 16:00:44 export const validateNaivePostgresDateString = (date = ''): boolean => /^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}$/.test(date); export const validationMessageNaivePostgresDateString = (date = '') => validateNaivePostgresDateString(date) ? undefined : `Invalid format (${VALIDATION_EXAMPLE_POSTGRES_NAIVE})`;