2
votes

Having unexpected results when using strtotime with timezone and calculations. My PHP is set to UTC. strtotime("-6 days 00:00:00 America/Chicago"); seems to be giving unexpected results. I thought by providing the timezone it would be 'calculated IN' that timezone, but that doesn't appear to the case.

In my example I expect the results to be the same, regardless of PHP's default timezone because 'America/Chicago' is used in strtotime. Instead it seems to take the current day (according to default timezone) and subtract days then calculate 00:00:00 in the timezone supplied.

Am I formatting this wrong?

I apologize for the messy code, but it is just an example demonstrating what I am talking about.

date_default_timezone_set("UTC");
echo date_default_timezone_get();
echo "</br>";
echo "</br>";

echo convertTS(time(), "America/Chicago");

echo "</br>";
echo "</br>";

$test = strtotime("-6 days 00:00:00 America/Chicago");

echo $test;

echo "</br>";

echo convertTS($test, "America/Chicago");

echo "</br>";

$test = strtotime("-7 days 00:00:00 America/Chicago");

echo $test;

echo "</br>";

echo convertTS($test, "America/Chicago");
echo "</br>";
echo "</br>";
echo "-----------------";
echo "</br>";
echo "</br>";

///now in America/Chicago

date_default_timezone_set("America/Chicago");
echo date_default_timezone_get();
echo "</br>";
echo "</br>";

echo convertTS(time(), "America/Chicago");

echo "</br>";
echo "</br>";

$test = strtotime("-6 days 00:00:00 America/Chicago");

echo $test;

echo "</br>";

echo convertTS($test, "America/Chicago");

echo "</br>";

$test = strtotime("-7 days 00:00:00 America/Chicago");

echo $test;

echo "</br>";

echo convertTS($test, "America/Chicago");

function convertTS($timestamp, $timezone)
{
    if( empty($timestamp) )
    {
        return 'n/a';
    }
    else
    {
        //output the time
        $dt = new DateTime('@'.$timestamp);
        $dt->setTimeZone(new DateTimeZone($timezone));
        return $dt->format('D, m/d/y @ g:i:s a T');
    }
}

result of the above at the time I ran it :

UTC

Fri, 01/04/19 @ 11:39:15 pm CST

1546149600
Sun, 12/30/18 @ 12:00:00 am CST
1546063200
Sat, 12/29/18 @ 12:00:00 am CST

-----------------

America/Chicago

Fri, 01/04/19 @ 11:39:15 pm CST

1546063200
Sat, 12/29/18 @ 12:00:00 am CST
1545976800
Fri, 12/28/18 @ 12:00:00 am CST

I made a sandbox to recreate this issue at all times here. The difference being a timestamp is provided rather than using time() to show how the default timezone affects the results depending on time of the current day.

3
Thanks... I couldn't find a php sandbox would could be shared. I edited the code a bit to recreate the issue at all times. repl.it/repls/AromaticChillySolaris - user756659

3 Answers

1
votes

First of all, let's start by explaining few small things:

  • Unix timestamps are always in UTC
  • The timestamp you supplied 1546578000 in ISO 8601 format is: "2019-01-04 05:00:00.000000", obviously UTC+00:00
  • CST is six hours behind UTC i.e. UTC−06:00
  • Your function convertTS will always return the same results irrelevant of the default timezone
  • It doesn't matter how many days you subtract, so let's make it simpler and say -2 days

When you call your convertTS function it takes the timestamp in UTC and converts and displays it in CST timezone. When you pass 1546578000 as an argument you will get "2019-01-03 23:00:00.000000".

strtotime takes in 2 parameters: int strtotime ( string $time [, int $now = time() ] ). However PHP documentation says that both parameters of this function use the default time zone unless a time zone is specified in that parameter. In your example you explicitly give the timezone in the first parameter each time. The second parameter reads the default timestamp and converts the UTC UNIX timestamp into that timezone before the calculation of the relative date.

Some pseudo code of what is happening

When the timezone is UTC:

date_default_timezone_set("UTC");
strtotime("-2 days 00:00:00 America/Chicago", 1546578000);
  1. Convert 1546578000 to UTC: 2019-01-04 05:00:00.000000
  2. Set time to 00:00:00 CST: 2019-01-04 06:00:00.000000 *UTC+00:00*
  3. Subtract 2 days: 2019-01-02 06:00:00.000000
  4. Return the timestamp as integer

