Frontender Magazine

Локализация времени в JavaScript

Давайте представим, что у вас есть определенное время, которое вы хотели бы отобразить на своем сайте. Время имеет формат вашего часового пояса. Конечно, вы можете показывать его с явным указанием часового пояса, скажем, так: 3:00 PM Eastern Standard Time. В этом случае каждый посетитель вашего сайта будет вынужден вручную сконвертировать время с поправкой на свой часовой пояс, ну или использовать этот отличный ресурс — Every Time Zone.

Но ведь разработчик может позаботиться о пользователе и локализовать время в соответствии с его часовым поясом, избавив от необходимости делать это самостоятельно. Тем более, что у нас есть отличный инструмент — JavaScript.

Уверен, для решения этой проблемы существует множество способов. Недавно мне пришлось столкнуться с подобной задачей на одном новом проекте, и я решил перенять опыт локализации дат у ребят из ShopTalk.

Библиотеки

Moment.js и Moment Timezone прекрасно подходят для решения задач, связанных с локализацией дат и различными манипуляциями с ними. Например, с помощью этих библиотек можно легко представить время в подобных форматах: "32 minutes ago", "12 de Agosto de 2015".

Ещё один важный момент заключается в том, что было бы неплохо автоматически определять текущий часовой пояс пользователя. К счастью, для этого есть отличное средство.

Итак, мы будем использовать:

Шаг 1: Получение часового пояса

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

if (!sessionStorage.getItem('timezone')) {
    var tz = jstz.determine() || 'UTC';
    sessionStorage.setItem('timezone', tz.name());
}
var currTz = sessionStorage.getItem('timezone');

Шаг 2: Получение времени

Когда мы создаем объект Moment, сначала выполняется проверка переданной в качестве аргумента строки на соответствие формату ISO 8601. Для UTC этот формат имеет следующий вид (обратите внимание на символ 'Z', стоящий сразу же после значения времени):

2015-08-12T14:30Z

Используя Moment можно легко изменять формат дат. Давайте рассмотрим небольшой пример. По умолчанию Moment создаёт объект, основанный на текущей дате, который мы преобразуем в строку нужного формата:

var date = moment().format("YYYY-MM-DD");

Затем, если необходимо, добавим к получившейся строке время в формате TH:mm:

var stamp = date + "T" + theTime + "Z";

И, наконец, преобразуем строку в новый объект Moment:

var momentTime = moment(stamp);

Шаг 3: Локализация времени

Теперь давайте посмотрим, как можно использовать Moment Timezone для локализации времени:

var tzTime = momentTime.tz(currTz);

И, конечно же, отформатируем получившееся значение для более удобного отображения:

var formattedTime = tzTime.format('h:mm A');

Шаг 4: Использование

Ну что же, теперь мы можем использовать описанный выше подход на любом сайте. Вместе с отформатированным временем вы можете отображать и текущий часовой пояс. Если же определение часового пояса по каким-то причинам завершилось неудачей, то время отобразится в UTC формате:

output.textContent = "Time in " + currTz + ": " + formattedTime;

Дополнительная настройка

Иногда бывает полезно предоставлять пользователям возможность вводить даты на сайте в их локальном часовом поясе, но хранить подобную информацию удобнее в формате UTC. Moment может достаточно легко это сделать с помощью методов, которые позволяют выполнять почасовой сдвиг:

moment().subtract(4, 'hours');
moment().add(3, 'hours');

Мы можем выполнить подобные манипуляции и на сервере, используя, например, PHP:

<?php $sixHoursAgo = strtotime("-6 hours", time()); ?>

хотя в подобных преобразованиях лучше использовать Moment Timezone, чтобы избежать неточностей, связанных с переводом времени на летнее.

Пример

В примере ниже мы будем использовать HTML5 time editor и на лету конвертировать время в локальное:

Посмотреть на CodePen: Приведение времени в соответствие локальному часовому поясу.

Другие способы локализации

У вас есть опыт решения задач локализации времени? В статье мы использовали довольно объёмные сторонние библиотеки, хотя в JavaScript уже есть нативные методы (например, getTimezoneOffset()), с помощью которых можно добиться аналогичных результатов. Было бы интересно увидеть ваши решения, напишите о них в комментариях.

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Chris Coyier
Сергей Алексеев
Переводчик:
Сергей Алексеев
GitHub:
SergeyAlexeev

Комментарии (6 комментариев, если быть точным)

Автар пользователя
ugnet

Мой пример реализации с помощью getTimezoneOffset() http://m5-web.com/blog/192 или http://codepen.io/m5studio/pen/xwBoJG

Автар пользователя
RamallahS

Ссылки в статье не рабочие.

Автар пользователя
ugnet

поправил

Автар пользователя
ua9msn

Чуть чуть оффтоп, не совсем про локализацию. moment.js несколько великоват и тянет в себе много лишнего. Если нужны только таймзоны то лучше пользовать https://github.com/mde/timezone-js.

Автар пользователя
limpbrains

У moment-timezone теперь есть своя угадывалка часового пояса.

moment.tz.guess();

http://momentjs.com/timezone/docs/#/using-timezones/guessing-user-timezone/

Автар пользователя
artuska

Момент.жс слишком большая библиотека, в топку. Время присылают в UTC — браузер сам сконвертирует в текущее время с учетом таймзоны. Для локализации использую Angular Translate и локализацию для дат записываю так (тут русский формат записи дат, в en.yml другой формат дат и так для любого языка):

mask: datetime: 'd mmmm, HH:MM' default: 'ddd, d mmmm yyyy, HH:MM:ss' datetimein: 'd mmmm в HH:MM' shortdate: 'm/d/yy' shorttime: 'h:MM TT' mediumdate: 'd mmm yyyy' mediumtime: 'h:MM:ss TT' longdate: 'd mmmm yyyy' longday: 'dddd, d mmmm' longtime: 'h:MM:ss TT Z' full: 'dddd, d mmmm yyyy, HH:MM:ss' isodate: 'yyyy-mm-dd' isotime: 'HH:MM:ss' isodatetime: yyyy-mm-dd'T'HH:MM:ss

Для парсинга формата даты использую мизерный скриптик — Date Format 1.2.3 — https://gist.github.com/eralston/968809 — слегка модифицированный.

Ну и всё это собирает директива, а выводится так:

<date timestamp="{{item.start_time}}" mask="HH:MM"></date>

<date timestamp="{{mix.created_at}}" mask="DATE.MASK.LONGDATE"></date>