How to store a datetime in MySQL with timezone info - mysql

I have thousands of photos that were taken in Tanzania and I want to store the date and time each photo was taken in a MySQL database. The server, however, is located in the U.S. and I run into problems when I try to store a Tanzanian date-time that falls within the "invalid" hour during spring Daylight Savings time (in the U.S.). Tanzania doesn't do DST, so the time is an actually valid time.
Additional complications are that there are collaborators from many different timezones who will need to access the date-time values stored in the database. I want them to always come out as Tanzanian time and not in the local times that various collaborator are in.
I'm reluctant to set session times because I know that there will be problems when someone sometime forgets to set a session time and gets the times out all wrong. And I do not have authority to change anything about the server.
I've read:
Daylight saving time and time zone best practices and
MySQL datetime fields and daylight savings time -- how do I reference the "extra" hour? and
Storing datetime as UTC in PHP/MySQL
But none of them seems to address my particular problem. I'm not an SQL expert; is there a way to specify timezone when setting DATETIMEs? I haven't seen one. Otherwise, any suggestions on how to approach this issue is greatly appreciated.
Edit:
Here's an example of the problem I'm running into. I send the command:
INSERT INTO Images (CaptureEvent, SequenceNum, PathFilename, TimestampJPG)
VALUES (122,1,"S2/B04/B04_R1/IMAG0148.JPG","2011-03-13 02:49:10")
And I get the error:
Error 1292: Incorrect datetime value: '2011-03-13 02:49:10' for column 'TimestampJPG'
This date and time exists in Tanzania, but not in the U.S., where the database is.

You said:
I want them to always come out as Tanzanian time and not in the local times that various collaborator are in.
If this is the case, then you should not use UTC. All you need to do is to use a DATETIME type in MySQL instead of a TIMESTAMP type.
From the MySQL documentation:
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.)
If you are already using a DATETIME type, then you must be not setting it by the local time to begin with. You'll need to focus less on the database, and more on your application code - which you didn't show here. The problem, and the solution, will vary drastically depending on language, so be sure to tag the question with the appropriate language of your application code.

None of the answers here quite hit the nail on the head.
How to store a datetime in MySQL with timezone info
Use two columns: DATETIME, and a VARCHAR to hold the time zone information, which may be in several forms:
A timezone or location such as America/New_York is the highest data fidelity.
A timezone abbreviation such as PST is the next highest fidelity.
A time offset such as -2:00 is the smallest amount of data in this regard.
Some key points:
Avoid TIMESTAMP because it's limited to the year 2038, and MySQL relates it to the server timezone, which is probably undesired.
A time offset should not be stored naively in an INT field, because there are half-hour and quarter-hour offsets.
If it's important for your use case to have MySQL compare or sort these dates chronologically, DATETIME has a problem:
'2009-11-10 11:00:00 -0500' is before '2009-11-10 10:00:00 -0700' in terms of "instant in time", but they would sort the other way when inserted into a DATETIME.
You can do your own conversion to UTC. In the above example, you would then have '2009-11-10 16:00:00' and '2009-11-10 17:00:00' respectively, which would sort correctly. When retrieving the data, you would then use the timezone info to revert it to its original form.
One recommendation which I quite like is to have three columns:
local_time DATETIME
utc_time DATETIME
time_zone VARCHAR(X) where X is appropriate for what kind of data you're storing there. (I would choose 64 characters for timezone/location.)
An advantage to the 3-column approach is that it's explicit: with a single DATETIME column, you can't tell at a glance if it's been converted to UTC before insertion.
Regarding the descent of accuracy through timezone/abbreviation/offset:
If you have the user's timezone/location such as America/Juneau, you can know accurately what the wall clock time is for them at any point in the past or future (barring changes to the way Daylight Savings is handled in that location). The start/end points of DST, and whether it's used at all, are dependent upon location, so this is the only reliable way.
If you have a timezone abbreviation such as MST, (Mountain Standard Time) or a plain offset such as -0700, you will be unable to predict a wall clock time in the past or future. For example, in the United States, Colorado and Arizona both use MST, but Arizona doesn't observe DST. So if the user uploads his cat photo at 14:00 -0700 during the winter months, was he in Arizona or California? If you added six months exactly to that date, would it be 14:00 or 13:00 for the user?
These things are important to consider when your application has time, dates, or scheduling as core function.
References:
MySQL Date/Time Reference
The Proper Way to Handle Multiple Time Zones in MySQL (Disclosure: I did not read this whole article.)

