PHP timezone function not working - php

I'm creating a forum, which also stores the time a post was sent, and I need to convert that into the user's timezone.
Now, the MySQL DataBase stores the time with UTC_TIMESTAMP() (in a column with the DATETIME type), and I created a little function from the code on http://www.ultramegatech.com/blog/2009/04/working-with-time-zones-in-php/ to convert the time to the user's timezone. This is the function:
function convertTZ($timestamp, $tz='UTC', $format='d-m-Y H:i:s') {
// timestamp to convert
$timestamp = strtotime($timestamp);
// the time formatting to use
$format = $format;
// the time zone provided
$tz = $tz;
// create the DateTimeZone object for later
$dtzone = new DateTimeZone($tz);
// first convert the timestamp into an RFC 2822 formatted date
$time = date('r', $timestamp);
// now create the DateTime object for this time
$dtime = new DateTime($time);
// convert this to the user's timezone using the DateTimeZone object
$dtime->setTimeZone($dtzone);
// print the time using your preferred format
$time = $dtime->format($format);
return $time;
}
And I made a test page at http://assets.momo40k.ch/timezones.php.
Now, when I insert a post into the DataBase at, say, 11:50 in my timezone (which is Europe/Rome), it inserts 09:50 in UTC, wich is correct, according to some online timezone converters.
But when I try to convert it back to Europe/Rome with the convertTZ() function, it returns 09:50, as if Europe/Rome is UTC. If I try converting it to a GMT+2:00 timezone, it returns 10:50. Can anyone fugure out why this is?
P.S: I'm not using the CONVERT_TZ() SQL function because my server does not support named timezones, so this function is my workaround.

Make sure your stored timestamps are UTC:
$date = new DateTime($timestamp, new DateTimeZone("UTC"));
$date->format(DATE_W3C); // does it gives the expected result ?
BTW your function can be simplified to this:
function convertTZ($timestamp, $tz='UTC', $format='d-m-Y H:i:s') {
$dtime = new DateTime($timestamp, new DateTimeZone("UTC"))
$dtime->setTimezone(new DateTimeZone("UTC"));
return $dtime->format($format);
}

