From 634d3ff2c658902fa3ed4eb0f988a79564cfe94e Mon Sep 17 00:00:00 2001
From: unknown <kaa@polly.local>
Date: Wed, 4 Oct 2006 17:13:32 +0400
Subject: [PATCH] Fixes a number of problems with time/datetime <-> string
 conversion functions:

- bug #11655 "Wrong time is returning from nested selects - maximum time exists
- input and output TIME values were not validated properly in several conversion functions
- bug #20927 "sec_to_time treats big unsigned as signed"
- integer overflows were not checked in several functions. As a result, input values like 2^32 or 3600*2^32 were treated as 0
- BIGINT UNSIGNED values were treated as SIGNED in several functions
- in cases where both input string truncation and out-of-range TIME value occur, only 'truncated incorrect time value' warning was produced


include/my_time.h:
  Added defines for the TIME limits
  Added defines for the warning flags set by str_to_time() and check_time_range()
  Added check_time_range() declaration
mysql-test/r/func_sapdb.result:
  Fixed testcases which relied on incorrect TIMEDIFF() behaviour
mysql-test/r/func_time.result:
  Fixed testcase which relied on incorrect behaviour
  Added testcases for out-of-range values in SEC_TO_TIME(), TIME_TO_SEC(), ADDTIME(), SUBTIME() and EXTRACT()
mysql-test/t/func_time.test:
  Added testcases for out-of-range values in SEC_TO_TIME(), TIME_TO_SEC(), ADDTIME(), SUBTIME() and EXTRACT()
sql-common/my_time.c:
  Added check_time_range() to be used from str_to_time() and item_timefunc.cc
  Added new out-of-range flag to str_to_time() warnings
  Use '%u' instead of '%d' in my_*_to_str() because the arguments are unsigned
sql/field.cc:
  Replaced out-of-range checks with checks for flags returned by str_to_time()
sql/item_timefunc.cc:
  Added wrappers over make_datetime() and make_time() which perform out-of-range checks on input values
  Moved common code in Item_func_sec_to_time::val_str() and Item_func_sec_to_time::val_int() into a separate function sec_to_time()
  Replaced calls to make_datetime() with make_datetime_with_warn() in Item_func_add_time and Item_func_timediff
  Checks for 'unsigned int' overflows in Item_func_maketime
  Use make_time_with_warn() instead of make_time() in Item_func_maketime
  Fixed incorrect sizeof() in Item_func_str_to_date::get_time()
sql/time.cc:
  Check for return value of str_to_time() along with warning flags
---
 include/my_time.h              |  14 +-
 mysql-test/r/func_sapdb.result |  15 ++-
 mysql-test/r/func_time.result  |  92 +++++++++++++-
 mysql-test/t/func_time.test    |  45 ++++++-
 sql-common/my_time.c           |  91 +++++++++----
 sql/field.cc                   |  35 +++--
 sql/item_timefunc.cc           | 226 ++++++++++++++++++++++++++-------
 sql/time.cc                    |   6 +-
 8 files changed, 429 insertions(+), 95 deletions(-)

diff --git a/include/my_time.h b/include/my_time.h
index 94701e159c4..11c653f70d0 100644
--- a/include/my_time.h
+++ b/include/my_time.h
@@ -44,12 +44,24 @@ typedef long my_time_t;
 #define TIME_FUZZY_DATE    1
 #define TIME_DATETIME_ONLY 2
 
+#define MYSQL_TIME_WARN_TRUNCATED    1
+#define MYSQL_TIME_WARN_OUT_OF_RANGE 2
+
+/* Limits for the TIME data type */
+#define TIME_MAX_HOUR 838
+#define TIME_MAX_MINUTE 59
+#define TIME_MAX_SECOND 59
+#define TIME_MAX_VALUE (TIME_MAX_HOUR*10000 + TIME_MAX_MINUTE*100 + \
+                        TIME_MAX_SECOND)
+
 enum enum_mysql_timestamp_type
 str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
                 uint flags, int *was_cut);
 
 bool str_to_time(const char *str,uint length, MYSQL_TIME *l_time,
-                 int *was_cut);
+                 int *warning);
+
+int check_time_range(struct st_mysql_time *time, int *warning);
 
 long calc_daynr(uint year,uint month,uint day);
 
