понедельник, 24 октября 2011 г.

Выпуск 3. Работа с датами в php


Сегодня выпуск в основном для программистов. В следующий раз исправлюсь, будет много про бизнес и управление временем.


Совет

Если вам написал заказчик с проблемой, которую вы не можете решить или даже рассмотреть сегодня, обязательно напишите ему, что вы задачу приняли, но сможете ей заняться не сразу. Например, "Да, вашу проблему вижу. Я её смогу исследовать в ближайшие 2 дня, исправлю не позже чем за 2 недели".

Благодаря такому подходу:

  1. заказчик будет доволен и видит, что вы про него не забыли
  2. заказчик будет знать, хотя бы примерно, когда ждать рез-та
  3. заказчик будет понимать, что вы работаете не только с ним и что любая его задача не обязательно выполняется в режиме "всё бросить и начать делать". Со временем он начнёт четче для себя представлять какие задачи для него сейчас приоритетнее, а какие были результатом мимолётного желания и могут подождать некоторое время.


Ну и без фанатизма, иногда действительно нужно всё бросить и делать, но не так часто, как это кажется.




Работа с датами в php


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



Time zone

Для начала правильно настройте текущую временную зону (TZ) в php и базе данных. В php смотрите на функцию date_default_timezone_set, в mysql - http://dev.mysql.com/doc/refman/5.1/en/time-zone-support.html. В mysql можно задать как дефолтную временную зону, так и зону для каждого соединения.

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



Форматы

Сначала разберемся с форматами.

1. Строки без временной зоны. Если вы используете дату в "стиле mysql" (YYYY-MM-DD HH:MM:SS) или подобных человеко-читаемых строковых форматах, где не содержится информация о временной зоне, всегда согласовывайте в какой временной зоне будут данные. Если вопрос идёт о каком-то внешнем API (как получение, так и приём), лучше всегда использовать форматы с указанием временной зоны или не зависящие от неё. На крайний случай согласовать временные зоны и нормализовать данные при обработке.

2. Строки с указанием временной зоны. Например, как в заголовках HTTP - "Thu, 19 Nov 1981 08:52:00 GMT". Основная проблема таких дат - их парсинг. Из-за неразберихи в форматах браузеры, например, хранят эту дату для If-Modified-Since напрямую в том виде, как передавал сервер. Использовать такое представления для внутреннего хранения не рекомендуется.

3. Timestamp, (micto timestamp) - это уже число, а не строка. Показывает кол-во секунд (микросекунд) с 00:00:00 01.01.1970 UTC. Не все знают, что это значение TZ независимо! Оно отсчитывается от времени по гринвичу (UTC), не зависимо от того, какую временную зону вы используете. Т.е. при преобразовании строка -> timestamp и обратно *важно*, какую временную зону вы используете. Например, в strtotime при работе в временной зоне MSK от полученного после парсинга значения даты вычитается 4 часа, затем считает timestamp. В обратную сторону аналогично. Зато если у вас есть время в виде timestamp вы можете не думать о TZ. Это подходящий формат для использования в API.

Важно ещё помнить, что отсчитывается timestamp от 1970 года и, например, хранить даты рождения в таком формате нельзя (ну по крайней мере ещё ближайшие лет 100, потом уже будет можно :) ), а вот записи в журнале изменения это самое оно.


Перевод форматов в php

1. Перевод строка -> timestamp:

1.1 strtotime

$timestamp = strtotime($dateString);


где можно безопасно использовать форматы, типа "YYYY-MM-DD HH:MM:SS" и некоторые другие. Обработка происходит "магическим" методом, поэтому ...

1.2 strptime

Для более точного задания формата можно использовать strptime. Только она возвращает не timestamp, а набор распознанных данных, но можно написать не сложную функцию по преобразованию уже в timestamp. Всё это актуально для php 5.1+, для более старых версий нужно найти реализацию strptime на чистом php.

1.3 через DateTime класс

Актуально для php 5.3+

$mydate = DateTime::createFromFormat('d-M-Y', '15-Feb-2009');
echo $datetime->format('U');

Плюс в том, что форматов поддерживается очень много, минус в том, что формат задаётся в формате, близком к функции date, а я, например, больше люблю формат strftime (%Y-%m-%d).


2. Timestamp -> строка

2.1 strftime

strftime(%Y-%m-%d %H:%M:%S, $timestamp);

2.2 date

date('Y-m-d H:i:s', $timestamp)

2.3 через класс DateTime

$mydate = DateTime::createFromFormat('@'.$timestamp);
$mydate->format('Y-m-d H:i:s');
strftime('%Y-%m-%d %H:%M:%S', (int) $mydate->format('U')); 
//но так ещё нужна проверка на unix эпоху


Операции с датами

И так, до 5.2 практически единственным способом операций с датами было использование strtotime, который позволяет писать так:

$tmpstmp = strtotime('now +1 week');
$tmpstmp = strtotime('2011-10-24 12:00:00 +1 month -5 days');

Но опять же из-за "магического свойства" этой функции, иногда были проблемы. Для обхода проблем нагородили кучу сложных способов, например, кто видел реализация Zend_Date, тот в цирке больше не смеётся. Плюс ко всему ниже unix эпохи (1970 год), опуститься нельзя.

В php 5.2 появился новый класс DateTime, а в php 5.3 к нему добавилось пару полезных возможностей. Теперь его можно рекомендовать в виде основного инструменты работы с датами. Там возможны любые операции:
- вычитание дат (DateTime) - (DateTime) = (DateInterval)
- добавление/вычитание из даты: (DateTime) +- (DateInterval) = (DateTime)
- форматирование (см. примеры выше)
- распознание строковых представлений (см. примеры выше)
- работа с временными зонами и пр.

Класс работает с датами от 0 до 9999 года (в "кишочках" используется 64bit целое число).
Крайне рекомендую.

Подроблее читайти manual - http://ru.php.net/manual/en/class.datetime.php

В PE/ЕИС я собираюсь немного похимичить с этими классами, чтобы заставить их принимать strftime формат, даже с некоторыми расширениями.

Результатом поделюсь со всеми желающими.

2 комментария:

  1. Спасибо! Прочитала, почти всё поняла.
    Если вдруг обнаружиться, что у ЧП дата рождения оказалась в 1970, буду знать, что не так :)
    Отдельное спасибо за совет в начале.

    ОтветитьУдалить
  2. Некоторые фичи внутри strtotime(), связанные с использованием слов last/first/next/prev,
    например
    strtotime("last Sunday"), strtotime("next month")
    не работают под win32

    ОтветитьУдалить