MySQL stores DATETIME without timezone information. Let's say you store '2019-01-01 20:00:00' into a DATETIME field, when you retrieve that value you're expected to know what timezone it belongs to.
So in your case, when you store a value into a DATETIME field, make sure it is Tanzania time. Then when you get it out, it will be Tanzania time. Yay!
Now, the hairy question is: When I do an INSERT/UPDATE, how do I make sure the value is Tanzania time? Two cases:
You do INSERT INTO table (dateCreated) VALUES (CURRENT_TIMESTAMP or NOW()).
You do INSERT INTO table (dateCreated) VALUES (?), and specify the current time from your application code.
CASE #1
MySQL will take the current time, let's say that is '2019-01-01 20:00:00' Tanzania time. Then MySQL will convert it to UTC, which comes out to '2019-01-01 17:00:00', and store that value into the field.
So how do you get the Tanzania time, which is '20:00:00', to store into the field? It's not possible. Your code will need to expect UTC time when reading from this field.
CASE #2
It depends on what type of value you pass as ?. If you pass the string '2019-01-01 20:00:00', then good for you, that's exactly what will be stored to the DB. If you pass a Date object of some kind, then it'll depend on how the db driver interprets that Date object, and ultimate what 'YYYY-MM-DD HH:mm:ss' string it provides to MySQL for storage. The db driver's documentation should tell you.

All the symptoms you describe suggest that you never tell MySQL what time zone to use so it defaults to system's zone. Think about it: if all it has is '2011-03-13 02:49:10', how can it guess that it's a local Tanzanian date?
As far as I know, MySQL doesn't provide any syntax to specify time zone information in dates. You have to change it a per-connection basis; something like:
SET time_zone = 'EAT';
If this doesn't work (to use named zones you need that the server has been configured to do so and it's often not the case) you can use UTC offsets because Tanzania does not observe daylight saving time at the time of writing but of course it isn't the best option:
SET time_zone = '+03:00';

Beginning with MySQL 8.0.19, you can specify a time zone offset when inserting TIMESTAMP and DATETIME values into a table.
The offset is appended to the time part of a datetime literal, with no intervening spaces, and uses the same format used for setting the time_zone system variable, with the following exceptions:
For hour values less than 10, a leading zero is required.
The value '-00:00' is rejected.
Time zone names such as 'EET' and 'Asia/Shanghai' cannot be used; 'SYSTEM' also cannot be used in this context.
The value inserted must not have a zero for the month part, the day part, or both parts. This is enforced beginning with MySQL 8.0.22, regardless of the server SQL mode setting.
EXAMPLE:
This example illustrates inserting datetime values with time zone offsets into TIMESTAMP and DATETIME columns using different time_zone settings, and then retrieving them:
mysql> CREATE TABLE ts (
-> id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> col TIMESTAMP NOT NULL
-> ) AUTO_INCREMENT = 1;
mysql> CREATE TABLE dt (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> col DATETIME NOT NULL
-> ) AUTO_INCREMENT = 1;
mysql> SET ##time_zone = 'SYSTEM';
mysql> INSERT INTO ts (col) VALUES ('2020-01-01 10:10:10'),
-> ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');
mysql> SET ##time_zone = '+00:00';
mysql> INSERT INTO ts (col) VALUES ('2020-01-01 10:10:10'),
-> ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');
mysql> SET ##time_zone = 'SYSTEM';
mysql> INSERT INTO dt (col) VALUES ('2020-01-01 10:10:10'),
-> ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');
mysql> SET ##time_zone = '+00:00';
mysql> INSERT INTO dt (col) VALUES ('2020-01-01 10:10:10'),
-> ('2020-01-01 10:10:10+05:30'), ('2020-01-01 10:10:10-08:00');
mysql> SET ##time_zone = 'SYSTEM';
mysql> SELECT ##system_time_zone;
+--------------------+
| ##system_time_zone |
+--------------------+
| EST |
+--------------------+
mysql> SELECT col, UNIX_TIMESTAMP(col) FROM dt ORDER BY id;
+---------------------+---------------------+
| col | UNIX_TIMESTAMP(col) |
+---------------------+---------------------+
| 2020-01-01 10:10:10 | 1577891410 |
| 2019-12-31 23:40:10 | 1577853610 |
| 2020-01-01 13:10:10 | 1577902210 |
| 2020-01-01 10:10:10 | 1577891410 |
| 2020-01-01 04:40:10 | 1577871610 |
| 2020-01-01 18:10:10 | 1577920210 |
+---------------------+---------------------+
mysql> SELECT col, UNIX_TIMESTAMP(col) FROM ts ORDER BY id;
+---------------------+---------------------+
| col | UNIX_TIMESTAMP(col) |
+---------------------+---------------------+
| 2020-01-01 10:10:10 | 1577891410 |
| 2019-12-31 23:40:10 | 1577853610 |
| 2020-01-01 13:10:10 | 1577902210 |
| 2020-01-01 05:10:10 | 1577873410 |
| 2019-12-31 23:40:10 | 1577853610 |
| 2020-01-01 13:10:10 | 1577902210 |
+---------------------+---------------------+
Note:
Sadly, the offset is not displayed when selecting a datetime value, even if one was used when inserting it.
The range of supported offset values is -13:59 to +14:00, inclusive.
Datetime literals that include time zone offsets are accepted as parameter values by prepared statements.
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.)

