MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Petr Chardin Date:October 20 2006 12:19pm
Subject:bk commit into 4.1 tree (petr:1.2533) BUG#9191
View as plain text  
Below is the list of changes that have just been committed into a local
4.1 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-10-20 16:19:12+04:00, petr@stripped +17 -0
  Fix Bug #9191 from_unixtime(power(2,31)-1) now epoch instead of 2038-01-19
  (4.1 version, with post-review fixes)
    
    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,31)-1 are valid.
    
    This patch extends allowed TIMESTAMP range so, that max
    TIMESTAMP value is power(2,31)-1. It also corrects
    FROM_UNIXTIME() and UNIX_TIMESTAMP() functions, so that
    max allowed UNIX_TIMESTAMP() is power(2,31)-1. FROM_UNIXTIME()
    is fixed accordingly to allow conversion of dates up to
    2038-01-19 03:14:07 UTC. The patch also fixes CONVERT_TZ()
    function to allow extended range of dates.
    
    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).

  acinclude.m4@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +24 -0
    Add new macro to check time_t range

  configure.in@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +7 -0
    call the macro to check time_t range

  include/my_time.h@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +10 -0
    move time-related defines to proper place

  mysql-test/r/func_time.result@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +38 -2
    update test result

  mysql-test/r/timezone.result@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +4 -4
    update test result

  mysql-test/r/timezone2.result@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +12 -12
    update test result

  mysql-test/r/timezone4.result@stripped, 2006-10-20 16:19:06+04:00, petr@stripped +6 -0
    New BitKeeper file ``mysql-test/r/timezone4.result''

  mysql-test/r/timezone4.result@stripped, 2006-10-20 16:19:06+04:00, petr@stripped +0 -0

  mysql-test/t/func_time.test@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +46 -6
    add test for Bug#9191 and update test to be consistent
    with new TIMESTAMP boundaries

  mysql-test/t/timezone.test@stripped, 2006-10-20 16:19:04+04:00, petr@stripped +4 -3
    update old tests to be consistent
    with new TIMESTAMP boundaries

  mysql-test/t/timezone2.test@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +5 -5
    update tests for convert_tz to be consistent with new
    TIMESTAMP boundaries

  mysql-test/t/timezone4-master.opt@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +1 -0
    New BitKeeper file ``mysql-test/t/timezone4-master.opt''

  mysql-test/t/timezone4-master.opt@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +0 -0

  mysql-test/t/timezone4.test@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +13 -0
    New BitKeeper file ``mysql-test/t/timezone4.test''

  mysql-test/t/timezone4.test@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +0 -0

  sql-common/my_time.c@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +138 -9
    Because of increased TIMESTAMP_MAX_VALUE overflows in my_system_gmt_sec()
    became possible. Here we make it safe against the overflows by stepping
    back from the boundary dates which are likely to trigger them.

  sql/item_timefunc.cc@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +4 -9
    fix convert_tz to allow dates from the new (extended)
    TIMESTAMP range

  sql/mysql_priv.h@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +0 -6
    move time handling defaults to my_time.h

  sql/time.cc@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +3 -6
    update TIME_to_timestamp to allow conversion of
    extended date range

  sql/tztime.cc@stripped, 2006-10-20 16:19:05+04:00, petr@stripped +68 -8
    Fix new (4.1) implementation of broken-down time representation
    to time_t conversion routine to avoid overflows during conversion
    of boundary dates

# 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.local
# Root:	/home/cps/mysql/trees/4.1-test

--- 1.142/acinclude.m4	2006-10-20 16:19:23 +04:00
+++ 1.143/acinclude.m4	2006-10-20 16:19:23 +04:00
@@ -1835,6 +1835,30 @@ dnl END OF MYSQL_CHECK_NDBCLUSTER SECTIO
 dnl ---------------------------------------------------------------------------
 
 
+dnl
+dnl  Macro to check time_t range: according to C standard
+dnl  array index myst be greater then 0 => if time_t is signed
+dnl  the code in the macros below won't compile.
+dnl
+
+AC_DEFUN([MYSQL_CHECK_TIME_T],[
+    AC_MSG_CHECKING(if time_t is unsigned)
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+        [[
+#include <time.h>
+        ]],
+        [[
+        int array[(((time_t)-1) > 0) ? 1 : -1];
+        ]] )
+    ], [
+    AC_DEFINE([TIME_T_UNSIGNED], 1, [Define to 1 if time_t is unsigned])
+    AC_MSG_RESULT(yes)
+    ],
+    [AC_MSG_RESULT(no)]
+    )
+])
+
+
 dnl By default, many hosts won't let programs access large files;
 dnl one must use special compiler options to get large-file access to work.
 dnl For more details about this brain damage please see:

