Javascript и часовые пояса — правильное время на сайте / Хабр

Utc не панацея

Поясню на примере. Допустим, мы создали тот же сервис отложенных сообщений. Зайдя на наш сайт пользователь может создать себе напоминание на любое время (разумеется, в будущем) по почте или СМС. Сайт наш предельно прост: задаём дату, время, вводим текст напоминания и канал связи (адрес email или номер телефона), полученные от пользователя данные складываем в базу и потом периодически делаем по ней выборки и отправляем сообщения. Всё, профит и уважение благодарных людей!

Нет, не всё. Следуя совету всегда везде хранить всё в UTC, мы преобразовали полученную от пользователя дату и время в UTC и положили их в базу данных. Пусть пользователь из Москвы зашёл на наш сайт 2 марта 2021 года и создал напоминание на 09:00 утра 3 ноября 2021 года.

Понимаете, к чему я клоню?

Да, 21 июля 2021 года Государственная дума Российской Федерации приняла законопроект об отмене летнего времени. Согласно этому закону, с 26 октября 2021 года, смещение для таймзоны Europe/Moscow стало «UTC 3» вместо «UTC 4» (а ещё переход на летнее время отменили, но речь сейчас не об этом).

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

С другой стороны, если вы будете хранить в базе данных локальное время пользователя и его таймзону, работать с такими данными будет практически невозможно. Вернёмся к нашему примеру сервиса уведомлений: два пользователя создали по уведомлению. Первый пользователь из Москвы, попросил прислать ему СМС 15 декабря 2021 года в 15:

Сейчас ищут техподдержку:  EDGE интернет — что это? Технические характеристики

00 (пишем в базу его локальное время «2021-12-15 15:00:00» и его часовой пояс «Europe/Moscow»). Второй пользователь из Нью-Йорка, попросил прислать ему письмо на электронную почту 15 декабря 2021 года в 7:00PM (пишем в базу его локальное время «2021-12-15 19:00:

00» и его часовой пояс «America/New_York»). Пока всё хорошо: у нас записано локальное время, в которое пользователь хотел бы получить своё уведомление, и он его получит строго в это время, даже если правительство одной из этих стран изменит один из этих часовых поясов (смещение, правила перехода на летнее время, всё, что угодно).

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

SELECT * FROM reminders WHERE remind_time < NOW();

При условии, что «SELECT NOW();» возвращает время в UTC. Но мы записали в базу локальное время пользователя и его часовой пояс, что же делать? Страдать 🙂 Ведь «NOW()» по UTC — это ” 3″ часа в Москве (и сообщение уже опоздало) и “-5” часов в Нью-Йорке (сообщение ещё рано отправлять).

Нет, конечно можно придумать много способов выборки из базы тех уведомлений, которые пора отправлять, но все они на более-менее нагруженном сервисе приведут к проблемам с производительностью, да и вообще мы же хотим сделать всё правильно, без «костылей», да?

Какие есть варианты? Их много, однако я вижу только один более-менее приемлимый вариант: хранить в базе три значения: время в UTC (для выборки по этому полю), локальное время пользователя и его часовой пояс (таймзону). Да, у нас будут храниться избыточные данные, однако я не знаю ни одного нагруженного сервиса, который не прибегал бы к денормализации данных.

Сейчас ищут техподдержку:  Местонахождение, график работы и контактные телефоны

В реальном мире это нормально. Какие плюсы мы получаем? В случае изменений часовых поясов, мы можем пройтись по записям для изменившихся таймзон специальным скриптом, и обновить время в UTC, если оно поменялось в результате обновления часового пояса. По моему скромному мнению, это хороший компромисс.

Всё ещё хуже, чем кажется

Вроде всё, да? Нет, мы только начали 🙂 Правительство может не только менять конфигурацию часовых поясов, но и добавлять новые и выкидывать старые таймзоны. Так, например, для жителей Российского города Чита (и не только для него, но сейчас не об этом)

с 26 октября 2021 года был введён новый часовой пояс «Asia/Chita» (раньше такого часового пояса не существовало) вместо употреблявшегося до этого «Asia/Yakutsk». Разница с UTC у прежнего часового пояса («Asia/Yakutsk») составляет ” 09:00″, а у нового часового пояса («Asia/Chita») эта разница составляет ” 08:00″.

Проблема заключается в том, что мы храним в базе только время и часовой пояс пользователя, но не его географическое положение. И для записей с часовым поясом «Asia/Yakutsk» мы никак не можем знать, из Читы ли наш пользователь, или из Якутстка, и мы никак не можем достоверно определить время отправки сообщения пользователю. Шах и мат! Не забываем страдать, друзья.

Не забывайте страдать

Вывод можно сделать простой и совершенно банальный:

никогда

не слушайте категоричных утверждений, рекомендующих вам

никогда

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

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

Сейчас ищут техподдержку:  Как исправить: Gmail не загружается - учебные пособия по Windows

Python 2.7.6

➜ date
воскресенье,  9 ноября 2021 г. 22:44:32 (MSK)
➜ python -c "import datetime; print datetime.datetime.now()"
2021-11-09 22:44:33.310904
➜ python -c "import datetime; print datetime.datetime.utcnow()"
2021-11-09 19:44:34.405287

Вроде всё хорошо. Смотрим дальше:

➜ date  %z
 0300
➜ python -c "import time; print time.timezone/3600"
-4


WAT? Нет, вроде это

, однако никому от этого не легче. Какой вообще смысл в коде, который в любой момент может сломаться (и ломается!)?

Firefox 33.0.3

new Date(2021, 0, 6) 
"Tue Jan 06 2021 00:00:00 GMT 0300 (Russia TZ 2 Standard Time)" 

new Date(2021, 0, 7) 
"Tue Jan 06 2021 23:00:00 GMT 0300 (Russia TZ 2 Standard Time)" 

new Date(2021, 0, 8) 
"Thu Jan 08 2021 00:00:00 GMT 0400 (Russia TZ 2 Daylight Time)"


WAT? Нет, я понимаю, что этот вопрос уже много раз

, но жить от этого не легче.

В общем, что могу сказать, не забывайте страдать 🙂

А как вы работаете с датами, временем и таймзонами?

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 5,00 из 5)
Загрузка...

Оставьте комментарий

Adblock
detector