Дата и время – метод OOP

Расширение PHP Date/Time представляет собой набор классов, которые позволяют решать практически все задачи, связанные с датой и временем.

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

  • Дата или время представляются объектом DateTime;
  • Часовой пояс представляется объектом DateTimeZone;
  • Объекты DateInterval представляют временной интервал. Например, когда мы говорим, что с этого момента прошло два дня, то в этом случае «два дня» как раз является временным интервалом. Объект DateInterval не указывает на конкретную дату или время;
  • Объекты DatePeriod задают период между двумя конкретными датами.

А теперь не позволите ли мне дать два вам два небольших совета: оба, я уверен, помогут вам в решении практических задач.

От date() к DateTime

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

Функция DateTime дает нам намного больше, чем просто возможность выводить в нужном формате дату и время. Однако прежде, чем мы пойдем дальше, вам нужно будет создать новый объект DateTime, который представляет дату и / или время.

После этого мы сможем выполнять различные интересные манипуляции с ним. Новый объект создается точно так же, как и любой другой класс PHP:

$date = new DateTime();

Конструктор по умолчанию принимает для него значение «сейчас» — текущие время и дата. Чтобы создать объект для определенной даты, вы должны присвоить ему значение этих даты и времени.

Определение форматов для объекта, как правило очень интуитивно. Ниже приводятся несколько примеров создания объектов DateTime:

new DateTime('2013, March 24') //DateTime, представляющий 24 марта 2013 года.
new DateTime('2013-03-24') //DateTime, представляющий 24 марта 2013 года.
new DateTime('+2 days') //DateTime, представляющий 2 дня, считая с сегодняшней даты.
new DateTime('tomorrow')

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

Если в ней отсутствует нужный вам формат, вы всегда можете задать свой собственный с помощью функции DateTime :: createFromFormat:

DateTime :: createFromFormat ( ' JM- Y' , '24 -Mar- 2013 ');

Теперь, когда у нас есть объект DateTime, мы довольно легко можем сделать с ним кучу интересных вещей.

Формат времени для Unix:

$date->getTimestamp(); //возвращает формат времени для unix

Изменение даты/времени:

$date->setDate(2013, 12, 30); //задает конкретную дату в формате гггг, мм, дд
$date->setTime(12, 3, 20); //чч, мм, сс (опционально) изменяет время
$date->modify('tomorrow'); //изменение текущего времени
$date->setTimestamp(1364798550); //изменение используемого формата времени для unix

Обратите внимание, что, если задается значение, выходящее за границы диапазона, PHP соответственно модифицирует дату. Например, запись $date->setDate(2013, 12, 35); будет преобразована в дату 2014-01-04. То же самое касается и времени.

Работа с несколькими датами

Теперь, когда вы создали объект DateTime, следующее, что я вам расскажу – это как ваши приложения будут задавать для него значения. Вам понравится работать с датой и временем, как никогда раньше. Теперь вы будете иметь дело с объектами DateTime.

А не строками «строками», которые вы использовали раньше, чтобы задавать значения через функцию StrToTime, когда нужны были некоторые математические вычисления.

Скажем, вам нужно сравнить два дня рождения:

$sheldon = new DateTime('May 20th, 1980');
$neo     = new DateTime('March 11th, 1962');
 
if ($sheldon > $neo)
    echo 'Sheldon is younger than neo';

Еще один сценарий сравнения двух дат. Мы можем сравнить их вот таким образом:

$diff = $neo->diff($sheldon);

Функция diff задает объект DateInterval . Если мы создали для него все соответствующие значения:

DateInterval Object
(
    [y] => 18
    [m] => 2
    [d] => 9
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 6645
)

Это публичные свойства. Вы можете создать и собственный привлекательный формат вывода объекта DateInterval:

$diff->format('Neo is older by %Y years and %m months older'); //Нео старше на 18 лет и 2 месяца

Но самое главное, что интервал, заданный в объекте DateInterval, можно применить к другому объекту DateTime :

$neo->add($diff); // дата рождения Нео заменена на дату Шелдона

Примечание: Изменения в объекте DateTime, такие как, например, добавление данных, не создает новые объекты DateTime – все изменения производятся с исходным объектом. Всегда помните об этом, когда ваши приложения используют объекты DateTime.

В PHP 5.5 введен новый класс, который задает новые объекты, не поддерживавшиеся предыдущими версиями.
Функция diff не единственный способ, которым можно генерировать объект DateInterval. Так как это отдельный класс, новые объекты можно инициировать обычным способом:

$new_diff = new DateInterval('P2Y');

Значения года / месяца / дня и т.д., передаются в конструктор в виде строки. Более подробную информацию можно найти в справочной документации.

