MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Petr Chardin Date:August 15 2006 5:29am
Subject:bk commit into 4.0 tree (petr:1.2193) BUG#9191
View as plain text  
Below is the list of changes that have just been committed into a local
4.0 repository of cps. When cps does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2006-08-15 09:29:07+04:00, petr@stripped +6 -0
  Fix Bug #9191 from_unixtime(power(2,31)-1) now epoch instead of 2038-01-19
  (4.0 version)
  
  The fix for another Bug (6439) limited FROM_UNIXTIME() to
  TIMESTAMP_MAX_VALUE which is 2145916799 or 2037-12-01 23:59:59 GMT,
  however unix timestamp in general is not considered to be limited 
  by this value. All dates up to power(2,32)-1 are valid.
  
  This patch extends allowed range for FROM_UNIXTIME() and
  UNIX_TIMESTAMP() functions, so that max UNIX_TIMESTAMP()
  is power(2,32)-1. FROM_UNIXTIME() is fixed accordingly to allow
  conversion of dates up to 2038-01-19 03:14:07 UTC.
  
  The main problem solved in the patch is possible overflows
  of variables, used in broken-time representation to time_t
  conversion (required for UNIX_TIMESTAMP).
  
  Note: for platforms where time_t is unsigned we should also
  make a trick for conversion of dates, close to 0. However
  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. The patch does not take into account these switches and 
  will give incorrect results for dates close to 1970-01-01 
  in the timezones mentioned above on QNX (the only platform
  we support, which has unsigned time_t)

  mysql-test/r/func_time.result@stripped, 2006-08-15 09:29:04+04:00, petr@stripped +11 -2
    Update result file

  mysql-test/r/timezone.result@stripped, 2006-08-15 09:29:04+04:00, petr@stripped +4 -4
    Update result file

  mysql-test/t/func_time.test@stripped, 2006-08-15 09:29:05+04:00, petr@stripped +20 -6
    add test for the bug

  mysql-test/t/timezone.test@stripped, 2006-08-15 09:29:05+04:00, petr@stripped +3 -3
    change test to check (updated) boundary dates

  sql/mysql_priv.h@stripped, 2006-08-15 09:29:05+04:00, petr@stripped +1 -1
    change TIMESTAMP_MAX_VALUE to reflect the bugfix:
    now all timestamp values, which from_unixtime
    representation <= then INT_MAX32 are valid

  sql/time.cc@stripped, 2006-08-15 09:29:05+04:00, petr@stripped +99 -13
    Step back from a boudary dates to avoid overflows

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	petr
# Host:	owlet
# Root:	/home/cps/mysql/trees/mysql-4.0-bug

--- 1.239/sql/mysql_priv.h	2006-08-15 09:29:12 +04:00
+++ 1.240/sql/mysql_priv.h	2006-08-15 09:29:12 +04:00
@@ -118,7 +118,7 @@ char* query_table_status(THD *thd,const 
 #define TIMESTAMP_MAX_YEAR 2038
 #define YY_PART_YEAR	   70
 #define TIMESTAMP_MIN_YEAR (1900 + YY_PART_YEAR - 1)
-#define TIMESTAMP_MAX_VALUE 2145916799
+#define TIMESTAMP_MAX_VALUE INT_MAX32
 #define TIMESTAMP_MIN_VALUE 1
 #define PRECISION_FOR_DOUBLE 53
 #define PRECISION_FOR_FLOAT  24

--- 1.30/sql/time.cc	2006-08-15 09:29:12 +04:00
+++ 1.31/sql/time.cc	2006-08-15 09:29:12 +04:00
@@ -59,13 +59,14 @@ void init_time(void)
 long my_gmt_sec(TIME *t, long *my_timezone)
 {
   uint loop;
-  time_t tmp;
+  time_t tmp= 0;
+  int shift= 0;
   struct tm *l_time,tm_tmp;
   long diff, current_timezone;
 
   if (t->year > TIMESTAMP_MAX_YEAR || t->year < TIMESTAMP_MIN_YEAR)
     return 0;
-    
+
   if (t->hour >= 24)
   {					/* Fix for time-loop */
     t->day+=t->hour/24;
@@ -85,15 +86,92 @@ long my_gmt_sec(TIME *t, long *my_timezo
     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).
+
+    However there are 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 on 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))
+  {
+    /* 2038, 19 of Jan is the max possible date within time_t range */
+    if (t->day > 19)
+      goto end;
+
+    /*
+      Below we will pass (uint) (t->day - shift) to calc_daynr.
+      As we don't want to get an owerflow here, we will shift
+      only safe dates.
+    */
+    if (t->day > 4)
+      shift= 2;
+  }
+#ifdef __QNX__
+  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.
+    */
+    if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12))
+    {
+      if ((t->day == 31))
+        shift= -2;
+      else
+        /* 1969, 31 of Dec is min possible date within time_t range */
+        goto end;
+    }
 