diff --git a/mysql-test/r/func_sapdb.result b/mysql-test/r/func_sapdb.result
index b18885e218a..447cadb9898 100644
--- a/mysql-test/r/func_sapdb.result
+++ b/mysql-test/r/func_sapdb.result
@@ -97,7 +97,9 @@ subtime("02:01:01.999999", "01:01:01.999999")
 01:00:00.000000
 select timediff("1997-01-01 23:59:59.000001","1995-12-31 23:59:59.000002");
 timediff("1997-01-01 23:59:59.000001","1995-12-31 23:59:59.000002")
-8807:59:59.999999
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '8807:59:59.999999'
 select timediff("1997-12-31 23:59:59.000001","1997-12-30 01:01:01.000002");
 timediff("1997-12-31 23:59:59.000001","1997-12-30 01:01:01.000002")
 46:58:57.999999
@@ -208,13 +210,16 @@ NULL	NULL
 SELECT TIMEDIFF(t1,t4) As ttt, TIMEDIFF(t2, t3) As qqq from test;
 ttt	qqq
 -744:00:00	NULL
-26305:01:02	22:58:58
--26305:01:02	-22:58:58
+838:59:59	22:58:58
+-838:59:59	-22:58:58
 NULL	26:02:02
 00:00:00	-26:02:02
 NULL	NULL
 NULL	NULL
 00:00:00	-24:00:00
+Warnings:
+Warning	1292	Truncated incorrect time value: '26305:01:02'
+Warning	1292	Truncated incorrect time value: '-26305:01:02'
 drop table t1, test;
 select addtime("-01:01:01.01", "-23:59:59.1") as a;
 a
@@ -224,7 +229,9 @@ a
 10000
 select microsecond(19971231235959.01) as a;
 a
-10000
+0
+Warnings:
+Warning	1292	Truncated incorrect time value: '19971231235959.01'
 select date_add("1997-12-31",INTERVAL "10.09" SECOND_MICROSECOND) as a;
 a
 1997-12-31 00:00:10.090000
diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result
index fab0bf01f58..381c147660a 100644
--- a/mysql-test/r/func_time.result
+++ b/mysql-test/r/func_time.result
@@ -330,7 +330,9 @@ extract(DAY_MINUTE FROM "02 10:11:12")
 21011
 select extract(DAY_SECOND FROM "225 10:11:12");
 extract(DAY_SECOND FROM "225 10:11:12")
-225101112
+8385959
+Warnings:
+Warning	1292	Truncated incorrect time value: '225 10:11:12'
 select extract(HOUR FROM "1999-01-02 10:11:12");
 extract(HOUR FROM "1999-01-02 10:11:12")
 10
