Конспект механізмів влади над часом у PHP та MySQL

Отсчет мирового времени ведется по усреднённому времени Гринвича с аббревиатурой GMT, которое после стандартизации именуется Всемирным координированным временем UTC.

Время UTC соответствует нулевому часовому поясу "00:00", от которого на восток отсчитываются часовые пояса до +14:00 и на запад до -11:00 (фактические используемые пояса).

Ниже изложены принципы и правила работа со временем в PHP и MySQL.

 The Current Epoch Unix Timestamp + Converter //www.unixtimestamp.com

//

MySQL

В MySQL предусмотрено два принципиально отличных типа, предназначенных для работы с датой и временем, это TIMESTAMP и DATETIME. Представление TIMESTAMP и DATETIME и операции с ними очень похожи, но хранимые значения принципиально отличаются.

Часовой пояс Timezone

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

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

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

Актуальные значения часового пояса сервера и сеанса отражаются глобальными переменными @@global.time_zone и @@session.time_zone:

TIMESTAMP

Концептуально TIMESTAMP наиболее точно представляет суть времени. Это знаковое целое число ведущее отсчет секунд от 1 января 1970 года. Отсчет времени ведется по GMT/UTC/"00:00", который является абсолютным, и не зависит от временной зоны.

В MySQL до версии 5.6 физически TIMESTAMP хранится целым числом x32, что накладывает ограничения на диапазон допустимых дат от '1901-12-13 20:45:52' до '2038-01-19 03:14:07', известное под названием "Проблема 2038 года".

В MySQL 5.6 физическое представление TIMESTAMP качественно изменили:

  • !!! физическое хранение реализуется знаковым целым x64 ?

Представление TIMESTAMP в строковом виде, например Unix-формате '2023-07-05 23:47:18 +03:00', осуществляется динамически в соответствии с установленным часовым поясом, поэтому определенное значение TIMESTAMP может получать различные строковые представления в разных временных зонах. Этот же феномен возникает во время обратного преобразования строкового представления к значению TIMESTAMP, которое может отличаться в зависимости от установленной временной зоны.

Запись

Правильная запись актуального времени события в журнал:

DATETIME

Семейство типов DATETIME представляет значение времени в формате строки от '1000-01-01 00:00:00' до '9999-12-31 23:59:59', которое хранится в формате упакованного числа. Такой способ не зависит от временной зоны, его представление всегда постоянно и соответствует сохраненному значению.

До версии MySQL 5.6.4 тип DATETIME сохранялся числом 8byte по шаблону YYYYMMDDHHMMSS, новые версии использую 5byte.

В семейство входят типы YEAR, DATE, TIME предназначенные сохранять и представлять соответствующий фрагмент времени.

Значение можно задать строковым литералом, при этом указание временной зоны игнорируется. !!!

Функции

//dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html

  • Представление
    • DATE_FORMAT(date,format)/
      • date значение типа DATETIME, TIMESTAMP или VARCHAR приводимый к первым типам
      • format строка произвольного сочетания символов и спецификаторов:
        • %a Abbreviated weekday name (Sun..Sat)
          %b Abbreviated month name (Jan..Dec)
          %c Month, numeric (0..12)
          %D Day of the month with English suffix (0th, 1st, 2nd, 3rd, …)
          %d Day of the month, numeric (00..31)
          %e Day of the month, numeric (0..31)
          %f Microseconds (000000..999999)
          %H Hour (00..23)
          %h Hour (01..12)
          %I Hour (01..12)
          %i Minutes, numeric (00..59)
          %j Day of year (001..366)
          %k Hour (0..23)
          %l Hour (1..12)
          %M Month name (January..December)
          %m Month, numeric (00..12)
          %p AM or PM
          %r Time, 12-hour (hh:mm:ss followed by AM or PM)
          %S Seconds (00..59)
          %s Seconds (00..59)
          %T Time, 24-hour (hh:mm:ss)
          %U Week (00..53), where Sunday is the first day of the week; WEEK() mode 0
          %u Week (00..53), where Monday is the first day of the week; WEEK() mode 1
          %V Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X
          %v Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x
          %W Weekday name (Sunday..Saturday)
          %w Day of the week (0=Sunday..6=Saturday)
          %X Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
          %x Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
          %Y Year, numeric, four digits
          %y Year, numeric (two digits)
          %% A literal % character
          %x x, for any “x” not listed above
    • STR_TO_DATE(string, format) Format a date from a string with STR_TO_DATE and return a date/datetime value. The string is what you want to reformat, while the format is a combination of specifiers that describe each element of the string.
  • Конвертирование
    • UNIX_TIMESTAMP([date]) возвращает метку времени UNIX заданной даты или текущего момента, представленную знакомым числом секунд от '1970-01-01 00:00:00 UTC'
    • FROM_UNIXTIME(uts[,format]) для заданной unix-метки времени uts возвращает соответствующее значение даты для часового пояса сеанса, тип значения DATETIME или VARCHAR определяется вторым необязательным параметром
      • format строковое значение
        • при опущенном параметре функция возвращает значение DATETIME

    • CONVERT_TZ(dt,from_tz,to_tz) для значения dt типа DATETIME в часовом поясе from_tz возвращает измененное значение для часового пояса to_tz
    • EXTRACT(unit FROM date) извлекает числовое значение фрагмента даты date, заданного спецификатором unit (см. DATE_ADD(), DATE_SUB())
    • DATE(expr) извлекает DATE (без времени) из DATETIME, VARCHAR и т.п.
  • Операции над значениями
    • ADDDATE/DATE_ADD(date, INTERVAL value unit) Add a time/date value to a date expression with the DATE_ADD or ADDDATE function. Replace date with the date expression you want to add a time/date to. The value unit is the time/date you want to add. It needs to be expressed in a numeric value along with the unit of the value.
    • SUBTIME(datetime,timevalue)
      DATE_SUB(date, INTERVAL value unit) Subtract a time/date value to a date expression with the DATE_SUB or SUBDATE function
    • DATEDIFF(expr1,expr2)
    • MAKEDATE(year,day)
    • MAKETIME(hour, minute, second)
  • !!!
    • TIMESTAMPADD(unit,value,datetime);
    • TIMESTAMPDIFF(unit,datetime1,datetime2);

 

PHP

В PHP не предусмотрен базовый тип для работы с датой и временем. PHP использует метку системного времени Unix, выраженную знаковым целым числом секунд от начала отсчета Unix-времени 1 января 1970 00:00:00 GMT.

Штатными средствами к метке времени применимо прямое и обратное преобразование к строке в Unix-формате '1970-01-01 00:00:00', которая соответствует шаблону 'Y-m-d H:i:s'.

Развитые средства работы с датой и временем предоставляет класс DateTime.

Функції для роботи з датою та часом

//

  • time(): int возвращает текущую метку системного времени Unix, которая равна количеству секунд, прошедших с начала эпохи Unix (1 января 1970 00:00:00 GMT) до текущего времени
  • hrtime(bool $as_number = false): array|int|float|false возвращает время высокого разрешения системы, отсчитываемое с произвольной точки времени, полученная временная метка неизменна и не может быть отрегулирована (рекомендуется для измерения производительности)
    • as_number  по умолчанию возвращает array [секунды, наносекунды]
      • в значении true устанавливает тип результата int выражающее наносекунды 10-9
        • точность значения ±10ns или еще хуже
  • microtime(bool $as_float = false): string|float возвращает текущую метку времени Unix с микросекундами. Эта функция доступна только на операционных системах, в которых есть системный вызов gettimeofday().
  • idate(string $format, ?int $timestamp = null): int|false возвращает из метки времени timestamp часть заданную параметром format:
    • базовые: d День месяца, m Месяц, y Год столетия, Y Год, o Год ISO-8601, h Час/12, H Час/24, i Минута, s Секунда
    • периодные: N День недели Пн(1)..Вс(7), w День недели Вс(0)..Сб(6), W Неделя года, z День года
    • вспомогательные: t Дней в месяце, I летнее время, L високосный год, Z Смещение секунд часового пояса
    • специальные: U Секунд от начала эпохи UNIX, B текущий бит суток в системе Swatch Internet Time (1/1000 суток = 86.4 секунд)
  • getdate(?int $timestamp = null): array возвращает ассоциативный массив, содержащий информацию о дате и времени, представленной меткой времени timestamp
  • date(string $format, ?int $timestamp = null): string представляет метку времени timestamp строкой по шаблону в строке format, допускающему форматные символы:
    • format форматный шаблон состоит и произвольной комбинации форматных символов //php.net/manual/ru/datetime.format.php
      наиболее часто используются:

      • d День 01..31, D День неделиMon..Sun, j День 1..31
      • m Месяц 01..12, M Месяц Jan..Dec, n Месяц 1..12
      • Y Год, y Год века 00..99
      • H Час 00..23, h Час 01..12, G Час 0..23
      • i Минута 00..59
      • s Секунда 00..59
    • timestamp метка времени для представления; если не указана, будет использована текущая метка из time()
      • ВНИМАНИЕ! представление метки времени выполняется для текущего часового пояса сервера!  Это значит, что одно значение timestamp будет представлено по-разному в зависимости от установленной timezone:
  • strtotime(string $datetime, ?int $baseTimestamp = null): int|false возвращает метку времени
    • по строке представления даты и времени '2022-10-12 19:44:59.000000'
    • по строке относительного формата '+30 sec -20 min +10 hour -5 day +2 month -1 year' //php.net/manual/ru/datetime.formats.relative.php
  • date_parse(string $datetime): array парсит строковое представление даты в ассоциативный массив
  • mktime( int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null ): int|false Возвращает метку времени Unix для заданной даты
    • допустимы нулевые и отрицательные значения месяца и дня, которые означают отступ от единичного значения (например: 0 месяц - це грудень попереднього року, -1 день - це передостанній день попереднього місяця)
    • також негативні значення доступні для годин, хвилин і секунд
    • дивись gmmktime(...) яка повертае локальну мітку часу Unix для часу за Грінвічем

