Due to the fact that several issues have accumulated and decisions on working with time, I decided to make a small review.

Work with different types of data in databases
MYSQL
In mysql there are several standard data types for representing time, we will look at
TIMESTAMP
and
DATETIME
.
The
documentation states that a conversion policy is applied to some data types, but not to some.
In practice, everything is much more interesting. Consider a few examples:
Create a table:
create table xxxDate(ts TIMESTAMP NOT NULL, dt DATETIME NOT NULL);
Let's set the current zone for Moscow (in Moscow, recently there is no daylight saving time, and UTC + 4):
set time_zone='Europe/Moscow';
Create two entries with summer and winter time, respectively:
insert into xxxDate values('2012-06-10 15:08:05', '2012-06-10 15:08:05'); insert into xxxDate values('2012-12-10 15:08:05', '2012-12-10 15:08:05');
Let's see what sample of these dates from the database shows:
select * from xxxDate; +
We see that in both columns the values are the same, this is because the
UNIX_TIMESTAMP
function considers the value of the argument in the current zone and converts it to UTC. It is obvious that the same values will be converted equally into the same value
Mon, 10 Dec 2012 11:08:05 UTC
.
Now we are moving to London!
set time_zone='Europe/London'; select * from xxxDate; +
There is nothing surprising, according to the
documentation ,
TIMESTAMP
, before being inserted into the database is converted to UTC, so after we changed the current zone, the database gives us the value of this time in the current zone.
DATETIME
values have not changed.
Now let's take a closer look at the work of the algorithm for Moscow. Values for ts when converted were converted to UTC, and when sampled, they were converted to values according to the current zone (as for London) 15 hours, and when UNIX_TIMESTAMP was selected, they were simply displayed as they were stored in the database.
Now the expected result for London:
select UNIX_TIMESTAMP(ts), UNIX_TIMESTAMP(dt) from xxxDate; +
The ts values have not changed, and the dt values are considered as values in the current zone, therefore daylight saving time (first record) is
1339337285 = Sun, 10 Jun 2012 14:08:05 GMT
, and winter time (lower record) is
1355152085 = Mon, 10 Dec 2012 15:08:05 GMT
.
Just in case, check the behavior for UTC.
set time_zone='UTC'; select * from xxxDate; +
Everything is as previously described, ts values have not changed, dt values are considered in the current zone, therefore they do not change
1339340885 = Sun, 10 Jun 2012 15:08:05 GMT; 1355152085 = Mon, 10 Dec 2012 15:08:05 GMT
(
1339340885 = Sun, 10 Jun 2012 15:08:05 GMT; 1355152085 = Mon, 10 Dec 2012 15:08:05 GMT
) .
Conclusion:
- When working with
DATETIME
and moving the server (incorrect setting of the time zone during insertion or import of data) with loss of information about the time of changing the server / connection time zone, you will lose information about the actual time of events. For example, we created records at 15 o'clock Moscow time (we imported data into the database from a backup), then we set up our server on UTC and did not notice that before this the time zone was Moscow. As a result, instead of 11 o'clock UTC, both of our orders are now made 4 hours later - at 15 o'clock, and could be on another day. Therefore, in my opinion it is necessary to work with TIMESTAMP
. - Also, to avoid unnecessary problems when debugging on the server, it is better to have a UTC zone, and work with data in UTC, and on the client side, display in the zone in which the client wants.
- It is also a good example at the end of the feedbee article .
- To avoid problems with leap second, it is also worth working with unix epochs in UTC (see the section on Leap second).
SQLite3
Consider the situation with sqlite3. According to the
documentation in sqlite there is no data type to save time, but there are
functions for working with time, saved as text, a floating point number and as an integer. In general, these views are fundamentally no different. We can assume that the current time zone is not used in sqlite if you do not use the localtime and utc modifiers. For example, regardless of the system settings,
CURRENT_TIMESTAMP
has a value in UTC.
$ date Mon Dec 10 22:05:50 MSK 2012 $ sqlite3 sqlite> select CURRENT_TIMESTAMP; 2012-12-10 18:06:05 sqlite> select datetime(CURRENT_TIMESTAMP, 'localtime'); 2012-12-10 22:06:35
Therefore, convert your data in utc in your program and use unix epochs in order not to look for errors when parsing lines.
Debugging features:
select strftime('%s', CURRENT_TIMESTAMP); 1355162582 select datetime(1355152085, 'unixepoch'); 2012-12-10 15:08:05
How user sees time
If you work with the datetime type and do not convert it, users will get confused in time. For example, if two users live in different time zones, then, seeing the same time line without specifying a zone, they will think about different times. In order not to repeat, here is a
link with an example.
C-functions to work with time
First, it is very useful to get acquainted with the description of work with time in
glibc . We will consider several examples concerning the results of the work of several functions in different time zones. It turns out that even the documentation states that the struct tm (hereinafter the broken-down time) is usually used
only for displaying to users (because of clarity), i.e. in your program it is better to use other more suitable data types.
Consider a few examples:
Function: struct tm * localtime_r(const time_t *time, struct tm *resultp)
Converts simple time to broken-down time, expressed relative to the user zone.
time_t t = 1339326485;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
output hour | eleven | 15 | 12 |
isdst output | 0 | 0 | one |
zone output | UTC | MSK | Bst |
time_t t = 1355137685;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
output hour | eleven | 15 | eleven |
isdst output | 0 | 0 | 0 |
zone output | UTC | MSK | GMT |
Function: struct tm * gmtime_r(const time_t *time, struct tm *resultp)
Returns the value for a UTC zone regardless of the user zone.
time_t t = 1339326485;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
output hour | eleven | eleven | eleven |
isdst output | 0 | 0 | 0 |
zone output | GMT | GMT | GMT |
time_t t = 1355137685;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
output hour | eleven | eleven | eleven |
isdst output | 0 | 0 | 0 |
zone output | GMT | GMT | GMT |
Function: time_t mktime(struct tm *brokentime)
(synonymous with
timelocal , but rarely)
Converts broken-down time to simple time.
Note:
sets the current zone for the argument.The tm_zone field is not considered an argument, it is considered that the time is set in the current time zone and the time is returned in UTC.
struct tm bdt; bdt.tm_sec = 5; // 05 sec bdt.tm_min = 8; // 08 min bdt.tm_hour = 11; // 11 h bdt.tm_mday = 10; // 10 bdt.tm_mon = 5; // 6th mon - Jun bdt.tm_year = 112;// 2012 - 1900 bdt.tm_wday = 0; // ignored bdt.tm_yday = 0; // ignored bdt.tm_isdst= 0; bdt.tm_gmtoff= 0; bdt.tm_zone = "UTC"; time_t t = mktime(&bdt); cout << t << endl; cout << bdt.tm_hour << endl; cout << bdt.tm_isdst << endl; cout << bdt.tm_gmtoff << endl; cout << bdt.tm_zone << endl;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
t output | 1339326485 (Sun, 10 Jun 2012 11:08:05 GMT) | 1339312085 (Sun, 10 Jun 2012 07:08:05 GMT) | 1339326485 (Sun, 10 Jun 2012 11:08:05 GMT) |
output hour | eleven | eleven | 12 |
isdst output | 0 | 0 | one |
gmtoff output | 0 | 14,400 (4 * 60 * 60) | 3600 (1 * 60 * 60) |
zone output | UTC | MSK | Bst |
Note that the tm_hour and tm_isdst fields have changed for London, this is part of the normalization of the structure fields of the broken-down time.
now for
bdt.tm_mon = 11;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
t output | 1355137685 (Mon, 10 Dec 2012 11:08:05 GMT) | 1355123285 (Mon, 10 Dec 2012 07:08:05 GMT) | 1355137685 (Mon, 10 Dec 2012 11:08:05 GMT) |
output hour | eleven | eleven | eleven |
isdst output | 0 | 0 | 0 |
gmtoff output | 0 | 14,400 (4 * 60 * 60) | 0 |
zone output | UTC | MSK | GMT |
Function: time_t timegm(struct tm *brokentime)
Works in UTC.
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
t output | 1339326485 (Sun, 10 Jun 2012 11:08:05 GMT) | 1339326485 (Sun, 10 Jun 2012 11:08:05 GMT) | 1339326485 (Sun, 10 Jun 2012 11:08:05 GMT) |
output hour | eleven | eleven | eleven |
isdst output | 0 | 0 | 0 |
gmtoff output | 0 | 0 | 0 |
zone output | GMT | GMT | GMT |
now for
bdt.tm_mon = 11;
zone in the system | UTC | Europe / Moscow | Europe / London |
---|
t output | 1355137685 (Mon, 10 Dec 2012 11:08:05 GMT) | 1355137685 (Mon, 10 Dec 2012 11:08:05 GMT) | 1355137685 (Mon, 10 Dec 2012 11:08:05 GMT) |
output hour | eleven | eleven | eleven |
isdst output | 0 | 0 | 0 |
gmtoff output | 0 | 0 | 0 |
zone output | GMT | GMT | GMT |
Conclusion:
If you want to display the time to the user in your program on the user's computer, then use the
timelocal/localtime
functions, if you are working on the server, then use the
timegm/gmtime
. Also, install a UTC zone on the server, in case any of your colleagues or a third-party library uses * local * functions. Even on the user's computer store and work over time in UTC, so if he changes his zone, all dates will remain correct.
')
Note
Setting time zones in linux
Consider only deb-based distributions and a couple of iron methods for setting up a time zone.
LEAP SECOND
In general, this is a separate topic, so in this article there are no examples relating to leap second. You can check for yourself how certain functions work, as well as how different databases behave,
here are examples of mysql .
- UTC includes leap second
- Very important note :
POSIX requires that time_t counted from 00:00:00 on January 1, 1970, UTC does not include leap seconds, but sometimes includes in practice. Also, depending on the leap second support, the difftime function works differently. Be careful. - Julian day also does not include leap second.
- In mysql you will not see leap second, i.e. instead of 60 or 61 seconds (s) there will always be 59 ( link ). But at the same time, everything is supported and works correctly if you are dealing with unix epochs in UTC.
- General recommendation for sqlite: store the date as an integer (integer), which already includes leap seconds (as in mysql). Then you will always know the exact time.
And further
- If the time_zone (mysql) variable is set to SYSTEM, then the system zone is selected as the current zone (which was configured in the system at the time the server was started).
- http://www.onlineconversion.com/unix_time.htm website for converting unix time to regular time.
- GMT - can be considered as an obsolete concept, so the article mainly uses UTC.