php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #60212 Unexpected behaviour while adding or subtracting relative time with strtotime
Submitted: 2011-11-03 15:07 UTC Modified: 2011-11-21 17:10 UTC
Votes:6
Avg. Score:4.8 ± 0.4
Reproduced:4 of 5 (80.0%)
Same Version:3 (75.0%)
Same OS:3 (75.0%)
From: reetz at krumedia dot de Assigned: danielc (profile)
Status: Closed Package: Date/time related
PHP Version: 5.3.8 OS: Linux version 2.6.32-5-amd64
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: reetz at krumedia dot de
New email:
PHP Version: OS:

 

 [2011-11-03 15:07 UTC] reetz at krumedia dot de
Description:
------------
This problem only exists while using a time zone other than UTC, in my case "Europe/Berlin".

I have to do relative time calculations and got an unexpected behaviour. I was "missing" one hour of data and some data was misplaced. First I assumed, I had forgotten an "UTC" or was using an "date" instead of "gmdate". Yet it was none of the former. As it turns out, strtotime is doing something extremely unexpected or even wrong. 

The output consists of five columns, each contains timestamps formatted with "d H:i \U\T\C"  
- sUnexpectedTimePrevious // minus one minute by strtotime
- sTimePrevious           // minus one minute as it should be
- sTimeNow                // 
- sTimeNext               // plus one minute as it should be
- sUnexpectedTimeNext     // plus one minute by strtotime

Lines with errors are marked with an exclamation marks. One line is of particular interest:
! 29 23:59 UTC | 30 00:59 UTC | 30 01:00 UTC | 30 01:01 UTC | 30 01:01 UTC
Instead of one hour into the future it's now suddenly two one into the past. 

For the record, yes I had done a search before submitting this report. I found #54799 and #53370, yet I feel that the example I submitted might help analysing the problem.

I am well aware that I could solve my problem by simply switching the default time zone to "UTC". But this is only solving a symptom, not the real problem.

sincerely yours
Michael Reetz

Test script:
---------------
//date_default_timezone_set('UTC');          // no problem 
date_default_timezone_set('Europe/Berlin');  // big problem

$iCurrent = strtotime('2011-10-29 23:55:00 UTC');
$iEnd = strtotime('2011-10-30 01:05:00 UTC');
while ($iCurrent <= $iEnd){
	$sTimeNow                = gmdate('d H:i \U\T\C',                       $iCurrent );
	$sTimePrevious           = gmdate('d H:i \U\T\C',              -60 +    $iCurrent );
	$sTimeNext               = gmdate('d H:i \U\T\C',               60 +    $iCurrent );
	$sUnexpectedTimePrevious = gmdate('d H:i \U\T\C', strtotime('- 1 minute',$iCurrent));
	$sUnexpectedTimeNext     = gmdate('d H:i \U\T\C', strtotime('+ 1 minute',$iCurrent));
	
	echo ($sUnexpectedTimePrevious != $sTimePrevious) ? '!':' ';
	echo " $sUnexpectedTimePrevious | $sTimePrevious | $sTimeNow | $sTimeNext | $sUnexpectedTimeNext ";
	echo ($sUnexpectedTimeNext != $sTimeNext) ? '!':' ';
	
	echo "\n";
	$iCurrent += 60;
}

Expected result:
----------------
// created with : date_default_timezone_set('UTC');
  29 23:54 UTC | 29 23:54 UTC | 29 23:55 UTC | 29 23:56 UTC | 29 23:56 UTC
  29 23:55 UTC | 29 23:55 UTC | 29 23:56 UTC | 29 23:57 UTC | 29 23:57 UTC
  29 23:56 UTC | 29 23:56 UTC | 29 23:57 UTC | 29 23:58 UTC | 29 23:58 UTC
  29 23:57 UTC | 29 23:57 UTC | 29 23:58 UTC | 29 23:59 UTC | 29 23:59 UTC
  29 23:58 UTC | 29 23:58 UTC | 29 23:59 UTC | 30 00:00 UTC | 30 00:00 UTC
  29 23:59 UTC | 29 23:59 UTC | 30 00:00 UTC | 30 00:01 UTC | 30 00:01 UTC
  30 00:00 UTC | 30 00:00 UTC | 30 00:01 UTC | 30 00:02 UTC | 30 00:02 UTC
  30 00:01 UTC | 30 00:01 UTC | 30 00:02 UTC | 30 00:03 UTC | 30 00:03 UTC