@@ -688,3 +690,91 @@ t1	CREATE TABLE `t1` (
   `from_unixtime(1) + 0` double(23,6) default NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
 drop table t1;
+SELECT SEC_TO_TIME(3300000);
+SEC_TO_TIME(3300000)
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '3300000'
+SELECT SEC_TO_TIME(3300000)+0;
+SEC_TO_TIME(3300000)+0
+8385959.000000
+Warnings:
+Warning	1292	Truncated incorrect time value: '3300000'
+SELECT SEC_TO_TIME(3600 * 4294967296);
+SEC_TO_TIME(3600 * 4294967296)
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '15461882265600'
+SELECT TIME_TO_SEC('916:40:00');
+TIME_TO_SEC('916:40:00')
+3020399
+Warnings:
+Warning	1292	Truncated incorrect time value: '916:40:00'
+SELECT ADDTIME('500:00:00', '416:40:00');
+ADDTIME('500:00:00', '416:40:00')
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '916:40:00'
+SELECT ADDTIME('916:40:00', '416:40:00');
+ADDTIME('916:40:00', '416:40:00')
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '916:40:00'
+Warning	1292	Truncated incorrect time value: '1255:39:59'
+SELECT SUBTIME('916:40:00', '416:40:00');
+SUBTIME('916:40:00', '416:40:00')
+422:19:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '916:40:00'
+SELECT SUBTIME('-916:40:00', '416:40:00');
+SUBTIME('-916:40:00', '416:40:00')
+-838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '-916:40:00'
+Warning	1292	Truncated incorrect time value: '-1255:39:59'
+SELECT MAKETIME(916,0,0);
+MAKETIME(916,0,0)
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '916:00:00'
+SELECT MAKETIME(4294967296, 0, 0);
+MAKETIME(4294967296, 0, 0)
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '4294967296:00:00'
+SELECT MAKETIME(-4294967296, 0, 0);
+MAKETIME(-4294967296, 0, 0)
+-838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '-4294967296:00:00'
+SELECT MAKETIME(0, 4294967296, 0);
+MAKETIME(0, 4294967296, 0)
+NULL
+SELECT MAKETIME(0, 0, 4294967296);
+MAKETIME(0, 0, 4294967296)
+NULL
+SELECT MAKETIME(CAST(-1 AS UNSIGNED), 0, 0);
+MAKETIME(CAST(-1 AS UNSIGNED), 0, 0)
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '18446744073709551615:00:00'
+SELECT EXTRACT(HOUR FROM '100000:02:03');
+EXTRACT(HOUR FROM '100000:02:03')
+838
+Warnings:
+Warning	1292	Truncated incorrect time value: '100000:02:03'
+CREATE TABLE t1(f1 TIME);
+INSERT INTO t1 VALUES('916:00:00 a');
+Warnings:
+Warning	1265	Data truncated for column 'f1' at row 1
+Warning	1264	Data truncated; out of range for column 'f1' at row 1
+SELECT * FROM t1;
+f1
+838:59:59
+DROP TABLE t1;
+SELECT SEC_TO_TIME(CAST(-1 AS UNSIGNED));
+SEC_TO_TIME(CAST(-1 AS UNSIGNED))
+838:59:59
+Warnings:
+Warning	1292	Truncated incorrect time value: '18446744073709551615'
+End of 4.1 tests
diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test
index b232fb14e1e..d15aa9c9516 100644
--- a/mysql-test/t/func_time.test
+++ b/mysql-test/t/func_time.test
@@ -358,4 +358,47 @@ create table t1 select now() - now(), curtime() - curtime(),
 show create table t1;
 drop table t1;
 
-# End of 4.1 tests
+#
+# Bug #11655: Wrong time is returning from nested selects - maximum time exists
+#
+# check if SEC_TO_TIME() handles out-of-range values correctly
+SELECT SEC_TO_TIME(3300000);
+SELECT SEC_TO_TIME(3300000)+0;
+SELECT SEC_TO_TIME(3600 * 4294967296);
+
+# check if TIME_TO_SEC() handles out-of-range values correctly
+SELECT TIME_TO_SEC('916:40:00');
+
+# check if ADDTIME() handles out-of-range values correctly
+SELECT ADDTIME('500:00:00', '416:40:00');
+SELECT ADDTIME('916:40:00', '416:40:00');
+
+# check if SUBTIME() handles out-of-range values correctly
+SELECT SUBTIME('916:40:00', '416:40:00');
+SELECT SUBTIME('-916:40:00', '416:40:00');
+
+# check if MAKETIME() handles out-of-range values correctly
+SELECT MAKETIME(916,0,0);
+SELECT MAKETIME(4294967296, 0, 0);
+SELECT MAKETIME(-4294967296, 0, 0);
+SELECT MAKETIME(0, 4294967296, 0);
+SELECT MAKETIME(0, 0, 4294967296);
+SELECT MAKETIME(CAST(-1 AS UNSIGNED), 0, 0);
+
+# check if EXTRACT() handles out-of-range values correctly
+SELECT EXTRACT(HOUR FROM '100000:02:03');
+
+# check if we get proper warnings if both input string truncation
+# and out-of-range value occur
+CREATE TABLE t1(f1 TIME);
+INSERT INTO t1 VALUES('916:00:00 a');
+SELECT * FROM t1;
+DROP TABLE t1;
+
+#
+# Bug #20927: sec_to_time treats big unsigned as signed
+#
+# check if SEC_TO_TIME() handles BIGINT UNSIGNED values correctly
+SELECT SEC_TO_TIME(CAST(-1 AS UNSIGNED));
+
+--echo End of 4.1 tests
diff --git a/sql-common/my_time.c b/sql-common/my_time.c
index 3c46a944ba9..baf9a3902d9 100644
--- a/sql-common/my_time.c
+++ b/sql-common/my_time.c
@@ -402,8 +402,10 @@ err:
                         There may be an optional [.second_part] after seconds
    length               Length of str
    l_time               Store result here
-   was_cut              Set to 1 if value was cut during conversion or to 0
-                        otherwise.
+   warning              Set MYSQL_TIME_WARN_TRUNCATED flag if the input string
+                        was cut during conversion, and/or
+                        MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is
+                        out of range.
 
    NOTES
      Because of the extra days argument, this function can only
@@ -414,16 +416,16 @@ err:
      1  error
 */
 
-bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
-                 int *was_cut)
+bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, int *warning)
 {
-  long date[5],value;
+  ulong date[5];
+  ulonglong value;
   const char *end=str+length, *end_of_days;
   bool found_days,found_hours;
   uint state;
 
   l_time->neg=0;
-  *was_cut= 0;
+  *warning= 0;
   for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
     length--;
   if (str != end && *str == '-')
@@ -438,13 +440,16 @@ bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
   /* Check first if this is a full TIMESTAMP */
   if (length >= 12)
   {                                             /* Probably full timestamp */
+    int was_cut;
     enum enum_mysql_timestamp_type
       res= str_to_datetime(str, length, l_time,
-                           (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), was_cut);
+                           (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut);
     if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR)
+    {
+      if (was_cut)
+        *warning|= MYSQL_TIME_WARN_TRUNCATED;
       return res == MYSQL_TIMESTAMP_ERROR;
-    /* We need to restore was_cut flag since str_to_datetime can modify it */
-    *was_cut= 0;
+    }
   }
 
   /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
