|
|
|
@ -730,15 +730,27 @@ long calc_daynr(uint year,uint month,uint day) |
|
|
|
RETURN VALUE |
|
|
|
Time in UTC seconds since Unix Epoch representation. |
|
|
|
*/ |
|
|
|
my_time_t |
|
|
|
my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, |
|
|
|
my_bool *in_dst_time_gap) |
|
|
|
my_time_t |
|
|
|
my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, |
|
|
|
bool *in_dst_time_gap) |
|
|
|
{ |
|
|
|
uint loop; |
|
|
|
time_t tmp; |
|
|
|
time_t tmp= 0; |
|
|
|
int shift= 0; |
|
|
|
MYSQL_TIME tmp_time; |
|
|
|
MYSQL_TIME *t= &tmp_time; |
|
|
|
struct tm *l_time,tm_tmp; |
|
|
|
long diff, current_timezone; |
|
|
|
|
|
|
|
/* |
|
|
|
Use temp variable to avoid trashing input data, which could happen in |
|
|
|
case of shift required for boundary dates processing. |
|
|
|
*/ |
|
|
|
memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME)); |
|
|
|
|
|
|
|
if (!validate_timestamp_range(t)) |
|
|
|
return 0; |
|
|
|
|
|
|
|
/* |
|
|
|
Calculate the gmt time based on current time and timezone |
|
|
|
The -1 on the end is to ensure that if have a date that exists twice |
|
|
|
@ -752,13 +764,89 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, |
|
|
|
Note: this code assumes that our time_t estimation is not too far away |
|
|
|
from real value (we assume that localtime_r(tmp) will return something |
|
|
|
within 24 hrs from t) which is probably true for all current time zones. |
|
|
|
|
|
|
|
Note2: For the dates, which have time_t representation close to |
|
|
|
MAX_INT32 (efficient time_t limit for supported platforms), we should |
|
|
|
do a small trick to avoid overflow. That is, convert the date, which is |
|
|
|
two days earlier, and then add these days to the final value. |
|
|
|
|
|
|
|
The same trick is done for the values close to 0 in time_t |
|
|
|
representation for platfroms with unsigned time_t (QNX). |
|
|
|
|
|
|
|
To be more verbose, here is a sample (extracted from the code below): |
|
|
|
(calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L |
|
|
|
would return -2147480896 because of the long type overflow. In result |
|
|
|
we would get 1901 year in localtime_r(), which is an obvious error. |
|
|
|
|
|
|
|
Alike problem raises with the dates close to Epoch. E.g. |
|
|
|
(calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L |
|
|
|
will give -3600. |
|
|
|
|
|
|
|
On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600) |
|
|
|
wil give us a date around 2106 year. Which is no good. |
|
|
|
|
|
|
|
Theoreticaly, there could be problems with the latter conversion: |
|
|
|
there are at least two timezones, which had time switches near 1 Jan |
|
|
|
of 1970 (because of political reasons). These are America/Hermosillo and |
|
|
|
America/Mazatlan time zones. They changed their offset on |
|
|
|
1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones |
|
|
|
the code below will give incorrect results for dates close to |
|
|
|
1970-01-01, in the case OS takes into account these historical switches. |
|
|
|
Luckily, it seems that we support only one platform with unsigned |
|
|
|
time_t. It's QNX. And QNX does not support historical timezone data at all. |
|
|
|
E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply |
|
|
|
historical information for localtime_r() etc. That is, the problem is not |
|
|
|
relevant to QNX. |
|
|
|
|
|
|
|
We are safe with shifts close to MAX_INT32, as there are no known |
|
|
|
time switches on Jan 2038 yet :) |
|
|
|
*/ |
|
|
|
tmp=(time_t) (((calc_daynr((uint) t->year,(uint) t->month,(uint) t->day) - |
|
|
|
(long) days_at_timestart)*86400L + (long) t->hour*3600L + |
|
|
|
(long) (t->minute*60 + t->second)) + (time_t) my_time_zone - |
|
|
|
3600); |
|
|
|
current_timezone= my_time_zone; |
|
|
|
if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4)) |
|
|
|
{ |
|
|
|
/* |
|
|
|
Below we will pass (uint) (t->day - shift) to calc_daynr. |
|
|
|
As we don't want to get an overflow here, we will shift |
|
|
|
only safe dates. That's why we have (t->day > 4) above. |
|
|
|
*/ |
|
|
|
t->day-= 2; |
|
|
|
shift= 2; |
|
|
|
} |
|
|
|
#ifdef TIME_T_UNSIGNED |
|
|
|
else |
|
|
|
{ |
|
|
|
/* |
|
|
|
We can get 0 in time_t representaion only on 1969, 31 of Dec or on |
|
|
|
1970, 1 of Jan. For both dates we use shift, which is added |
|
|
|
to t->day in order to step out a bit from the border. |
|
|
|
This is required for platforms, where time_t is unsigned. |
|
|
|
As far as I know, among the platforms we support it's only QNX. |
|
|
|
Note: the order of below if-statements is significant. |
|
|
|
*/ |
|
|
|
|
|
|
|
if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1) |
|
|
|
&& (t->day <= 10)) |
|
|
|
{ |
|
|
|
t->day+= 2; |
|
|
|
shift= -2; |
|
|
|
} |
|
|
|
|
|
|
|
if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12) |
|
|
|
&& (t->day == 31)) |
|
|
|
{ |
|
|
|
t->year++; |
|
|
|
t->month= 1; |
|
|
|
t->day= 2; |
|
|
|
shift= -2; |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) - |
|
|
|
(long) days_at_timestart)*86400L + (long) t->hour*3600L + |
|
|
|
(long) (t->minute*60 + t->second)) + (time_t) my_time_zone - |
|
|
|
3600); |
|
|
|
|
|
|
|
current_timezone= my_time_zone; |
|
|
|
localtime_r(&tmp,&tm_tmp); |
|
|
|
l_time=&tm_tmp; |
|
|
|
for (loop=0; |
|
|
|
@ -810,7 +898,24 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, |
|
|
|
*in_dst_time_gap= 1; |
|
|
|
} |
|
|
|
*my_timezone= current_timezone; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* shift back, if we were dealing with boundary dates */ |
|
|
|
tmp+= shift*86400L; |
|
|
|
|
|
|
|
/* |
|
|
|
This is possible for dates, which slightly exceed boundaries. |
|
|
|
Conversion will pass ok for them, but we don't allow them. |
|
|
|
First check will pass for platforms with signed time_t. |
|
|
|
instruction above (tmp+= shift*86400L) could exceed |
|
|
|
MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen. |
|
|
|
So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms |
|
|
|
with unsigned time_t tmp+= shift*86400L might result in a number, |
|
|
|
larger then TIMESTAMP_MAX_VALUE, so another check will work. |
|
|
|
*/ |
|
|
|
if ((tmp < TIMESTAMP_MIN_VALUE) || (tmp > TIMESTAMP_MAX_VALUE)) |
|
|
|
tmp= 0; |
|
|
|
end: |
|
|
|
return (my_time_t) tmp; |
|
|
|
} /* my_system_gmt_sec */ |
|
|
|
|
|
|
|
|