Line data Source code
1 : // SPDX-License-Identifier: LGPL-2.1-or-later
2 : /* Wrappers around Intl.DateTimeFormat which use Cockpit's current locale, and define a few standard formats.
3 : * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
4 : *
5 : * Time stamps are given in milliseconds since the epoch.
6 : */
7 345 : import cockpit from "cockpit";
8 :
9 345 : const _ = cockpit.gettext;
10 :
11 : // this needs to be dynamic, as some pages don't initialize cockpit.language right away
12 258 : export const dateFormatLang = (): string => cockpit.language.replace('_', '-');
13 :
14 : type Time = Date | number;
15 :
16 : // general Intl.DateTimeFormat formatter object
17 258 : export const formatter = (options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat(dateFormatLang(), options);
18 :
19 : // common formatters; try to use these as much as possible, for UI consistency
20 : // 07:41 AM
21 126 : export const time = (t: Time): string => formatter({ timeStyle: "short" }).format(t);
22 : // 7:41:26 AM
23 0 : export const timeSeconds = (t: Time): string => formatter({ timeStyle: "medium" }).format(t);
24 : // June 30, 2021
25 138 : export const date = (t: Time): string => formatter({ dateStyle: "long" }).format(t);
26 : // 06/30/2021
27 : export const dateShort = (t: Time): string => formatter().format(t);
28 : // Jun 30, 2021, 7:41 AM
29 50 : export const dateTime = (t: Time): string => formatter({ dateStyle: "medium", timeStyle: "short" }).format(t);
30 : // Jun 30, 2021, 7:41 AM if `t` is in UTC format
31 92 : export const dateTimeUTC = (t: Time): string => formatter({ dateStyle: "medium", timeStyle: "short", timeZone: "UTC" }).format(t);
32 : // Jun 30, 2021, 7:41:23 AM
33 5 : export const dateTimeSeconds = (t: Time): string => formatter({ dateStyle: "medium", timeStyle: "medium" }).format(t);
34 : // Jun 30, 7:41 AM
35 78 : export const dateTimeNoYear = (t: Time): string => formatter({ month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }).format(t);
36 : // Wednesday, June 30, 2021
37 11 : export const weekdayDate = (t: Time): string => formatter({ dateStyle: "full" }).format(t);
38 :
39 : // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/format
40 345 : const units: { name: Intl.RelativeTimeFormatUnit, max: number }[] = [
41 345 : { name: "second", max: 60 },
42 345 : { name: "minute", max: 3600 },
43 345 : { name: "hour", max: 86400 },
44 345 : { name: "day", max: 86400 * 7 },
45 345 : { name: "week", max: 86400 * 30 },
46 345 : { name: "month", max: 86400 * 365 },
47 345 : { name: "year", max: Infinity },
48 345 : ];
49 :
50 : // "1 hour ago" for past times, "in 1 hour" for future times
51 116 : export function distanceToNow(t: Time): string {
52 : // Calculate the difference in seconds between the given date and the current date
53 20 : const t_timestamp = t?.valueOf() ?? t;
54 116 : const secondsDiff = Math.round((t_timestamp - Date.now()) / 1000);
55 :
56 : // special case for < 1 minute, like date-fns
57 : // we don't constantly re-render pages and there are delays, so seconds is too precise
58 114 : if (secondsDiff <= 0 && secondsDiff > -60)
59 77 : return _("less than a minute ago");
60 22 : if (secondsDiff > 0 && secondsDiff < 60)
61 22 : return _("in less than a minute");
62 :
63 : // find the appropriate unit based on the seconds difference
64 51 : const unitIndex = units.findIndex(u => u.max > Math.abs(secondsDiff));
65 : // get the divisor to convert seconds to the appropriate unit
66 20 : const divisor = unitIndex ? units[unitIndex - 1].max : 1;
67 :
68 116 : const formatter = new Intl.RelativeTimeFormat(dateFormatLang(), { numeric: "auto" });
69 116 : return formatter.format(Math.round(secondsDiff / divisor), units[unitIndex].name);
70 116 : }
71 :
72 : /***
73 : * sorely missing from Intl: https://github.com/tc39/ecma402/issues/6
74 : * based on https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/weekData.json#L59
75 : * However, we don't have translations for most locales, and cockpit.language does not even contain
76 : * the country in most cases, so this is just an approximation.
77 : * Most locales start the week on Monday (day 1), so default to that and enumerate the others.
78 : */
79 :
80 162 : const first_dow_sun = ['en', 'ja', 'ko', 'pt', 'pt_BR', 'sv', 'zh_CN', 'zh_TW'];
81 :
82 5 : export function firstDayOfWeek(): number {
83 0 : return first_dow_sun.indexOf(cockpit.language) >= 0 ? 0 : 1;
84 5 : }
|