Часовий пояс

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

  • date_default_timezone_set(string $timezoneId): bool устанавливает часовой пояс по умолчанию для всех функций даты/времени в скрипте
    • timezoneId строковый идентификатор часового пояса вида 'Europe/Kyiv', 'UTC' и другие
  • geoip_time_zone_by_country_and_region(string $country_code, string $region_code = ?): string вернёт часовой пояс и код региона соответствующей страны.В США код региона соответствует двухбуквенному сокращению каждого штата. В Канаде код региона соответствует двухбуквенному сокращению провинции или территориальный код соответствующий канадской почте.Для остальной части мира, для представления регионов GeoIP использует коды FIPS 10-4. Вы можете просмотреть » http://www.maxmind.com/app/fips10_4 для получения полного списка FIPS 10-4 кодов.Эта функция всегда доступна при использовании библиотеки GeoIP версии 1.4.1 или новее. Данные берутся непосредственно из библиотеки GeoIP, а не из какой-либо базы данных.
    • country_code двухбуквенный код страны (смотрите geoip_country_code_by_name())
    • region_code
      Двухбуквенный (или цифровой) код региона (смотрите geoip_region_by_name())
    • Возвращаемые значения ¶
      Возвращает часовой пояс в случае успешного выполнения или false, если страна и одновременно код региона не могут быть найдены.
  • timezone_abbreviations_list(): array возвращает каскадный ассоциативный массив, содержащий флаг перехода на летнее время, смещение и имя часового пояса; содержит все исторические случаи использования аббревиатур, что может привести к правильным, но запутанным записям., включая некоторые противоречия, поэтому список для создания списка выбора часового пояса пользователем не подходит
    • 'timezone_id' => 'Etc/GMT',
    • 'timezone_id' => 'Europe/Kiev',
  • timezone_transitions_get(DateTimeZone $zone, int $tsBegin = PHP_INT_MIN, int $tsEnd = PHP_INT_MAX): array|false возвращает массив сезонных переводов времени для часового пояса zone в период с tsBegin до tsEnd
      • возвращает индексированный массив ассоциативных массивов
      • объект [0] в массиве представляет метку времени начала периода tsBegin
        • этот объект исключается из массива, если значение метки tsBegin совпадает с меткой первого перевода времени
      • объект 'ts' => 1698541200,
        'time' => '2023-10-29T01:00:00+0000',
        'offset' => 7200,
        'isdst' => false,
        'abbr' => 'EET',
  • //

Классы DateTime, DateTimeZone

//

Классы DateTime

