import dayjs from 'dayjs'; // Format pairs: [24h base, 12h variant]. Only formats with both variants are listed. // Formats without a counterpart (ISO, date-only, week, etc.) are intentionally omitted — // iso_datetime_formatter passes those through unchanged regardless of use_12h. const FORMAT_PAIRS: [string, string][] = [ ['datetime_iso_no_seconds', 'datetime_iso_12_no_seconds'], ['datetime_short', 'datetime_12_short'], ['datetime_medium', 'datetime_12_medium'], ['datetime_long', 'datetime_12_long'], ['datetime_medium_sec', 'datetime_12_medium_sec'], ['time_long', 'time_12_long'], ['time_short', 'time_12_short'], ['time_short_no_leading', 'time_12_short_no_leading'], ]; // Build lookup maps from the pairs above. Both directions are derived from the same source. const TO_12H: Record = Object.fromEntries( FORMAT_PAIRS.map(([h24, h12]) => [h24, h12]) ); const TO_24H: Record = Object.fromEntries( FORMAT_PAIRS.map(([h24, h12]) => [h12, h24]) ); export const iso_datetime_formatter = function iso_datetime_formatter( raw_datetime: null | string | Date = null, named_format: string = 'datetime_iso_no_seconds', // date_iso, datetime_iso_no_seconds // Pass true/false to resolve to the correct 12h or 24h variant automatically. // null (default) leaves named_format unchanged — all existing call sites unaffected. use_12h: boolean | null = null, // When true, treats a naive datetime string (no Z / offset) as UTC so dayjs // converts it to local browser time on display. Use for timestamps stored as // UTC in the DB but returned without a timezone indicator. treat_as_utc: boolean = false ) { // console.log('*** iso_datetime_formatter() ***'); // https://en.wikipedia.org/wiki/ISO_8601 // https://day.js.org/docs/en/display/format // ISO 8601-1:2019 includes the T before the time portion // ISO 8601-1:2019 midnight may only be referred to as "00:00", corresponding to the beginning of a calendar day // and "24:00" is no longer allowed corresponding to the end of a day // 60 is only used to denote an added leap second // ISO 8601 UTC: 2021-03-04T19:04:44+00:00 // ISO 8601 UTC: 2021-03-04T19:04:44Z // ISO 8601 UTC: 20210304T190444Z // datetime_iso 'YYYY-MM-DD HH:mm:ss' // datetime_iso_12 'YYYY-MM-DD hh:mm:ss A' // datetime_iso_12_short 'YY-MM-DD hh:mm A' // datetime_iso_tz 'YYYY-MM-DD HH:mm:ss Z' // datetime_12_no_seconds 'YYYY-MM-DD hh:mm A' // datetime_long 'dddd, MMMM D, YYYY hh:mm:ss A' // datetime_medium 'ddd, MMM D, YYYY hh:mm:ss A' // datetime_short 'MMM D, YY hh:mm A' // date_iso 'YYYY-MM-DD' // date_long 'dddd, MMMM D, YYYY' // date_medium 'ddd, MMM D, YYYY' // date_short 'MMM D, YY' // time_iso 'HH:mm:ss' // time_iso_12 'hh:mm:ss A' // time_iso_tz 'HH:mm:ss Z' // time_iso_12_tz 'hh:mm:ss A Z' // time_long 'hh:mm:ss A' // time_medium 'h:m:s A' // time_short 'hh:mm A' // dayjs(raw_datetime).format('dddd, MMMM D, YYYY hh:mm:ss A'); if (!raw_datetime) { raw_datetime = new Date(); // Get the current datetime if one was not passed. } // Append 'Z' to naive UTC strings so dayjs converts to local browser time. // Guards against double-appending if the backend ever adds timezone info. if (treat_as_utc && typeof raw_datetime === 'string' && !raw_datetime.match(/Z$|[+-]\d{2}:?\d{2}$/)) { raw_datetime = raw_datetime + 'Z'; } if (use_12h !== null) { named_format = use_12h ? (TO_12H[named_format] ?? named_format) : (TO_24H[named_format] ?? named_format); } let datetime_string = null; switch (named_format) { case 'datetime_iso': datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD HH:mm:ss'); break; case 'datetime_iso_no_seconds': datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD HH:mm'); break; case 'datetime_iso_12_short': datetime_string = dayjs(raw_datetime).format('YY-MM-DD hh:mm A'); break; case 'datetime_iso_12_short_month': datetime_string = dayjs(raw_datetime).format('MM-DD hh:mm A'); break; case 'datetime_iso_tz': datetime_string = dayjs(raw_datetime).format( 'YYYY-MM-DD HH:mm:ss Z' ); break; case 'datetime_iso_12_no_seconds': datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD hh:mm A'); break; // case 'datetime_12_no_seconds': // datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD hh:mm A'); // break; case 'datetime_us': datetime_string = dayjs(raw_datetime).format( 'MM/DD/YYYY hh:mm:ss A' ); break; case 'datetime_short': datetime_string = dayjs(raw_datetime).format('MMM D, YY HH:mm'); break; case 'datetime_12_short': datetime_string = dayjs(raw_datetime).format('MMM D, YY hh:mm A'); break; case 'datetime_medium': datetime_string = dayjs(raw_datetime).format('MMM D, YYYY H:mm'); break; case 'datetime_12_medium': datetime_string = dayjs(raw_datetime).format('MMM D, YYYY h:mm A'); break; case 'datetime_long': datetime_string = dayjs(raw_datetime).format('MMMM D, YYYY HH:mm'); break; case 'datetime_12_long': datetime_string = dayjs(raw_datetime).format( 'MMMM D, YYYY hh:mm A' ); break; case 'datetime_medium_sec': datetime_string = dayjs(raw_datetime).format('MMM D, YYYY H:mm:ss'); break; case 'datetime_12_medium_sec': datetime_string = dayjs(raw_datetime).format( 'MMM D, YYYY h:mm:ss A' ); break; case 'datetime_short_month': datetime_string = dayjs(raw_datetime).format('MMM D hh:mm A'); break; case 'datetime_long_month': datetime_string = dayjs(raw_datetime).format('MMMM D hh:mm A'); break; case 'datetime_short_day': datetime_string = dayjs(raw_datetime).format('D hh:mm A'); break; case 'date_iso': datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD'); break; case 'date_us': datetime_string = dayjs(raw_datetime).format('MM/DD/YYYY'); break; case 'date_long_month_day': datetime_string = dayjs(raw_datetime).format('MMMM D'); break; case 'date_short': datetime_string = dayjs(raw_datetime).format('MMM D, YY'); break; case 'date_short_no_year': datetime_string = dayjs(raw_datetime).format('MMM D'); break; case 'date_long': datetime_string = dayjs(raw_datetime).format('MMMM D, YYYY'); break; case 'date_full': datetime_string = dayjs(raw_datetime).format('dddd, MMMM D, YYYY'); break; case 'date_full_no_year': datetime_string = dayjs(raw_datetime).format('dddd, MMMM D'); break; case 'time_iso': datetime_string = dayjs(raw_datetime).format('HH:mm:ss'); break; case 'time_iso_12_tz': datetime_string = dayjs(raw_datetime).format('hh:mm:ss A Z'); break; case 'time_long': datetime_string = dayjs(raw_datetime).format('HH:mm:ss'); break; case 'time_12_long': datetime_string = dayjs(raw_datetime).format('hh:mm:ss A'); break; case 'time_short': datetime_string = dayjs(raw_datetime).format('HH:mm'); break; case 'time_short_no_leading': datetime_string = dayjs(raw_datetime).format('H:mm'); break; case 'time_12_short': datetime_string = dayjs(raw_datetime).format('hh:mm A'); break; case 'time_12_short_no_leading': datetime_string = dayjs(raw_datetime).format('h:mm A'); break; case 'week_long': datetime_string = dayjs(raw_datetime).format('dddd'); break; case 'week_medium': datetime_string = dayjs(raw_datetime).format('ddd'); break; case 'week_short': datetime_string = dayjs(raw_datetime).format('dd'); break; default: // console.log(`The named format passed (${named_format}) did not match a common name. Trying to format with the named format value.`); datetime_string = dayjs(raw_datetime).format(named_format); // datetime_string = dayjs(raw_datetime).format('YYYY-MM-DD HH:mm:ss'); } return datetime_string; };