So here's the scenario:
My application has a transaction section in it. It logs actions and stores the time as a UNIX timestamp (UTC).
So yesterday I got paid £100.00 and this was logged into the database with a unix timestamp of 1477762205 which equates to Sat, 29 Oct 2016 17:30:05 UTC/GMT.
(This is 19:30 local time for me)
My application (written in PHP) outputs using a datetime object and passing in the output timezone (Europe/London).
Yesterday it outputted correctly local time as 19:30 and due to the clock time change this morning at 2am it now reads 18:30.
This is technically correct but if I remember back relative to the event when logging the transaction in the first place this will now seem incorrect as I remember it being 19:30 local time.
My question is what is the best way to output the time from UTC, relative to the event? Do I need to store the timezone with the UTC log to determine the timezone at the point the log took place?
Code:
When storing the log:
(This is part of a logger class I'm creating)
if($dateTime == null) {
$dateTime = time();
}
$query = "INSERT INTO `".$this->databaseName."`.`log`(`datetime`,`action`) VALUES(?, ?);";
if(!$stmt = $this->database->prepare($query)) {
return $this->database->error;
}
$stmt->bind_param('ss',$dateTime,$action);
$stmt->execute();
When outputting this:
$dateTime = new DateTime(null, new DateTimeZone('UTC'));
$dateTime->setTimestamp($log->timeStamp);
$dateTime->setTimezone(new DateTimeZone('Europe/London'));
echo $dateTime->format('d/m/Y H:i');
Techincally correct output: 29/10/2016 18:30
But I wish for it to display being DST aware relative to the actual timestamp, not the DST relative to now.
So I wish for it to output: 29/10/2016 19:30
First of all, let's summarise the expected local values for 1477762205 Unix timestamp:
UTC: 17:30:05 (UTC, +0000)
London: 18:30:05 (BST, +0100 aka 3600, DST=1)
Madrid: 19:30:05 (CEST, +0200 aka 7200, DST=1)
Stuff seems to work as expected as long as you use city-based time zone identifiers:
foreach (['UTC', 'Europe/London', 'Europe/Madrid'] as $time_zone_id) {
$dt = new DateTime('#1477762205');
$tz = new DateTimeZone($time_zone_id);
$dt->setTimezone($tz);
echo $time_zone_id . ': ' . $dt->format('H:i:s [e=T, O]') . PHP_EOL;
}
UTC: 17:30:05 [UTC=UTC, +0000]
Europe/London: 18:30:05 [Europe/London=BST, +0100]
Europe/Madrid: 19:30:05 [Europe/Madrid=CEST, +0200]
As soon as we use named zones acronyms, strange things happen:
foreach (['UTC', 'BST', 'GMT', 'CEST', 'CET'] as $time_zone_id) {
$dt = new DateTime('#1477762205');
$tz = new DateTimeZone($time_zone_id);
$dt->setTimezone($tz);
echo $time_zone_id . ': ' . $dt->format('H:i:s [e=T, O]') . PHP_EOL;
}
UTC: 17:30:05 [UTC=UTC, +0000]
BST: 17:30:05 [BST=BST, +0000]
GMT: 17:30:05 [GMT=GMT, +0000]
CEST: 18:30:05 [CEST=CEST, +0100]
CET: 18:30:05 [CET=CET, +0100]
There's probably some relation with the information (or lack of it) about time zone transitions available in the underlying database:
$dt = new DateTime('#1477762205');
foreach (['UTC', 'BST', 'Europe/London', 'CEST', 'Europe/Madrid'] as $time_zone_id) {
$tz = new DateTimeZone($time_zone_id);
$dt->setTimezone($tz);
echo $time_zone_id . PHP_EOL;
echo '- Time zone offset: ' . $tz->getOffset($dt) . ' seconds' . PHP_EOL;
$transitions = $tz->getTransitions(mktime(0, 0, 0, 1, 1, 2016), mktime(0, 0, 0, 12, 31, 2016));
if ($transitions===false) {
echo '- Error fetching transitions' . PHP_EOL;
} else {
echo '- ' . count($transitions) . ' transitions found' . PHP_EOL;
}
}
UTC
- Time zone offset: 0 seconds
- 1 transitions found
BST
- Time zone offset: 0 seconds
- Error fetching transitions
Europe/London
- Time zone offset: 3600 seconds
- 3 transitions found
CEST
- Time zone offset: 3600 seconds
- Error fetching transitions
Europe/Madrid
- Time zone offset: 7200 seconds
- 3 transitions found
It's really hard to say how much of this is a plain bug and how much is counter-intuitive but documented; the PHP bug database is crowded with not a bug entries which are indeed a misunderstanding but I've personally found weird but actual bugs in date calculations involving DST boundaries.
Related
In time-zones observing Daylight Saving Time, the clock typically:
moves forward during transition from winter to summer
is set back during transition from summer to winter
For example, in the Europe/Paris time-zone, the UTC offset changes from +02:00 to +01:00 during the transition from summer to winter, at 3:00 AM on the last Sunday of the month of October.
In other words:
At 3:00 AM (+02:00) on 2014-10-26, clocks are set back to 2:00 AM (+01:00).
Which means that creating a DateTime for 2014-10-26 at 02:30 AM in the Europe/Paris time-zone is ambiguous, as it can represent either:
2014-10-26T02:30+01:00 (timestamp 1414287000)
2014-10-26T02:30+02:00 (timestamp 1414283400)
Java's ZonedDateTime documentation explains this problem very well, and their API offers a way to choose the preferred offset if needed.
In PHP however, it seems that this ambiguity is resolved by choosing arbitrarily the winter time:
$dt = new DateTime('2014-10-26T02:30', new DateTimeZone('Europe/Paris'));
echo $dt->format(DateTime::ISO8601); // 2014-10-26T02:30:00+0100
echo $dt->getTimestamp(); // 1414287000
echo $dt->getOffset(); // 3600
echo $dt->getTimeZone->getName(); // Europe/Paris
(By arbitrarily, I mean that I could not find any documentation about it).
Is there a way to choose the preferred offset when creating a DateTime from a date and time that fall within a DST overlap for the given time-zone?
Or in other words:
How can I create a DateTime object that would exhibit the following characteristics:
echo $dt->format(DateTime::ISO8601); // 2014-10-26T02:30:00+0200
echo $dt->getTimestamp(); // 1414283400
echo $dt->getOffset(); // 7200
echo $dt->getTimeZone->getName(); // Europe/Paris
That is, an object representing this date/time in the Europe/Paris time-zone in summer time?
First, consider that there is a known bug in PHP that will affect you here. Consider:
$dt = new DateTime('2014-10-26T02:30', new DateTimeZone('Europe/Paris'));
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")\n";
$dt->setTimeStamp($dt->getTimeStamp() - 3600);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")\n";
Output:
2014-10-26T02:30:00+0100 (1414287000)
2014-10-26T02:30:00+0100 (1414287000)
Even though you adjusted the timestamp back an hour to reflect summer time, PHP erroneously advanced it to the winter time position.
You can work around this for display purposes by using UTC as an intermediary.
$tz = new DateTimeZone('Europe/Paris');
$dt = new DateTime('2014-10-26T02:30', $tz);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")\n";
$ts = $dt->getTimeStamp() - 3600;
$dt = new DateTime("#$ts", new DateTimeZone('UTC'));
$dt->setTimeZone($tz);
echo $dt->format(DateTime::ISO8601) . " (" . $dt->getTimeStamp() . ")\n";
Output:
2014-10-26T02:30:00+0100 (1414287000)
2014-10-26T02:30:00+0200 (1414287000)
Note that even though the wrong timestamp is returned (it should be 1414283400), it does retain the desired summer-time offset of +0200.
Now, lets tackle the problem of knowing when to apply this. We'll examine the transitions and use that to decide whether or not to subtract an hour.
// set up the original input values
$tz = new DateTimeZone('Europe/Paris');
$dt = new DateTime('2014-10-26T02:30', $tz);
echo $dt->format(DateTime::ISO8601) . "\n";
// check for a transition +/- an hour from the current time stamp
$ts = $dt->getTimestamp();
$transitions = $tz->getTransitions($ts - 3600, $ts + 3600);
if (count($transitions) > 1) {
// see if we are moving backwards, creating the ambiguity
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift < 0)
{
// apply the difference in offsets to move back to summer time
$ts = $ts + $shift;
$dt = new DateTime("#$ts", new DateTimeZone('UTC'));
$dt->setTimeZone($tz);
}
}
echo $dt->format(DateTime::ISO8601) . "\n";
Output:
2014-10-26T02:30:00+0100
2014-10-26T02:30:00+0200
You may also wish to read this related question and answer.
Okay so I have a server in Denver with a user in New Zealand. I know everything about the user (timezone etc) and through the program they request something to happen in advance - let's say at 11:30am on August 5th 2013. I have a CRON job that runs every 15 minutes and asks the database if any requests are pending for the next 15 minute period, but how do I convert their stored time to the servers equivalent.
I set the default timezone for calculations: date_default_timezone_set('America/Denver')
I take the time now on the server and turn it into epoch: strtotime(date('Y-m-d H:i:s'))
I add the 15 minutes to create a range: $forward15 = strtotime('now +15 minutes')
I get the user chosen date from the database (and their timezone): 2013-08-05 11:30:00
Now what? If I convert that into epoch, it'll just be the servers version of that date.
NEW SOLUTION SEE BELOW!
If you know the timezone you can simply "add" it to your time.
For example:
server time: 01/01/01 00:00
the time the user wants: 01/01/01 01:00
the timezone of the user: GMT - 5
Just get the time (01/01/01 01:00) and add +5 => 01/01/01 06:00
So: your script needs to be executed at 01/01/01 06:00
(convert to timestamp where needed)
Added a little php to demonstrate
<?php
$servertime = time(); //timestamp of 01/01/01 00:00
$usertime = "01/01/01 06:00";//database
$userUTC = "-5";//database
strreplace($userUTC, "-", "+";
$replacetext = $userUTC . " days";
$usertime = strtotime($replacetext, $usertime);//now usertime is in your local timezone
$crontime = date("d/m/Y H:i");//the time you want the script to be executed
?>
I'm just assuming that the timezone is saved as "-5" for example and not Europe/Amsterdam Just tell me if i'm wrong.
edit 14:37
This could be a even better solution i think!
<?php
$usertime = "01/01/01 06:00";
$userUTC = "-5";
$userdate = $usertime . " " . $userUTC;
$usertimestamp = strtotime($userdate);//now you have the timestamp with correct timezone
$crontime = date("d/m/Y H:i", $usertimestamp);//formatted to the right date
echo $crontime;
?>
Edit: 25-07-2013 14:26
New solution to suit your database:
<?php
$usertime = "01/01/01 06:00";
$userUTC = "Pacific/Auckland";//get from database
$userdate = $usertime . " " . $userUTC;
$usertimestamp = strtotime($userdate);//now you have the timestamp with correct timezone
$crontime = date("d/m/Y H:i", $usertimestamp);//formatted to the right date
echo $crontime;
?>
the server is in which GMT time zone here is extremely easy way to get time and date for any time zone. This is done with time() and gmdate() function. gmdate() function normally give us GMT time but by doing a trick with time() function we can get GMT+N or GMT-N means we can get time for any GMT time zone.
For example you have to get time for GMT+5 we will do it like following
<?php
$offset=5*60*60; //converting 5 hours to seconds.
$dateFormat="d-m-Y H:i";
$timeNdate=gmdate($dateFormat, time()+$offset);
?>
Now if you have to get the time which is GMT-5 now we will just subtract the offset from the time() instead of adding into time like in following example we are getting time for GMT-4
<?php
$offset=4*60*60; //converting 5 hours to seconds.
$dateFormat="d-m-Y H:i";
$timeNdate=gmdate($dateFormat, time()-$offset);
?>
I'm currently reporting file modified time like so:
$this->newScanData[$key]["modified"] = filemtime($path."/".$file);
$modifiedtime = date($date_format." ".$time_format, $this->newScanData[$key]["modified"]);
To me I thought there was nothing wrong with that but a user of my code is reporting the time being 4 hours out. The only reason why I can think of this is because the server is in a different timezone to the user. Each user has a variable I can use $gmt_offset that stores the time zone that user is in. $gmt_offset is stored as a basic float offset.
The server could be in any timezone, not necessarily in GMT-0. The server might not be in the same timezone as the user.
How do I get $modifiedtime to have the correct time for the user in his timezone based on $gmt_offset?
filemtime() will return a unix timestamp based on the server's clock. Since you have user to gmt offset available, you must convert the unix timestamp to GMT and then into user's timszone as follows:
<?php
list($temp_hh, $temp_mm) = explode(':', date('P'));
$gmt_offset_server = $temp_hh + $temp_mm / 60;
$gmt_offset_user = -7.0;
$timestamp = filemtime(__FILE__);
echo sprintf('
Time based on server time.........: %s
Time converted to GMT.............: %s
Time converted to user timezone...: %s
Auto calculated server timezone...: %s
',
date('Y-m-d h:i:s A', $timestamp),
date('Y-m-d h:i:s A', $timestamp - $gmt_offset_server * 3600),
date('Y-m-d h:i:s A', $timestamp - $gmt_offset_server * 3600 + $gmt_offset_user * 3600),
$gmt_offset_server
);
// Output based on server timezone = PKT (+05:00 GMT) and user timezone = PDT (-07:00 GMT)
// Time based on server time.........: 2011-06-09 03:54:38 PM
// Time converted to GMT.............: 2011-06-09 10:54:38 AM
// Time converted to user timezone...: 2011-06-09 03:54:38 AM
// Auto calculated server timezone...: 5
What you need is the strtotime() function. Changed date to gmdate, converting your servers time to GMT
For example if you need the time format like 10:00:00
gmdate("H:i:s", strtotime($gmt_offset . " hours"));
More info here:
http://php.net/manual/en/function.strtotime.php
http://php.net/manual/en/function.gmdate.php
$modifiedtime = date($date_format." ".$time_format, $this->newScanData[$key]["modified"] + ($gmt_offset * 3600));
$gmt_offset should be of type float, not int -- some time zones can have fractional difference, like GMT +09:30 for Adelaide
Alright, so i'm not sure if im converting user input time to GMT properly. I will be having users across several timezones entering "events" and they will have to be able to see "how long untill" or "how long since" the current time();
This is how I was planning to convert the time they input. It will start as something like 07/21/2011 01:30 am Then,
echo gmdate('Y-m-d H:i:s',strtotime('07/21/2011 01:30 am'));
gives me 2011-07-21 08:30:00
So I was planning to take the value of gmdate('Y-m-d H:i:s',strtotime('07/21/2011 01:30 am')); then take time() and display "how long until this event" to users. But it seems like there is always 10 hours added onto the result, so if if i was scheduling an event 30 min from now it would say 10 hours 30 min from now. So, im thinking im not converting the local time correctly or something.
What am I missing? Maybe I just dont properly understand GMT. How can I make sure all the times involved are GMT so all times are universal to all the users on the website?
Other info if it helps:
The server timezone is America/Los_Angeles
EDIT:
After everyones suggestions i've tried setting this at the top of my php code:
date_default_timezone_set("GMT");
and I tried using date('Y-m-d H:i:s') to do the comparison to figure out the diff, but its saying "3 hours ago" rather than the 10 hours from now. So this definately changed things.
But still not correct.
I've confirmed date('Y-m-d H:i:s') is returning the correct and current GMT. So thats good.
But the user input date is off. How am I converting it incorrectly?
EDIT AGAIN(including some test results after Salman A's suggestions):
2:55am - my current local time EST
date('Y-m-d H:i:s') shows up as 2011-07-21 06:56:43 - which is correct
3:00am EST is the time in the future I submitted as 07/21/2011 03:00 am
Here's how I get the time "convert it" and submit it to my DB:
$time = $_POST['time'];
//there is where im assuming it turns my EST time to the GMT equivalent.
$the_date = strtotime($time . ' GMT');
$utctime = gmdate('Y-m-d H:i:s',$the_date);
I'm expecting my function to tell me the event is 5 minutes from now, but its hours off.
just to make sure the user submitted time was actually converted to GMT i display $utctime and it shows up as 2011-07-21 03:00:00 - which is not 08:00 or 07:00 (which i think one of those would be the GMT equivalent)
So how do I convert it?
So, what im seeing is strtotime($time . ' GMT'); doesn't seem to be applying the GMT to the local time I supply. On a side note: somone suggested I have date_default_timezone_set("GMT"); in my code, so i have it at the top. Should I remove it? but i noticed if i remove it the GMT is incorrect. So thats why I left it.
If you simply need to calculate the difference between two time values:
<?php
$time = '07/21/2011 11:30 am';
$timeleft = strtotime($time) - time();
// target time....: 2011-07-21 11:30:00
// current time...: 2011-07-21 11:13:45
// difference.....: 975 seconds (16 min, 15 seconds)
The above example assumes that $time has same timezone as that used by the time() function i.e. the server's timezone.
If the timezones differ, you must normalize them in order for subtraction to work as expected. So for example if you're storing GMT date/time in your database then the above example becomes:
<?php
$time = '07/21/2011 06:30 am';
$timeleft = strtotime($time . ' GMT') - time();
// target time............: 2011-07-21 06:30:00 GMT
// converted local time...: 2011-07-21 11:30:00 PKT
// current time...........: 2011-07-21 11:34:48 PKT
// difference.............: -288 seconds (minus 4 minutes, 48 seconds)
Edit 1
Regarding this code:
$time = $_POST['time'];
If your users are from various parts of the world, you should either:
ask them to enter the date/time in GMT
ask them to enter a timezone for the date entered
You can later convert the date on server side and store it in database:
<?php
$source_time = '2011-07-21 17:00';
$source_offset = '-0700'; // PDT
$local_timestamp = strtotime($source_time . ' ' . $source_offset); // 2011-07-22 05:00 PKT (SERVER TIME)
list(
$temp_hh,
$temp_mm
) = explode(':', date('P')); // returns difference between SERVER TIME and GMT
$local_offset = $temp_hh * 3600 + $temp_mm * 60;
$gmt_timestamp = $local_timestamp + $local_offset;
echo date("Y-m-d H:i:s", $gmt_timestamp); // 2011-07-21 10:00:00
// THIS is what you store in your database
// Same as 2011-07-21 17:00:00 minus 7 hours
Without the timezone information your calculations will be unreliable.
Edit #2
Actually... it is much simpler:
<?php
$source_time = '2011-07-21 17:00';
$source_offset = -7.0; // -0700
echo date("Y-m-d H:i:s", strtotime($source_time) + $source_offset * 3600);
// 2011-07-21 10:00:00
// THIS is what you store in your database
Edit #3
<input type="text" name="time" id="time" value="07/21/2011 17:00">
<input type="text" name="offset" id="offset">
<script type="text/javascript">
document.getElementById("time").onchange = function(){
var d = new Date(this.value);
alert('Date entered: ' + d + '\nDate to GMT: ' + d.toUTCString());
}
document.getElementById("offset").value = (new Date()).getTimezoneOffset() / 60;
</script>
Demo here
A good idea is to explicitly set the timezone for your scripts. For example:
date_default_timezone_set('Europe/London');
This will make all date functions returns dates in GMT, and I believe accounting for BST too.
Hello I am living in Poland and we have a CET time.
To convert CET to GMT I am using function:
function convertCETtoGMT($timeCET)
{
date_default_timezone_set('EUROPE/London');
$time = $timeCET." CET";
$timeGMT = date('Y-m-d H:i:s', strtotime($time));
date_default_timezone_set('EUROPE/Warsaw'); //set back to CET
return $timeGMT;
}
im writing a twitter web service in php. When a user signs in, i receive this node:
<utc_offset>-18000</utc_offset>
I have to change the script's timezone so that it adapts to the user's real timezone. The only php function i have found for this is: date_default_timezone_set($timezone_identifier) but it won't let me use -18000 as a the $timezone_identifier parameter.
So, how can i change the current user timezone based on two values: Server UTC offset and User UTC offset
BTW, this is how i'm getting the server UTC offset value:
$this_tz_str = date_default_timezone_get();
$this_tz = new DateTimeZone($this_tz_str);
$now = new DateTime("now", $this_tz);
$offset = $this_tz->getOffset($now);
Any ideas? Thanks!
To get current server time
date_default_timezone_set(date_default_timezone_get());
echo date('Y-m-d H:i:s', time());
Output for Europe/Paris (my server settings; UTC+2)
2011-04-12 20:39:43
To get user's time by offset
$user_offset = '-18000';
date_default_timezone_set('UTC');
$diff = "$user_offset seconds";
if ((substr($diff,0,1) != '+') && (substr($diff,0,1) != '-')) $diff = '+' . $diff;
$usertime = strtotime($diff, time());
echo date('Y-m-d H:i:s', $usertime);
Output UTC-5 (Ecuador -> Quito time NO DST), php timezone identifier 'America/Guayaquil'.
2011-04-12 13:39:43
PHP.net manual:
Timezone offset in seconds. The offset
for timezones west of UTC is always
negative, and for those east of UTC is
always positive. (-43200 through
50400)
The date_default_timezone... functions expect a string giving something like "Africa/Luanda" or whatever.
I suggest programmatically searching through the timezone database for a matching offset. If I recall correctly, those are in minutes from UTC, so you should divide the offset you are given by 60.