You can't... you will find a lot of answers saying you "it is not necessary, store using UTC", but it is: you need to store datetimes with the timezone and MySQL can't...
I worked last 10 years in Postgres and all this kind of problems doesn't exist (date times and timezones are managed with no friction, you can store and compare datetimes expressed in different time zones transparently, the ISOString format is managed naturally,etc...).
I actually work in MariaDB and I can't understand why in 2022, in a globalized world, MySQL continues not supporting per value timezones.

I once also faced such an issue where i needed to save data which was used by different collaborators and i ended up storing the time in unix timestamp form which represents the number of seconds since january 1970 which is an integer format.
Example todays date and time in tanzania is Friday, September 13, 2019 9:44:01 PM which when store in unix timestamp would be 1568400241
Now when reading the data simply use something like php or any other language and extract the date from the unix timestamp. An example with php will be
echo date('m/d/Y', 1568400241);
This makes it easier even to store data with other collaborators in different locations. They can simply convert the date to unix timestamp with their own gmt offset and store it in a integer format and when outputting this simply convert with a

Related

Insert a 4-digit as Time in MySql

My data contains hour & minute of time and I want to load this data into MySql database.
Sample Data: 305 -- This means Hours=03 & Minutes=05
But when I upload the data into MySql column of type TIME, it is stored as 00:03:05 (HH:MM:SS).
Whereas, I want it to be stored as 03:05:00.
I can make changes to my data using Python & then load into MySql. However, I was wondering if there is a way to do it using MySql itself.
It is not up to MySQL to guess what you mean by "305". It's up to you to convert/format yor data to "03:05:00", then MySQL will undertand it properly.
insert into try (elasp) values ("03:05:00")
See MySQL documentation: https://dev.mysql.com/doc/refman/8.0/en/time.html
While converting to TIME datatype the numeric value is assumed to have HHMMSS format.
MySQL 8.0 Reference Manual / ... / The TIME Type:
Be careful about assigning abbreviated values to a TIME column. MySQL interprets abbreviated TIME values with colons as time of the day. That is, '11:12' means '11:12:00', not '00:11:12'. MySQL interprets abbreviated values without colons using the assumption that the two rightmost digits represent seconds (that is, as elapsed time rather than as time of day). For example, you might think of '1112' and 1112 as meaning '11:12:00' (12 minutes after 11 o'clock), but MySQL interprets them as '00:11:12' (11 minutes, 12 seconds). Similarly, '12' and 12 are interpreted as '00:00:12'.
If you want to use HHMM format during convertion then simply multiply the value by 100.
CREATE TABLE test (int_source INT, time_destination TIME);
INSERT INTO test (int_source) VALUES ('305');
SELECT * FROM test;
UPDATE test SET time_destination = int_source * 100;
SELECT * FROM test;
✓
✓
int_source | time_destination
---------: | :---------------
305 | null
✓
int_source | time_destination
---------: | :---------------
305 | 03:05:00
db<>fiddle here
Or, if the value to be converted to TIME datatype has some string type you may concatenate '00' to it:
SET time_destination = CONCAT(int_source, '00')

Convert Bigint data to human readable date-time in Sql

I have created a MySql table and feed data therein with below code
CREATE TABLE IF NOT EXISTS DB (
INN VARCHAR(200) NOT NULL,
Time BIGINT not NULL
);
Now I want to get a table which will report the Maximum and Minimum values of Time when grouped by INN. Below is my code -
SELECT INN, from_unixtime(MIN(Time)), from_unixtime(MAX(Time)) FROM DB GROUP BY INN
I want to get the Time values reported as normal date-time instead of Epoch time. But with above code, I am getting <NA> values.
A typical Time value is like 1546380001264082944
Can someone please help me to get the correct code to achieve the same.
The problem here is to do with the precision of the unix timestamp you are using.
Consider this:
SELECT FROM_UNIXTIME(1546380001), FROM_UNIXTIME(1546380001264082944)
The output is:
2019-01-01T22:00:01Z (null)
The timestampt value you have, 1546380001264082944, contains a level of precision beyond that accepted by MySQL.
The definition of FROM_UNIXTIME is:
FROM_UNIXTIME(unix_timestamp[,format])
The doc states:
unix_timestamp is an internal timestamp value representing seconds
since '1970-01-01 00:00:00' UTC
The precision of your timestamp is significantly greater than seconds since the Unix Epoch.
The docs are available here.
The value 1546380001264082944 is too big to be epoch seconds or even milliseconds. This is easily verified by putting this value on https://currentmillis.com/.
You have stored precision upto a nanosecond. So, divide the column value by 1e9 before passing them to from_unixtime.
SELECT INN, from_unixtime(MIN(Time) / 1000000000), from_unixtime(MAX(Time) / 1000000000)
FROM DB
GROUP BY INN

Saving 30-Feb on Mysql (Date Formating)

I have a problem saving 'contable dates' because every month on this way has 30 days each. I need to save a element (2014-02-30) using a type date-like (not a varchar/text/blob/etc) to save this because in this project we need that. Is it possible?
Saving such a DATE "value" in a DATE or DATETIME column is possible using the sql_mode ALLOW_INVALID_DATES and no strict mode:
ALLOW_INVALID_DATES
Do not perform full checking of dates. Check only that the month is in
the range from 1 to 12 and the day is in the range from 1 to 31. This
is very convenient for Web applications where you obtain year, month,
and day in three different fields and you want to store exactly what
the user inserted (without date validation). This mode applies to DATE
and DATETIME columns. It does not apply TIMESTAMP columns, which
always require a valid date.
So checking the date for an allowed contable date could be done with triggers, since there's no other check too. I assume that for this application the 31th of each month would be an invalid date.
Example:
CREATE TABLE example (
contable_date DATE NOT NULL
) ENGINE=INNODB;
-- set the sql_mode (here for the session)
SET ##SESSION.sql_mode = 'ALLOW_INVALID_DATES';
INSERT INTO example (contable_date) VALUES ("2014-02-30");
SELECT
DAY(contable_date) as cday,
MONTH(contable_date) as cmonth,
TIMESTAMPDIFF(DAY, contable_date, '2014-03-30') as cdiff
FROM
example;
Result:
cday cmonth cdiff
-------------------
30 2 28
Demo
Using MySQL Workbench I get with
SELECT contable_date FROM example
following result:
contable_date
-------------
2014-02-30
but this doesn't work at sqlfiddle.com.
I wouldn't recommend this though, especially because one's not able to use strict SQL mode. One should consider the effect on the date and time functions too.

Datetime vs Date and Time Mysql

I generally use datetime field to store created_time updated time of data within an application.
But now i have come across a database table where they have kept date and time separate fields in table.
So what are the schema in which two of these should be used and why?
What are pros and cons attached with using of two?
There is a huge difference in performance when using DATE field above DATETIME field. I have a table with more then 4.000.000 records and for testing purposes I added 2 fields with both their own index. One using DATETIME and the other field using DATE.
I disabled MySQL query cache to be able to test properly and looped over the same query for 1000x:
SELECT * FROM `logs` WHERE `dt` BETWEEN '2015-04-01' AND '2015-05-01' LIMIT 10000,10;
DATETIME INDEX:
197.564 seconds.
SELECT * FROM `logs` WHERE `d` BETWEEN '2015-04-01' AND '2015-05-01' LIMIT 10000,10;
DATE INDEX:
107.577 seconds.
Using a date indexed field has a performance improvement of: 45.55%!!
So I would say if you are expecting a lot of data in your table please consider in separating the date from the time with their own index.
I tend to think there are basically no advantages to storing the date and time in separate fields. MySQL offers very convenient functions for extracting the date and time parts of a datetime value.
Okay. There can be some efficiency reasons. In MySQL, you can put separate indexes on the fields. So, if you want to search for particular times, for instance, then a query that counts by hours of the day (for instance) can use an index on the time field. An index on a datetime field would not be used in this case. A separate date field might make it easier to write a query that will use the date index, but, strictly speaking, a datetime should also work.
The one time where I've seen dates and times stored separately is in a trading system. In this case, the trade has a valuation date. The valuation time is something like "NY Open" or "London Close" -- this is not a real time value. It is a description of the time of day used for valuation.
The tricky part is when you have to do date arithmetic on a time value and you do not want a date portion coming into the mix. Ex:
myapptdate = 2014-01-02 09:00:00
Select such and such where myapptdate between 2014-01-02 07:00:00 and 2014-01-02 13:00:00
1900-01-02 07:00:00
2014-01-02 07:00:00
One difference I found is using BETWEEN for dates with non-zero time.
Imagine a search with "between dates" filter. Standard user's expectation is it will return records from the end day as well, so using DATETIME you have to always add an extra day for the BETWEEN to work as expected, while using DATE you only pass what user entered, with no extra logic needed.
So query
SELECT * FROM mytable WHERE mydate BETWEEN '2020-06-24' AND '2020-06-25'
will return a record for 2020-06-25 16:30:00, while query:
SELECT * FROM mytable WHERE mydatetime BETWEEN '2020-06-24' AND '2020-06-25'
won't - you'd have to add an extra day:
SELECT * FROM mytable WHERE mydatetime BETWEEN '2020-06-24' AND '2020-06-26'
But as victor diaz mentioned, doing datetime calculations with date+time would be a super inefficient nightmare and far worse, than just adding a day to the second datetime. Therefore I'd only use DATE if the time is irrelevant, or as a "cache" for speeding queries up for date lookups (see Elwin's answer).

