Очередным путешествием во времени обернулись раскопки недавно заведённого (якобы) бага в нашей платформе. Автор тикета создавал в БД через платформу некие записи с указанием их точной временной метки, а потом считывал их обратно с указанием той же метки, но полученной расчётами. И если созданные недавно записи считывались успешно, то относительно старые почему-то не находились. Точнее, находились, но только когда в запросе временная метка была сдвинута на 1 час позже 🧐
В #Java коде указание временной метки при добавлении записи сводилось вот к такому вызову:
ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, second, nanos, zoneId);
, где
zoneId — строковый идентификатор временной зоны. Например, если запись создавалась на полночь 30 марта 2004 года в Красноярске (GMT+7), то строка выше неявно преобразовывалась в:ZonedDateTime zdt = ZonedDateTime.of(2004, 3, 30, 0, 0, 0, 0, "GMT+7");
А при считывании та же временная метка формировалась путем прибавления или вычитания периодов из некой отправной метки, заданной примерно как:
ZonedDateTime baseDateTime = ZonedDateTime.now(ZoneId.of("Asia/Krasnoyarsk"));Арифметика там точно правильная, но старые записи почему-то не находятся🤷♂️
Заметили разницу в указании часовых зон? Казалось бы, если Красноярск опережает Лондон на 7 часов, то какая разница, как это указывать:
GMT+7 или Asia/Krasnoyarsk? 🤔И действительно никакой, если говорить о том, как он живёт сейчас. Но не факт, что так было в прошлом. Например, в том же 2004 году в Красноярске, как и во всей России, ещё действовали переходы на зимнее и летнее время, а значит, только полгода город жил по GMT+7, а остальные полгода — по GMT+8. И угадайте, в какой период попадала проблемная дата 30.03.2004?.. Правильно 😉
Фишка в том, что при указании
GMT+7 автор тикета фактически задавал фиксированное смещение, а не часовой пояс. Разница в том, что пояс может давать разные смещения в зависимости от того, к какой дате он применён, то есть он ещё учитывает историю изменений смещения в заданном регионе. Для этого в состав каждой JDK/JRE встроена база данных временных зон, которую ведёт международная организация с красивым именем IANA. Эта база лежит в виде файлика $JAVA_HOME/lib/tzdb.dat. С ним и сверяется JDK, когда выбирает смещение для заданной даты в заданном регионе 🌍Мораль сей басни такова:
— для всякого "абсолютного" времени указывайте (или запрашивайте) временную зону, даже если это UTC;
— если речь о времени в конкретном месте (точке, городе, области), то её временную зону указывайте не смещением, а названием региона.
Поддерживаемые названия регионов и их характеристики можно посмотреть в этой статье 📚
