dayjs icon indicating copy to clipboard operation
dayjs copied to clipboard

Question / Feature request regarding localized time format

Open dsl101 opened this issue 5 years ago • 14 comments

This may be possible, but I couldn't find it in the docs. I would like to display a time in a users locally preferred format—either 24h or 12h with am/pm. I tried localizedFormat with 'LT', but that gives me 1:36 PM in the UK, where the preferred time format is 24h (as returned by new Date().toLocaleString().

Is there a way to show a time either as HH:mm or h:mm a depending on the user's locale / machine preferences?

dsl101 avatar Jan 20 '21 13:01 dsl101

Yes, a/A format token is available according to the locale.

iamkun avatar Jan 20 '21 15:01 iamkun

Sorry—perhaps I wasn't clear. In the UK, the format should be HH:mm, but in the US it should be h:mm a. I couldn't see any way of specifying a single format option to dayjs().format() which selected one of those depending on the user's locale. This is my hacky workaround which feels like it should be possible with just dayjs():

const preferredTimeFormat = new Intl.DateTimeFormat(undefined, { hour: 'numeric' }).format(0).includes(' ') ? 'h:mm a' : 'HH:mm'
dayjs().format(preferredTimeFormat)

That call to Intl.DateTimeFormat() returns, for example, 7 PM or 19 depending on the user's locale, and so checking if there is a space allows the correct format string to be passed to dayjs().

Specifying dayjs().format('LT') does not give a time in locale-preferred format. It seems to use 12h with am/pm regardless of the user's locale.

dsl101 avatar Jan 20 '21 15:01 dsl101

You can check Localized formats here https://day.js.org/docs/en/display/format#localized-formats

note:

This dependent on LocalizedFormat plugin to work

iamkun avatar Jan 21 '21 02:01 iamkun

I'm not sure how else to explain this. I'm running it in the console on the day.js.org website (where I assumed, from the console message, all the modules were loaded—is that not the case?)

image

As you can see, both new Date().toLocaleString() and new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: 'numeric' }).format(new Date()) return the time (correctly) in 24-hour format. dayjs().format('LT') does not.

What am I missing?

To double-check, passing either en-gb or en-us to Intl.DateTimeFormat() also returns the expected result:

image

dsl101 avatar Jan 21 '21 08:01 dsl101

You should use the correct locale such as dayjs().locale('zh-cn').format('A')

iamkun avatar Jan 21 '21 08:01 iamkun

Are we at cross purposes here? If I specify the locale, I might as well specify the format, but I want the format to be dependent on the user's locale wherever they are. There is a preferred time format (12 / 24, leading zero, am/pm or not) for each locale, no? As produced by the Date() and new Intl.DateTimeFormat(). Is it simply that dayjs() cannot read the user's locale?

dsl101 avatar Jan 21 '21 09:01 dsl101

Yes, Day.js do not read the user's locale, en locale by default, while you can change to some other one.

iamkun avatar Jan 21 '21 12:01 iamkun

Right—so this is a feature request then, to make LocalizedFormat actually use the local format. Is that something you'd consider? At the moment I still have to use regular date objects to get time displayed as the local users want, which kind of defeats the purpose of using a library like dayjs...

dsl101 avatar Jan 21 '21 12:01 dsl101

Yes, maybe we could support it via another plugin. However, ATM, you have to set the correct locale by yourself.

iamkun avatar Jan 21 '21 12:01 iamkun

Another plugin? I must admit I had thought that's exactly what LocalizedFormat was supposed to do...

dsl101 avatar Jan 21 '21 19:01 dsl101

LocalizedFormat plugin is based on the locale you passed in

iamkun avatar Jan 22 '21 01:01 iamkun

Regardless of whether it reads the current locale, I still see this:

image

That is not the localised format of a time for en-gb, as shown by calling the native Intl.DateTimeFormat() version:

image

dsl101 avatar Jan 22 '21 10:01 dsl101

For what it's worth, in my use case, using native JS Date objects rather than dayjs is probably the right answer:

d.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric', timeZoneName: 'short' } )

gives exactly the output I'm after in all locales tested so far.

dsl101 avatar Jan 22 '21 10:01 dsl101

For our project, we built a custom dayjs plugin that selects the browser's locale and sets it as the locale for dayjs. We referred https://github.com/iamkun/dayjs/issues/732 to understand and address timezone mismatches. As a result, we believe we have minimized these mismatches as much as possible.

import() helps with bundle splitting. It ensures that only the required locale bundle is loaded during runtime.

