Можно ли сравнить время файла с временем года при изменении летнего времени?

У меня есть некоторый 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

2

Решение

Это глубоко сбивает с толку. В моем часовом поясе, где 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, Ни один из них не особенно элегантен, но мы здесь.

1

Другие решения

Похоже на 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 вариант тоже.

1

По вопросам рекламы [email protected]