Коварная (?) функция strtotime() в PHP

Наверное, вы скажете — «да какая она коварная? просто знать её надо, да и всё!» — что-то в этом духе.

В общем, расскажу историю!

Один из моих проектов взаимодействует по 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 UTC1 января 1970 года (четверг); время с этого момента называют «эрой UNIX» (англ. Unix Epoch).

Т.е. на UTC. Но я не сразу понял в чём же дело (хотя понял, что разгадка близко).

Далее, я пошёл на PHP.net и почитал документацию к функции strtotime, которую, опять же, прекрасно знал и начал чувствовать, что разгадка ещё ближе…

(между тем, кстати, я опробовал кучу сервисов с конвертацией моего времени в миллисекундах в дату — и некоторые сервисы давали время корректное время, а некоторые — нет, но почему — ниже!)

Разумеется, я дебажил все значения дат у себя в коде: до, после, перед, в разных форматах и т.п. А на «той стороне» мне всё твердили, что мой проект присылает некорректную дату (а я, ещё и предположил, что проблема, может и на их стороне (за что потом извинился и за что мне стыдно) ?).

И тут меня осенило!

Дело в том, что любой вывод даты всегда осуществляется с использованием локальной временной зоны. Она у меня была в UTC+3 (разумеется). А вот функция strtotime всегда возвращает свое число (число прошедших секунд с 1 января 1970 года) в UTC! Т.е. я:

  1. Брал дату в строке, которая в UTC+3
  2. Вычитал из неё 3 часа и получал дату в строке в UTC+0
  3. Конвертировал её в UNIX timestamp и затем в миллисекунды

Но я не знал, что дата в UNIX timestamp ВСЕГДА в UTC (UTC+0). Т.е. мне вместо вычитания трёх часов достаточно было просто сконвертировать мою строковую дату во временную метку, только и всего! А когда я конвертировал строковую дату, которая итак была в UTC+0 в timestamp, умный язык PHP вычитал ещё 3 часа (ввиду имеющейся локальной временной зоны), дабы привести дату к формату UTC (конечно, я мог оперировать датой в строковом формате с указанием временного сдвига, типа 2018-01-01T00:00:00+03:00, например — но я так не делал).

И, кстати говоря, в указанной мной документации ясно и понятно написано обо всём том, на что я наткнулся.

Вот, такие дела!