Get ISO 8601 using Intl.DateTimeFormat
Asked Answered
Z

5

37

I want to use Intl.DateTimeFormat to format a Date, and in the examples it says

// when requesting a language that may not be supported, such as
// Balinese, include a fallback language, in this case Indonesian

Great, so I want my fallback to be ISO 8601 in the case a language doesn't exist

// i.e. the same as/similar to
new Date().toISOString(); // "2014-07-31T02:42:06.702Z"

however

//  Intl.DateTimeFormat([locales [, options]])
var o = {};
o.year = o.month = o.day = o.hour = o.minute = o.second = 'numeric';
new Intl.DateTimeFormat(['foo', 'iso8601'], o);
// RangeError: Invalid language tag: iso8601

This seems to be because iso8601 isn't part of

locales A string with a BCP 47 language tag, or an array of such strings.

I've also tried using one I know works, e.g. en-GB with a u-ca-iso8601 suffix but this doesn't produce any different result to without the suffix

var f = new Intl.DateTimeFormat(['foo', 'en-GB-u-ca-iso8601'], o);
f.format(new Date());
// 31/7/2014 03:35:26

Why isn't this working? Is there even a locale which will give me the output I'm looking for?


I'd rather not have to write some complicated wrapper using e.g.

if (Intl.DateTimeFormat.supportedLocalesOf(['foo']).length === 0)
Zicarelli answered 31/7, 2014 at 2:42 Comment(1)
Trackback: Once unicode.org/cldr/trac/ticket/10014 is implemented, en-GB-u-ca-iso8601 should do what you want.Basketball
C
4

Since there does not seem to be a way to customize the definitions of locales in Intl, you would need to find a locale that uses an ISO 8601 format. Checking the CLDR definitions for the yMd format in By-Type Chart: Date & Time:Gregorian, I found some that resemble ISO 8601. However, support to specific locales in browsers or other JavaScript implementations is not guaranteed.

In practice, among such locales in CLDR, fo (Faroese) seems to come closest to being ISO 8601 and supported by browsers. Testing with Intl.DateTimeFormat(['foo', 'iso8601'], o) gives the following results:

2014-7-31 10:26:50      in Chrome
2014-07-31 10:26:50     in Firefox and IE

Thus, Chrome does not quite apply the correct (as per CLDR) format, and all of these browsers use a space and not T as a separator. However, the space makes the presentation more readable, and it is now accepted alternative according to current ISO 8601, namely ISO 8601:2004, which says,

4.3.2 NOTE: By mutual agreement of the partners in information interchange, the character [T] may be omitted in applications where there is no risk of confusing a date and time of day representation with others defined in this International Standard.

However, it seems safer to use a wrapper, as in the question; it’s not too complicated, compared with the risks and kludgy nature of using some selected locale. (Even if fo is supported by all implementations, there is no guarantee that Faroese authorities won’t decide that the locale definition must be changed.)

Candlepin answered 31/7, 2014 at 7:39 Comment(1)
In Node.js 10.11 using locale eo will return an ISO8601 string, but without leading zeroes in the date. If only the date portion is needed, then fr-CA can be used to get a zero-padded date.Troy
E
26

Years later I find that the accepted answer which is based on the 'fo' or 'foo' (Faroese) locale no longer works. Perhaps it is as Jukka speculated: the Faroese authorities decided to switch (to d/m/y) or their locale info got corrected.

And, unfortunately, the explicit 'ISO8601' locale that is proposed has still not been added so that is a long way off if it ever happens.

Regarding the comment by @felixbuenemann, I'm a little puzzled as Node 10 does not support locale data and just gives "en-us" results for all (perhaps it was a custom build of Node).

But I concur that the Canadian locales ('fr-CA' and 'en-CA') are good substitutes - the point is to use a locale such as Canada or Sweden where ISO 8601 has been adopted so the locale format is very unlikely to change. Sweden (sv-SE) is actually better than Canada because it doesn't add a non-standard comma and uses more conformant timezone identifiers.

So, what I'm suggesting is

new Date().toLocaleString( 'sv-SE', o );
// where the options 'o' is defined as in the question or however you want 

which produces "2019-09-03 05:29:54". Note that the space delimiter instead of 'T' is standard conformant too. Don't append the 'Z' at the end (as in the question) unless you are overriding local time by setting the timeZone option to GMT.

Evangelinaevangeline answered 30/10, 2019 at 21:9 Comment(5)
I think you mean 'sv-SE' not underscore.Connieconniption
By default node instances run with just en-US. To get more you need to enable ICU.Shortterm
note fr-CA displays the time as 16 h 20, instead of 16:20.Kovar
sv-SE will not work correctly if you have year = undefined in the options (it outputs "dd/MM" instead of the expected "MM-dd"), but from my own tests, using lt as locale will work fine!Tapp
I find new Intl.DateTimeFormat('sv-SE',{dateStyle: 'short', timeStyle: 'medium'}).format(new Date()) also prints '2023-03-15 16:06:29' as expected.Modal
I
5