Mysql converting TIMESTAMP to INTEGER - timezones

I need to convert some TIMESTAMP fields to INT in our MySQL (InnoDB) DB. I realize that converting a TIMESTAMP to INT is unusual, but we still need to do it :)
It seems straight-forward enough to do, but there are some timezone and daylight saving errors.
I have a script that generates my SQL code per column. For example, it generates:
ALTER TABLE alarmLog ADD COLUMN started_tmp INT UNSIGNED;
UPDATE alarmLog SET started_tmp = UNIX_TIMESTAMP(started);
ALTER TABLE alarmLog DROP started;
alter TABLE alarmLog CHANGE started_tmp started INT UNSIGNED NULL DEFAULT 0;
If I compare the before and after data using select FROM_UNIXTIME(1291788036);, the result looks good.
The idea is then to change all the client-side software to convert to UTC and use that INT when storing it. When retrieving, that INT is converted to the current time zone.
But then the docs warn me about this scenario (daylight savings in CET):
mysql> SELECT UNIX_TIMESTAMP('2005-03-27 02:00:00');
+---------------------------------------+
| UNIX_TIMESTAMP('2005-03-27 02:00:00') |
+---------------------------------------+
| 1111885200 |
+---------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT UNIX_TIMESTAMP('2005-03-27 03:00:00');
+---------------------------------------+
| UNIX_TIMESTAMP('2005-03-27 03:00:00') |
+---------------------------------------+
| 1111885200 |
+---------------------------------------+
1 row in set (0.00 sec)
How do API and OS's normally deal with daylight savings? I know my PC has its clock in UTC and in summer time, the OS adds two hours to it, and in winter time one. I assume it uses the UTC time to determine whether it's DST or not.
So, how do I deal with this? Is the only solution to add a field to the database to specify DST offset?
You don't need to store the time in INT's. MySQL's TIMESTAMP type does that anyway (it uses standard Unix timestamps to store the time) and they are always in UTC timezone.
You only need to set the session timezone and all TIMESTAMP columns will be converted from/to your zone when you update/select them.
You can set the zone at connect/initialization time once:
SET time_zone = '+10:00';
And then you can select/update the time in your zone directly
SELECT timestamp_column FROM table ...
I'm not very familiar with datetime libs but I guess they use the timezone you provided and the time in question to determine timezone and daylight savings offsets.
In the example you provided I think one of the values is actually invalid, because the clock is supposed to jump from 01:59:59 to 03:00:00 and 02:00:00 never actually happened. The UNIX_TIMESTAMP function probably returns the nearest second in that case.