简体   繁体   中英

Can filemtime be compared with strtotime at the daylight savings change?

I have some PHP code to send warnings and automatically shut down a service if the source data files are out of date. This is done using the following comparisons:

Warning: filemtime($file_location) < strtotime('-15 minute')

Consider service down: filemtime($file_location) < strtotime('-1 hour')

For 1 hour beginning at 3:00 AM CDT when daylight savings time begins, this incorrectly reports the file as being over 15 minutes old. Toward the end of the hour, it also incorrectly reports the file as being more than 1 hour old. At 4:00 AM CDT things return to normal.

Is there something I don't know about either the filemtime() or strtotime() functions that would explain this behaviour? It's my understanding that both return UNIX timestamps, and that timestamps are by definition in UTC, so I'm not sure what could cause this problem.

Based on the answers so far, I ran this test on our server:

`

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";
}

`

-3600s: 1457848800, 2016-03-13 00:00:00

-1 hour: 1457848800, 2016-03-13 00:00:00

sub PT1H: 1457848800, 2016-03-13 00:00:00

TS 1457852400, 2016-03-13 01:00:00

-3600s: 1457852400, 2016-03-13 01:00:00

-1 hour: 1457856000, 2016-03-13 03:00:00

sub PT1H: 1457856000, 2016-03-13 03:00:00

TS 1457856000, 2016-03-13 03:00:00

-3600s: 1457852400, 2016-03-13 01:00:00

-1 hour: 1457856000, 2016-03-13 03:00:00

sub PT1H: 1457856000, 2016-03-13 03:00:00

TS 1457856000, 2016-03-13 03:00:00

-3600s: 1457856000, 2016-03-13 03:00:00

-1 hour: 1457856000, 2016-03-13 03:00:00

sub PT1H: 1457856000, 2016-03-13 03:00:00

TS 1457859600, 2016-03-13 04:00:00

That's deeply confusing. In my timezone, where daylight savings begins on 27 March, all of the following expressions return the same UNIX timestamp:

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"));

If I think hard enough about this, I could perhaps believe it's the intended behaviour. 01:00 on 27 March does not exist here . But it's certainly not intuitive.

Even more confusingly, subtracting 15 minutes from 02:00 gives an answer in the future , namely 02:45. Even if the first result makes a little sense if you squint enough, this result has got to be a bug.

There is an open bug report about this which has been untouched for nearly 4 years.

This leaves you with two choices. You could add an if clause to your code, subtracting 2 hours or 75 minutes if the initial subtraction doesn't decrease the timestamp. Or you could simply subtract 3600 or 900 seconds from the current timestamp instead of using strtotime . Neither is particularly elegant, but there we are.

Seems like strtotime is not smart enough to take DST changes into consideration when operating on relative values:

// 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

In your particular case I'd simply compare the timestamps directly (the difference would be in seconds, obviously), but using DateTime with TimeInterval is an option, too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM