Работа с датами и временем в PHP и MySQL

При работе с любым языком программирования, задачи, касающиеся даты и времени, часто является тривиальными и простыми. И PHP, к счастью, имеет один из самых мощных инструментов по работе с датой/временем, который поможет вам легко справиться со всеми видами задач связанных с временем: работа с Unix timestamp, форматирование дат для лучшего восприятия их человеком, работа с часовыми поясами, подсчет количества дней между сегодняшним днем ​​и, например, вторым вторником в следующем месяце, и т.д. В этой статье вы ознакомитесь с основными временными функциями PHP (time(), mktime() и date()) и их объектно-ориентированными "коллегами", а затем познакомитесь с MySQL-датами, и мы покажем вам, как подружить их с PHP.

PHP-функции даты и времени

Большая часть этой статьи будет посвящена работе с Unix-временем или POSIX. Время здесь представлено в виде количества секунд, прошедших с полуночи 1 января 1970 года, UTC. Если вы заинтересованы в полной истории времени Unix, то ознакомьтесь со статьей Unix time в Википедии.

UTC, известное также под именем Coordinated Universal Time, также известное как GMT, а иногда и Zulu time, это время нулевого градуса долготы.   Все остальные часовые пояса в мире являются положительным или отрицательным смещением относительно этого времени. UTC и Unix время сделает вашу жизнь проще, когда вам придется иметь дело с часовыми поясами. Я расскажу об этом чуть позже, а пока рассмотрим некоторые функции для работы со временем.

Получение текущего времени Unix

Функция time() без аргументов возвращает количество секунд, прошедших с начала эпохи Unix. Чтобы проиллюстрировать это я буду использовать интерактивную консоль PHP.

sean@beerhaus:~$ php -a
php > print time();
1324402770

Если вам нужен массив для работы с Unix-временем, используйте функцию getdate(). Она принимает необязательный аргумент Unix timestamp, если его нет, то по умолчанию это значение функции time().

php > $unixTime = time();
php > print_r(getdate($unixTime));
Array
(
 [seconds] => 48
 [minutes] => 54
 [hours] => 12
 [mday] => 20
 [wday] => 2
 [mon] => 12
 [year] => 2011
 [yday] => 353
 [weekday] => Tuesday
 [month] => December
 [0] => 1324403688
)

Форматирование времени Unix

Unix-время может быть легко отформатировано в почти любую строку, чтобы человек мог его прочитать. Функция date() используется для форматирования Unix timestamp в понятную для человека строку и принимает аргументы форматирования и необязательный аргумент "время". Если необязательный аргумент "время" не указан, используется значение функции time(), т.е. текущее время.

php > print date("r", $unixTime);
Tue, 20 Dec 2011 12:54:48 -0500

Аргумент "r" при форматировании строки возвращает время в формате RFC 2822. Конечно, Вы можете использовать другие спецификаторы, чтобы определить свои собственные форматы.

php > print date("m/d/y h:i:s a", $unixTime);
12/20/11 12:54:48 pm
php > print date("m/d/y h:i:s a");
12/20/11 01:12:11 pm
php > print date("jS \of F Y", $unixTime);
20th of December 2011

Полный список допустимых значений для форматирования, смотрите на странице функции date() в документации по PHP. Функция становится еще более полезной, когда используется в сочетании с функциями mktime() и strtotime(), это вы увидите в дальнейших примерах.

Создание Unix-времени от заданного времени

Функция mktime() используется, чтобы создать метку времени на основе списка значений, которые соответствуют каждой части даты (секунды, минуты, часы, год и т.д.). Она принимает ряд целочисленных аргументов для установки каждой части даты в следующем порядке:

mktime(часы, минуты, секунды, месяц, день, год, isDST)

Вы можете установить isDST равный 1, если используется летнее время, в противном случае 0, или -1, если это неизвестно (значение по умолчанию).

php > print date("r", mktime(12, 0, 0, 1, 20, 1987));
Tue, 20 Jan 1987 12:00:00 -0500
php > print date("r", mktime(0, 0, 0, date("n"), date("j"), date("Y")));
Tue, 20 Dec 2011 00:00:00 -0500
php > print date("r", mktime(23, 59, 59, date("n"), date("j"), date("Y")));
Tue, 20 Dec 2011 23:59:59 -0500

Вы можете видеть, что mktime() может быть очень полезна при работе с запросами к базе данных, которые используют диапазоны дат определенные пользователем. Например, если вы храните timestamp в виде целых чисел (Unix время) в MySQL, очень легко настроить общий временной диапазон для запроса.