When the default timezone is CST:

date_default_timezone_set("America/Chicago");
strtotime("-2 days 00:00:00 America/Chicago", 1546578000);
  1. Convert 1546578000 to CST: 2019-01-03 23:00:00.000000
  2. Set time to 00:00:00 CST: 2019-01-03 06:00:00.000000 *UTC+00:00*
  3. Subtract 2 days: 2019-01-01 06:00:00.000000
  4. Return the timestamp as integer, of course in UTC+00:00, see the first point

PHP parser for relative dates is not perfect. In fact I believe it is wrong when it converts UNIX timestamp to the default timezone... or my understanding of it might also be flawed.

How I gathered the data:

I changed the code to use DateTime class.

date_default_timezone_set("UTC");

$dt = (new \DateTime())->setTimestamp(1546578000);
$dt->modify("-2 days 00:00:00 America/Chicago");
echo "UTC: ".$dt->format('D, m/d/y @ g:i:s a T');

echo "</br>";
echo "</br>";
echo "-----------------";
echo "</br>";
echo "</br>";

///now in America/Chicago
date_default_timezone_set("America/Chicago");

$dt = (new \DateTime())->setTimestamp(1546578000);
$dt->modify("-2 days 00:00:00 America/Chicago");
echo "CST: ".$dt->format('D, m/d/y @ g:i:s a T');

I then used XDebug to debug the code and see what are the internal states of the $dt variable.
enter image description here

And after changing the timezone:
enter image description here

0
votes

The time zone in the string is definitively taken into account:

date_default_timezone_set('UTC');
var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
var_dump(date('r', strtotime('-6 days 00:00:00 Europe/Madrid')));
string(31) "Sun, 30 Dec 2018 06:00:00 +0000"
string(31) "Sat, 29 Dec 2018 23:00:00 +0000"

Perhaps the confusing part is that the result is displayed in the configured time zone:

date_default_timezone_set('UTC');
var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
date_default_timezone_set('Europe/Madrid');
var_dump(date('r', strtotime('-6 days 00:00:00 America/Chicago')));
string(31) "Sun, 30 Dec 2018 06:00:00 +0000"
string(31) "Sun, 30 Dec 2018 07:00:00 +0100"

... but the actual moment in time is the same:

date_default_timezone_set('UTC');
var_dump(strtotime('-6 days 00:00:00 America/Chicago'));
date_default_timezone_set('Europe/Madrid');
var_dump(strtotime('-6 days 00:00:00 America/Chicago'));
string(10) "1546149600"
string(10) "1546149600"
0
votes

With the help of @Dharman pointing out how strtotime actually handles things (which I still think is wrong) I was able to modify a few things and come up with the results I expect regardless of php's default time zone. This provides the results I expect... where the calculations (subtracting days and setting to 00:00:00) happen in the timezone specified.

sandbox example

date_default_timezone_set("UTC");

$time = time();
//$time = 1546578000;
$tz = "Europe/Amsterdam";

$dt = new DateTime('@'.$time);
$dt->setTimeZone(new DateTimeZone('UTC'));
echo $dt->format('D, m/d/y @ g:i:s a T');
echo "\n";
echo $dt->format('U');

echo "\n";
echo "\n";

$dt = new DateTime('@'.$time);
$dt->setTimeZone(new DateTimeZone('UTC'));
$dt->modify("-2 days 00:00:00");
echo $dt->format('D, m/d/y @ g:i:s a T');
echo "\n";
echo $dt->format('U');

echo "\n";
echo "\n";
echo "---------------";
echo "\n";
echo "\n";

$dt = new DateTime('@'.$time);
$dt->setTimeZone(new DateTimeZone($tz));
echo $dt->format('D, m/d/y @ g:i:s a T');
echo "\n";
echo $dt->format('U');

echo "\n";
echo "\n";

$dt = new DateTime('@'.$time);
$dt->setTimeZone(new DateTimeZone($tz));
$dt->modify("-2 days 00:00:00");
echo $dt->format('D, m/d/y @ g:i:s a T');
echo "\n";
echo $dt->format('U');

Which in this case would output :

Sat, 01/05/19 @ 6:51:18 pm UTC
1546714278

Thu, 01/03/19 @ 12:00:00 am UTC
1546473600

---------------

Sat, 01/05/19 @ 7:51:18 pm CET
1546714278

Thu, 01/03/19 @ 12:00:00 am CET
1546470000