I've run in to an issue whereby PHP and MySQL seem to disagree on which timezone they should be using when converting certain timestamps back to date and time.
The problem arises when I have a timestamp which I would like to convert back to a datetime format. PHP gives the date and time based on GMT being the timezone, but MySQL seems to think it is operating in GMT+1, and converts accordingly.
Simple reproduction
$original_date = '2009-08-31 23:59:59';
$timestamp = strtotime($original_date);
echo $timestamp; // Gives 1251763199
echo date('Y-m-d H:i:s', $timestamp); // PHP: 2009-08-31 23:59:59
echo db_field('SELECT FROM_UNIXTIME(1251763199)'); // MySQL: 2009-09-01 00:59:59
The timestamp given by PHP seems to be the correct one for the date given, assuming timezone is GMT. The result from MySQL would have been the correct one had we been running BST (timestamp given fell within GMT+1 at that time).
If I try the above with a $timestamp from today (1267640942), PHP and MySQL both seem to be happy to tell me that it is 2010-03-03 18:29:02 (both returning GMT).
What timezone is set on the servers?
I've checked the MySQL docs, which say that if my timezone is set to system than the OS will provide the timezone info. This appears to be the case at the moment:
mysql> SELECT ##global.time_zone, ##session.time_zone;
+--------------------+---------------------+
| ##global.time_zone | ##session.time_zone |
+--------------------+---------------------+
| SYSTEM | SYSTEM |
+--------------------+---------------------+
The default timezone on my web server is GMT. This is also the default system timezone on my database server (according to the date command run on the cl on each server).
foo#bar:/home/foo$ date
Wed Mar 3 18:45:02 GMT 2010
So according to the docs, my DB server should be running on GMT, which is what we want. Yet the query output given in my test scripts suggests that it's running in GMT+1.
I know there are a number of workarounds for this problem (all date arithmetic being done fully in either PHP or MySQL, etc) but I'd love to get to the bottom of what's causing this discrepancy so we can sort it out and prevent anyone else on the team from being tripped up by it.
Does anyone know if there's a very basic setting that I've over-looked here, or know what could be causing this discrepancy?
Thanks!
i use this methodology:
take care of PHP and leave mysql alone
it is recommended to set default timezone for php via date_default_timezone_set php function.
use a TIMESTAMP field type for keeping date record in mySql
then when you insert:
$sDate = date("Y-m-d H:i:s");
mysql_query("insert into `table` set `created_on` = '$sDate' ");
and when you select:
mysql_query("select `created_on` from `table` ");
$iTime = strtotime($aRow['created_on']);
you can always have access to global time using gmdate php function:
$iTime_Global = gmdate("U", $iTime);
the mysql timezone would have no effect in your application if you just take care of your PHP code. (that is made by timezone set)
I would suggest using UTC on your server and MySQL install and convert the timezone(s) to what ever you want. The conversion in MySQL and PHP are fairly simple.
http://www.w3schools.com/php/php_ref_date.asp (getTimezone and setTimezone)
http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html
Related
I'm trying to add datetime for check record changes. I'm using datetime datatype in table.
`date_added` datetime DEFAULT '0000-00-00 00:00:00',
I use following php built-in function using for datetime column in the query
date("Y-m-d H:i:s");
Problem is that this function date("Y-m-d H:i:s"); giving me two different date and time when i check in same time on server.
Localhost Result
date("Y-m-d H:i:s"); == 2016-07-12 13:10:04
Server Result
date("Y-m-d H:i:s"); == 2016-07-12 05:08:07
So when i use TimeAgo function on date_added column it is giving me wrong time, I mean the server time. For example I add a record then function will return me Record Added 8 Hours Ago so its totally wrong. I would like to know how can i add real time of an event into database that i can show using TimeAgo() function.
Is there any way to do that without change the server timezone, because if I change the timezone then it will be showing correct time only for those who are in the same region but what will be get others? I think they will face same issue.
I wanted to develop something like Facebook DateTime Functionality.
Can any one guide me how can I achieve this kind functionality? I would like to appreciate. Thank You
Instead of fiddling with timezones, why not just do
ALTER TABLE `your_table`
CHANGE `date_added` `date_added`
TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
This will change your column from a DATE column to a TIMESTAMP column, converting all the dates to their respective UTC timestamps in the process.
When a new row is inserted, it will use the current timestamp as a value. Timestamps are always in UTC, so you don't have to change the timezone on your MySql server, nor supply the date when inserting a new row.
If you cannot or want not change your columns, you can also just select the timestamp via
SELECT UNIX_TIMESTAMP('date_added') FROM your_table;
For your TimeAgo, you can then just do
$now = new DateTime;
$dateAdded = new DateTime("#$yourTimestampFromDb");
$dateAdded->setTimezone($now->getTimezone());
$timeSinceAdded = $dateAdded->diff($now);
When you supply a timestamp to DateTime, it will always use UTC regardless of your default server timezone set. Consequently, you have to either convert $dateAdded to the default timezone (as shown above) or convert $timeSinceAdded to UTC.
To change the dateTime to the currently visiting user's timezone, you either
need to have this information in your database, e.g. because you are asking registered users to supply this information
or you determine it at runtime, usually by doing a GeoIP lookup on the visiting user's IP or by sending the DateTime Offset from the user's browser.
In any case, you then just change both DateTimes to that timezone. This is easily done via setTimezone().
The $timeSinceAdded will then be a DateInterval object, which you can use like this
echo $timeSinceAdded->format('%a total days');
Please refer to the links for further details, for instance on the available format modifiers.
If you're accessing the same database server from clients with different timezone settings, you could also insert and check the date/time fields in sql:
INSERT INTO my_table SET date_added = NOW();
and then also check with something like
SELECT * FROM my_table WHERE TIMESTAMPDIFF(SECOND, date_added, NOW()) > 3600;
to select rows that are older than 1 hour.
Your question is a bit ambiguous but i'll try to explain a workaround that i think should fix this issues.
If you allow other users to add or update your database then, you should be having some information about them, like which city/continent they are coming from. You might also have telephone contacts and more about them.
If it is true that you possess such information about your users in your database then use that information to detect and load their timezone when they log into your system.
You can have a table with all the timezones or create an array that will hold all the known timezones so that when you call
date_default_timezone_set('continent/city')
function you can dynamically change the parameters to suit the current users timezone and later use that to affect date added field.
Problem
TimeAgo/nicetime function uses strtotime() to convert your datetime field value to unix timestamp. You receive a number of seconds since January 1 1970 00:00:00 UTC until the date you passed as a string. Then time() function returns the number of seconds until now, and nicetime compares the difference. The problem is in strtotime, when we send to it the text like "2016-07-12 05:08:07", it has no idea what time zone that is in and how it should be converted to UTC, so it uses the best guess, often incorrect.
Quick Solution
Specify the time zone of your date that you pass into nicetime() function. Instead of doing this:
$date = '2016-07-04 17:45'; // get from database
print nicedate($date);
try this:
$date = '2016-07-04 17:45';
print nicedate($date . ' America/Denver');
// mind the gap --------^
That should fix it.
Before one blindly goes ahead and starts comparing times, or performing date / time calculations on values retrieved from a database, it is essential that we understand the individual database's configuration settings to ensure our calculations are correct.
It should be noted that the MySQL timezone variable's default setting is SYSTEM at MySQL startup. The SYSTEM value is obtained from the the operating system's GLOBAL time_zone environment variable.
MySQL's default timezone variable can be initialised to a different value at start-up by providing the following command line option:
--default-time-zone=timezone
Alternatively, if you are supplying the value in an options file, you should use the following syntax to set the variable:
--default-time-zone='timezone'
If you are a MySQL SUPER user, you can set the SYSTEM time_zone variable at runtime from the MYSQL> prompt using the following syntax:
SET GLOBAL time_zone=timezone;
MySQL also supports individual SESSION timezone values which defaults to the GLOBAL time_zone environment variable value. To change the session timezone value during a SESSION, use the following syntax:
SET time_zone=timezone;
In order to interrogate the existing MYSQL timezone setting values, you can execute the following SQL to obtain these values:
SELECT ##global.time_zone, ##session.time_zone;
It should be noted also that:
The current session time zone setting affects display and storage of time values that are zone-sensitive. This includes the values displayed by functions such as NOW() or CURTIME(), and values stored in and retrieved from TIMESTAMP columns. Values for TIMESTAMP columns are converted from the current time zone to UTC for storage, and from UTC to the current client time zone for retrieval.
To obtain values in UTC time, use the UTC_DATE(), UTC_TIME() or UTC_TIMESTAMP() functions instead. To convert to another time zone, pass the value of the appropriate UTC function return to convert_tz(), which requires the zoneinfo tables to be generated (see below).
In your circumstances, if you DO NOT want to / CAN NOT change the SERVER time_zone value, you will have to explicitly set the individual SESSION timezone values for each client connection which will enable you to draw a line in the sand and have a known base from which you can convert and display a facebook user's post time into a viewer's local timezone.
To explicitly set the session timezone when connecting, issue the following command:
SET SESSION time_zone = '+10:00';
When you explicitly set the SESSION time_zone, and store a TIMESTAMP value, the server converts it from the client's time_zone to UTC and stores the UTC value (Internally the server stores a TIMESTAMP value). When you select data from the database, the opposite conversion takes place and provides the client with a UTC time in the client's timezone.
On the topic of data types and time zone's, in PHP you are better off using the DatTimeZone class if you would like to improve the accuracy of your date and time values by facilitating daylight saving aware dates and times.
As noted earlier, if your database is MySQL, you can load / generate the zoneinfo tables with the following command:
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
* where root is the username to be substituted.
Performing the generation of the zoneinfo tables allows you to use the convert_tz() function which accurately converts dates and times from one time zone to another, like so:
select DATE_FORMAT(convert_tz(now(), 'UTC', 'Australia/Perth'), '%e/%c/%Y %H:%i') AS PERTH_TIME;
PERTH_TIME;
+-----------------+
| PERTH_TIME |
+-----------------+
| 19/7/2016 19:42 |
+-----------------+
Additionally, you can generate an array of UTC time zones programmatically by calling the static function listIdentifiers() in the PHP DateTimeZone class.
May the force be with you.
I'm working on a new project which has a lot of different data and it's all stored in database with same time zone timestamp, East coast, I make sure the server time zone is switched to it before anything is loaded via
date_default_timezone_set("America/New_York");
However I can't seem to wrap my head around it, I want to have a search function that allows a time zone selection, what if they select west coast time zone, should I only change the PHP part? Or should I query database differently? All the data is very time sensitive and it's proving very hard to wrap this around my head.
Any help or logic would be appreciated :P
The main problem is that MySQL's support for timezones is laughable. Most other databases let you define the timezone as part of the datetime column, but not MySQL. Instead, they force you to do a connection-wide or server-wide timezone setting.
Your best bet is making sure MySQL stores all datetimes as GMT / UTC. You can enforce this by running SET time_zone = 'UTC' on connect. Do it at the same time as you set the character set. You are setting the connection character set, right?
PHP-side, you can then use a combination of DateTime and DateTimeZone to take the datetimes from MySQL and display them in the user's timezone. For example, let's pretend that we get the date 2012-11-13 14:15:16 from a MySQL DATETIME column. From the PHP interactive prompt:
php > $from_mysql = '2012-11-13 14:15:16';
php > $utc = new DateTimeZone('UTC');
php > $ts = new DateTime($from_mysql, $utc);
php > $pdt = new DateTimeZone('America/Los_Angeles');
php > $ts_pdt = clone $ts;
php > $ts_pdt->setTimezone($pdt);
php > echo $ts_pdt->format('r'), "\n";
Tue, 13 Nov 2012 06:15:16 -0800
As demonstrated, you just need to create the DateTime by expressly telling it you're UTC, if UTC isn't the timezone you've set using date_default_timezone_set. Switching the timezone of a DateTime is as easy as giving it a new one. I've used clone here to work on a copy. DateTimes are mutable, and it's sometimes easy to find yourself accidentally clobbering it.
Reverse date math works the same way, just transform their selection into UTC and run the math on the native numbers.
tl;dr:
Store and perform calculations on datetimes in UTC (GMT) only
Display all datetimes in the user's timezone only
My DB server (running MySql 5.5) is set to UTC, and dates are stored as Unix timestamps in the database using UNSIGNED INT. The database is primarily used for storing tasks which are run at a specific time (exec_time).
I insert tasks by creating a timestamp in PHP using the timezone of the user logged in (BST in this instance). For example, I have a task set to run at 1351396800 which is for tomorrow morning at 4am GMT.
I pluck tasks out of the database with the following query:
SELECT * FROM tasks WHERE exec_time <= UNIX_TIMESTAMP();
When the clocks roll back one hour tomorrow at 2am will this setup be ok?
Update: PHP is converting the dates fine. With PHP timezone set to Europe/Dublin (Currently BST) Two events added for 12 midnight and then 4am are stored as follows:
mysql> select exec_time, FROM_UNIXTIME(exec_time) from tasks order by id desc limit 2;
+-------------+----------------------------+
| exec_time | FROM_UNIXTIME(exec_time) |
+-------------+----------------------------+
| 1351378800 | 2012-10-27 23:00:00 |
| 1351396800 | 2012-10-28 04:00:00 |
tl;dr You should be fine as long as your exec_time column has a TIMESTAMP data type.
There isn't an explicit UNIX_TIMESTAMP column datatype. There is a TIMESTAMP column data type. Values for columns of this data type are converted automatically from your client connection's time zone to UTC (a/k/a Z or Zulu time, f/k/a Greenwich Mean Time) when being converted from a date/time string and from UTC to your client connection's time zone upon conversion to a date/time string.
So, if you're storing your exec_time column as a TIMESTAMP, you should be able to use the clause you propose:
WHERE exec_time <= UNIX_TIMESTAMP()
This will work because both your exec_time values and the result of the UNIX_TIMESTAMP() function call are handled in UTC on the server side. Your exec_time values will be stored in UTC.
If you're storing exec_time as an UNSIGNED INT or a similar numeric data type, you won't have been able to take advantage of the automatic conversion to UTC before storing.
You can mess with the display conversion behavior by setting your client connection time_zone as follows:
SET time_zone='SYSTEM' /* for your system's local time */
or
SET time_zone='+0:00' /* for UTC */
or
SET time_zone'America/New_York' /* or 'Europe/Vienna' or whatever */
Once you've issued one of these SET operations, do
SELECT exec_time, FROM_UNIXTIME(exec_time)
to get a sense of how your values are stored server side and translated.
If you want to see what will happen eight days on, try this:
SELECT 3600*24*8+exec_time, FROM_UNIXTIME(3600*24*8+exec_time)
http://dev.mysql.com/doc/refman/5.5/en//time-zone-support.html
In answer to your question, it depends how critical the time fields are, and whether the server's local time will change or not. If it's UTC then it probably won't change.
The temporal types in MySQL aren't timezone aware. You'll have to implement timezones yourself, perhaps by always storing a UTC timestamp/datetime and a separate timezone column which contains an interval offset from +12 to -12 hours for how much time to add or subtract to the UTC timestamp for the timezone.
The actual handling of what value to put in the timezone field and the work needed to retrieve a timestamp adjusted for the timezone are up to you, unfortunately.
If switching to Postgres is an option then you can always use the TIMESTAMP WITH TIMEZONE type that Postgres supplies.
From my android smartphone application (made by myself) I send a request to a server in Denver to save my location and time. The TIMESTAMP it saves is Denver's current time (9 hours difference from local time). Now after 16 hours from the last request I wrote in my php script
$query = "SELECT * FROM `tblLoc` WHERE datetime > (CURRENT_TIMESTAMP - 86400)";
so as if I wanted to show me past 24 hours....
THIS WAS MISTAKE!
$query = "SELECT * FROM `tblLoc` WHERE datetime > CURRENT_TIMESTAMP() - INTERVAL 24 HOUR";
in SQL 86400 != 24 HOUR!!!
You need to either change MySQL's time zone instead of PHP's, or change your query so that PHP provides the timestamp, like so:
$query = "SELECT * FROM tblLoc WHERE dateTime > (". time() ." - 86400)";
EDIT based on comment
UTC_TIMESTAMP() might be a good way to go, but make sure you are inserting/updating based on this same timestamp as well, and not based on CURRENT_TIMESTAMP().
UTC timestamp is based on GMT (Greenwich Mean Time) and is at GMT+0. Dallas is at GMT-6 and Asia/Jerusalem is at GMT+2. So UTC_TIMESTAMP() always means the current time in GMT timezone, regardless of what your current time zone setting is. CURRENT_TIMESTAMP() means the current time in your current timezone.
HOWEVER your results may still not be quite right. They may just look right for the moment, but may be off by a couple hours. But if you are inserting and updating based on CURRENT_TIMESTAMP and then selecting based on UTC_TIMESTAMP your results will not be correct.
I would suggest one of the following three solutions:
Switch your server's timezone to Asia/Jerusalem (make sure to restart MySQL to apply the change).
Set the timezone at the beginning of each script using 'SET time_zone = timezone;'.
Make a TZ environment variable with the correct timezone so that MySQL sets that timezone as the default on startup (again, make sure to restart MySQL).
I want to be independent from the timezone configured on a server so in a script I set the time zone like this:
mysql_query("SET time_zone = '".date_default_timezone_get()."';");
The server is currently configured to Europe/Moscow which currently is UTC+4
Then in a PHP site I select something from the database like this:
date_default_timezone_set('Europe/Berlin');
$sth = $dbh->prepare("SET time_zone = '".date_default_timezone_get()."';");
$sth->execute();
$sth = $dbh->prepare("SELECT * from logs WHERE time like '2011-06-1%'");
$sth->execute();
I am using Timestamp field type and not Datetime.
Not what I get displayed is a timestamp that is 2 hours too far in the future.
The mysql doc says:
Values for TIMESTAMP columns are
converted from the current time zone
to UTC for storage, and from UTC to
the current time zone for retrieval.
So this brings me to the 3 possible cases:
Storage conversion works, select does not: No - because then The timestamp would be 2 (or 1 in winter) hours too far in the past
Storage conversion does not work, but select does: No - because then i would see UTC which is -1 hours which is not the case
Storage conversion doesn't work, select conversion doesn't work: Looks just like it!
Now the timestamp I write into the database is constructed and written PHP side:
$hourprec = "Y-m-d H:00:00";
$hour = date($hourprec); // mysql compatible
...
REPLACE INTO logs (time,...) VALUES('".$hour."','"....
I can imagine that this makes problems with mysqls time conversion because it comes as a string and I should do FROM_UNIXTIME or something.
But shouldn't it work at least with the select then?
Am I missing something? How do I have to do it if I want to store and read timestamps correctly in UTC in a mysql database but read/write them in scripts that have different time zones?
The answer was quite trivial.
The approach above is just fine, MySql just didn't know ANY timezones.
You can test this with the command SET time_zone = 'UTC';
If you have the same problem as the questioner you should recieve the following error:
#1298 - Unknown or incorrect time zone: 'UTC'
This can be easily fixed with the following command:
mysql_tzinfo_to_sql /usr/share/zoneinfo/|mysql -u root mysql -p