MySQL always stores TIMESTAMP fields in UTC internally (that's the definition of a unix timestamp, in fact). So when you SELECT or UPDATE/INSERT/REPLACE, the time you get or set is always in the MySQL server's local time zone.
So a common mistake is to store UTC_TIMESTAMP(), which MySQL interprets as a local time and so the current time gets double-converted to UTC when it stores it internally in the field as a unix TIMESTAMP.

Related

Converting a date from local timezone to UTC

I have a Laravel-based app that is used by people from various parts of the US.
I am capturing a timestamp in Javascript when the user takes a specific action, and then I am submitting that timestamp as form data, for the Laravel/PHP to process.
The timestamp that I am capture in Javascript is in typical "YYYY-MM-DD HH:MM:SS" format.
I have the timezone the user is in stored in a database.
I basically want to take that timestamp, and convert it to UTC time, so that all timestamps in the database are UTC.
That is where I am struggling.
I have the following PHP code:
$defaultTime = request('submitted-time-stamp'); //In this case, we'll say 2022-12-21 12:01:01
$defaultTZ = $user->time_zone; //Translates to America/Denver
$utcTime = new DateTime($defaultTime);
$convertedTime = $utcTime1->setTimeZone(new DateTimeZone('UTC'));
$formattedTime = $convertedTime->format("Y-m-d H:i:s");
echo $formattedTime;
This code – it isn't producing any errors per sé... but it is showing the wrong time. It's showing the time that it went in as, not the time converted to UTC.
Basically, if I submit "2022-12-21 12:01:01" as the time, the converted time SHOULD be "2022-12-21 19:01:01", but it's still just echoing out "2022-12-21 12:01:01".
What am I missing here?
setTimezone() changes the timezone of the object from whatever default it was created with. I.e., it means, "convert from the existing timezone to this new timezone." It does not mean, "interpret the time as if it were in this timezone." If the original string didn't contain some sort of timezone identifier, then that default is whatever your PHP config says.
$when = new DateTime('2022-12-21 12:01:01');
echo $when->getTimeZone()->getName();
This will be the same as:
echo date_default_timezone_get();
Which is probably not what you want unless all your users are in the same timezone as your server.
In order to create a DateTime object in a specific known timezone that is not the same as your server's default, you'll need one of two things -- either a timezone representation in the input string:
$when = new DateTime('2022-12-21 12:01:01 America/New_York');
Or an explicit default timezone passed as a second parameter to the DateTime constructor:
$userDefaultTzStr = 'America/New_York'; // read this value from the database
$defaultTz = new DateTimeZone($userDefaultTzStr);
$when = new DateTime('2022-12-21 12:01:01', $defaultTz);
This latter method is (probably) preferred. If the input string contains any sort of timezone identifier, that will be used and the second parameter will be ignored. But if the input string does not contain any sort of timezone identifier, then the string will be interpreted as if it were in the indicated timezone.
Using Carbon it's very trivial.
use Carbon\Carbon;
$date = Carbon::create(request('submitted-time-stamp'), $user->time_zone);
$date->tz('UTC');
echo $date->format('Y-m-d H:i:s');
It should be the same thing with Laravel's Date facade.
use Illuminate\Support\Facades\Date;
$date = Date::create(request('submitted-time-stamp'), $user->time_zone);
$date->tz('UTC');
echo $date->format('Y-m-d H:i:s');

Why is the difference between timestamp unix time and time() incorrect?

I'm trying do execute a php script with cron after 4 hours after saving an record in database. My problem here is not with cron, all here works fine(I'm sure of this because I'm receiving test mails from daemon cron every 4 hours as it should).
The problem is when I calculate the difference between the timestamp, converted to unix, and the current time and then execute some script.
But the diff bewtween these two times are adding 3 hours more to them and I don't know why but my script executes after 7 hours, instead of 4 hours. Can anyone help me please?
My code looks like this:
<?php
require_once('some-path/wp-load.php');
global $wpdb;
$constant= 4*3600;
$table = 'notifications';
$data = $wpdb->get_results("SELECT * FROM $table WHERE status = 'pending'");
$time = time();
foreach ($data as $r){
$temp_data = strtotime($r->date_created);
if ($time - $temp_data > $constant){
$email = $r->email;
$message = 'test';
$subject = 'test';
$headers = 'From: test <test#test.com>' . "\r\n";
$headers .= 'Content-type: text/html; charset=utf-8' . "\r\n";
$mail_client = wp_mail($email, $subject, $message, $headers);
if($mail_client){
$wpdb->update($tabel, array('status' => 'sent'), array('id_raport'=>$r->id_raport), array('%s'), array('%d'));
}
}
}
Example of date from database Using strtotime(): 1458814621, Directly from database "2016-03-24 10:17:01"
Now, I know I can do something like this to get the real localtime:
date_default_timezone_set('Europe/Bucharest');
$date = date('m/d/Y h:i:s a', time());
$time = strtotime($date);
Update.
But this returns the same thing as the time() method.
Now, I know I can do something like this to get the real localtime:
date_default_timezone_set('Europe/Bucharest');
$date = date('m/d/Y h:i:s a', time());
$time = strtotime($date);
A UNIX timestamp is not "local". A UNIX timestamp is the same all over the world. What you're doing in this code is merely converting a timestamp (time()) to a human readable format, and then reinterpreting it into a timestamp; the result is identical to the original time() value (or at least it should be!).
A human readable date/time format, such as you receive from MySQL (2016-03-24 10:17:01) is incomplete without a timezone. There are more than 24 different absolute points in time at which it is "2016-03-24 10:17:01" somewhere in the world. That timestamp by itself doesn't mean very much.
When you convert it into a UNIX timestamp using strtotime, it must take in additional information to convert such an ambiguous relative time format into an absolute point in time. That information comes from date_default_timezone_set, or whatever is set equivalently in your php.ini.
Your problem almost certainly just boils down to the timezone PHP assumes when doing strtotime not being the same timezone as what your MySQL datetime string is actually for. E.g., if your MySQL datetime expresses the time for UTC, but strtotime assumes Bucharest as the timezone to use, you'll see a difference of a few hours in the resulting absolute point in time.
Simply set/use the correct timezone in your PHP code; think about/be aware/decide on what timezone your MySQL dates are actually stored in.
Also see Does PHP time() return a GMT/UTC Timestamp?
The value returned by time() (a timestamp) is the number of seconds since Jan 1, 1970, 00:00:00 UTC. It is an absolute value.
The value you retrieve from the database (2016-03-24 10:17:01) is a relative value. It can represent different timestamps, depending on what time zone you use.
How to use the DateTime and DateTimeZone classes:
// Timestamp generated from PHP code
// Current time
$date1 = new DateTime(); // it uses the default timezone set in php.ini
// or by a previous call to date_default_timezone_set()
// be more specific
$date2 = new DateTime('now', new DateTimeZone('US/Eastern'));
// $date1 and $date2 represent the same moment in time ("now")
echo(($date1 == $date2) ? 'Yes' : 'No'); // It displays "Yes"
// Display them as timestamps
echo($date1->format('U')); // 1458816551
echo($date2->format('U')); // also 1458816551
// Display $date1 as human-readable format:
echo($date1->format('Y-m-d H:i:s e'));
// It displays: 2016-03-24 12:49:11 Europe/Bucharest
// Change $date1 to use the same timezone as $date2
$date1->setTimezone(new DateTimeZone('US/Eastern'));
echo($date1->format('Y-m-d H:i:s e'));
// Now it displays: 2016-03-24 06:49:11 US/Eastern
// It provides easy ways to generate another date:
$date3 = clone $date1; // create a duplicate
$date3->add(new DateInterval('P2D')); // modify the duplicate
// $date3 is 2 days in the future
echo($date3->format('Y-m-d H:i:s e'));
// It displays: 2016-03-26 06:49:11 US/Eastern
// Get the difference between $date3 and $date1
$diff = $date3->diff($date1)
// you get the difference in date components (days, hours, minutes etc).
print_r($diff);
A date-time value you extract from the database is incomplete. It lacks the timezone. If you stored the value in the database in the past then you should know what timezone it uses. If you get the datetime from the database using SELECT NOW() then the timezone is the default timezone used by the server. It is stored in the system_time_zone server variable and can be queried with SELECT ##system_time_zone
The query:
SELECT NOW() AS now, ##system_time_zone AS tz
returns the local date and time and the timezone used by the MySQL server.
You can use them to create a DateTime object to work with, as in the example code provided above.
As a general rule, always use a single time for the values you store as datetime in the database. I suggest using UTC because everything is relative to it and it doesn't observe DST. Or you can use columns of type TIMESTAMP instead (absolute timestamp do not care about timezones and DST) but they are more difficult to handle.

How can I create a DateTime object from a string that is already in a specific timezone?

I am working within an environment (wordpress) in which all default timezone settings are set to UTC.
Within that, I want to create a timer function that deals with the time always in the local server time (+8).
The goal is to have a function that returns a DateTime object to any given date (input with a 'Y-m-d H:i:s' format, always in the local +8 timezone
function datetime_obj($date = NULL) {
$date_new = new DateTime($date);
$date_new->setTimezone(new DateTimeZone('Asia/Hong_Kong'));
return $date_new;
}
This works great when I try to get today's date/time information ($date = NULL). However if I have an existing time (in HKG time) as a string (say from a datetime field in a MySQL database), I cannot generate a new datetime object from it since it is treated as UTC. The resulting DateTime object from the function above has always added 8 hours to it.
How can I change the above function so that a $date that is inserted is accepted as already being in the correct +8 timezone and not changed +8 as an output?
I haven't tried it, but the doc says:
public DateTime::__construct() ([ string $time = "now" [, DateTimeZone $timezone = NULL ]] )
time: A date/time string. Valid formats are explained in Date and Time
Formats. Enter NULL here to obtain the current time when using the
$timezone parameter.
timezone: A DateTimeZone object representing the timezone of $time. If
$timezone is omitted, the current timezone will be used.
Note: The $timezone parameter and the current timezone are ignored
when the $time parameter either is a UNIX timestamp (e.g. #946684800)
or specifies a timezone (e.g. 2010-01-28T15:00:00+02:00).
http://at2.php.net/manual/en/datetime.construct.php
so... this:
$date_new = new DateTime($date."+08:00");
or this:
$date = new DateTime($date, new DateTimeZone('Asia/Hong_Kong'));
should do the job

Converting from UTC in PHP using a variable timezome

I store timestamps on my server using a simple timestamp in SQL. When I pull down that timestamp, I run it through the following function in order to format the time.
How do I add to the following function in order to convert $timestamp into whatever timezone the user is querying from?
// Returns the formatted time
function displayDate($timestamp)
{
$secAgo = time() - $timestamp;
// 1 day
if ($secAgo < 86400)
return date('h:i:A', $timestamp);
// 1 week
if ($secAgo < (86400 * 7))
return date('l', $timestamp);
// older than 1 week
return date('m/t/y', $timestamp);
}
If you have timezone name to change, you can use something like:
$date = new DateTime(date('Y-m-d H:i:s', $timestamp), new DateTimeZone('UTC'));
$date->setTimezone(new DateTimeZone('Asia/Vladivostok'));
return $date->format('m/t/y');
Where Asia/Vladivostok is the user's custom timezone.
#zerkms's method is the best way to convert timestamps to any timezone.
But if you do that every time you need to display a time, it may have a significant performance hit because you're setting up and tearing down a timezone object every time. There's a convenient shortcut, if all the timestamps on the page are going to be in the same timezone. (Which is usually the case, because users don't change timezones in the middle of an HTTP request.)
Somewhere outside of the function, do:
date_default_timezone_set('America/New_York'); // or any other timezone
It's probably a good idea to tie this into one of your session management functions, so that the same user always gets the same timezone.
Once you do this, date() will automatically start using the correct timezone, assuming that $timestamp represents a Unix timestamp. It will also automatically correct for daylight saving time. So there's no need to change anything in that function.
Use timezone_identifiers_list() to retrieve a list of valid timezones.

How can I change timezones in a php date?

I have a date stored in a database in this format:
2011-02-23 13:00:00
I need to return it in ISO8601 format, but it needs to be set to a specific time zone (which is not necessarily the time zone of the server.) What I want to return is this:
2011-02-23T13:00:00-0600
Using this code:
echo date(DATE_ISO8601, strtotime("2011-02-23 13:00:00"));
I get this:
2011-02-23T13:00:00+0000
Is there any way to reset the time zone in the date or strtotime function, or do I need to strip off the 5 rightmost characters and concatenate the desired timezone stamp to the remaining date/time?
EDITED TO ADD:
Although I did accept the solution below of using new DateTime and setting new DateTimeZone, I found an easier way if you don't need to keep resetting the time zone:
date_default_timezone_set('America/Chicago');
$startTime = date(DATE_ISO8601, strtotime("2011-02-23 13:00:00"));
You could use the DateTime class. Datetime objects can be initialized with a specific time zone, and easily transposed to others.
Modified from the manual:
$date = new DateTime('2011-02-23 13:00:00', new DateTimeZone('Pacific/Nauru'));
echo $date->format('c') . "\n";
$date->setTimezone( new DateTimeZone('Europe/Berlin'));
echo $date->format('c') . "\n";

Categories