One liner:

new Intl.DateTimeFormat('sv-SE', {timeZone: 'Asia/Jakarta', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZoneName: 'short'}).format(new Date())
// "2021-09-02 21:19:35 GMT+7"
Isadoraisadore answered 2/9, 2021 at 14:20 Comment(0)
C
4

Since there does not seem to be a way to customize the definitions of locales in Intl, you would need to find a locale that uses an ISO 8601 format. Checking the CLDR definitions for the yMd format in By-Type Chart: Date & Time:Gregorian, I found some that resemble ISO 8601. However, support to specific locales in browsers or other JavaScript implementations is not guaranteed.

In practice, among such locales in CLDR, fo (Faroese) seems to come closest to being ISO 8601 and supported by browsers. Testing with Intl.DateTimeFormat(['foo', 'iso8601'], o) gives the following results:

2014-7-31 10:26:50      in Chrome
2014-07-31 10:26:50     in Firefox and IE

Thus, Chrome does not quite apply the correct (as per CLDR) format, and all of these browsers use a space and not T as a separator. However, the space makes the presentation more readable, and it is now accepted alternative according to current ISO 8601, namely ISO 8601:2004, which says,

4.3.2 NOTE: By mutual agreement of the partners in information interchange, the character [T] may be omitted in applications where there is no risk of confusing a date and time of day representation with others defined in this International Standard.

However, it seems safer to use a wrapper, as in the question; it’s not too complicated, compared with the risks and kludgy nature of using some selected locale. (Even if fo is supported by all implementations, there is no guarantee that Faroese authorities won’t decide that the locale definition must be changed.)

Candlepin answered 31/7, 2014 at 7:39 Comment(1)
In Node.js 10.11 using locale eo will return an ISO8601 string, but without leading zeroes in the date. If only the date portion is needed, then fr-CA can be used to get a zero-padded date.Troy
C
2

Slightly shorter variant than those posted:

> new Intl.DateTimeFormat('sv-SE', { dateStyle: 'short' }).format(new Date())
'2021-12-01'

Edit 2023-05-03 The en-CA locale no longer produces ISO8601 strings.

Cerebrovascular answered 1/12, 2021 at 15:7 Comment(0)
H
2

The problem with using Intl.DateTimeFormat, is that locale settings are subject to change in the future, and any methodology developed may break due to such changes.

A locale-independent approach would be given in this answer.

However, if needing to use Intl.DateTimeFormat, below would be the way to most closely match an ISO string in 2023:

const localTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
const desiredLocale = "foo";
const fallbackRequired = !Intl.DateTimeFormat.supportedLocalesOf([desiredLocale]).length;
// In 2023, apparently Swedish (sv-SE) is the closest locale to ISO format amongst all locales. 
// Even "ISO" and "UTC" locales return widely different results.
const isoLocaleMatch = fallbackRequired ? "sv-SE" : desiredLocale;
const localeBackupFormatterOptions = {
    timeZone: localTZ,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    fractionalSecondDigits: 3,
    hour12: false,
};
const utcBackupFormatterOptions = { ...localeBackupFormatterOptions};
utcBackupFormatterOptions.timeZone = "UTC";

const nowDate = new Date();
// The replace() at the end changes the value from i.e. '2023-06-01 15:30:11,960' to '2023-06-01T15:30:11.960'
const localNow = fallbackRequired ? Intl.DateTimeFormat(isoLocaleMatch, localeFormatterOptions)
    .format(nowDate)
    .replace(",", ".")
    .replace(" ", "T") : Intl.DateTimeFormat(isoLocaleMatch);
const utcNow = fallbackRequired ? `${Intl.DateTimeFormat(isoLocaleMatch, utcFormatterOptions)
    .format(nowDate)
    .replace(",", ".")
    .replace(" ", "T")}Z` : Intl.DateTimeFormat(isoLocaleMatch);


nowDate.toISOString()
> 2023-06-01T20:30:51.880Z // Trailing "Z" indicates "Zero" timezone (UTC)
localNow
> 2023-06-01T15:30:51.880  // Lack of trailing "Z" indicates local timezone (from this machine, UTC-5)
utcNow
> 2023-06-01T20:30:51.880Z
nowDate.toISOString() === utcNow
> true

If desiring to make a match for new Date().toISOString() exactly, change the localTZ value above to "UTC" and append a trailing "Z".

Hist answered 1/6, 2023 at 22:21 Comment(2)
Thanks @Hist this gets really close. Ideally I'd like to be able to do this without additional logic so it can be a fallback. I read a proposal somewhere to add it as locale value but not sure what stage it is in now.Zicarelli
@PaulS. you are right, there is not much you can do to avoid a small amount of conditional logic for this. I edited the answer to include the minimum logic required (no if statement required!), which could be tidied up easily into a function (and could even allow for parameterization to choose between UTC time or a different time)Hist

© 2022 - 2024 — McMap. All rights reserved.