We run a Laravel app with MySQL DB. The DB consists of many entries relating to different pojects. The idea is that each project has its own timezone and outputs relevant entries with their dates formatted accordingly. Thus, in order to be able to change timezones and for compatibility sake, all entries are stored in UTC timezone (app default timezone is set to UTC as well).
The problem is that, for instance, we need to get today's entries, but in project's timezone, not UTC. We use Carbon, but it only formats the date for output and still compares them in UTC timezone.
For example, there is an entry made on December 7, 22.32 in UTC. But according to Europe/Moscow, it was made on December 8, 01.32, and it should be considered as today's entry for a project with Europe/Moscow timezone. Still, even with Moscow timezone given, Carbon considers it as yesterday's entry (according to UTC). We use whereDate in the query:
whereDate( 'created_at', Carbon::now($project->timezone)->startOfDay() )
The problem is that Carbon makes no difference between Carbon::now('UTC') and Carbon::now('Europe/Moscow'). It gives zero if you compare them:
$tz1 = Carbon::now('UTC');
$tz2 = Carbon::now('Europe/Moscow');
echo tz1->diffInHours(tz2) //0
What do I miss? How should I do it properly?
$tz1 and $tz2 have timezone info with them, and both contain information of now() time. That's why there is no difference. Change the timezone data to make them sane timezone after retrieving using shiftTimezone().
so
$tz1 = Carbon::now('UTC');
$tz2 = Carbon::now('Europe/Moscow')->shiftTimezone('UTC');
echo tz1->diffInHours(tz2) //should be appropriate
Related
Right now my Laravel application save() any items into the database base on this timestamp. America/New_York because I configured it as 'timezone' => 'America/New_York', in config/app.php.
Goal
I wish to overwrite timestamp based on other tz instead, ex. America/Chicago
How do I do that?
You don't need too
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.)
see https://dev.mysql.com/doc/refman/5.7/en/datetime.html
So when you change the server to another timezone all timestamps will get the new time zone
setting the time zone goe like
SET GLOBAL time_zone = 'America/Chicago';
You don't have to do crazy stuff... What you have to do is:
Store the time in a known timezone and never change that timezone again, it would be awesome if you use UTC as a default timezone.
When you want to "convert" a timezone, you just $model->created_at (or anything that is a Carbon object) and do $model->created_at->setTimezone('America/Chicago'); (for example).
The main idea is that when you already have a Carbon instance with a timestamp, you just change the timezone with setTimezone to the new one you want and it will return a new Carbon instance with that timezone...
Have a look at this SO topic.
Also, remember that timestamp is just an integer representing how many seconds have passed since 1970-01-01 00:00:01 (UTC), so if you say "give me a timestamp of a specific date and time on specific timezone" the timestamp will be always the same even if you change the timezone each time... that is the main idea of the timestamp...
If I say "What timestamp is for 1970-01-01 00:00:10?", if you are on UTC you would get 10, because 10 seconds passed since that specific datetime, if you are on UTC+1, it would still be 10 seconds, but you will display 1970-01-01 00:01:10, because you are 1 hour ahead of UTC, if you are on UTC-1 it will be 1969-12-31 23:00:10, because you are 1 hour behind UTC, but you know how to do the conversion, that is why the value will be always the same disregarding the timezone, and that is also why it is 1970-01-01 00:00:00 and not any other specific datetime, because if you do not know which is the specific datetime you would not know how to do the conversion.
It is very important that you understand what you are working with, so to help you understand better, have a look at this blog explaining the same thing but in more detail.
The timestamp generated in PHP is in UTC time regardless of your local timezone setting.
You can adjust the timezone of the timestamp before it is displayed in the UI using setTimezone.
Reference:
https://www.php.net/manual/en/function.time.php
https://www.php.net/manual/en/datetime.gettimestamp.php
I'm working on something where the user can select their own timezone and the software will be able to be used by others on their sites as well but I want to make sure that the timezone within the database is always set to UTC.
Now I know how you set the default timezone for PHP, such as:
date_default_timezone_set('Australia/Sydney');
...but I'm not sure how to make sure MySQL is using UTC? ...and even once you have made sure it is using UTC I guess you would have to convert your PHP dates/times into UTC before passing it to the database?
I guess I am wondering about many different date formats such as TIMESTAMP, DATETIME & even UNIX EPOCH integer timestamps which would simply be stored as a int datatype for example.
Then there is the whole retrieving dates/times from the DB and converting it to the respective timezone and lastly how does DST come into all of this?
I know there is a lot of similar questions out there, but I guess none really answered all my questions.
MySQL's data type timestamp stores the dates in UTC. For this to work properly, MySQL uses server's time zone and does the date conversion. It converts the date from servers's current time zone to UTC for storage. This implies that the database server should never change its time zone for this feature to work properly.
When you send the data to such a database, you send the UTC time as well. The easiest way to do this is to format a result of time() according to what MySQL wants (m-d-Y H:i:s).
In PHP, when you format the date for insertion to MySQL, it's the best to use DateTime class. It lets you offset the date with the time zone information, meaning that you don't have to use date_default_timezone_set function - that can lead to mistakes.
An example of DateTime in action:
$date = '1.12.2015 13:37:37'; // Format is day.month.year hour:minute:second
// We create DateTime from custom date format, for the person who resides in Australia/Sydney time zone
$dt = DateTime::createFromFormat('d.m.Y H:i:s', $date, new DateTimeZone('Australia/Sydney');
// Now we change the date's time zone into UTC, and we can insert it into MySQL
$dt->setTimeZone(new DateTimeZone('UTC'));
// This is the formatted date-string that can be safely inserted into MySQL
$date_string_for_mysql = $dt->format('m-d-Y H:i:s');
Alternatively, you can use int type in MySQL for timestamp storage and insert result of time() but this has a huge disadvantage of not being able to use date-related functions.
for current session of mysql you can try something like
SET time_zone = timezonename;
for more details you can also look into this answer https://dba.stackexchange.com/questions/20217/mysql-set-utc-time-as-default-timestamp
In my "tool box" i'm using this function:
function dataAttuale() {
$now = new DateTime();
$dataAttuale = $now->format(DateTime::ISO8601);
$offset = $now->getOffset();
date_default_timezone_set('UTC');
$nowUTC = new DateTime();
$dataUTC = $nowUTC->format(DateTime::ISO8601);
$orario = array();
$orario['dataAttuale'] = $dataAttuale;
$orario['dataUTC'] = $dataUTC;
$orario['offset'] = $offset;
return $orario;
}
I get this array
Array
(
[dataAttuale] => 2013-10-18T11:03:52+0200
[dataUTC] => 2013-10-18T09:03:52+0000
[offset] => 7200
)
So i could save in a datetime MySql field a datetime referred to UTC.
Now, i've some trouble about this.
1) I would save also offset (in seconds). What's best Mysql field? I think max seconds can be +14hour * 60 * 60 = 50400 and -12hours*60*60 = -43200
2) Do you think is notable save also offset? I.e., for example, several API services return a date in UTC + offset...
Thank you very much!
UPDATE:
Thank you to both people. Now i'm saving in MySQL datetime in UTC format and varchar timezone. With a couple of code I'm getting what I want:
$orario = new DateTime($value['creazione'], new DateTimeZone($value['timezone']));
$orario = $orario->format(DateTime::ISO8601);
The output is (for Europe/Rome)
2013-10-19T09:27:54+0200
And for America/Montreal
2013-10-19T09:29:16-0400
And for Australia/Melbourne
2013-10-19T09:30:31+1100
(difference of minutes//seconds it the time to change in my PHP scripts the default Timezone).
Now I think that:
1) I can laugh about Y2038 bug, abandoning (sigh :( ) timestamp :(
2) I can safely travel around the world and use my own Calendar (naaaa... i'll use forever Google Calendar, of course)
It doesn't make a lot of sense to save the offset. There are two possible values you can be interested in with a timestamp:
the general global timestamp, e.g. "the point in time in this world at which it was 12:52am on Sept. 6 2013 UTC"
the specific local time of some point in time, e.g. "17:34 on Dec. 19th 2012 in Manila, Philippines"
Notice that both of these are actually the same thing, they express a point in time in the notation of wall clock time and date at a specific location or timezone. The only difference is that UTC is a specified standard "location" relative to which other timezone offsets are expressed; but there's no reason Manila in the Philippines couldn't be used for the same purpose.
So when you want to store an absolute timestamp, you either:
decide that all your times are stored in a specific timezone like UTC and simply store that timestamp
decide that you are interested in a specific local time and store the timestamp and its timezone
Either way you need the timestamp and you need to know which timezone it's in. In 1. you decide in advance that all timestamps are in the same defined timezone and don't need to store it, in 2. you explicitly save that timezone information.
An offset is not a good thing to store, because it varies throughout the year. The offset in summer may be +6 hours to UTC, but in winter may be +7. If you need to do date calculations on a localized time later on, an offset is misleading and doesn't help you much. If you know the timezone you're talking about, you can get the offset for any time of the year later on.
MySQL doesn't support a DATETIME + TIMEZONE field (Postgres for example does), so you need to store the timezone (e.g. "Europe/Berlin") in a separate text field. If you don't need to associate a timestamp with a specific location at all, then there's no need for a timezone and you just need to store the normalized timestamp, e.g. normalized to UTC.
MySQL is award of timezones (it does not store the timezone with the date, but it converts it to a normalized format), so most of the time you do not need to have an additional field with the offset.
You just need to make sure that you set the correct time_zone for your connection.
So if you have a date and you want to store it in your database you have different possibilities:
You can use SET time_zone = timezone; for your connection. Way you tell MySQL that the date you send or receive from MySQL should be in the give timezone. MySQL will internally convert it to a normalized format.
If you want to insert dates that have different timezones then set for the time_zone then you could use CONVERT_TZ(dt,from_tz,to_tz). from_tz is the timezone of your date, to_tz the one that is set for your connection.
There are for sure situations where the timezone could matter. If that is true for your case is not exactly clear out of your question.
The date I store in mysql looks fine when viewing the database, but when I am viewing the date it is always behind a day.
The mysql data type is "date".
I am getting the date column from mysql using UNIX_TIMESTAMP.
I am displaying the date using php's date() function.
I am using bluehost and configured php.ini to use Los Angeles time zone.
Example: I create a new article and key in 2013-05-08. It gets stored as a date type in mysql showing 2013-05-18. But when I go to view it later it shows 2013-05-07.
The timestamp is 1367992800, which is Wed, 08 May 2013 06:00:00 GMT in GMT, but in my timezone Tuesday, May 07, 2013 11:00:00 PM.
How do I display the originally intended date of 2013-05-18? I do not want to change my timezone to another since Los Angeles is my correct time zone and for all my other methods where we need to record a timestamp, it is accurate.
If you want your date to be independent on timezone you should store it in a column that does not transform the value. So the date column type you've chosen is quite appropriate.
The problem comes when you try get that date in UNIX TIMESTAMP. UNIX TIMESTAMP is an amount of seconds since 01.01.1970 in GMT. It means that mysql converts your date into timestamp in GMT using it's timezone settings.
PHP date function also depends on php timezone settings and converts timestamp into date in the current timezone.
To get date from MySQL without converting it into current timezone just do not fetch it using UNIX_TIEMSTAMP function. Get it as is or using DATE_FORMAT function.
You should use the DateTime object.
$timezone = new DateTimeZone('Europe/London');
$date = new DateTime();
$date->setTimestamp($timestamp);
$date->setTimezone($timezone);
echo $date->format('Y-m-d H:i:s');
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