The backend returns naive UTC datetime strings (no Z suffix). dayjs was treating them as local time, so staff in ET saw UTC times. Added treat_as_utc param to iso_datetime_formatter (default false, zero breakage to existing call sites) that appends Z before parsing so dayjs converts to browser-local time on display. Applied to all print timestamp call sites in badge list (first/last print visible row + debug row) and to created_on/updated_on in the debug row. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
225 lines
8.9 KiB
TypeScript
225 lines
8.9 KiB
TypeScript
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<string, string> = Object.fromEntries(
|
|
FORMAT_PAIRS.map(([h24, h12]) => [h24, h12])
|
|
);
|
|
const TO_24H: Record<string, string> = 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;
|
|
};
|