Работа с часовыми поясами

При создании новых объектов DateTime, второй аргумент конструктора предназначен для задания часового пояса. Если мы пропускаем его, тогда часовой пояс присваивается по умолчанию, на основании значения date.timezone файла php.ini. Вы можете изменить его в любое время через функцию date_default_timezone_set:

date_default_timezone_set('America/New_York');
new DateTime('today'); //объект datetime, принадлежит часовому поясу Нью-Йорка

Вы также можете изменить часовые пояса по ходу работы. Для этого, как вы уже догадались, сначала мы должны создать объект Timezone:

$timezone = new DateTimeZone('America/New_York');
$sheldon->setTimezone($timezone); //день рождения Шелдона теперь исчисляется по Нью-Йоркскому времени

Также вы можете задать часовой пояс при создании нового объекта DateTime :

$sheldon = new DateTime('May 20th, 1980', $timezone);

Опять же, важно помнить, что функция setTimezone изменяет ваш исходный объект DateTime. Обычно, когда мы используем метод setTimezone задача у нас стоит так: «привязать эту дату, к этому часовому поясу». В последнем же примере нам наоборот нужно было «создать дату в конкретном часовом поясе».

Список часовых поясов доступен в онлайн-документации.

DatePeriods

Я думаю, что в официальном руководстве дано наиболее подходящее определение:
«DatePeriods позволяет перечислить множества дат и времен, повторяющихся через определенные интервалы времени в течение заданного периода».

DatePeriod позволяет создать набор объектов DateTimes, используя два дня, ограничивающие временной интервал. Основными элементами набора являются: дата начала периода, интервал и дата окончания. Для каждого временного интервала создается новый объект DateTime.

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

//с момента его рождения это событие происходит каждый год с интервалом в один год
$interval = new DateInterval('P1Y');
 
//в качестве третьего аргумента задается дата окончания периода, новый объект DateTime()
//== прямо сейчас
$period   = new DatePeriod($sheldon, $interval, new DateTime());
 
foreach($period as $dt) {
    //DateTime objects
    echo $dt->format('Y-m-d - D'), "n";    
}

Результат обработки будет выглядеть таким образом:

1981-05-20 - Wed
1982-05-20 - Thu
1983-05-20 - Fri
1984-05-20 - Sun
1985-05-20 - Mon
1986-05-20 - Tue

Теперь по умолчанию объект DatePeriod включает дату начала временного интервала. Однако с помощью четвертого аргумента можно пропустить начальную дату:

$period   = new DatePeriod($sheldon, $interval, new DateTime(), DatePeriod::EXCLUDE_START_DATE);

Теперь давайте посмотрим, сколько дней рождения успел отпраздновать Нео, до того, как родился Шелдон:

$bdays   = new DatePeriod($neo, $interval, $sheldon, DatePeriod::EXCLUDE_START_DATE);
echo iterator_count($bdays);

Расширения

Все классы, которые мы сегодня рассмотрели, могут быть расширены под использование в сочетании с собственными методами. Одно из самых популярных расширений — это использование объекта DateTime с методом __ ToString, которое позволяет выводить корректно DateTime без обращения к формату.

Несколько примеров использования

Лично я в своей практике часто использую объекты DateTime, когда имею дело со столбцами базы данных, в которых записываются значения даты / времени. Все даты хранятся в таблице в виде даты по Гринвичскому времени.

Код приложения работает только с объектами DateTime, однако пока в ходе обработки запроса не будут сгенерированы даты в соответствующих форматах, все они исчисляются в часовом поясе по Гринвичу. Данный подход позволил мне довольно просто работать с датами, которые принадлежат разным часовым поясам.

Я могу работать с временным объектом, исчисляемым по Нью-Йоркскому времени, и не заботиться о его формате, когда данные записываются в базу данных. Я могу легко переключаться между форматом Unix и регулярным форматом даты-времени в любой момент, мои приложения обрабатывают только объекты DateTime.

Я также использовал объект DateInterval, чтобы упростить логику скрипта, обрабатывающего поступающие платежи за подписку. Использовав объекты DateInterval для определения интервалов между двумя регулярными платежами, я сделал его намного проще.

Мне всего лишь нужно применить заданный интервал к дате последней оплаты.
У вас есть свои собственные примеры работы с датой / временем? Поделитесь ими в комментариях.

На будущее

Расширение DateTime может иметь столь большое количество применений. Если вы следите за последними событиями в этой области, то знаете, что появились новые классы и интерфейсы, принятые уже после PHP 5.5.

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

Перевод статьи «Dates and Time – The OOP Way» был подготовлен дружной командой проекта Сайтостроение от А до Я.