Работа с часовыми поясами и летним временем в Javascript

Мое одностраничное приложение javascript извлекает данные в формате JSON с помощью вызовов REST. Даты форматируются с использованием часового пояса UTC в стандартном формате ISO8601, например 2011-02-04T19:31:09Z.

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

Я знаю, как преобразовать строку UTC в дату. Я понимаю, что Javascript представляет даты только в местном часовом поясе.

Но у меня возникли проблемы с выяснением как отображать дату, отформатированную для часового пояса, ДРУГОГО, чем локальный часовой пояс пользователя. Он должен учитывать летнее время на все даты. Внутренне я хочу обрабатывать все даты как UTC и преобразовывать только в строковое представление даты в другом часовом поясе во время отображения. Мне нужно отображать даты в часовом поясе, выбранном в профиле пользователя, а не в часовом поясе его браузера.

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

Любые предложения или пример кода о том, как отображать даты, отформатированные в разных часовых поясах? Варианты, которые я нашел до сих пор:

  1. Сервер отправляет даты в виде строк, уже отформатированных в правильном часовом поясе, и на клиенте не выполняется синтаксический анализ или манипуляции с датой. Это затрудняет выполнение определенных действий на клиенте, если не делает невозможным.
  2. Используйте такую ​​библиотеку, как https://github.com/mde/timezone-js, которая включает всю базу данных Olson TZ в Javascript. Это означает более длительное время загрузки, большее использование памяти и т. Д.
  3. Отправляйте значение timezoneOffsetMillis с каждой датой, отправленной клиенту. Это приводит к беспорядочным данным JSON и неоптимальным интерфейсам REST.

Есть ли более простые или лучшие решения?


person Tauren    schedule 04.02.2011    source источник
comment
Это может не работать в вашем приложении, но в Интернете часто проще отобразить возраст чего-либо, чем пытаться надежно отобразить дату и время в часовом поясе пользователя. Например, спросил 6 минут назад на SO.   -  person Alex Jasmin    schedule 05.02.2011
comment
@Alexandre - Спасибо, и я уже делаю это там, где это возможно, например, даты последнего входа в систему, даты публикации и т. Д. Но мое приложение должно отображать определенные даты и время для событий, которые происходят в разных часовых поясах. Я не могу использовать для этого относительное время или возраст.   -  person Tauren    schedule 05.02.2011
comment
Я бы определенно выбрал timezone-js, это был бы правильный способ сделать это. Вы также можете связать его со следующим скриптом определения часового пояса: bitbucket.org/pellepim/jstimezonedetect.   -  person Jon Nylander    schedule 07.02.2011
comment
@Jon, я согласен, это правильный путь, но он наверняка добавит немного раздувания. Хотелось бы, чтобы JS с самого начала поддерживал часовые пояса. Спасибо за указатель на jstimezonedetect. Я видел его демонстрационную страницу раньше, но забыл о ней.   -  person Tauren    schedule 08.02.2011
comment
В этом случае я бы разгрузил работу tz, используя файлы zoneinfo, на сервер, и он возвращал бы локальное datetime через json, отформатированное.   -  person Jon Nylander    schedule 09.02.2011


Ответы (3)


2 - плохая идея, поскольку, как вы отметили, увеличивает время загрузки. На вашем месте я бы использовал комбинацию 1 и 3. Я не согласен с тем, что это делает данные JSON беспорядочными или интерфейс REST неоптимальным.

Это классический компромисс, когда нужно принять немного более сложное решение в протоколе, чтобы упростить код на стороне клиента.

person Michael Dillon    schedule 05.02.2011
comment
Спасибо, но у меня есть идея сохранить JSON в чистоте. Что, если бы я отправил все даты, которые должны отображаться в локальных часовых поясах пользователя, в формате UTC, например 2011-02-03T09:31:35Z. Любые даты, которые должны отображаться в определенном часовом поясе, будут отправлены в формате этого часового пояса, например 2011-02-03T14:00:00-0800. Когда я первоначально анализировал дату, я мог сохранить смещение в настраиваемом свойстве объекта даты. Я собираюсь попробовать. - person Tauren; 05.02.2011
comment
@Tauren: При таком подходе следите за автоматическим преобразованием часового пояса, если вы занимаетесь математикой даты (добавляя или удаляя время) на клиенте. Например, если вы добавите 24 часа к дате и пересечете переход на летнее время, время будет отображаться с отклонением на час при преобразовании в строку. Кроме того, когда мы переводим наши часы назад, этот час времени в JavaScript неоднозначен. И когда мы переводим наши часы вперед, пропущенный час не представляется возможным (все при условии, что местное время соответствует летнему времени), даже если это может быть время в выбранном вами часовом поясе. - person Mike M. Lin; 03.03.2011

Перенесемся в 2015 год. Когда дело доходит до форматирования даты из другого часового пояса с использованием определенного языкового стандарта, есть два варианта.

В этом примере я использую французский язык и буду использовать дату (UTC + 5) 27 марта 2016 года, 2 часа 15 минут , которая не наблюдается в Западной Европе (из-за перехода на летнее время, часы переводятся с С 2:00 до 3:00), что является частым источником ошибок.

Для максимальной переносимости используйте библиотеку moment.js. Moment поставляется со связанными более 80 языками.

  1. Используя метку времени и смещение UTC:

    moment(1459026900000).utcOffset(300).locale('fr').format("LLLL")
    // "dimanche 27 mars 2016 02:15"
    
  2. Использование массива целых чисел (обратите внимание, что в настоящий момент месяц отсчитывается от 0, как и в собственном объекте JS Date)

    moment.utc([2016,3-1,27,2,15]).locale('fr').format("LLLL")
    // "dimanche 27 mars 2016 02:15"
    

Вы можете протестировать эти методы, запустив код в инструментах разработчика браузера на странице http://momentjs.com/

Сложная часть здесь заключается в использовании moment.utc, который создает объект даты момента с флагом UTC, что означает, что при форматировании этой даты она не будет преобразована в часовой пояс пользователя, а будет отображаться как есть (и поскольку UTC не соблюдает DST, он не подвержен ошибкам с переходом на летнее время).

Будущее собственное решение: использование объекта Intl

(обратите внимание, что по состоянию на конец 2015 года это решение поддерживается Chrome, Firefox, IE11, но все еще не поддерживается в Safari 9)

Используя массив целых чисел:

    new Intl.DateTimeFormat('fr-FR', { timeZone: 'UTC', weekday: 'long',
          month: 'long', year: 'numeric', day: 'numeric', hour: 'numeric',
          minute: 'numeric' })
      .format(Date.UTC(2016,3-1,27,2,15))
    // "dimanche 27 mars 2016 02:15"

Ключевым моментом здесь является использование Date.UTC и timeZone: 'UTC', чтобы гарантировать, что указанная дата не будет преобразована в местный часовой пояс пользователя.


Обратите внимание, что в обоих случаях мы используем методы UTC, чтобы убедиться, что дата будет использоваться как есть, без преобразований. Однако важно понимать, что эти даты являются поддельными датами в формате UTC (они представляют время в данном произвольном часовом поясе, а не в формате UTC) и должны использоваться только для форматирования и отображения даты - не должны передаваться .

person jakub.g    schedule 04.11.2015
comment
Примечание 2019: проверьте также date-fns.org, который значительно меньше, чем moment.js, и, возможно, больше подходит для точка зрения размера пакета - person jakub.g; 16.04.2019

есть такая же проблема. Думаю, №1 - это решение. Вы должны отправить все компоненты даты (год, месяц, день, часы) в специальный контейнер от клиента к серверу.

person SamFlushing    schedule 16.03.2011