Объект класса DateTime реализует метку времени и методы.

  • public __construct(string $datetime = "now", ?DateTimeZone $timezone = null ) конструктор позволяет сразу задать значение метки времени и часовой пояс
    • datetime задает начальное значение метки времени великим множеством способов
      • 'now' и по умолчанию устанавливает метку в текущее временя UTC, при этом часовой пояс заданные в timezone на значение метки не повлияет
      • строкой в Unix-формает 'Y-m-d H:i:s' для заданного в timezone часового пояса
      • строкой из форматов и их комбинаций //www.php.net

        • 'yesterday' | 'today' | 'tomorrow'
        • 'midnight'' | 'noon'
        • 'weekday' | 'weekdays'
        • 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
        • 'first' | 'second' | 'third' | 'fourth' | 'fifth' | 'sixth' | 'seventh' | 'eighth' | 'ninth' | 'tenth' | 'eleventh' | 'twelfth'
        • 'next' | 'last' | 'previous' | 'this'
    • timezone  //
      • 'UTC', 'Europe/Kiev', Europe/Brussels, Asia/Jerusalem, America/New_York
        //
  • public setDate( int $year, int $month, int $day ): DateTime
  • public setISODate( int $year, int $week, int $dayOfWeek = 1 ): DateTime
  • public setTime( int $hour, int $minute, int $second = 0, int $microsecond = 0 ): DateTime
  • public static createFromFormat(string $format, string $datetime,?DateTimeZone $timezone = null ): DateTime|false создает объект DateTime из строки произвольного формата
  • public getTimestamp(): int возвращает смещение установленной
  • public setTimezone( DateTimeZone $timezone): DateTime
  • public getOffset(): int возвращает смещение часового пояса в секундах от UTC
  • public add( DateInterval $interval ): DateTime сдвигает вперед метку времени на интервал
  • public sub( DateInterval $interval ): DateTime сдвигает назад метку времени на интервал
  • public diff( DateTimeInterface $targetObject, bool $absolute = false ): DateInterval
    public format( string $format ): string
    public getOffset(): int

Класс часового пояса DateTimeZone

Объективно существует 24 часовых пояса. Отсчет исторически ведется от положения Британской Гринвичской королевской обсерватории. Это - пояс GMT. Новый современный отсчет поясов называется Всемирное координированное время UTC, и он полностью эквивалентен GMT.

По многим организационным причинам официальных и неофициальных часовых поясов существует около 200:

  • UTC Coordinated Universal Time UTC±00
  • GMT Greenwich Mean Time UTC±00
  • EET Eastern European Time UTC+2/GMT+2
  • EEST Eastern European Summer Time UTC+3/GMT+3
  • IST Israel Standard TimeUTC+2/GMT+2
  • IDT Israel Daylight Time UTC+3 GMT+3
  • и другие //en.wikipedia.org/wiki/List_of_time_zone_abbreviations

//

  • public __construct(string $timezone)
  • public getLocation(): array|false
  • public getName(): string
  • public getOffset(DateTimeInterface $datetime): int
  • public getTransitions(int $timestampBegin = PHP_INT_MIN,int $timestampEnd = PHP_INT_MAX): array|false возвращает в заданных границах времени все изменения/переходы для часового пояса
    • данные возвращаются в форме индексированного массива ассоциативных массивов, каждый из которых содержит значения с индексами:
      • ts метка времени начала действия изменения/перехода
      • time время метки в строковом Unix-формате
      • offset смещение от UTC в секундах
      • isdst признак летнего времени
      • abbr аббревиатура часового пояса

      Наприклад відображення історичної події переходу України 30 червня 1990 року з московського часового поясу за літнім часом (MSD) до поясу Eastern European Summer Time (EEST):

      результат у json-поданні:

  • public static listIdentifiers(): array возвращает индексированный массив строковых идентификаторов всех  часовых поясов (~420 поясов в 11 группах)

DateTimeImmutable
DateTimeInterface

DateTime
DateTimeImmutable
DateTimeInterface
DateTimeZone

Интернет ресурсы

Unix Timestamp

The Current Epoch Unix Timestamp онлайн конвертер //www.unixtimestamp.com

Веб-сервіс IP-API

//ip-api.com/docs

