Получить ISO 8601, используя Intl.DateTimeFormat

Я хочу использовать Intl.DateTimeFormat отформатировать дату, и в примерах это говорит

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

Отлично, поэтому я хочу, чтобы мой запасной вариант был ISO 8601 в случае, если язык не существует

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

тем не мение

//  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

Это, кажется, потому что iso8601 не является частью

locales Строка с языковым тегом BCP 47 или массив таких строк.

Я также пытался использовать один, я знаю, работает, например, en-GB с u-ca-iso8601 суффикс, но это не приводит к какому-либо другому результату без суффикса

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

Почему это не работает? Есть ли даже locale что даст мне результат, который я ищу?


Я бы предпочел не писать какую-то сложную оболочку, например

if (Intl.DateTimeFormat.supportedLocalesOf(['foo']).length === 0)

5 ответов

Решение

Поскольку, похоже, нет способа настроить определения локалей в Intl, вам нужно будет найти локаль, которая использует формат ISO 8601. Проверяя определения CLDR для формата yMd в By-Type Chart: Date & Time: Gregorian, я обнаружил, что они похожи на ISO 8601. Однако поддержка определенных локалей в браузерах или других реализациях JavaScript не гарантируется.

На практике, среди таких языков в CLDR, fo (Faroese), похоже, наиболее близок к ISO 8601 и поддерживается браузерами. Тестирование с Intl.DateTimeFormat(['foo', 'iso8601'], o) дает следующие результаты:

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

Таким образом, Chrome не совсем применяет правильный (согласно CLDR) формат, и все эти браузеры используют пробел, а не T в качестве разделителя. Тем не менее, пространство делает презентацию более читабельной, и теперь она принята как альтернатива в соответствии с действующим стандартом ISO 8601, а именно ISO 8601:2004, который гласит:

4.3.2 ПРИМЕЧАНИЕ. По взаимному согласию партнеров по обмену информацией символ [T] может быть опущен в приложениях, где нет риска спутать представление даты и времени дня с другими, определенными в этом международном стандарте.

Тем не менее, кажется более безопасным использовать обертку, как в вопросе; это не слишком сложно, по сравнению с рисками и грязной природой использования некоторой выбранной локали. (Даже если fo поддерживается всеми реализациями, нет никаких гарантий, что власти Фарерских островов не примут решение об изменении определения локали.)

Спустя годы я обнаружил, что принятый ответ, основанный на локали 'fo' или 'foo' (фарерский язык), больше не работает. Возможно, это так, как предположил Юкка: власти Фарерских островов решили перейти (на д / м / год) или их информация о местоположении была исправлена.

И, к сожалению, явная предлагаемая локаль "ISO8601" до сих пор не добавлена, так что это очень далеко, если это когда-либо произойдет.

Что касается комментария @felixbuenemann, я немного озадачен, поскольку Node 10 не поддерживает данные локали и просто дает результаты "en-us" для всех (возможно, это была специальная сборка Node).

Но я согласен с тем, что канадские языковые стандарты ('fr-CA' и 'en-CA') являются хорошими заменами - суть в том, чтобы использовать языковой стандарт, такой как Канада или Швеция, где был принят ISO 8601, поэтому формат локали вряд ли будет менять. Швеция (sv-SE) на самом деле лучше, чем Канада, потому что в ней не добавляются нестандартные запятые и используются более совместимые идентификаторы часовых поясов.

Итак, я предлагаю

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

который производит "2019-09-03 05:29:54". Обратите внимание, что разделитель пробелов вместо "T" также соответствует стандарту. Не добавляйте букву "Z" в конце (как в вопросе), если вы не переопределяете местное время, устанавливая для параметра timeZone значение GMT.

Один лайнер:

      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"

Чуть более короткий вариант, чем опубликованные:

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

Проблема с использованием Intl.DateTimeFormat заключается в том, что настройки локали могут быть изменены в будущем, и любая разработанная методология может выйти из строя из-за таких изменений.

В этом ответе будет дан независимый от локали подход .

Однако, если вам необходимо использовать Intl.DateTimeFormat, ниже будет описан способ наиболее точного соответствия строке ISO в 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

Если вы желаете найти совпадение дляточно, измените значение localTZ, указанное выше, на «UTC» и добавьте в конце «Z».

Другие вопросы по тегам