<?php
$startTime = mktime(0, 0, 0, 1, 1, date("y"));
$endTime  &amp;nbsp;= mktime(0, 0, 0, date("m"), date("d"), date("y")); ?>

Преобразование английских дат в Unix время

Почти магическая функция strtotime() принимает строку даты/времени в качестве первого аргумента, и Unix timestamp, чтобы использовать в качестве основы для преобразования. Смотрите документацию для получения информации о допустимых форматах даты.

php > print strtotime("now");
1324407707
php > print date("r", strtotime("now"));
Tue, 20 Dec 2011 14:01:51 -0500
php > print strtotime("+1 week");
1325012569
php > print date("r", strtotime("+1 week"));
Tue, 27 Dec 2011 14:03:03 -0500
php > print date("r", strtotime("next month"));
Fri, 20 Jan 2012 14:04:20 -0500
php > print date("r", strtotime("next month", mktime(0, 0, 0)));
Fri, 20 Jan 2012 00:00:00 -0500
php > print date("r", strtotime("next month", mktime(0, 0, 0, 1, 31)));
Thu, 03 Mar 2011 00:00:00 -0500

Объекты DateTime и DateTimeZone

Объект PHP DateTime является объектно-ориентированным подходом к работе с датами и часовыми поясами. Конструктор принимает строковое представление времени, очень похоже на функцию strtotime(), о которой говорилось выше. Значение по умолчанию, если аргумент не указан, "now" (сейчас).

php > $dt = new DateTime("now");
php > print $dt->format("r");
Tue, 20 Dec 2011 16:28:32 -0500
php > $dt = new DateTime("December 31 1999 12:12:12 EST");
php > print $dt->format("r");
Fri, 31 Dec 1999 12:12:12 -0500

Метод format() объекта DateTime работает так же, как функция date(), и принимает все те же символы форматирования. Объект DateTime также имеет несколько полезных констант, которые могут быть переданы в метод format().

php > print $dt->format(DATE_ATOM);
2011-12-20T15:57:45-05:00
php > print $dt->format(DATE_ISO8601);
2011-12-20T15:57:45-0500
php > print $dt->format(DATE_RFC822);
Tue, 20 Dec 11 15:57:45 -0500
php > print $dt->format(DATE_RSS);
Tue, 20 Dec 2011 15:57:45 -0500

Полный список констант можно найти на странице документации DateTime.

Поскольку мы будем работать с часовыми поясами, давайте установим часовой пояс по умолчанию. В файле конфигурации php.ini (у меня есть один для CLI и один для Apache) найдите раздел, который выглядит следующим образом:

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
; date.timezone =

Если значение не указано для date.timezone, PHP будет пытаться сделать все возможное, чтобы определить часовой пояс системы которая установлена на вашем сервере. Вы можете проверить, какое значения использует PHP при помощи функции date_default_timezone_get().

php > print date_default_timezone_get();
America/New_York

Давайте установим временную зону сервера на время UTC (date.timezone = UTC) и сохраним файл конфигурации. Вы должны перезагрузить Apache или CLI, чтобы увидеть изменения.

Объект DateTime включает в себя внутренний экземпляр класса DateTimeZone для отслеживания часовых поясов. Когда вы создаете новый экземпляр DateTime, DateTimeZone должен быть установлен по умолчанию как указано в php.ini.

php > $dt = new DateTime();
php > print $dt->getTimeZone()->getName();
UTC

Полный список допустимых имен часовых поясов можете найти на соответствующей странице документации.

Теперь вы можете увидеть разницу во времени, между двумя объектами DateTime, созданных для разных часовых поясов. Например, вот пример, который преобразует время из UTC в America/New_York(EST).

php > $dt = new DateTime();
php > print $dt->format("r");
Tue, 20 Dec 2011 20:57:45 +0000
php > $tz = new DateTimeZone("America/New_York");
php > $dt->setTimezone($tz);
php > print $dt->gt;format("r");
Tue, 20 Dec 2011 15:57:45 -0500

Обратите внимание на смещение -0500 в декабре месяце. Если вы измените значение времени на летний день, например, 1 июля, то увидите, что он "знает" летнее время (EDT).

php > $tz = new DateTimeZone("America/New_York");
php > $july = new DateTime("7/1/2011");
php > $july->setTimezone($tz);
php > print $july->>format("r");
Thu, 30 Jun 2011 20:00:00 -0400

