Наверное, вы скажете – “да какая она коварная? просто знать её надо, да и всё!” – что-то в этом духе.
В общем, расскажу историю!
Один из моих проектов взаимодействует по API с одним сервисом. В некоторых API-запросах используется дата (промежуток времени) и эта дата передаётся в достаточно казуальном формате ‘Y-m-d’. Но есть нюанс (тоже достаточно казуальный)! Сервис, с которым взаимодействует мой проект, работает во временной зоне UTC, а мой проект во временной зоне UTC+3. И, разумеется, все данные, что мой проект отправляет и все данные, что мой проект получает, в которых содержится дата – конвертируются. Т.е. мой проект перед отправкой вычитает из отправляемых данных 3 часа, а при приёме – наоборот – прибавляет 3 часа.
Так вот!
У этого самого сервиса долгое время разрабатывалась вторая версия, более новая, на GraphQL’е, вся модная, крутая, быстрая! И одной из отличительных черт второй версии этого сервиса было то, что теперь дата должна передаваться в миллисекундах, т.е. в UNIX timestamp’е, помноженном на тысячу.
При интеграции со второй версией сервиса я повторял привычные мне преобразования даты, за тем исключением, что перед отправкой я ещё и переводил строковую дату формата ‘Y-m-d’ (разумеется, сконвертированную в UTC из моих UTC+3) посредством функции strtotime() в число. Так вот, данные на мои API-запросы постоянно возвращались странные, я никогда не получал того, что запросил. В ходе анализа присылаемых мной дат было выяснено, что я присылаю даты в формате UTC-3. Т.е. как будто бы я вычитаю из своих дат не 3 часа, а целых 6.
Разработчиками с той стороны мне было предложено просто перестать конвертировать мою дату, да и всё! Но я так не могу, мне необходимо понять!
Далее, я пошёл в Википедию прочесть о UNIX Timestamp’е то, что итак прекрасно знал и втыкая в статью некоторое время обратил внимание на следующее (выделил красным):
UNIX-время (англ. Unix time) или POSIX-время — система описания моментов во времени, принятая в UNIX и других POSIX-совместимых операционных системах. Определяется как количество секунд, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года (четверг); время с этого момента называют «эрой UNIX» (англ. Unix Epoch).
Т.е. на UTC. Но я не сразу понял в чём же дело (хотя понял, что разгадка близко).
Далее, я пошёл на PHP.net и почитал документацию к функции strtotime, которую, опять же, прекрасно знал и начал чувствовать, что разгадка ещё ближе…
(между тем, кстати, я опробовал кучу сервисов с конвертацией моего времени в миллисекундах в дату – и некоторые сервисы давали время корректное время, а некоторые – нет, но почему – ниже!)
Разумеется, я дебажил все значения дат у себя в коде: до, после, перед, в разных форматах и т.п. А на “той стороне” мне всё твердили, что мой проект присылает некорректную дату (а я, ещё и предположил, что проблема, может и на их стороне (за что потом извинился и за что мне стыдно) ?).
И тут меня осенило!
Дело в том, что любой вывод даты всегда осуществляется с использованием локальной временной зоны. Она у меня была в UTC+3 (разумеется). А вот функция strtotime всегда возвращает свое число (число прошедших секунд с 1 января 1970 года) в UTC! Т.е. я:
- Брал дату в строке, которая в UTC+3
- Вычитал из неё 3 часа и получал дату в строке в UTC+0
- Конвертировал её в UNIX timestamp и затем в миллисекунды
Но я не знал, что дата в UNIX timestamp ВСЕГДА в UTC (UTC+0). Т.е. мне вместо вычитания трёх часов достаточно было просто сконвертировать мою строковую дату во временную метку, только и всего! А когда я конвертировал строковую дату, которая итак была в UTC+0 в timestamp, умный язык PHP вычитал ещё 3 часа (ввиду имеющейся локальной временной зоны), дабы привести дату к формату UTC (конечно, я мог оперировать датой в строковом формате с указанием временного сдвига, типа 2018-01-01T00:00:00+03:00, например – но я так не делал).
И, кстати говоря, в указанной мной документации ясно и понятно написано обо всём том, на что я наткнулся.
Вот, такие дела!