[.. skipped several lines ..]
  30 00:57 UTC | 30 00:57 UTC | 30 00:58 UTC | 30 00:59 UTC | 30 00:59 UTC
  30 00:58 UTC | 30 00:58 UTC | 30 00:59 UTC | 30 01:00 UTC | 30 01:00 UTC
  30 00:59 UTC | 30 00:59 UTC | 30 01:00 UTC | 30 01:01 UTC | 30 01:01 UTC
  30 01:00 UTC | 30 01:00 UTC | 30 01:01 UTC | 30 01:02 UTC | 30 01:02 UTC
  30 01:01 UTC | 30 01:01 UTC | 30 01:02 UTC | 30 01:03 UTC | 30 01:03 UTC
  30 01:02 UTC | 30 01:02 UTC | 30 01:03 UTC | 30 01:04 UTC | 30 01:04 UTC
  30 01:03 UTC | 30 01:03 UTC | 30 01:04 UTC | 30 01:05 UTC | 30 01:05 UTC
  30 01:04 UTC | 30 01:04 UTC | 30 01:05 UTC | 30 01:06 UTC | 30 01:06 UTC


Actual result:
--------------
// created with : date_default_timezone_set('Europe/Berlin');
  29 23:54 UTC | 29 23:54 UTC | 29 23:55 UTC | 29 23:56 UTC | 29 23:56 UTC
  29 23:55 UTC | 29 23:55 UTC | 29 23:56 UTC | 29 23:57 UTC | 29 23:57 UTC
  29 23:56 UTC | 29 23:56 UTC | 29 23:57 UTC | 29 23:58 UTC | 29 23:58 UTC
  29 23:57 UTC | 29 23:57 UTC | 29 23:58 UTC | 29 23:59 UTC | 29 23:59 UTC
  29 23:58 UTC | 29 23:58 UTC | 29 23:59 UTC | 30 00:00 UTC | 30 01:00 UTC !
  29 23:59 UTC | 29 23:59 UTC | 30 00:00 UTC | 30 00:01 UTC | 30 01:01 UTC !
! 30 01:00 UTC | 30 00:00 UTC | 30 00:01 UTC | 30 00:02 UTC | 30 01:02 UTC !
! 30 01:01 UTC | 30 00:01 UTC | 30 00:02 UTC | 30 00:03 UTC | 30 01:03 UTC !
[.. skipped several lines ..]
! 30 01:57 UTC | 30 00:57 UTC | 30 00:58 UTC | 30 00:59 UTC | 30 01:59 UTC !
! 30 01:58 UTC | 30 00:58 UTC | 30 00:59 UTC | 30 01:00 UTC | 30 02:00 UTC !
! 29 23:59 UTC | 30 00:59 UTC | 30 01:00 UTC | 30 01:01 UTC | 30 01:01 UTC
  30 01:00 UTC | 30 01:00 UTC | 30 01:01 UTC | 30 01:02 UTC | 30 01:02 UTC
  30 01:01 UTC | 30 01:01 UTC | 30 01:02 UTC | 30 01:03 UTC | 30 01:03 UTC
  30 01:02 UTC | 30 01:02 UTC | 30 01:03 UTC | 30 01:04 UTC | 30 01:04 UTC
  30 01:03 UTC | 30 01:03 UTC | 30 01:04 UTC | 30 01:05 UTC | 30 01:05 UTC
  30 01:04 UTC | 30 01:04 UTC | 30 01:05 UTC | 30 01:06 UTC | 30 01:06 UTC


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2011-11-03 16:53 UTC] anon at anon dot anon
No matter how many times I see these reports, it still absolutely boggles my mind that so many PROGRAMMERS -- people you'd really think were capable of more sense, can't spot DAYLIGHT SAVING TIME even when it's staring them right in the face.
 [2011-11-03 18:16 UTC] reetz at krumedia dot de
Please take the time and read my report properly .

I am well aware of "DAYLIGHT SAVING TIME". I am even aware that not all countries have their DST on the same date, into the same direction or at all.

For example

Australian (except NT, WA and QLD)
Standard Time: 3 April 2011 to 2 October 2011
Summer Time  : 2 October 2011 to 1 April 2012

European
Summer Time  : 27 March 2011 to 30 October 2011
Standard Time: 30 October 2011 to 25 March 2012

Rusia        : DST no longer in use
Saudi Arabia : DST never used

So, please do not use such a dismissive language. 

I was using UTC values. There is no room for DST interpretation in UTC. My whole application calculates in UTC.  All time conversions are done with strtotime('.... UTC') and gmdate('Y-m-d H:i:s \U\T\C') because of DST. I was supplying an DST-free Time. I was already searching for a "strtotime('-1 minute UTC',..)", no such luck!

Why is 
php -r "echo strtotime('-1 minute', strtotime('2011-10-30 01:00 UTC'));"
not the same as
php -r "echo -60 + strtotime('2011-10-30 01:00 UTC');"

The first returns 1319932740 and the second returns 1319936340 while strtotime('2011-10-30 01:00 UTC') is 1319936400, but (1319936400 - 1319932740) equals 3660, that's one hour and 1 minute. Not one minute as requested!

