signal-desktop/ts/util/formatTimestamp.ts

98 lines
2.8 KiB
TypeScript

// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { HourCyclePreference } from '../types/I18N';
import { assertDev } from './assert';
function getOptionsWithPreferences(
options: Intl.DateTimeFormatOptions
): Intl.DateTimeFormatOptions {
const hourCyclePreference = window.SignalContext.getHourCyclePreference();
if (options.hour12 != null) {
return options;
}
if (hourCyclePreference === HourCyclePreference.Prefer12) {
return { ...options, hour12: true };
}
if (hourCyclePreference === HourCyclePreference.Prefer24) {
return { ...options, hour12: false };
}
return options;
}
/**
* Chrome doesn't implement hour12 correctly
*/
function fixBuggyOptions(
locales: Array<string>,
options: Intl.DateTimeFormatOptions
): Intl.DateTimeFormatOptions {
const resolvedOptions = new Intl.DateTimeFormat(
locales,
options
).resolvedOptions();
const resolvedLocale = new Intl.Locale(resolvedOptions.locale);
let { hourCycle } = resolvedOptions;
// Most languages should use either h24 or h12
if (hourCycle === 'h24') {
hourCycle = 'h23';
}
if (hourCycle === 'h11') {
hourCycle = 'h12';
}
// Only Japanese should use h11 when using hour12 time
if (hourCycle === 'h12' && resolvedLocale.language === 'ja') {
hourCycle = 'h11';
}
return {
...options,
hour12: undefined,
hourCycle,
};
}
function getCacheKey(
locales: Array<string>,
options: Intl.DateTimeFormatOptions
) {
return `${locales.join(',')}:${Object.keys(options)
.sort()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map(key => `${key}=${(options as any)[key]}`)
.join(',')}`;
}
const formatterCache = new Map<string, Intl.DateTimeFormat>();
export function getDateTimeFormatter(
options: Intl.DateTimeFormatOptions
): Intl.DateTimeFormat {
const preferredSystemLocales =
window.SignalContext.getPreferredSystemLocales();
const localeOverride = window.SignalContext.getLocaleOverride();
const locales =
localeOverride != null ? [localeOverride] : preferredSystemLocales;
const optionsWithPreferences = getOptionsWithPreferences(options);
const cacheKey = getCacheKey(locales, optionsWithPreferences);
const cachedFormatter = formatterCache.get(cacheKey);
if (cachedFormatter) {
return cachedFormatter;
}
const fixedOptions = fixBuggyOptions(locales, optionsWithPreferences);
const formatter = new Intl.DateTimeFormat(locales, fixedOptions);
formatterCache.set(cacheKey, formatter);
return formatter;
}
export function formatTimestamp(
timestamp: number,
options: Intl.DateTimeFormatOptions
): string {
const formatter = getDateTimeFormatter(options);
try {
return formatter.format(timestamp);
} catch (err) {
assertDev(false, 'invalid timestamp');
return '';
}
}