Files
emaui/libs/shared/src/lib/date/date-displayer.ts
estifanos 9897a9bf78 feat: implement date display utility across various pages
- Added `useDateDisplayer` hook and `dateDisplayer` function to format dates consistently based on the user's language.
- Updated multiple components and pages in both backoffice and portal applications to utilize the new date display functionality, ensuring proper formatting for dates in lists, tables, and detail views.
- Introduced Ethiopian date formatting for Amharic language support.
- Refactored date handling in components such as ExamAppealsPage, ResultPage, SeafarerRegistryPage, and others to improve localization and user experience.
2026-08-12 10:22:39 +00:00

58 lines
2.1 KiB
TypeScript

import { ethTimeLabel, toAmharicDisplay } from './ethiopic';
/** Wire values carrying no time of day, e.g. a date column serialised as `2026-08-10`. */
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
/** Midnight UTC — how backends commonly serialise a plain date column. */
const UTC_MIDNIGHT = /T00:00:00(\.0+)?Z$/;
/**
* The one way a date is shown to a user.
*
* Timestamps render in the viewer's local timezone — `2026-08-10T06:57:34.507Z`
* reads as `Aug 10, 2026 9:57am` in Addis. Values with no real time of day
* render as a bare date: a birth date or a licence expiry stamped `12:00am`
* reads as precision the data does not have. Those are also formatted in UTC,
* because parsing `2026-08-10` yields UTC midnight, and converting that to a
* behind-UTC local timezone would show the previous day.
*
* `language` is a parameter rather than read from i18next because each app runs
* a DEDICATED i18n instance, not the global singleton (see `app/i18n/config.ts`)
* — the same reason `localized()` in licensing.helpers.ts takes one. Components
* should not call this directly; use `useDateDisplayer()` so the text actually
* re-renders when the language changes.
*/
export function dateDisplayer(
value: string | number | Date | null | undefined,
language = 'en',
): string {
if (value === null || value === undefined || value === '') return '—';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
const dateOnly =
typeof value === 'string' && (DATE_ONLY.test(value) || UTC_MIDNIGHT.test(value));
if (language.startsWith('am')) {
const day = toAmharicDisplay(date); // ሐምሌ 22/2018
return dateOnly ? day : `${day} - ${ethTimeLabel(date)}`;
}
const day = date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
...(dateOnly ? { timeZone: 'UTC' } : {}),
});
if (dateOnly) return day;
// Intl gives "9:57 AM"; the house format is "9:57am".
const time = date
.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
.replace(' ', '')
.toLowerCase();
return `${day} ${time}`;
}