@@ -524,7 +529,7 @@ fractional:
     if (field_length > 0)
       value*= (long) log_10_int[field_length];
     else if (field_length < 0)
-      *was_cut= 1;
+      *warning|= MYSQL_TIME_WARN_TRUNCATED;
     date[4]=value;
   }
   else
@@ -538,10 +543,7 @@ fractional:
        ((str[1] == '-' || str[1] == '+') &&
         (end - str) > 2 &&
         my_isdigit(&my_charset_latin1, str[2]))))
-  {
-    *was_cut= 1;
     return 1;
-  }
 
   if (internal_format_positions[7] != 255)
   {
@@ -560,12 +562,12 @@ fractional:
     }
   }
 
-  /* Some simple checks */
-  if (date[2] >= 60 || date[3] >= 60)
-  {
-    *was_cut= 1;
+  /* Integer overflow checks */
+  if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
+      date[2] > UINT_MAX || date[3] > UINT_MAX ||
+      date[4] > UINT_MAX)
     return 1;
-  }
+  
   l_time->year=         0;                      /* For protocol::store_time */
   l_time->month=        0;
   l_time->day=          date[0];
@@ -575,6 +577,10 @@ fractional:
   l_time->second_part=  date[4];
   l_time->time_type= MYSQL_TIMESTAMP_TIME;
 
+  /* Check if the value is valid and fits into TIME range */
+  if (check_time_range(l_time, warning))
+    return 1;
+  
   /* Check if there is garbage at end of the TIME specification */
   if (str != end)
   {
@@ -582,7 +588,7 @@ fractional:
     {
       if (!my_isspace(&my_charset_latin1,*str))
       {
-        *was_cut= 1;
+        *warning|= MYSQL_TIME_WARN_TRUNCATED;
         break;
       }
     } while (++str != end);
@@ -591,6 +597,47 @@ fractional:
 }
 
 