--- 1.416/configure.in	2006-10-20 16:19:23 +04:00
+++ 1.417/configure.in	2006-10-20 16:19:23 +04:00
@@ -1820,6 +1820,13 @@ then
   AC_MSG_ERROR("MySQL needs a off_t type.")
 fi
 
+dnl
+dnl check if time_t is unsigned
+dnl
+
+MYSQL_CHECK_TIME_T
+
+
 # do we need #pragma interface/#pragma implementation ?
 # yes if it's gcc 2.x, and not icc pretending to be gcc, and not cygwin
 AC_MSG_CHECKING(the need for @%:@pragma interface/implementation)

--- 1.105/sql/item_timefunc.cc	2006-10-20 16:19:23 +04:00
+++ 1.106/sql/item_timefunc.cc	2006-10-20 16:19:23 +04:00
@@ -1825,15 +1825,10 @@ bool Item_func_convert_tz::get_date(TIME
     return 1;
   }
 
-  /* Check if we in range where we treat datetime values as non-UTC */
-  if (ltime->year < TIMESTAMP_MAX_YEAR && ltime->year > TIMESTAMP_MIN_YEAR ||
-      ltime->year==TIMESTAMP_MAX_YEAR && ltime->month==1 && ltime->day==1 ||
-      ltime->year==TIMESTAMP_MIN_YEAR && ltime->month==12 && ltime->day==31)
-  {
-    my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, &not_used);
-    if (my_time_tmp >= TIMESTAMP_MIN_VALUE && my_time_tmp <= TIMESTAMP_MAX_VALUE)
-      to_tz->gmt_sec_to_TIME(ltime, my_time_tmp);
-  }
+  my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, &not_used);
+  /* my_time_tmp is guranteed to be in the allowed range */
+  if (my_time_tmp)
+    to_tz->gmt_sec_to_TIME(ltime, my_time_tmp);
   
   null_value= 0;
   return 0;