+    if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1)
+        && (t->day <= 10))
+      shift= -2;
+  }
+#endif
+
+  /*
+    Compute an estimation of time_t value for the date. Shift several days
+    for boundary dates.
+
+    Without a shift we would get an overflow during tmp computation near
+    boundaries. E.g.
+    (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(-360)
+    wil give us a date around 2106 year. Which is no good.
+  */
+  tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month,
+                              (uint) (t->day - shift)) -
+                   (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;
        loop < 2 &&
 	 (t->hour != (uint) l_time->tm_hour ||
@@ -102,7 +180,7 @@ long my_gmt_sec(TIME *t, long *my_timezo
        loop++)
   {					/* One check should be enough ? */
     /* Get difference in days */
-    int days= t->day - l_time->tm_mday;
+    int days= (t->day - shift) - l_time->tm_mday;
     if (days < -1)
       days= 1;					// Month has wrapped
     else if (days > 1)
@@ -127,7 +205,7 @@ long my_gmt_sec(TIME *t, long *my_timezo
   */
   if (loop == 2 && t->hour != (uint) l_time->tm_hour)
   {
-    int days= t->day - l_time->tm_mday;
+    int days= (t->day - shift) - l_time->tm_mday;
     if (days < -1)
       days=1;					// Month has wrapped
     else if (days > 1)
@@ -141,10 +219,18 @@ long my_gmt_sec(TIME *t, long *my_timezo
       tmp-=t->minute*60 + t->second;		// Move to previous hour
   }
   *my_timezone= current_timezone;
-  
-  if (tmp < TIMESTAMP_MIN_VALUE || tmp > TIMESTAMP_MAX_VALUE)
+
+  /* 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.
+  */
+  if ((tmp < TIMESTAMP_MIN_VALUE) || (tmp > TIMESTAMP_MAX_VALUE))
     tmp= 0;
-  
+
+end:
   return (long) tmp;
 } /* my_gmt_sec */
 
@@ -467,7 +553,7 @@ time_t str_to_timestamp(const char *str,
   if (str_to_TIME(str,length,&l_time,0) != TIMESTAMP_NONE &&
       !(timestamp= my_gmt_sec(&l_time, &not_used)))
     current_thd->cuted_fields++;
-  
+
   return timestamp;
 }
 

--- 1.24/mysql-test/r/func_time.result	2006-08-15 09:29:12 +04:00
+++ 1.25/mysql-test/r/func_time.result	2006-08-15 09:29:12 +04:00
@@ -473,9 +473,18 @@ unix_timestamp('1969-12-01 19:00:01')
 select from_unixtime(-1);
 from_unixtime(-1)
 NULL
-select from_unixtime(2145916800);
-from_unixtime(2145916800)
+select from_unixtime(2147483647);
+from_unixtime(2147483647)
+2038-01-19 06:14:07
+select from_unixtime(2147483648);
+from_unixtime(2147483648)
 NULL
 select from_unixtime(0);
 from_unixtime(0)
 1970-01-01 03:00:00
+select unix_timestamp(from_unixtime(2147483647));
+unix_timestamp(from_unixtime(2147483647))
+2147483647
+select unix_timestamp(from_unixtime(2147483648));
+unix_timestamp(from_unixtime(2147483648))
+NULL

--- 1.21/mysql-test/t/func_time.test	2006-08-15 09:29:12 +04:00
+++ 1.22/mysql-test/t/func_time.test	2006-08-15 09:29:12 +04:00
@@ -227,12 +227,26 @@ select unix_timestamp(@a);
 select unix_timestamp('1969-12-01 19:00:01');
 
 #
-# Test for bug #6439 "unix_timestamp() function returns wrong datetime 
-# values for too big argument" and bug #7515 "from_unixtime(0) now
-# returns NULL instead of the epoch". unix_timestamp() should return error
-# for too big or negative argument. It should return Epoch value for zero
-# argument since it seems that many user's rely on this fact.
+# Tests for bug #6439 "unix_timestamp() function returns wrong datetime 
+# values for too big argument", bug #7515 "from_unixtime(0) now
+# returns NULL instead of the epoch" and bug #9191
+# "from_unixtime(power(2,31)-1) now epoch instead of 2038-01-19.
+# unix_timestamp() should return error for too big or negative argument.
+# It should return Epoch value for zero argument since it seems that many
+# user's rely on this fact, from_unixtime() should work with values
+# up to INT_MAX32 because of the same reason.
 #
 select from_unixtime(-1);
-select from_unixtime(2145916800);
+# the following is in fact check for argument equal to 2^31-1 and 2^31
+select from_unixtime(2147483647);
+select from_unixtime(2147483648);
 select from_unixtime(0);
+
+#
+# Some more tests for bug #9191 "from_unixtime(power(2,31)-1) now epoch
+# instead of 2038-01-19. Here we test that from_unixtime and unix_timestamp
+# are consistent, when working with boundary dates
+#
+select unix_timestamp(from_unixtime(2147483647));
+select unix_timestamp(from_unixtime(2147483648));
+

--- 1.4/mysql-test/r/timezone.result	2006-08-15 09:29:12 +04:00
+++ 1.5/mysql-test/r/timezone.result	2006-08-15 09:29:12 +04:00
@@ -34,7 +34,7 @@ ts	from_unixtime(ts)
 DROP TABLE t1;
 select unix_timestamp('1970-01-01 01:00:00'), 
 unix_timestamp('1970-01-01 01:00:01'),
-unix_timestamp('2038-01-01 00:59:59'),
-unix_timestamp('2038-01-01 01:00:00');
-unix_timestamp('1970-01-01 01:00:00')	unix_timestamp('1970-01-01 01:00:01')	unix_timestamp('2038-01-01 00:59:59')	unix_timestamp('2038-01-01 01:00:00')
-0	1	2145916799	0
+unix_timestamp('2038-01-19 04:14:07'),
+unix_timestamp('2038-01-19 04:14:08');
+unix_timestamp('1970-01-01 01:00:00')	unix_timestamp('1970-01-01 01:00:01')	unix_timestamp('2038-01-19 04:14:07')	unix_timestamp('2038-01-19 04:14:08')
+0	1	2147483647	0

--- 1.6/mysql-test/t/timezone.test	2006-08-15 09:29:12 +04:00
+++ 1.7/mysql-test/t/timezone.test	2006-08-15 09:29:12 +04:00
@@ -41,9 +41,9 @@ SELECT ts,from_unixtime(ts) FROM t1;
 DROP TABLE t1;
 
 #
-# Test for fix for Bug#2523
+# Test fix for Bug#2523. Check that boundary dates are processed correctly
 #
 select unix_timestamp('1970-01-01 01:00:00'), 
        unix_timestamp('1970-01-01 01:00:01'),
-       unix_timestamp('2038-01-01 00:59:59'),
-       unix_timestamp('2038-01-01 01:00:00');
+       unix_timestamp('2038-01-19 04:14:07'),
+       unix_timestamp('2038-01-19 04:14:08');
Thread
bk commit into 4.0 tree (petr:1.2193) BUG#9191Petr Chardin15 Aug