+/*
+  Check 'time' value to lie in the TIME range
+
+  SYNOPSIS:
+    check_time_range()
+    time     pointer to TIME value
+    warning  set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
+
+  DESCRIPTION
+  If the time value lies outside of the range [-838:59:59, 838:59:59],
+  set it to the closest endpoint of the range and set
+  MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
+
+  RETURN
+    0        time value is valid, but was possibly truncated
+    1        time value is invalid
+*/
+
+int check_time_range(struct st_mysql_time *time, int *warning) 
+{
+  longlong hour;
+
+  if (time->minute >= 60 || time->second >= 60)
+    return 1;
+
+  hour= time->hour + (24*time->day);
+  if (hour <= TIME_MAX_HOUR &&
+      (hour != TIME_MAX_HOUR || time->minute != TIME_MAX_MINUTE ||
+       time->second != TIME_MAX_SECOND || !time->second_part))
+    return 0;
+
+  time->day= 0;
+  time->hour= TIME_MAX_HOUR;
+  time->minute= TIME_MAX_MINUTE;
+  time->second= TIME_MAX_SECOND;
+  time->second_part= 0;
+  *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE;
+  return 0;
+}
+
+
 /*
   Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
 
@@ -776,7 +823,7 @@ void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
 int my_time_to_str(const MYSQL_TIME *l_time, char *to)
 {
   uint extra_hours= 0;
-  return my_sprintf(to, (to, "%s%02d:%02d:%02d",
+  return my_sprintf(to, (to, "%s%02u:%02u:%02u",
                          (l_time->neg ? "-" : ""),
                          extra_hours+ l_time->hour,
                          l_time->minute,
@@ -785,7 +832,7 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to)
 
 int my_date_to_str(const MYSQL_TIME *l_time, char *to)
 {
-  return my_sprintf(to, (to, "%04d-%02d-%02d",
+  return my_sprintf(to, (to, "%04u-%02u-%02u",
                          l_time->year,
                          l_time->month,
                          l_time->day));
@@ -793,7 +840,7 @@ int my_date_to_str(const MYSQL_TIME *l_time, char *to)
 
 int my_datetime_to_str(const MYSQL_TIME *l_time, char *to)
 {
-  return my_sprintf(to, (to, "%04d-%02d-%02d %02d:%02d:%02d",
+  return my_sprintf(to, (to, "%04u-%02u-%02u %02u:%02u:%02u",
                          l_time->year,
                          l_time->month,
                          l_time->day,
diff --git a/sql/field.cc b/sql/field.cc
index 3cb0c0d3a7c..484758fb0e7 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -3961,9 +3961,10 @@ int Field_time::store(const char *from,uint len,CHARSET_INFO *cs)
 {
   TIME ltime;
   long tmp;
-  int error;
+  int error= 0;
+  int warning;
 
-  if (str_to_time(from, len, &ltime, &error))
+  if (str_to_time(from, len, &ltime, &warning))
   {
     tmp=0L;
     error= 2;
@@ -3972,29 +3973,27 @@ int Field_time::store(const char *from,uint len,CHARSET_INFO *cs)
   }
   else
   {
-    if (error)
+    if (warning & MYSQL_TIME_WARN_TRUNCATED)
       set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                            ER_WARN_DATA_TRUNCATED,
                            from, len, MYSQL_TIMESTAMP_TIME, 1);
-
-    if (ltime.month)
-      ltime.day=0;
-    tmp=(ltime.day*24L+ltime.hour)*10000L+(ltime.minute*100+ltime.second);
-    if (tmp > 8385959)
+    if (warning & MYSQL_TIME_WARN_OUT_OF_RANGE)
     {
-      tmp=8385959;
       set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                            ER_WARN_DATA_OUT_OF_RANGE,
                            from, len, MYSQL_TIMESTAMP_TIME, !error);
       error= 1;
     }
+    if (ltime.month)
+      ltime.day=0;
+    tmp=(ltime.day*24L+ltime.hour)*10000L+(ltime.minute*100+ltime.second);
     if (error > 1)
       error= 2;
   }
   
   if (ltime.neg)
     tmp= -tmp;
-  error |= Field_time::store((longlong) tmp);
+  int3store(ptr,tmp);
   return error;
 }
 
@@ -4003,16 +4002,16 @@ int Field_time::store(double nr)
 {
   long tmp;
   int error= 0;
-  if (nr > 8385959.0)
+  if (nr > (double)TIME_MAX_VALUE)
   {
-    tmp=8385959L;
+    tmp= TIME_MAX_VALUE;
     set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                          ER_WARN_DATA_OUT_OF_RANGE, nr, MYSQL_TIMESTAMP_TIME);
     error= 1;
   }
-  else if (nr < -8385959.0)
+  else if (nr < (double)-TIME_MAX_VALUE)
   {
-    tmp= -8385959L;
+    tmp= -TIME_MAX_VALUE;
     set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                          ER_WARN_DATA_OUT_OF_RANGE, nr, MYSQL_TIMESTAMP_TIME);
     error= 1;
@@ -4040,17 +4039,17 @@ int Field_time::store(longlong nr)
 {
   long tmp;
   int error= 0;
-  if (nr > (longlong) 8385959L)
+  if (nr > (longlong) TIME_MAX_VALUE)
   {
-    tmp=8385959L;
+    tmp= TIME_MAX_VALUE;
     set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                          ER_WARN_DATA_OUT_OF_RANGE, nr,
                          MYSQL_TIMESTAMP_TIME, 1);
     error= 1;
   }
-  else if (nr < (longlong) -8385959L)
+  else if (nr < (longlong) -TIME_MAX_VALUE)
   {
-    tmp= -8385959L;
+    tmp= -TIME_MAX_VALUE;
     set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, 
                          ER_WARN_DATA_OUT_OF_RANGE, nr,
                          MYSQL_TIMESTAMP_TIME, 1);
diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
index 2da0e8956c2..80e4cf8d456 100644
--- a/sql/item_timefunc.cc
+++ b/sql/item_timefunc.cc
@@ -95,6 +95,124 @@ static bool make_datetime(date_time_format_types format, TIME *ltime,
 }
 
 
+/*
+  Wrapper over make_datetime() with validation of the input TIME value
+
+  NOTE
+    see make_datetime() for more information
+
+  RETURN
+    1    if there was an error during converion
+    0    otherwise
+*/
+
+static bool make_datetime_with_warn(date_time_format_types format, TIME *ltime,
+                                    String *str)
+{
+  int warning= 0;
+  bool rc;
+
+  if (make_datetime(format, ltime, str))
+    return 1;
+  if (check_time_range(ltime, &warning))
+    return 1;
+  if (!warning)
+    return 0;
+
+  make_truncated_value_warning(current_thd, str->ptr(), str->length(),
+                               MYSQL_TIMESTAMP_TIME);
+  return make_datetime(format, ltime, str);
+}
+
+
+/*
+  Wrapper over make_time() with validation of the input TIME value
+
+  NOTE
+    see make_time() for more info
+
+  RETURN
+    1    if there was an error during conversion
+    0    otherwise
+*/
+
+static bool make_time_with_warn(const DATE_TIME_FORMAT *format,
+                                TIME *l_time, String *str)
+{
+  int warning= 0;
+  make_time(format, l_time, str);
+  if (check_time_range(l_time, &warning))
+    return 1;
+  if (warning)
+  {
+    make_truncated_value_warning(current_thd, str->ptr(), str->length(),
+                                 MYSQL_TIMESTAMP_TIME);
+    make_time(format, l_time, str);
+  }
+
+  return 0;
+}
+
+
+/*
+  Convert seconds to TIME value with overflow checking
+
+  SYNOPSIS:
+    sec_to_time()
+    seconds          number of seconds
+    unsigned_flag    1, if 'seconds' is unsigned, 0, otherwise
+    ltime            output TIME value
+
+  DESCRIPTION
+    If the 'seconds' argument is inside TIME data range, convert it to a
+    corresponding value.
+    Otherwise, truncate the resulting value to the nearest endpoint, and
+    produce a warning message.
+
+  RETURN
+    1                if the value was truncated during conversion
+    0                otherwise
+*/
+  
+static bool sec_to_time(longlong seconds, bool unsigned_flag, TIME *ltime)
+{
+  uint sec;
+
+  bzero((char *)ltime, sizeof(*ltime));
+  
+  if (seconds < 0)
+  {
+    if (unsigned_flag)
+      goto overflow;
+    ltime->neg= 1;
+    if (seconds < -3020399)
+      goto overflow;
+    seconds= -seconds;
+  }
+  else if (seconds > 3020399)
+    goto overflow;
+  
+  sec= (uint) ((ulonglong) seconds % 3600);
+  ltime->hour= (uint) (seconds/3600);
+  ltime->minute= sec/60;
+  ltime->second= sec % 60;
+
+  return 0;
+
+overflow:
+  ltime->hour= TIME_MAX_HOUR;
+  ltime->minute= TIME_MAX_MINUTE;
+  ltime->second= TIME_MAX_SECOND;
+
+  char buf[22];
+  int len= (int)(longlong10_to_str(seconds, buf, unsigned_flag ? 10 : -10)
+                 - buf);
+  make_truncated_value_warning(current_thd, buf, len, MYSQL_TIMESTAMP_TIME);
+  
+  return 1;
+}
+
+
 /*
   Date formats corresponding to compound %r and %T conversion specifiers
 
@@ -1487,8 +1605,6 @@ int Item_func_now::save_in_field(Field *to, bool no_conversions)
 String *Item_func_sec_to_time::val_str(String *str)
 {
   DBUG_ASSERT(fixed == 1);
-  longlong seconds=(longlong) args[0]->val_int();
-  uint sec;
   TIME ltime;
 
   if ((null_value=args[0]->null_value) || str->alloc(19))
@@ -1497,19 +1613,8 @@ String *Item_func_sec_to_time::val_str(String *str)
     return (String*) 0;
   }
 
-  ltime.neg= 0;
-  if (seconds < 0)
-  {
-    seconds= -seconds;
-    ltime.neg= 1;
-  }
-
-  sec= (uint) ((ulonglong) seconds % 3600);
-  ltime.day= 0;
-  ltime.hour= (uint) (seconds/3600);
-  ltime.minute= sec/60;
-  ltime.second= sec % 60;
-
+  sec_to_time(args[0]->val_int(), args[0]->unsigned_flag, &ltime);
+  
   make_time((DATE_TIME_FORMAT *) 0, &ltime, str);
   return str;
 }
@@ -1518,16 +1623,15 @@ String *Item_func_sec_to_time::val_str(String *str)
 longlong Item_func_sec_to_time::val_int()
 {
   DBUG_ASSERT(fixed == 1);
-  longlong seconds=args[0]->val_int();
-  longlong sign=1;
+  TIME ltime;
+  
   if ((null_value=args[0]->null_value))
     return 0;
-  if (seconds < 0)
-  {
-    seconds= -seconds;
-    sign= -1;
-  }
-  return sign*((seconds / 3600)*10000+((seconds/60) % 60)*100+ (seconds % 60));
+
+  sec_to_time(args[0]->val_int(), args[0]->unsigned_flag, &ltime);
+
+  return (ltime.neg ? -1 : 1) *
+    ((ltime.hour)*10000 + ltime.minute*100 + ltime.second);
 }
 
 
@@ -2531,7 +2635,9 @@ String *Item_func_add_time::val_str(String *str)
   }
   if (l_time1.neg != l_time2.neg)
     l_sign= -l_sign;
-
+  
+  bzero((char *)&l_time3, sizeof(l_time3));
+  
   l_time3.neg= calc_time_diff(&l_time1, &l_time2, -l_sign,
 			      &seconds, &microseconds);
 
@@ -2560,9 +2666,9 @@ String *Item_func_add_time::val_str(String *str)
   }
   
   l_time3.hour+= days*24;
-  if (!make_datetime(l_time1.second_part || l_time2.second_part ?
-		     TIME_MICROSECOND : TIME_ONLY,
-		     &l_time3, str))
+  if (!make_datetime_with_warn(l_time1.second_part || l_time2.second_part ?
+                               TIME_MICROSECOND : TIME_ONLY,
+                               &l_time3, str))
     return str;
 
 null_date:
@@ -2617,6 +2723,8 @@ String *Item_func_timediff::val_str(String *str)
   if (l_time1.neg != l_time2.neg)
     l_sign= -l_sign;
 
+  bzero((char *)&l_time3, sizeof(l_time3));
+  
   l_time3.neg= calc_time_diff(&l_time1, &l_time2, l_sign,
 			      &seconds, &microseconds);
 
@@ -2630,9 +2738,9 @@ String *Item_func_timediff::val_str(String *str)
 
   calc_time_from_sec(&l_time3, (long) seconds, microseconds);
 
-  if (!make_datetime(l_time1.second_part || l_time2.second_part ?
-		     TIME_MICROSECOND : TIME_ONLY,
-		     &l_time3, str))
+  if (!make_datetime_with_warn(l_time1.second_part || l_time2.second_part ?
+                               TIME_MICROSECOND : TIME_ONLY,
+                               &l_time3, str))
     return str;
 
 null_date:
@@ -2650,29 +2758,57 @@ String *Item_func_maketime::val_str(String *str)
 {
   DBUG_ASSERT(fixed == 1);
   TIME ltime;
+  bool overflow= 0;
 
-  long hour=   (long) args[0]->val_int();
-  long minute= (long) args[1]->val_int();
-  long second= (long) args[2]->val_int();
+  longlong hour=   args[0]->val_int();
+  longlong minute= args[1]->val_int();
+  longlong second= args[2]->val_int();
 
   if ((null_value=(args[0]->null_value || 
-		   args[1]->null_value ||
-		   args[2]->null_value || 
-		   minute > 59 || minute < 0 || 
-		   second > 59 || second < 0 ||
-		   str->alloc(19))))
+                   args[1]->null_value ||
+                   args[2]->null_value ||
+                   minute < 0 || minute > 59 ||
+                   second < 0 || second > 59 ||
+                   str->alloc(19))))
     return 0;
 
+  bzero((char *)&ltime, sizeof(ltime));
   ltime.neg= 0;
+
+  /* Check for integer overflows */
   if (hour < 0)
   {
-    ltime.neg= 1;
-    hour= -hour;
+    if (args[0]->unsigned_flag)
+      overflow= 1;
+    else
+      ltime.neg= 1;
+  }
+  if (-hour > UINT_MAX || hour > UINT_MAX)
+    overflow= 1;
+
+  if (!overflow)
+  {
+    ltime.hour=   (uint) ((hour < 0 ? -hour : hour));
+    ltime.minute= (uint) minute;
+    ltime.second= (uint) second;
+  }
+  else
+  {
+    ltime.hour= TIME_MAX_HOUR;
+    ltime.minute= TIME_MAX_MINUTE;
+    ltime.second= TIME_MAX_SECOND;
+    char buf[28];
+    char *ptr= longlong10_to_str(hour, buf, args[0]->unsigned_flag ? 10 : -10);
+    int len = (int)(ptr - buf) +
+      my_sprintf(ptr, (ptr, ":%02u:%02u", (uint)minute, (uint)second));
+    make_truncated_value_warning(current_thd, buf, len, MYSQL_TIMESTAMP_TIME);
+  }
+  
+  if (make_time_with_warn((DATE_TIME_FORMAT *) 0, &ltime, str))
+  {
+    null_value= 1;
+    return 0;
   }