--- 1.379/sql/mysql_priv.h	2006-10-20 16:19:23 +04:00
+++ 1.380/sql/mysql_priv.h	2006-10-20 16:19:23 +04:00
@@ -142,12 +142,6 @@ MY_LOCALE *my_locale_by_name(const char 
 /* Characters shown for the command in 'show processlist' */
 #define PROCESS_LIST_WIDTH 100
 
-/* Time handling defaults */
-#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_MIN_VALUE 1
 #define PRECISION_FOR_DOUBLE 53
 #define PRECISION_FOR_FLOAT  24
 

--- 1.53/sql/time.cc	2006-10-20 16:19:23 +04:00
+++ 1.54/sql/time.cc	2006-10-20 16:19:23 +04:00
@@ -230,14 +230,11 @@ my_time_t TIME_to_timestamp(THD *thd, co
 
   *in_dst_time_gap= 0;
 
-  if (t->year < TIMESTAMP_MAX_YEAR && t->year > TIMESTAMP_MIN_YEAR ||
-      t->year == TIMESTAMP_MAX_YEAR && t->month == 1 && t->day == 1 ||
-      t->year == TIMESTAMP_MIN_YEAR && t->month == 12 && t->day == 31)
+  timestamp= thd->variables.time_zone->TIME_to_gmt_sec(t, in_dst_time_gap);
+  if (timestamp)
   {
     thd->time_zone_used= 1;
-    timestamp= thd->variables.time_zone->TIME_to_gmt_sec(t, in_dst_time_gap);
-    if (timestamp >= TIMESTAMP_MIN_VALUE && timestamp <= TIMESTAMP_MAX_VALUE)
-      return timestamp;
+    return timestamp;
   }
 
   /* If we are here we have range error. */

--- 1.7/mysql-test/r/timezone2.result	2006-10-20 16:19:23 +04:00
+++ 1.8/mysql-test/r/timezone2.result	2006-10-20 16:19:23 +04:00
@@ -106,7 +106,7 @@ create table t1 (ts timestamp);
 set time_zone='UTC';
 insert into t1 values ('0000-00-00 00:00:00'),('1969-12-31 23:59:59'),
 ('1970-01-01 00:00:00'),('1970-01-01 00:00:01'),
-('2037-12-31 23:59:59'),('2038-01-01 00:00:00');
+('2038-01-19 03:14:07'),('2038-01-19 03:14:08');
 Warnings:
 Warning	1264	Data truncated; out of range for column 'ts' at row 2
 Warning	1264	Data truncated; out of range for column 'ts' at row 3
@@ -117,13 +117,13 @@ ts
 0000-00-00 00:00:00
 0000-00-00 00:00:00
 1970-01-01 00:00:01
-2037-12-31 23:59:59
+2038-01-19 03:14:07
 0000-00-00 00:00:00
 delete from t1;
 set time_zone='MET';
 insert into t1 values ('0000-00-00 00:00:00'),('1970-01-01 00:30:00'),
 ('1970-01-01 01:00:00'),('1970-01-01 01:00:01'),
-('2038-01-01 00:59:59'),('2038-01-01 01:00:00');
+('2038-01-19 04:14:07'),('2038-01-19 04:14:08');
 Warnings:
 Warning	1264	Data truncated; out of range for column 'ts' at row 2
 Warning	1264	Data truncated; out of range for column 'ts' at row 3
@@ -134,13 +134,13 @@ ts
 0000-00-00 00:00:00
 0000-00-00 00:00:00
 1970-01-01 01:00:01
-2038-01-01 00:59:59
+2038-01-19 04:14:07
 0000-00-00 00:00:00
 delete from t1;
 set time_zone='+01:30';
 insert into t1 values ('0000-00-00 00:00:00'),('1970-01-01 01:00:00'),
 ('1970-01-01 01:30:00'),('1970-01-01 01:30:01'),
-('2038-01-01 01:29:59'),('2038-01-01 01:30:00');
+('2038-01-19 04:44:07'),('2038-01-19 04:44:08');
 Warnings:
 Warning	1264	Data truncated; out of range for column 'ts' at row 2
 Warning	1264	Data truncated; out of range for column 'ts' at row 3
@@ -151,7 +151,7 @@ ts
 0000-00-00 00:00:00
 0000-00-00 00:00:00
 1970-01-01 01:30:01
-2038-01-01 01:29:59
+2038-01-19 04:44:07
 0000-00-00 00:00:00
 drop table t1;
 show variables like 'time_zone';
@@ -213,12 +213,12 @@ convert_tz('2003-10-26 02:59:59', 'MET',
 select convert_tz('2003-10-26 04:00:00', 'MET', 'UTC');
 convert_tz('2003-10-26 04:00:00', 'MET', 'UTC')
 2003-10-26 03:00:00
-select convert_tz('2038-01-01 00:59:59', 'MET', 'UTC');
-convert_tz('2038-01-01 00:59:59', 'MET', 'UTC')
-2037-12-31 23:59:59
-select convert_tz('2038-01-01 01:00:00', 'MET', 'UTC');
-convert_tz('2038-01-01 01:00:00', 'MET', 'UTC')
-2038-01-01 01:00:00
+select convert_tz('2038-01-19 04:14:07', 'MET', 'UTC');
+convert_tz('2038-01-19 04:14:07', 'MET', 'UTC')
+2038-01-19 03:14:07
+select convert_tz('2038-01-19 04:14:08', 'MET', 'UTC');
+convert_tz('2038-01-19 04:14:08', 'MET', 'UTC')
+2038-01-19 04:14:08
 select convert_tz('2103-01-01 04:00:00', 'MET', 'UTC');
 convert_tz('2103-01-01 04:00:00', 'MET', 'UTC')
 2103-01-01 04:00:00

--- 1.7/mysql-test/t/timezone2.test	2006-10-20 16:19:23 +04:00
+++ 1.8/mysql-test/t/timezone2.test	2006-10-20 16:19:23 +04:00
@@ -107,21 +107,21 @@ create table t1 (ts timestamp);
 set time_zone='UTC';
 insert into t1 values ('0000-00-00 00:00:00'),('1969-12-31 23:59:59'),
                       ('1970-01-01 00:00:00'),('1970-01-01 00:00:01'),
-                      ('2037-12-31 23:59:59'),('2038-01-01 00:00:00');
+                      ('2038-01-19 03:14:07'),('2038-01-19 03:14:08');
 select * from t1;
 delete from t1;
 # MET time zone has range shifted by one hour
 set time_zone='MET';
 insert into t1 values ('0000-00-00 00:00:00'),('1970-01-01 00:30:00'),
                       ('1970-01-01 01:00:00'),('1970-01-01 01:00:01'),
-                      ('2038-01-01 00:59:59'),('2038-01-01 01:00:00');
+                      ('2038-01-19 04:14:07'),('2038-01-19 04:14:08');
 select * from t1;
 delete from t1;
 # same for +01:30 time zone
 set time_zone='+01:30';
 insert into t1 values ('0000-00-00 00:00:00'),('1970-01-01 01:00:00'),
                       ('1970-01-01 01:30:00'),('1970-01-01 01:30:01'),
-                      ('2038-01-01 01:29:59'),('2038-01-01 01:30:00');
+                      ('2038-01-19 04:44:07'),('2038-01-19 04:44:08');
 select * from t1;
 
 drop table t1;
@@ -171,8 +171,8 @@ select convert_tz('2003-10-26 01:00:00',
 select convert_tz('2003-10-26 02:00:00', 'MET', 'UTC');
 select convert_tz('2003-10-26 02:59:59', 'MET', 'UTC');
 select convert_tz('2003-10-26 04:00:00', 'MET', 'UTC');
-select convert_tz('2038-01-01 00:59:59', 'MET', 'UTC');
-select convert_tz('2038-01-01 01:00:00', 'MET', 'UTC');
+select convert_tz('2038-01-19 04:14:07', 'MET', 'UTC');
+select convert_tz('2038-01-19 04:14:08', 'MET', 'UTC');
 select convert_tz('2103-01-01 04:00:00', 'MET', 'UTC');
 
 # Let us test variable time zone argument

--- 1.21/sql/tztime.cc	2006-10-20 16:19:23 +04:00
+++ 1.22/sql/tztime.cc	2006-10-20 16:19:23 +04:00
@@ -885,9 +885,14 @@ TIME_to_gmt_sec(const TIME *t, const TIM
   my_time_t local_t;
   uint saved_seconds;
   uint i;
+  int shift= 0;
 
   DBUG_ENTER("TIME_to_gmt_sec");
 
+  if (!validate_timestamp_range(t))
+    return 0;
+
+
   /* We need this for correct leap seconds handling */
   if (t->second < SECS_PER_MIN)
     saved_seconds= 0;
@@ -895,11 +900,29 @@ TIME_to_gmt_sec(const TIME *t, const TIM
     saved_seconds= t->second;
 
   /*
-    NOTE If we want to convert full my_time_t range without MySQL
-    restrictions we should catch overflow here somehow.
+    NOTE: to convert full my_time_t range we do a shift of the
+    boundary dates here to avoid overflow of my_time_t.
+    We use alike approach in my_system_gmt_sec().
+
+    However in that function we also have to take into account
+    overflow near 0 on some platforms. That's because my_system_gmt_sec
+    uses localtime_r(), which doesn't work with negative values correctly
+    on platforms with unsigned time_t (QNX). Here we don't use localtime()
+    => we negative values of local_t are ok.
   */
 
-  local_t= sec_since_epoch(t->year, t->month, t->day,
+  if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
+  {
+    /*
+      We will pass (t->day - shift) to sec_since_epoch(), and
+      want this value to be a positive number, so we shift
+      only dates > 4.01.2038 (to avoid owerflow).
+    */
+    shift= 2;
+  }
+
+
+  local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
                            t->hour, t->minute,
                            saved_seconds ? 0 : t->second);
 
@@ -918,6 +941,22 @@ TIME_to_gmt_sec(const TIME *t, const TIM
   /* binary search for our range */
   i= find_time_range(local_t, sp->revts, sp->revcnt);
 
+  /*
+    As there are no offset switches at the end of TIMESTAMP range,
+    we could simply check for overflow here (and don't need to bother
+    about DST gaps etc)
+  */
+  if (shift)
+  {
+    if (local_t > (TIMESTAMP_MAX_VALUE - shift*86400L +
+                   sp->revtis[i].rt_offset - saved_seconds))
+    {
+      DBUG_RETURN(0);                           /* my_time_t overflow */
+    }
+    else
+     local_t+= shift*86400L;
+  }
+
   if (sp->revtis[i].rt_type)
   {
     /*
@@ -927,10 +966,16 @@ TIME_to_gmt_sec(const TIME *t, const TIM
       beginning of the gap.
     */
     *in_dst_time_gap= 1;
-    DBUG_RETURN(sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds);
+    local_t= sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds;
   }
   else
-    DBUG_RETURN(local_t - sp->revtis[i].rt_offset + saved_seconds);
+    local_t= local_t - sp->revtis[i].rt_offset + saved_seconds;
+
+  /* check for TIMESTAMP_MAX_VALUE was already done above */
+  if (local_t < TIMESTAMP_MIN_VALUE)
+    local_t= 0;
+
+  DBUG_RETURN(local_t);
 }
 
 
@@ -1294,9 +1339,24 @@ Time_zone_offset::Time_zone_offset(long 
 my_time_t
 Time_zone_offset::TIME_to_gmt_sec(const TIME *t, bool *in_dst_time_gap) const
 {
-  return sec_since_epoch(t->year, t->month, t->day,
-                         t->hour, t->minute, t->second) -
-         offset;
+  my_time_t local_t;
+
+  /*
+    Check timestamp range.we have to do this as calling function relies on
+    us to make all validation checks here.
+  */
+  if (!validate_timestamp_range(t))
+    return 0;
+
+  local_t= sec_since_epoch(t->year, t->month, t->day,
+                           t->hour, t->minute, t->second) -
+           offset;
+
+  if (local_t >= TIMESTAMP_MIN_VALUE && local_t <= TIMESTAMP_MAX_VALUE)
+    return local_t;
+
+  /* range error*/
+  return 0;
 }
 
 

--- 1.7/include/my_time.h	2006-10-20 16:19:23 +04:00
+++ 1.8/include/my_time.h	2006-10-20 16:19:23 +04:00
@@ -38,6 +38,14 @@ typedef long my_time_t;
 #define MY_TIME_T_MAX LONG_MAX
 #define MY_TIME_T_MIN LONG_MIN
 
+
+/* Time handling defaults */
+#define TIMESTAMP_MAX_YEAR 2038
+#define YY_PART_YEAR	   70
+#define TIMESTAMP_MIN_YEAR (1900 + YY_PART_YEAR - 1)
+#define TIMESTAMP_MAX_VALUE INT_MAX32
+#define TIMESTAMP_MIN_VALUE 1
+
 #define YY_PART_YEAR	   70
 
 /* Flags to str_to_datetime */
@@ -54,6 +62,8 @@ bool str_to_time(const char *str,uint le
 long calc_daynr(uint year,uint month,uint day);
 
 void init_time(void);
+
+bool validate_timestamp_range(const MYSQL_TIME *t);
 
 my_time_t 
 my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap);

--- 1.15/sql-common/my_time.c	2006-10-20 16:19:23 +04:00
+++ 1.16/sql-common/my_time.c	2006-10-20 16:19:23 +04:00
@@ -48,6 +48,29 @@ static long my_time_zone=0;
 
 
 /*
+  Function to check sanity of a TIMESTAMP value
+
+  DESCRIPTION
+    Check if a given MYSQL_TIME value fits in TIMESTAMP range.
+    This function doesn't make precise check, but rather a rough
+    estimate.
+
+  RETURN VALUES
+    FALSE   The value seems sane
+    TRUE    The MYSQL_TIME value is definitely out of range
+*/
+
+inline bool validate_timestamp_range(const MYSQL_TIME *t)
+{
+  if ((t->year > TIMESTAMP_MAX_YEAR || t->year < TIMESTAMP_MIN_YEAR) ||
+      (t->year == TIMESTAMP_MAX_YEAR && (t->month > 1 || t->day > 19)) ||
+      (t->year == TIMESTAMP_MIN_YEAR && (t->month < 12 || t->day < 31)))
+    return FALSE;
+
+  return TRUE;
+}
+
+/*
   Convert a timestamp string to a MYSQL_TIME value.
 
   SYNOPSIS
@@ -667,15 +690,28 @@ long calc_daynr(uint year,uint month,uin
   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.
@@ -688,13 +724,89 @@ my_system_gmt_sec(const MYSQL_TIME *t, l
     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;
@@ -746,7 +858,24 @@ my_system_gmt_sec(const MYSQL_TIME *t, l
     *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 */
 

--- 1.40/mysql-test/r/func_time.result	2006-10-20 16:19:23 +04:00
+++ 1.41/mysql-test/r/func_time.result	2006-10-20 16:19:23 +04:00
@@ -483,12 +483,48 @@ 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
+select unix_timestamp('2039-01-20 01:00:00');
+unix_timestamp('2039-01-20 01:00:00')
+0
+select unix_timestamp('1968-01-20 01:00:00');
+unix_timestamp('1968-01-20 01:00:00')
+0
+select unix_timestamp('2038-02-10 01:00:00');
+unix_timestamp('2038-02-10 01:00:00')
+0
+select unix_timestamp('1969-11-20 01:00:00');
+unix_timestamp('1969-11-20 01:00:00')
+0
+select unix_timestamp('2038-01-20 01:00:00');
+unix_timestamp('2038-01-20 01:00:00')
+0
+select unix_timestamp('1969-12-30 01:00:00');
+unix_timestamp('1969-12-30 01:00:00')
+0
+select unix_timestamp('2038-01-17 12:00:00');
+unix_timestamp('2038-01-17 12:00:00')
+2147331600
+select unix_timestamp('1970-01-01 03:00:01');
+unix_timestamp('1970-01-01 03:00:01')
+1
+select unix_timestamp('2038-01-19 07:14:07');
+unix_timestamp('2038-01-19 07:14:07')
+0
 CREATE TABLE t1 (datetime datetime, timestamp timestamp, date date, time time);
 INSERT INTO t1 values ("2001-01-02 03:04:05", "2002-01-02 03:04:05", "2003-01-02", "06:07:08");
 SELECT * from t1;

--- 1.35/mysql-test/t/func_time.test	2006-10-20 16:19:23 +04:00
+++ 1.36/mysql-test/t/func_time.test	2006-10-20 16:19:23 +04:00
@@ -236,15 +236,55 @@ 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);
+# check for from_unixtime(2^31-1) and from_unixtime(2^31)
+select from_unixtime(2147483647);
+select from_unixtime(2147483648);
 select from_unixtime(0);
+
+#
+# Some more tests for bug #9191 "9191 "TIMESTAMP/from_unixtime() no
+# longer accepts 2^31-1". 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));
+
+# check for invalid dates
+
+# bad year
+select unix_timestamp('2039-01-20 01:00:00');
+select unix_timestamp('1968-01-20 01:00:00');
+# bad month
+select unix_timestamp('2038-02-10 01:00:00');
+select unix_timestamp('1969-11-20 01:00:00');
+# bad day
+select unix_timestamp('2038-01-20 01:00:00');
+select unix_timestamp('1969-12-30 01:00:00');
+
+#
+# Check negative shift (we subtract several days for boundary dates during
+# conversion).
+select unix_timestamp('2038-01-17 12:00:00');
+
+#
+# Check positive shift. (it happens only on
+# platfroms with unsigned time_t, such as QNX)
+#
+select unix_timestamp('1970-01-01 03:00:01');
+
+# check bad date, close to the boundary (we cut them off in the very end)
+select unix_timestamp('2038-01-19 07:14:07');
+
 
 #
 # Test types from + INTERVAL

--- 1.6/mysql-test/r/timezone.result	2006-10-20 16:19:23 +04:00
+++ 1.7/mysql-test/r/timezone.result	2006-10-20 16:19:23 +04:00
@@ -41,7 +41,7 @@ Warning	1299	Invalid TIMESTAMP value in 
 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.9/mysql-test/t/timezone.test	2006-10-20 16:19:23 +04:00
+++ 1.10/mysql-test/t/timezone.test	2006-10-20 16:19:23 +04:00
@@ -52,11 +52,12 @@ INSERT INTO t1 (ts) VALUES ('2003-03-30 
 DROP TABLE t1;
 
 #
-# Test for fix for Bug#2523
+# Test for 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');
 
 # End of 4.1 tests
--- New file ---
+++ mysql-test/r/timezone4.result	06/10/20 16:19:06
select from_unixtime(0);
from_unixtime(0)
1969-12-31 14:00:00
select unix_timestamp('1969-12-31 14:00:01');
unix_timestamp('1969-12-31 14:00:01')
1

--- New file ---
+++ mysql-test/t/timezone4-master.opt	06/10/20 16:19:05
--timezone=GMT+10

--- New file ---
+++ mysql-test/t/timezone4.test	06/10/20 16:19:05
#
# Tests for time functions. The difference from func_time test is the
# timezone. In func_time it's GMT-3. In our case it's GMT+10
#

#
# Test for bug bug #9191 "TIMESTAMP/from_unixtime() no longer accepts 2^31-1"
#

select from_unixtime(0);
# check 0 boundary
select unix_timestamp('1969-12-31 14:00:01');


Thread
bk commit into 4.1 tree (petr:1.2533) BUG#9191Petr Chardin20 Oct