Бесплатный для некоммерческого использования сервис, ключ API не требуется. Легко интегрируется, доступен в форматах JSON, XML, CSV, Newline, PHP. Обслуживаем более 1 миллиарда запросов в день, которому доверяют тысячи компаний.

  • API реализуется ответом на HTTP-запрос
    • для ответа в формате JSON
      http://ip-api.com/json/<ip>[?<query>]
  • ответом на запрос является json объекта, который по умолчанию содержит часть свойств из возможного набора:

    • status (string) "success"|"fail" success
    • message (string) included only when status is fail
      Can be one of the following: private range, reserved range, invalid query invalid query
    • continent (string) Continent name North America
    • continentCode (string) Two-letter continent code NA
    • country (string) Country name United States
    • countryCode (string) Two-letter country code ISO 3166-1 alpha-2 US
    • region (string) Region/state short code (FIPS or ISO) CA or 10
    • regionName (string) Region/state California
    • city (string) City Mountain View
    • district (string) District (subdivision of city) Old Farm District
    • zip (string) Zip code 94043
    • lat (float) Latitude 37.4192 float
    • lon (float) Longitude -122.0574 float
    • timezone (string) Timezone (tz) America/Los_Angeles
    • offset (int) Timezone UTC DST offset in seconds -25200
    • currency (string) National currency USD
    • isp (string) ISP name Google
    • org (string) Organization name Google
    • as (string) AS number and organization, separated by space (RIR). Empty for IP blocks not being announced in BGP tables. AS15169 Google Inc.
    • asname (string) AS name (RIR). Empty for IP blocks not being announced in BGP tables. GOOGLE
    • reverse (string) Reverse DNS of the IP (can delay response) wi-in-f94.1e100.net
    • mobile (bool) Mobile (cellular) connection true
    • proxy (bool) Proxy, VPN or Tor exit address true
    • hosting (bool) Hosting, colocated or data center true
    • query (string) IP used for the query 173.194.67.94

Приклад використання

Простейший запрос временной зоны:

Веб-сервис IPSTACK

https://ipstack.com/documentation

Реализует API-интерфейс геолокации в режиме реального времени на основе ip-адреса.

  • API реализуется ответом на HTTP-запрос
    http://api.ipstack.com/<ip>?access_key=<key>

    • HTTPS доступен для платных тарифных планов
  • запрос должен включать ключ доступа <key> - уникальный строковый ключ длиной 32 символа, который можно получить после создания учетной записи на сайте ipstack, с одним из тарифных планов:
    • FREE $0 до 1000 http-запросов в месяц
    • BASIC $12.99 до 50'000 https-запросов в месяц
    • PROFESSIONAL $59.99 до 500'000 https-запросов в месяц
    • PROFESSIONAL PLUS $99.99 до 2'000'000 https-запросов в месяц
    • ENTERPRISE ...
  • ответ по умолчанию содержит данные в формате JSON (или в формате XML)

    • ip
    • документация описывает больше данных, чем было получено во время тестирования в тарифном плане FREE

 

//

Веб-сервис geoPlugin

API //www.geoplugin.com/webservices/php

Вызов api выполняется http-запросом

В содержание ответа находится php-сериализация ассоциативного массива:

  • geoplugin_request '46.185.12.123' обработанный ip-адрес
  • geoplugin_status 200 код успешного ответа
  • geoplugin_delay '1ms' время выполнения
  • geoplugin_credit 'Some of the returned data includes GeoLite data created by MaxMind, available from <a href=\\'http://www.maxmind.com\\'>http://www.maxmind.com</a>.'
  • geoplugin_city 'Kyiv' город
  • geoplugin_region 'Kyiv City' регион/область
  • geoplugin_regionCode '30' код региона/области
  • geoplugin_regionName 'Kyiv City' имя региона/области
  • geoplugin_areaCode '' код территории
  • geoplugin_dmaCode '' ?
  • geoplugin_countryCode 'UA' код страны
  • geoplugin_countryName 'Ukraine' название страны
  • geoplugin_inEU 0 признак вхождения в Евросоюз (0: нет, 1: да)
  • geoplugin_euVATrate false налоговый признак
  • geoplugin_continentCode 'EU' код континента
  • geoplugin_continentName 'Europe' название континента
  • geoplugin_latitude '50.458'  широта
  • geoplugin_longitude '30.5303' долгота
  • geoplugin_locationAccuracyRadius '200' радиус размещения
  • geoplugin_timezone 'Europe/Kyiv' часовой пояс
  • geoplugin_currencyCode 'UAH' код национальной валюты
  • geoplugin_currencySymbol '₴' символ национальной валюты
  • geoplugin_currencySymbol_UTF8 '₴' символ национальной валюты в кодировке UTF-8
  • geoplugin_currencyConverter '36.8532' текущий курс национальной валюты к EURO

//

Джерела

Datetime или timestamp lexxscorp@habr.com (Jun 4, 2009)

Leave a Reply