>>>Taken out of the manual:
strtotime ".... will try to parse that format into a Unix timestamp (the number of seconds since January 1 1970 00:00:00 UTC)"

Please enlighten me ! Why should "1319932740" be the result of
php -r "echo strtotime('-1 minute', 1319936400);" 
even IF my operation systems time zone is "Europe/Berlin". The integer-value of a Unix-Timestamp has no Timezone. That is the whole point of Unix-timestamp. Why should -1 Minute of an integer value (of any date) be relevant to DST. The input is an Unix Timestamp, the output is an Unix Timestamp, the difference is 60 seconds. Where is the DST in this calculation? Really, tell me. The answer should be easy for someone with so much "more sense". 


Sincerely Yours 
Michael Reetz

P.S. sorry for that last sentence, I am feeling better now.
 [2011-11-16 16:57 UTC] rasmus@php.net
-Status: Open +Status: Feedback
 [2011-11-16 16:57 UTC] rasmus@php.net
php -a
Interactive shell
php > echo strtotime('-1 minute', strtotime('2011-10-30 01:00 UTC'));
1319936340
php > echo -60 + strtotime('2011-10-30 01:00 UTC');
1319936340 

Looks the same for me both in 5.3.8 and 5.4.0
 [2011-11-16 17:04 UTC] rasmus@php.net
Of course, if you are not working in UTC then strtotime("-1 minute") is going to 
use the current timezone to figure out the timestamp of 1 minute ago. "-1 minute 
UTC" makes no sense because that is a relative time. It needs to know what to 
subtract 1 minute from in order to give you an absolute timestamp of 1 minute 
ago. A better approach is to use DateTime objects as per php.net/datetime and 
then you can use the date_sub() function to subtract intervals.
 [2011-11-16 20:28 UTC] reetz at krumedia dot de
-Status: Feedback +Status: Open
 [2011-11-16 20:28 UTC] reetz at krumedia dot de
For clarification of my problem I have made another demo code. Here, the difference of the timezone, it is clearly demonstrated. I have included your suggestion with the DateTime objects. It works perfectly. Unfortunately I still have a PHP 5.2 on my productive system and can not upgrade it. I am now using the version "-60" for these PHP environments.
 
But still, since "strtotime" shows the same behaviour on PHP 5.3, I am left wondering if it does what it should do? If the answer to this is "yes", it may be good to consider some warnings in the manual.

php -r "
date_default_timezone_set('Europe/Berlin');
echo date_default_timezone_get().PHP_EOL;
echo date('O').PHP_EOL;
echo strtotime('2011-10-30 01:00 UTC').PHP_EOL;
echo (-60+strtotime('2011-10-30 01:00 UTC')).PHP_EOL;
echo strtotime('-1 minute',strtotime('2011-10-30 01:00 UTC')).PHP_EOL;
echo PHP_EOL;
date_default_timezone_set('UTC');
echo date_default_timezone_get().PHP_EOL;
echo date('O').PHP_EOL;
echo strtotime('2011-10-30 01:00 UTC').PHP_EOL;
echo (-60+strtotime('2011-10-30 01:00 UTC')).PHP_EOL;
echo strtotime('-1 minute',strtotime('2011-10-30 01:00 UTC')).PHP_EOL;
echo PHP_EOL;
date_default_timezone_set('Europe/Berlin');
echo date_default_timezone_get().PHP_EOL;
echo date('O').PHP_EOL;
\$d = new DateTime('2011-10-30 01:00 UTC');
echo \$d->getTimestamp().PHP_EOL;
\$d->sub(DateInterval::createFromDateString('1 minute'));
echo \$d->getTimestamp().PHP_EOL;
"
Europe/Berlin
+0100
1319936400
1319936340
1319932740

UTC
+0000
1319936400
1319936340
1319936340

Europe/Berlin
+0100
1319936400
1319936340


Sincerely Yours 
Michael Reetz
 [2011-11-21 17:10 UTC] danielc@php.net
Automatic comment from SVN on behalf of danielc
Revision: http://svn.php.net/viewvc/?view=revision&amp;revision=319642
Log: Clarify time zone situation and specify that add/sub/modify are better for math (closes bug #60212).
 [2011-11-21 17:10 UTC] danielc@php.net
Mixing and matching time zones is your problem.  Date mathematics should use add()/sub() in PHP >= 5.3 or modify() in PHP 5.2.

I have updated the manual accordingly.  The changes will show up the next time the manual is built.
 [2011-11-21 17:10 UTC] danielc@php.net
-Status: Open +Status: Closed -Type: Bug +Type: Documentation Problem -Assigned To: +Assigned To: danielc
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Apr 26 02:01:29 2024 UTC