У меня есть некоторый PHP-код для отправки предупреждений и автоматического закрытия службы, если файлы с исходными данными устарели. Это делается с помощью следующих сравнений:
Предупреждение:
filemtime($file_location) < strtotime('-15 minute')
Рассмотрим обслуживание вниз:
filemtime($file_location) < strtotime('-1 hour')
Для 1 часа, начинающегося в 3:00 AM CDT, когда начинается переход на летнее время, этот файл неверно сообщает о том, что его возраст более 15 минут. В конце часа он также неверно сообщает, что файл старше 1 часа. В 4:00 утра CDT все возвращается на круги своя.
Есть ли что-то, чего я не знаю ни о функциях filemtime (), ни strtotime (), которые бы объяснили это поведение? Насколько я понимаю, оба возвращают метки времени UNIX, и что метки времени по определению указаны в UTC, поэтому я не уверен, что может вызвать эту проблему.
Основываясь на ответах, я запустил этот тест на нашем сервере:
`
for($i = 1; $i <=4; $i++){
// let's say current time is this:
date_default_timezone_set('America/Winnipeg');
$current_timestamp = strtotime('2016-03-13 0'.$i.':00:00'); // 1457856060
// DST start at 03:00 that day, so 60 minutes before the time should be 01:01
$ts = $current_timestamp - 1*60*60;
echo '-3600s: ', $ts, ", ", date('Y-m-d H:i:s', $ts), "\n";
// 1457852460, 2016-10-30 02:59:00, correct
// now let's test strtotime with -1 hour
$ts = strtotime('-1 hour', $current_timestamp);
echo '-1 hour: ', $ts, ", ", date('Y-m-d H:i:s', $ts), "\n";
// 1457856060, 2016-10-30 03:01:00, completely wrong
// DateTime implementation seems smarter:
$dt = new DateTime();
$dt->setTimestamp($current_timestamp);
$dt->sub(new DateInterval('PT1H'));
echo 'sub PT1H: ', $dt->getTimestamp(), ", ", $dt->format('Y-m-d H:i:s'), "\n";
// 1457856060, 2016-10-30 03:01:00, correct
echo "\n\n";
echo 'TS ', $current_timestamp, ", ", date('Y-m-d H:i:s', $current_timestamp), "\n";
echo "\n\n";
}
`
-3600: 1457848800, 2016-03-13 00:00:00
-1 час: 1457848800, 2016-03-13 00:00:00
суб PT1H: 1457848800, 2016-03-13 00:00:00
TS 1457852400, 2016-03-13 01:00:00
-3600: 1457852400, 2016-03-13 01:00:00
-1 час: 1457856000, 2016-03-13 03:00:00
суб PT1H: 1457856000, 2016-03-13 03:00:00
TS 1457856000, 2016-03-13 03:00:00
-3600: 1457852400, 2016-03-13 01:00:00
-1 час: 1457856000, 2016-03-13 03:00:00
суб PT1H: 1457856000, 2016-03-13 03:00:00
TS 1457856000, 2016-03-13 03:00:00
-3600: 1457856000, 2016-03-13 03:00:00
-1 час: 1457856000, 2016-03-13 03:00:00
суб PT1H: 1457856000, 2016-03-13 03:00:00
TS 1457859600, 2016-03-13 04:00:00
Это глубоко сбивает с толку. В моем часовом поясе, где 27 марта начинается переход на летнее время, все следующие выражения возвращают одну и ту же метку времени UNIX:
var_dump(strtotime("27 March 2016 02:00"));
var_dump(strtotime("- 60 minute", strtotime("27 March 2016 02:00")));
var_dump(strtotime("- 1 hour", strtotime("27 March 2016 02:00")));
var_dump(strtotime("27 March 2016 02:00 - 1 hour"));
var_dump((new DateTime("27 March 2016 02:00"))->format("U"));
var_dump((new DateTime("27 March 2016 02:00"))->sub(new DateInterval("PT1H"))->format("U"));
Если бы я достаточно серьезно подумал об этом, я мог бы поверить, что это намеченное поведение. 01:00 27 марта здесь не существует. Но это конечно не интуитивно понятно.
Еще более запутанно, вычитая 15 минут с 02:00 дает ответ в будущем, а именно 02:45. Даже если первый результат имеет небольшой смысл, если вы достаточно щурились, этот результат должен быть ошибкой.
Есть открытый отчет об ошибке об этом, который был нетронутым в течение почти 4 лет.
Это оставляет вам два варианта. Вы можете добавить if
пункт к вашему коду, вычитая 2 часа или 75 минут, если первоначальное вычитание не уменьшает временную метку. Или вы можете просто вычесть 3600 или 900 секунд из текущей временной метки вместо использования strtotime
, Ни один из них не особенно элегантен, но мы здесь.
Похоже на strtotime
недостаточно умен, чтобы учитывать изменения DST при работе с относительными значениями:
// let's say current time is this:
date_default_timezone_set('Europe/Berlin');
$current_timestamp = strtotime('2016-10-30 01:59:00'); // 1477785540
// DST ends at 03:00 that day, so 120 minutes later the time should be 02:59
$ts = $current_timestamp + 2*60*60;
echo $ts, ", ", date('Y-m-d H:i:s', $ts), "\n";
// 1477792740, 2016-10-30 02:59:00, correct
// now let's test strtotime with +2 hours
$ts = strtotime('+2 hours', $current_timestamp);
echo $ts, ", ", date('Y-m-d H:i:s', $ts), "\n";
// 1477796340, 2016-10-30 03:59:00, completely wrong
// DateTime implementation seems smarter:
$dt = new DateTime();
$dt->setTimestamp($current_timestamp);
$dt->add(new DateInterval('PT2H'));
echo $dt->getTimestamp(), ", ", $dt->format('Y-m-d H:i:s');
// 1477792740, 2016-10-30 02:59:00, correct
В вашем конкретном случае я бы просто сравнил временные метки напрямую (очевидно, разница была бы в секундах), но используя DateTime
с TimeInterval
вариант тоже.