Учет часовых поясов при определении времени
Задача
Необходимо вычислить время в различных часовых поясах. Например, надо сообщить пользователю информацию, привязанную к его местному времени, а не к местному времени вашего сервера.
Решение
В случае простых вычислений можно непосредственно добавить или вычесть разность между двумя часовыми поясами:
// если локальное время – это зона EST
$time_parts = localtime();
// Калифорния (PST) на три часа раньше
$california_time_parts = localtime(time() - 3 * 3600);
В UNIX-системах, если не известно смещение между временными зо-
нами, достаточно установить значение переменной окружения рав-
ным целевому часовому поясу:
putenv('TZ=PST8PDT');
$california_time_parts = localtime();
Обсуждение
Перед тем как начать обходить все углы и закоулки часовых поясов, мы хотим передать заявление, которое военно-морская обсерватория США представляет на http://tycho.1111.navy.mil/tzones.html. А именно, что официальная информация о часовых поясах мира в некотором роде непостоянна, «поскольку нации суверенны и могут изменять и изменяют свои системы хранения времени так, как они считают нужным». Поэтому, помня о превратностях международных отношений, которые властвуют над нами, мы прибегаем к нескольким способам, позволяющим правильно работать с часовыми поясами. Для относительно простой обработки смещений между часовыми поясами в программе организуется массив, в котором хранятся различные смещения относительно пояса UTC. Определив часовой пояс пользователя, просто прибавьте это смещение к соответствующему UTC времени, и функции, которые выводят на печать UTC время (т. е. gmdate(), gmstrftime()), смогут напечатать соответствующим образом скорректированное время.
// Определяем текущее время
$now = time();
// Калифорния на 8 часов отстает от зоны UTC
$now += $pc_timezones['PST'];
// Используйте функции gmdate() или gmstrftime()
// для вывода Калифорнийского времени
print gmstrftime('%c',$now);
В вышеприведенном коде смещения относительно зоны UTC хранятся в массиве $pc_timezones:
// Из Perl Time::Timezone
$pc_timezones = array(
'GMT' => 0, // Среднее время по гринвичскому меридиану
'UTC' => 0, // Универсальное (Скоординированное) время
'WET' => 0, // Западноевропейское время
'WAT' => -1*3600, // Западноафриканское время
'AT' => -2*3600, // Время Азорских островов
'NFT' => -3*3600-1800, // Время Ньюфаундленда
'AST' => -4*3600, // Стандартное Атлантическое время
'EST' => -5*3600, // Восточное стандартное время
'CST' => -6*3600, // Центральное стандартное время
'MST' => -7*3600, // Стандартное Горное время
'PST' => -8*3600, // Стандартное Тихоокеанское время
'YST' => -9*3600, // Стандартное Юконское время
'HST' => -10*3600, // Стандартное Гавайское время
'CAT' => -10*3600, // Время Центральной Аляски
'AHST' => -10*3600, // Стандартное время Аляска-Гавайи
'NT' => -11*3600, // Время города Ном
'IDLW' => -12*3600, // К западу от международной линии смены дат
'CET' => +1*3600, // Центральноевропейское время
'MET' => +1*3600, // Среднеевропейское время
'MEWT' => +1*3600, // Зимнее Среднеевропейское время
'SWT' => +1*3600, // Зимнее Шведское время
'FWT' => +1*3600, // Зимнее Французское время
'EET' => +2*3600, // Восточноевропейское время, СССР Зона 1
'BT' => +3*3600, // Багдадское время, СССР Зона 2
'IT' => +3*3600+1800, // Иранское время
'ZP4' => +4*3600, // СССР Зона 3
'ZP5' => +5*3600, // СССР Зона 4
'IST' => +5*3600+1800, // Стандартное Индийское время
'ZP6' => +6*3600, // СССР Зона 5
'SST' => +7*3600, // Южносуматранское время, СССР Зона 6
'WAST' => +7*3600, // Стандартное Западноавстралийское время
'JT' => +7*3600+1800, // Время острова Ява
'CCT' => +8*3600, // Время Китайского побережья, СССР Зона 7
'JST' => +9*3600, // Стандартное Японское, СССР Зона 8
'CAST' => +9*3600+1800, // Стандартное Центральноавстралийское время
'EAST' => +10*3600, // Стандартное Восточноавстралийское время
'GST' => +10*3600, // Стандартное Гуамское время, СССР Зона 9
'NZT' => +12*3600, // Новозеландское время
'NZST' => +12*3600, // Стандартное Новозеландское время
'IDLE' => +12*3600 // К востоку от международной линии смены дат
);
В UNIX-системах преобразования можно выполнять при помощи библиотеки zoneinfo. Это позволяет сделать код более компактным, а обработку DST более прозрачной. Чтобы получить преимущества библиотеки zoneinfo в PHP, все вычисления с международными датами надо выполнять на основе меток времени UNIX. Последние следует генерировать из частей времени с помощью функции pc_mktime().
function pc_mktime($tz,$hr,$min,$sec,$mon,$day,$yr) {
putenv("TZ=$tz");
$a = mktime($hr,$min,$sec,$mon,$day,$yr);
putenv('TZ=EST5EDT'); // замена пояса EST5EDT на часовой пояс
вашего сервера!
return $a;
}
Вызов функции putenv() до вызова mktime() позволяет обмануть системную функцию mktime(), заставляя ее думать, что она находится в другом часовом поясе. После вызова функции mktime() необходимо восстановить истинный часовой пояс. На восточном побережье Соединеных Штатов это пояс EST5EDT. Замените его соответствующим значением географического положения вашего компьютера. Части времени преобразуются в метки времени с помощью функции pc_mktime(). Ее двойником, который превращает метки времени в форматированную строку времени и части времени, является функция pc_strftime().
function pc_strftime($tz,$format,$timestamp) {
putenv("TZ=$tz");
$a = strftime($format,$timestamp);
putenv('TZ=EST5EDT'); // замена пояса EST5EDT на часовой пояс
вашего сервера!
return $a;
}
В этом примере применен тот же способ обмана системной функции, к которому прибегает функция pc_mktime() для получения правильного результата от функции strftime(). Существенным в этих функциях является то, что не надо беспокоиться о смещениях различных временных поясов относительно UTC независимо от того, действует ли переход на летнее время или какие-либо другие особенности часовых поясов. Достаточно установить соответствующий часовой пояс, а системная библиотека сделает все остальное. Учтите, что значение переменной $tz в обеих функциях должно быть именем не часового пояса, а зоны zoneinfo. Пояса zoneinfo имеют больше особенностей, чем часовые пояса, так как они соответствуют определенным местам. Последняя колонка показывает, происходит ли в данном поясе переход на летнее время. В разных странах переход на летнее и зимнее время не происходит в один и тот же день. Чтобы вычислить время с учетом перехода на летнее время в соответствии с географическим положением пункта, выберите пояс zoneinfo, который ближе всего находится к данному пункту.