Работа с временными зонами в PHP

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

    Далее перевод :-)
    Автор: Derick Rethans
    Url: www.derickrethans.nl/talks.php

    Содержание


    Проблемы временных зон
    • Большинство мест на земле имеют часовое смещение
    • Некоторые места имеют получасовое или пятнадцати-минутное временное смещение
    • В некоторых местах временная зона меняется в течении года

    Проблемы перевода времени
    • Множество стран переводит время дабы увеличить продолжительность светлой части суток
    • Это делают не во всех странах
    • Перевод часов происходит в разное время
    • Существует большое количество исключений

    Другие проблемы временных зон и «перевода стрелок»
    • В Австралии зоны строго вертикальные
    • В Бразилии дата, когда переводят срелки, меняется каждый год
    • На остров Lord Howe (Австралия) переводят стрелки только на почаса
    • Непал переводит стрелки на 15 минут

    Проблемы в наименовании временных зон
    • Одно именование может означать разные временные зоны:
      Например:PST — Pacific Standart Time и Pakistan Standart Time
      EST — Easten Standart Time (США), Easten Standart Time (Австралия) и Easten Brazil Standart Time
    • Одна зона может иметь несколько наименований
    Более того, в разных операционных системах имена зон могут различаться.

    Проблемы в представлении даты и времени
    • Двусмысленность
      06/08/04
      6 Августа 2004 года
      8 Июня 2004 года
      4 Августа 2006
    • Нечитаемость
      20040425010541
      25 Апреля 2004 года 01:05:41
    • Другие «магические» форматы
      third saturday
      2004-03-10 16:33:17.11403+1
      2001-11-29T13:20:01.123-05:00
      23:41:00.0Z
      04:05:07.789 +0930
      1999.238

    Функции в php 4 и php 5 для работы с временем и датами
    • checkdate — Проверяет правильность даты по грегорианскому календарю
    • date(gmdate) — Форматирует системную дату/время (по Гринвичу)
    • getdate — Возвращает информацию о дате/времени
    • gettimeofday — Возвращает текущее время
    • localtime — Возвращает системное время
    • mktime(gmmktime) — Возвращает метку времени для заданной даты (по Гринвичу)
    • strftime(gmstrftime) — Форматирует дату/время с учетом текущей локали (по Гринвичу)
    • strtotime — Преобразует текстовое представление даты на английском языке в метку времени Unix

    Ограничения работы с датами
    • Использование Unix-таймстампа в качестве базы (количество секунд, прошедшее с 1 января 1970 года по Гринвичу)
    • Таймстамп представляется ввиде 32-битного целого, что ограничивает использование данного представления диапазоном 1902-2038 годами
    • На многих операционных системах данный тип может быть только положительным, ограничение диапазона 1970-2038
    • strtotime — достачно сложная функция, реализация которой подвержена большим количеством ошибок
    • Нет вариантов корректной работы с временными зонами
    • Некоторые функции являются зависимыми от операционной системы

    Улучшения работы с датами в php 5.1 и выше
    • Использование 64-битного типа для хранения даты. Теперь граница интервала использования данного типа 4 Декабря 292277026596 год
    • Функция strtorime была переписана
    • Больше нет зависимости в реализации от операционной системы
    • Полная поддержка временных зон, перевода часов и модификаторов дат
    • Новый формат модификаторов временных зон
    • Более продвинутые функции для работы с датами

    Форматы данных в php 5.1 и выше
    Американский формат: 9/11;4:08 pm;12/22/78; 8:51:00 am
    Комбинированный формат: Sat, 24 Apr 2004 21:48:40 +0200; 2001-11-29T13:20:01.123-05:00
    Описательный формат: tomorrow; four months ago; last saturday; +20 days 2 hours
    Текстовый формат: December 22. 1978; 22-december-1978
    Все ISO 8601 форматы: 1978/12/22; 13:03:12.45678; 13:03:12.45678 +0100; 15:57-8; 1978-12-22; 15:57:41 pdt; 13:03:12.45678 CEST; 231431 CEST; 70-4-25; 13:03 CEST; 04:05 -0930; 23:41F
    Форматы баз данных: 1999-Jan-08; 1999.238
    Поддержка временных зон в php 5.1 и выше
    • Встроенная база на 551 временную зону
    • Нет зависимости от аббревиатуры временной зоны
    • Названия временных зон имеют формат — Continent/Location или Continent/Location/Sublocation

    Поддержка временных зон. Как использовать?
    • Каждая временная зона идентифицируется городом с наибольшим населением на данной территории
    • Зоны разделены на 10 больших групп: Африка, Америка, Антарктика, Арктика, Азия, Европа, Индия, Тихий океан
    • Существует также группа Другие, состоящая из вышедших из употребления названий и служащая для обеспечения обратной совместимости
    • Больше информации по аббревиатурам временных зон по указанному адресу

    Изменение информации по временным зонам
    • Обновления информации по временным зонам происходят около 20 раз в год
    • Большинство обновлений происходят неожиданно
    • Чтобы не потерять актуальность, релизы php должны быть достаточно часты
    • Расширение pecl, которое исправляет данные недостатки, называется timezonedb (pecl install timezonedb)

    Разбор дат (прошлое)
    Разбор строки с информации о дате и времени используя функцию strtotime
    $ts=strtotime(«2005-07-11 22:16:50 CEST»);

    Использование инициализации таймстампом
    $date=strtotime(«2005-07-11 22:16:50 CEST»);
    $ts=strtotime(«next week»,$date);

    Возвращаемое значение представляет собой 32-битное целое
    Разбор дат (будущее)
    Разбор строки, содержащей информацию о дате и времени, используя функцию date_create
    «$ts=date_create(«1978-12-22 09:15:50»);»
    В качестве альтернативы можно сосздать объект типа DateTime
    $ts=new DateTime(«1978-12-22 09:15:50»);

    Эти функции уже не используют 32-битное целое, они возвращают объект класса DateTime, который является оберткой над 64-битным целым, для доступа к которому следует использовать следующую конструкцию
    $ts=new DateTime(«1978-12-22 09:15:50»);
    echo $ts->format('U');
    Разбор строки, используя функцию date_parse
    $date=«22apr2006 8:36:14.43 #^ Europe/Oslo CEST»;
    $t=date_parse($date);
    echo $t['year'].'-'.$t['month'].'-'.$t['date'].' ';
    echo $t['hour'].':'.$t['minute'].':'.$t['second'].' ';
    echo $t['tz_id']."\n";

    Формат дат
    date_default_timezone_set(«Europe/Oslo»);
    $ts=date_create(«1979-12-31 09:15»);
    echo date_format($ts,«D Y-m-d H:i:s -\I\S\O \W/\Y: W/o»);

    Все модификаторы форматов, поддерживаемые функцией date, поддерживаются тоже
    Предопределенные форматы:
    date_default_timezone_set(«Europe/Oslo»);
    $ts=date_create(«December 22nd, 2005 15:41»);
    echo date_format($ts, DATE_ISO8601);
    echo date_format($ts, DateTime::RFC1036);
    echo date_format($ts, DATE_RSS);

    Локали в php6
    date_default_timezone_set(«Europe/Oslo»);
    $ts=date_create(«December 22nd, 2005 15:41»);
    $locales=array('en_US','fr_CA','nb_NO','ru_RU','ar_SA','ja_JP');
    foreach($locales as $locale)
    {
        locale_set_default($locale);
        echo date_format_locale($ts,DATE_RFC2822)."\n";
    }

    Обновление даты и времени
    $date=new DateTime('now');
    echo $date->format(DateTime::ISO8601)."\n";
    $date->setTime(15,0,7);
    echo $date->format(DateTime::ISO8601)."\n";
    $date->setDate(2006,12,22);
    echo $date->format(DateTime::ISO8601)."\n";
    $date->setIsoDate(2006,45,2);
    echo $date->format(DateTime::ISO8601)."\n";

    Изменение даты и времени
    date_default_timezone_set(«Europe/Oslo»);
    $date=new DateTime('now');
    echo $ts->format(DATE_RFC2822)."\n";
    $ts->modify("+2 days");
    echo $ts->format(DATE_RFC2822)."\n";
    $ts->modify(«fifth month»);
    echo $ts->format(DATE_RFC2822)."\n";
    $ts->modify(«Friday +3 weeks»);
    echo $ts->format(DATE_RFC2822)."\n";
    $ts->modify(«next friday»);
    echo $ts->format(DATE_RFC2822)."\n";

    Использование временных зон
    Установка сокращенного названия временной зоны при разборе даты
    $ts=date_create(«1978-12-22 09:15 CEST»);

    Использование аббревиатур временных зон признано устаревшим, необходимо установить временную зону по умолчанию или полный идентификатор временной зоны
    $ts=date_create(«1978-12-22 09:15 Europe/Oslo»)

    Временная зона по умолчанию
    Установка временной зоны по умолчанию
    date_default_timezone_set(«Europe/Oslo»);
    $ts=date_create(«1978-12-22 09:15»);
    echo $ts->format('e');

    Получение временной зоны по умолчанию
    echo date_default_timezone_get();

    Временная зона по умолчанию определяется в соответствии с
    • значением установленным функцией date_default_timezone_set()
    • переменной окружения TZ
    • значением date.timezone в php.ini
    • системным отображением сокращения имени временной зоны

    Использование временных зон. Объект временная зона
    Создание временной зоны
    $tz=timezone_open('Asia/Singapore');

    Использование временной зоны при разборе строкового представления
    $tz=timezone_open('Pacific/Honolulu');
    $ts=date_create(«1978-12-22 09:15',$tz);

    Использование временной зоны в качестве аргумента функций не перекрывает временную зону, указанную в разбираемой строке
    $tz=new DateTimeZone('Pacific/Honolulu');
    $ts1=new DateTime('1978-12-22 09:15 CEST',$tz);
    $ts2=new DateTime('1978-12-22 09:15 Europe/Amsterdam',$tz);
    echo $ts2->format(DateTime::RFC2822);

    Получение названия временной зоны
    $tz=timezone_open('Asia/Singapore');
    echo timezone_name_get($tz);

    $tz=timezone_open('CEST');
    echo timezone_name_get($tz);

    Получение текущего смещения от Гринвича
    $tz=new DateTimeZone(»Europe/Amsterdam");
    $d=new DateTime(«2005-01-22 09:15»);
    echo $tz->getOffset($d);
    $d->modify("+6 months");
    echo $tz->getOffset($d);

    Использование временных зон. Изменение временной зоны
    Использование временной зоны при разборе строкового представления
    $tz1=timezone_open('Pacisic/Honolulu');
    $tz2=timezone_open('Europe/Amsterdam');
    $tz3=timezone_open('Australia/Melbourne');

    $ts=date_create(«1978-12-22 09:15»,$tz1);
    echo $ts->getTimezone()->getName().':'.$ts->format(DATE_RFC822)."\n";
    $ts->setTimezone($tz2);
    echo $ts->getTimezone()->getName().':'.$ts->format(DATE_RFC822)."\n";
    $ts->setTimezone($tz3);
    echo $ts->getTimezone()->getName().':'.$ts->format(DATE_RFC822)."\n";

    Таблицы перевода времени
    $tz=timezone_open('Europe/Amsterdam');
    $trs=timezone_transitions_get($tz); // или $trs=$tz->getTransitions();
    foreach($trs as $ts)
        printf("%20s %7d %d %s\n",$tr['time'],$tr['offset'],$tr['isdst'],$tr['abbr']);

    Аббревиатуры и идентификаторы
    Получение всех поддерживаемыех идентификаторов
    $ids = timezone_identifiers_list();
    echo «Number of identifiers: ».count($ids)."\n";
    echo implode(", ", array_slice($ids, 0, 5)).'...'.implode(", ", array_slice($ids, -5));

    Получение всех поддерживаемых аббревиатур
    $abbrs = timezone_abbreviations_list();
    foreach ($abbrs as $abbr => $ids)
        foreach ($ids as $id)
            printf("%-6s %6d %d %s\n", strtoupper($abbr),$id['offset'],$id['dst'], $id['timezone_id']);

    Перевод стрелок. Когда будет следующий?
    date_default_timezone_set(«America/New_york»);
    $tz = new DateTimeZone(«America/New_york»);
    foreach (timezone_transitions_get($tz) as $tr)
        if ($tr['ts'] > time()) break;
    $d = new DateTime( "@{$tr['ts']}" );
    printf(«The timezone %s switches to %s on %s.\nThe new GMT offset will be: %d (%s)\n», $tz->getName(), $tr['isdst']? «DST»: «standard time», $d->format('d M Y @ H:i T'), $tr['offset'], $tr['abbr']);

    Когда появится данная функциональность
    В php 5.1 необходимо скомпилировать, используя флаг CFLAGS=-DEXPERIMENTAL_DATE_SUPPORT=1
    В php 5.2 и выше по умолчанию

    Дополнительно опубликовано здесь
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 37
    • +6
      хорошая подборка, спасибо. Но для навигации уже требуется оглавление, добавите?
      • 0
        Хорошо. Полезно. Грамотно.
        Только опечаток полно :-)
        • 0
          Действительно, очень полезная статья, но ИМХО, ничего нового я здесь не узнал...
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              >статейку можно повесить на стену
              Полностью согласен, уже сделал :)
              • 0
                хотел сначала написать от себя взгляд, потом перевод авторской статьи
            • 0
              Прекрасная подборка. Но всё это касается работы с временной зоной, которая как-то была задана. А вот как узнать временную зону пользователя, кроме прямого вопроса "введите Вашу временную зону"? :-)

              Еще никак не могу придумать, как получить временную зону (точнее, часовое смещение в данный момент времени) для точки с заданными координатами. Ведь есть же карта часовых поясов, на которой они описаны довольно простыми кривыми... Теоретически, можно проверять принадлежность точки областям внутри этих кривых, если они где-то заданы...
              • 0
                Сходу - аяксовый запрос времени клиента или геоип. Конечно, на компе может стоять левое время, и человек может сидеть на иностранном провайдере и вообще через прокси, но по-моему, разумно при регистрации показывать юзеру полученное таким(и) способам(и) его же время и спрашивать что-нибудь в духе "нам кажется, у вас щас вот столько-то времени. угадали? ;)" и вариант изменения этого времени. Именно времени! Потому что поиск своего часвого пояса - не такая простая задача для людей, которые с этим не сталкивались или сталкивались, но редко. По времени - удобней.
                Ну и верифаить это счастье можно при каждом новом визите, мол, "ой, у вас время сменилось. а хотите его и тут сменить?".
                Да, конечно, это время сервера, это ресурсы, запросы и т.д. Но кто тут всё-таки "высшее звено пищевой цепочки (ц)", а? ;)
                • 0
                  Как вариант, возможно. Для зарегистрированных пользователей так и буду делать. Даже без верификации. Один раз указал и не надо надоедать пользователю... Если надо — зайдет в настройки и сменит.
                  • 0
                    Я про аяксовский запрос. ГеоИП уж больно много ресурсов съест, а результат менее надежный, на мой взгляд.
                    • 0
                      Каких ресурсов? О_О
                      ГеоИП - это один запрос в базу. Ну и 6 метров данных, конечно, в мускуле-то, но не размеры же БД считать ресурсом =)
              • 0
                привязка к зоне по айпи должна ведь, по идее, довольно точно работать?
                • 0
                  Если Вы про первую часть вопроса, то боюсь, что GeoIP съест все ресурсы.

                  А вот со второй частью что бы придумать..? (Определить часовой пояс по известным географическим координатам.)
                  • 0
                    Я ошибался насчет GeoIP. Поставил базу от MaxMind, скомпилировал примеры на C и долго удивлялся:

                    GeoIP City
                    60000 lookups made in 2.902537 seconds

                    GeoIP City with GEOIP_INDEX_CACHE
                    240000 lookups made in 1.788291 seconds

                    GeoIP City with GEOIP_MEMORY_CACHE
                    600000 lookups made in 0.934157 seconds

                    С PHP еще не пробовал, но с СИ всё просто летает. Дело было на VDS (1.5GHz, RAM 512Mb).
                • 0
                  на сколько мне не изменяет память, в javascript есть метод какой-то.
                  Date::getTimezoneOffset
                  в php - перечисленными ниже способами
                • 0
                  >Теперь граница интервала использования данного типа 4 Декабря 292277026596 год
                  Что же будем делать 5 декабря 292277026596 года? )
                  • +1
                    то же что и всегда)
                    седеть на хабре:)
                    • 0
                      именно седеть :)
                      • 0
                        Не то что седеть… Разлагаться уже даже не будем, я думаю.
                  • 0
                    Спасибо. Очень пригодится.
                    Читается как тематический справочник.
                    • +1
                      Мать моя женщина...
                      Скоро напишут фреймворк, зуб даю :)
                      • +1
                        скорее библиотеку
                        • –1
                          Библиотека - это уже старо и немодно :)
                          Должен быть фреймворк, обязательно MVC! :)
                        • 0
                          Уже. Zend Framework.
                        • 0
                          Хорошо, вот только КАК автоматически задетектить TimeZone клиента? Где-то видел, что в JavaScript есть функция, дающая офсет браузерной зоны в часах, но это не дает полной стандартной TZ. Кроме того, как показывает практика, показ на сайте локального времени вводит клиента в заблуждение, поскольку он уже привык к "официальному" (напр. московскому) времени. А выводить дату в формате с указанием зоны неэстетично. Поэтому возможным простым решением будет указывать на сайте серверную дату/время.
                          • +5
                            Мне одному "временная зона" режет глаз? На русский язык "time zone" лучше бы было переводить как "часовой пояс".
                            • 0
                              Спасибо, мне тоже очень понравилсась статья
                              • 0
                                Вау!.. Не поверите, у меня буквально вчера встал снова вопрос про хранение и отображение времени для многопользовательского сервиса. Причем уже приходилось сталкиваться с этим, поэтому мат часть известна. Но вот реализацию хочется оптимизировать.

                                Спецы, может поможете разобраться?

                                Вобщем идея такова (вобщем-то не нова, но я все же опишу).

                                - При сохранении записи переводить все времена в таймстампы в UTC.
                                - В профиле юзера есть опция time zone offset. С помощью getTimezoneOffset юзеру предлагается какое-то значение, он в праве согласиться или выставить свое. Для незалогиненых юзеров есть дефолтное значение.
                                - Также какждый раз при заходе пользователя в систему (или же в профайле, я еще не решил где лучше..) проверяется getTimezoneOffset с офсетом сохраненным в его профайле, если отличаются на 1, предлагается обновить это значение (имеется в виду переход на летнее время).
                                - Дальше отображение дат, это интересней и немного не понятней. Прошу помощи здесь..
                                1. Функция time() - возвращает таймстамп в UTC. Верно?
                                2. Функция date('format',$timestamp) возвращает время с учетом часового пояса сервера. В следствие этого используем функцию gmdate() а не date для отображения. Верно?
                                3. Следовательно отображение происходит следующим образом
                                date('format',$timestamp+$user_offset)

                                Что скажете? Прошу сильно не быть.. Это мой первый комментарий здесь.. ;)
                                • 0
                                  1) time() - Возвращает количество секунд, прошедших с начала Эпохи Unix (The Unix Epoch, 1 января 1970, 00:00:00 GMT) до текущего времени.
                                  2) http://ru2.php.net/manual/ru/function.gm… пример и комментарии по этой ссылке ответят на все вопросы
                                  • 0
                                    1. Верно.
                                    2. gmdate - дата по GMT.
                                    3. Не совсем, вы не учитываете переход на летнее время и еще много чего.

                                    Именно поэтому, чтобы нам не париться с датами, создатели PHP вводят новые функции.
                                    • 0
                                      посмотрите на его 3 пункт, он там пишет про время сервера.
                                      Кстати хороший тон держать время на сервере в GMT, в связи с чем программист должен сразу понимать работу с временными зонами
                                • 0
                                  только не забывать до использования strtotime/strftime установить локаль setlocale(), чтобы не получать нотис.
                                  • 0
                                    Отличная статья!
                                    • 0
                                      спасибо, добавил в мемориз
                                      • 0
                                        За это я люблю Хабр: "сидишь, краем мозга думаешь, что очень хорошо бы в проекте предусмотреть то-то или то-то", и +- пару дней появляется заметка по теме - читаешь её, копаешь и используешь знания!

                                        Спасибо!
                                        • 0
                                          Ох, может кто-то в курсе,

                                          date_default_timezone_set('Europe/Kiev');
                                          echo date('Y-m-d H:i:s');

                                          Выдает +1 час от правильного времени. tzdata обновил — не помогает :(

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