-  ltime.hour=   (ulong) hour;
-  ltime.minute= (ulong) minute;
-  ltime.second= (ulong) second;
-  make_time((DATE_TIME_FORMAT *) 0, &ltime, str);
   return str;
 }
 
@@ -2878,7 +3014,7 @@ bool Item_func_str_to_date::get_date(TIME *ltime, uint fuzzy_date)
     goto null_date;
 
   null_value= 0;
-  bzero((char*) ltime, sizeof(ltime));
+  bzero((char*) ltime, sizeof(*ltime));
   date_time_format.format.str=    (char*) format->ptr();
   date_time_format.format.length= format->length();
   if (extract_date_time(&date_time_format, val->ptr(), val->length(),
diff --git a/sql/time.cc b/sql/time.cc
index ef832ac5a70..ddc5e6435be 100644
--- a/sql/time.cc
+++ b/sql/time.cc
@@ -255,9 +255,9 @@ my_time_t TIME_to_timestamp(THD *thd, const TIME *t, bool *in_dst_time_gap)
 bool
 str_to_time_with_warn(const char *str, uint length, TIME *l_time)
 {
-  int was_cut;
-  bool ret_val= str_to_time(str, length, l_time, &was_cut);
-  if (was_cut)
+  int warning;
+  bool ret_val= str_to_time(str, length, l_time, &warning);
+  if (ret_val || warning)
     make_truncated_value_warning(current_thd, str, length, MYSQL_TIMESTAMP_TIME);
   return ret_val;
 }