summaryrefslogtreecommitdiff
path: root/sql-common
diff options
context:
space:
mode:
authorpetr/cps@outpost.site <>2006-11-10 15:05:38 +0300
committerpetr/cps@outpost.site <>2006-11-10 15:05:38 +0300
commite06f74f9f9ad87a088fa6cf90c97c5b670f31636 (patch)
treeb66a727b703588ca78df0c1428526178a483cba2 /sql-common
parentf8931288398b545a08af9b5aef2a690f6ccaeb57 (diff)
parent8de747b0c716e49898f7bf188dab1de5cf1a708e (diff)
downloadmariadb-git-e06f74f9f9ad87a088fa6cf90c97c5b670f31636.tar.gz
Merge pchardin@bk-internal.mysql.com:/home/bk/mysql-4.1
into outpost.site:/home/cps/mysql/trees/4.1-runtime-bug9191
Diffstat (limited to 'sql-common')
-rw-r--r--sql-common/my_time.c124
1 files changed, 115 insertions, 9 deletions
diff --git a/sql-common/my_time.c b/sql-common/my_time.c
index baf9a3902d9..0d8ad167ff5 100644
--- a/sql-common/my_time.c
+++ b/sql-common/my_time.c
@@ -714,15 +714,28 @@ 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, 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
(like 2002-10-27 02:00:0 MET), we will find the initial date.
@@ -735,13 +748,89 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap)
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;
@@ -793,7 +882,24 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap)
*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 */