// localePlugin.js
//
// Usage
// dayjs.extend(localizedFormat);
// dayjs.extend(localePlugin);

const DEFAULT_LOCALE = "en";
const LOCALES = {
  af: () => import("dayjs/locale/af"),
  am: () => import("dayjs/locale/am"),
  "ar-dz": () => import("dayjs/locale/ar-dz"),
  "ar-iq": () => import("dayjs/locale/ar-iq"),
  "ar-kw": () => import("dayjs/locale/ar-kw"),
  "ar-ly": () => import("dayjs/locale/ar-ly"),
  "ar-ma": () => import("dayjs/locale/ar-ma"),
  "ar-sa": () => import("dayjs/locale/ar-sa"),
  "ar-tn": () => import("dayjs/locale/ar-tn"),
  ar: () => import("dayjs/locale/ar"),
  az: () => import("dayjs/locale/az"),
  be: () => import("dayjs/locale/be"),
  bg: () => import("dayjs/locale/bg"),
  bi: () => import("dayjs/locale/bi"),
  bm: () => import("dayjs/locale/bm"),
  "bn-bd": () => import("dayjs/locale/bn-bd"),
  bn: () => import("dayjs/locale/bn"),
  bo: () => import("dayjs/locale/bo"),
  br: () => import("dayjs/locale/br"),
  bs: () => import("dayjs/locale/bs"),
  ca: () => import("dayjs/locale/ca"),
  cs: () => import("dayjs/locale/cs"),
  cv: () => import("dayjs/locale/cv"),
  cy: () => import("dayjs/locale/cy"),
  da: () => import("dayjs/locale/da"),
  "de-at": () => import("dayjs/locale/de-at"),
  "de-ch": () => import("dayjs/locale/de-ch"),
  de: () => import("dayjs/locale/de"),
  dv: () => import("dayjs/locale/dv"),
  el: () => import("dayjs/locale/el"),
  "en-au": () => import("dayjs/locale/en-au"),
  "en-ca": () => import("dayjs/locale/en-ca"),
  "en-gb": () => import("dayjs/locale/en-gb"),
  "en-ie": () => import("dayjs/locale/en-ie"),
  "en-il": () => import("dayjs/locale/en-il"),
  "en-in": () => import("dayjs/locale/en-in"),
  "en-nz": () => import("dayjs/locale/en-nz"),
  "en-sg": () => import("dayjs/locale/en-sg"),
  "en-tt": () => import("dayjs/locale/en-tt"),
  en: () => import("dayjs/locale/en"),
  eo: () => import("dayjs/locale/eo"),
  "es-do": () => import("dayjs/locale/es-do"),
  "es-mx": () => import("dayjs/locale/es-mx"),
  "es-pr": () => import("dayjs/locale/es-pr"),
  "es-us": () => import("dayjs/locale/es-us"),
  es: () => import("dayjs/locale/es"),
  et: () => import("dayjs/locale/et"),
  eu: () => import("dayjs/locale/eu"),
  fa: () => import("dayjs/locale/fa"),
  fi: () => import("dayjs/locale/fi"),
  fo: () => import("dayjs/locale/fo"),
  "fr-ca": () => import("dayjs/locale/fr-ca"),
  "fr-ch": () => import("dayjs/locale/fr-ch"),
  fr: () => import("dayjs/locale/fr"),
  fy: () => import("dayjs/locale/fy"),
  ga: () => import("dayjs/locale/ga"),
  gd: () => import("dayjs/locale/gd"),
  gl: () => import("dayjs/locale/gl"),
  "gom-latn": () => import("dayjs/locale/gom-latn"),
  gu: () => import("dayjs/locale/gu"),
  he: () => import("dayjs/locale/he"),
  hi: () => import("dayjs/locale/hi"),
  hr: () => import("dayjs/locale/hr"),
  ht: () => import("dayjs/locale/ht"),
  hu: () => import("dayjs/locale/hu"),
  "hy-am": () => import("dayjs/locale/hy-am"),
  id: () => import("dayjs/locale/id"),
  is: () => import("dayjs/locale/is"),
  "it-ch": () => import("dayjs/locale/it-ch"),
  it: () => import("dayjs/locale/it"),
  ja: () => import("dayjs/locale/ja"),
  jv: () => import("dayjs/locale/jv"),
  ka: () => import("dayjs/locale/ka"),
  kk: () => import("dayjs/locale/kk"),
  km: () => import("dayjs/locale/km"),
  kn: () => import("dayjs/locale/kn"),
  ko: () => import("dayjs/locale/ko"),
  ku: () => import("dayjs/locale/ku"),
  ky: () => import("dayjs/locale/ky"),
  lb: () => import("dayjs/locale/lb"),
  lo: () => import("dayjs/locale/lo"),
  lt: () => import("dayjs/locale/lt"),
  lv: () => import("dayjs/locale/lv"),
  me: () => import("dayjs/locale/me"),
  mi: () => import("dayjs/locale/mi"),
  mk: () => import("dayjs/locale/mk"),
  ml: () => import("dayjs/locale/ml"),
  mn: () => import("dayjs/locale/mn"),
  mr: () => import("dayjs/locale/mr"),
  "ms-my": () => import("dayjs/locale/ms-my"),
  ms: () => import("dayjs/locale/ms"),
  mt: () => import("dayjs/locale/mt"),
  my: () => import("dayjs/locale/my"),
  nb: () => import("dayjs/locale/nb"),
  ne: () => import("dayjs/locale/ne"),
  "nl-be": () => import("dayjs/locale/nl-be"),
  nl: () => import("dayjs/locale/nl"),
  nn: () => import("dayjs/locale/nn"),
  "oc-lnc": () => import("dayjs/locale/oc-lnc"),
  "pa-in": () => import("dayjs/locale/pa-in"),
  pl: () => import("dayjs/locale/pl"),
  "pt-br": () => import("dayjs/locale/pt-br"),
  pt: () => import("dayjs/locale/pt"),
  rn: () => import("dayjs/locale/rn"),
  ro: () => import("dayjs/locale/ro"),
  ru: () => import("dayjs/locale/ru"),
  rw: () => import("dayjs/locale/rw"),
  sd: () => import("dayjs/locale/sd"),
  se: () => import("dayjs/locale/se"),
  si: () => import("dayjs/locale/si"),
  sk: () => import("dayjs/locale/sk"),
  sl: () => import("dayjs/locale/sl"),
  sq: () => import("dayjs/locale/sq"),
  "sr-cyrl": () => import("dayjs/locale/sr-cyrl"),
  sr: () => import("dayjs/locale/sr"),
  ss: () => import("dayjs/locale/ss"),
  "sv-fi": () => import("dayjs/locale/sv-fi"),
  sv: () => import("dayjs/locale/sv"),
  sw: () => import("dayjs/locale/sw"),
  ta: () => import("dayjs/locale/ta"),
  te: () => import("dayjs/locale/te"),
  tet: () => import("dayjs/locale/tet"),
  tg: () => import("dayjs/locale/tg"),
  th: () => import("dayjs/locale/th"),
  tk: () => import("dayjs/locale/tk"),
  "tl-ph": () => import("dayjs/locale/tl-ph"),
  tlh: () => import("dayjs/locale/tlh"),
  tr: () => import("dayjs/locale/tr"),
  tzl: () => import("dayjs/locale/tzl"),
  "tzm-latn": () => import("dayjs/locale/tzm-latn"),
  tzm: () => import("dayjs/locale/tzm"),
  "ug-cn": () => import("dayjs/locale/ug-cn"),
  uk: () => import("dayjs/locale/uk"),
  ur: () => import("dayjs/locale/ur"),
  "uz-latn": () => import("dayjs/locale/uz-latn"),
  uz: () => import("dayjs/locale/uz"),
  vi: () => import("dayjs/locale/vi"),
  "x-pseudo": () => import("dayjs/locale/x-pseudo"),
  yo: () => import("dayjs/locale/yo"),
  "zh-cn": () => import("dayjs/locale/zh-cn"),
  "zh-hk": () => import("dayjs/locale/zh-hk"),
  "zh-tw": () => import("dayjs/locale/zh-tw"),
  zh: () => import("dayjs/locale/zh"),

  // special cases
  no: () => import("dayjs/locale/nb"), // Norwegian
  zn: () => import("dayjs/locale/zh-cn"), // Chinese
};

const getDayjsLocale = () => {
  for (const language of navigator.languages) {
    const lang = language.toLowerCase();

    if (lang in LOCALES) return lang;

    // handle locales like `fr-be` and `fr-lu`.
    const langPrefix = lang.split("-")[0];
    if (langPrefix in LOCALES) {
      return langPrefix;
    }
  }

  return DEFAULT_LOCALE;
};

export default async (_o, _c, d) => {
  try {
    const locale = getDayjsLocale();
    await LOCALES[locale]();
    d.locale(locale);
  } catch {
    d.locale(DEFAULT_LOCALE);
  }
};

AbhayVAshokan avatar Jun 04 '25 18:06 AbhayVAshokan