Использование даты в MySQL и PHP

Если вы использовали MySQL на любом уровне, вы, вероятно, обратили внимание на тип данных DATETIME. Он выглядит как дата, и если бы вы сказали, что это дата, то вы были бы правы. Но поскольку вы выбираете его из MySQL, в PHP эта дата уже представляет собой строку, которая выглядит как дата.

* Да, я знаю, что MySQL имеет множество функций форматирования даты. Почему же мы хотим отформатировать дату прямо из базы данных в таком случае? У нас может быть несколько различных преобразований и форматов, которые мы хотим применить. И лучше всего форматировать дату только тогда, когда человек собирается её увидеть.

<?php
$db = new PDO("mysql:host=localhost;dbname=testdb", "dbuser", "dbpassword");

$result = $db->query("SELECT dt_date FROM some_table");
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
 print_r($row);
 var_dump($row["dt_date"]);
}
$result->closeCursor();

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

Array
(
 [dt_date] => 2012-01-16 13:03:49
)
string(19) "2012-01-16 13:03:49"

Значение DATETIME - это время локального сервера MySQL в любом часовом поясе. Если все, что вы делаете, касается только одного сервера в одном часовом поясе, DATETIME, вероятно, подходит для большинства ваших потребностей, и я очень завидую вам.

Итак, как вам справляться с датами и часовыми поясами в PHP и MySQL? Сохранять даты как Unix timestamps.

Вы уже знаете, что время Unix - это количество секунд, прошедших с 1 января 1970 года в UTC, так что это дает вам постоянный часовой пояс для работы, и, надеюсь, вы заметили, что многие PHP функции даты/времени основываются на Unix timestamps.

При работе с MySQL, я обычно создаю столбец таблицы, который будет хранить даты как INTEGER UNSIGNED. При вставке даты можно использовать функции time() из PHP или UNIX_TIMESTAMP() из MySQL.

mysql> SELECT UNIX_TIMESTAMP();
+------------------+
| UNIX_TIMESTAMP() |
+------------------+
|       1326738918 |
+------------------+

Если вы хотите использовать MySQL для форматирования даты, то вы можете. Но время будет соответствовать часовому поясу сервера и я советую вам воздержаться от любого типа форматирования, то тех пока вы не находитесь в шаблоне/на уровне представления в вашем веб-приложении и готовы, чтобы человеческий глаз увидел это.

mysql> SELECT FROM_UNIXTIME(UNIX_TIMESTAMP());
+---------------------------------+
| FROM_UNIXTIME(UNIX_TIMESTAMP()) |
+---------------------------------+
| 2012-01-16 13:37:16             |
+---------------------------------+

В любом приложении, как правило, есть таблица, которая отслеживает пользователей, пользовательские настройки часового пояса, которые затем будут считаны в значение $_SESSION. Если у вас есть запись в сессии, вроде этой:

$_SESSION["userTZ"] => "America/Chicago"

Вы можете легко конвертировать сохраненный Unix timestamp (UTC) в любую дату в часовом поясе конкретного пользователя.

<?php
$result = $db->query("SELECT int_date FROM some_table");
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
 $dt = new DateTime();
 $tz = new DateTimeZone($_SESSION["userTZ"]);
 $dt->setTimestamp($row["int_date"]);
 $dt->setTimezone($tz);
 print $dt->format("r");
}

Это вернет дату "Mon, 16 Jan 2012 12:03:49 -0600". Где -0600 говорит нам, что это 6 часов от временной зоны UTC, которая имеет смещение 0.

Если бы мы изменили часовой пояс на America/Los_Angeles, то в результате дата выглядела бы так:

Mon, 16 Jan 2012 10:03:49 -0800

А America/New_York вернет:

Mon, 16 Jan 2012 13:03:49 -0500

Заключение

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

Вы видели, как легко получить метку времени, как отформатировать дату в любой формат, как преобразовать английское представление даты в метку времени (timestamp) и как преобразовывать время между часовыми поясами. Если выделить две основных темы из статьи, то это: 1) Unix время и 2) UTC, как основная временная зона для всех дат при работе с PHP и MySQL.

Идея отсчитывать всё время по UTC относится не только к PHP и MySQL, это считается хорошей практикой в любом языке.

Перевод статьи с phpmaster.com


Если у Вас возникли вопросы, то для скорейшего получения ответа рекомендуем воспользоваться нашим форумом

You have no rights to post comments