Commit bdd82f3a authored by unknown's avatar unknown

Fix for bug #8068 "TIMEDIFF with first negative argument gives wrong

result" (and similar bug in ADDTIME/SUBTIME).

Both Item_func_add_time/Item_func_timediff::val_str() now use
calc_time_diff() function which was backported from 5.0 (and which
was was fixed to properly handle microseconds part of its second 
argument). Also now we correctly set sign of result in case when
first argument is negative and second is positive.  


mysql-test/r/func_sapdb.result:
  Added test for bug #8068 "TIMEDIFF with first negative argument gives wrong 
  result".
mysql-test/t/func_sapdb.test:
  Added test for bug #8068 "TIMEDIFF with first negative argument gives wrong 
  result".
sql/item_timefunc.cc:
  - Backported calc_time_diff() function from 5.0 tree. Changed it to
    accept time value as its second argument when its first argument
    is datetime value. Fixed wrong handling of microsecond part of
    second argument.
  - Item_func_add_time::val_str()/Item_func_timediff::val_str()
    Removed similar pieces of code calculating difference 
    between two datetime values (or their sum) in microseconds.
    Now we use calc_time_diff() function instead. 
    Also now we correctly set sign of result in case when first 
    argument is negative and second is positive.
parent 43c3be4a
...@@ -185,6 +185,7 @@ insert into test values ...@@ -185,6 +185,7 @@ insert into test values
('2001-01-01 01:01:01', '-01:01:01', '-23:59:59', "1997-12-31 23:59:59.000001"), ('2001-01-01 01:01:01', '-01:01:01', '-23:59:59', "1997-12-31 23:59:59.000001"),
('1997-12-31 23:59:59.000001', '-23:59:59', '-01:01:01', '2001-01-01 01:01:01'), ('1997-12-31 23:59:59.000001', '-23:59:59', '-01:01:01', '2001-01-01 01:01:01'),
('2001-01-01 01:01:01', '01:01:01', '-1 01:01:01', null), ('2001-01-01 01:01:01', '01:01:01', '-1 01:01:01', null),
('2001-01-01 01:01:01', '-01:01:01', '1 01:01:01', '2001-01-01 01:01:01'),
('2001-01-01 01:01:01', null, '-1 01:01:01', null), ('2001-01-01 01:01:01', null, '-1 01:01:01', null),
(null, null, null, null), (null, null, null, null),
('2001-01-01 01:01:01', '01:01:01', '1 01:01:01', '2001-01-01 01:01:01'); ('2001-01-01 01:01:01', '01:01:01', '1 01:01:01', '2001-01-01 01:01:01');
...@@ -194,6 +195,7 @@ ttt qqq ...@@ -194,6 +195,7 @@ ttt qqq
2001-01-01 00:00:00 -25:01:00 2001-01-01 00:00:00 -25:01:00
1997-12-31 00:00:00 -25:01:00 1997-12-31 00:00:00 -25:01:00
2001-01-01 02:02:02 -24:00:00 2001-01-01 02:02:02 -24:00:00
2001-01-01 00:00:00 24:00:00
NULL NULL NULL NULL
NULL NULL NULL NULL
2001-01-01 02:02:02 26:02:02 2001-01-01 02:02:02 26:02:02
...@@ -203,6 +205,7 @@ ttt qqq ...@@ -203,6 +205,7 @@ ttt qqq
26305:01:02 22:58:58 26305:01:02 22:58:58
-26305:01:02 -22:58:58 -26305:01:02 -22:58:58
NULL 26:02:02 NULL 26:02:02
00:00:00 -26:02:02
NULL NULL NULL NULL
NULL NULL NULL NULL
00:00:00 -24:00:00 00:00:00 -24:00:00
......
...@@ -100,6 +100,7 @@ insert into test values ...@@ -100,6 +100,7 @@ insert into test values
('2001-01-01 01:01:01', '-01:01:01', '-23:59:59', "1997-12-31 23:59:59.000001"), ('2001-01-01 01:01:01', '-01:01:01', '-23:59:59', "1997-12-31 23:59:59.000001"),
('1997-12-31 23:59:59.000001', '-23:59:59', '-01:01:01', '2001-01-01 01:01:01'), ('1997-12-31 23:59:59.000001', '-23:59:59', '-01:01:01', '2001-01-01 01:01:01'),
('2001-01-01 01:01:01', '01:01:01', '-1 01:01:01', null), ('2001-01-01 01:01:01', '01:01:01', '-1 01:01:01', null),
('2001-01-01 01:01:01', '-01:01:01', '1 01:01:01', '2001-01-01 01:01:01'),
('2001-01-01 01:01:01', null, '-1 01:01:01', null), ('2001-01-01 01:01:01', null, '-1 01:01:01', null),
(null, null, null, null), (null, null, null, null),
('2001-01-01 01:01:01', '01:01:01', '1 01:01:01', '2001-01-01 01:01:01'); ('2001-01-01 01:01:01', '01:01:01', '1 01:01:01', '2001-01-01 01:01:01');
......
...@@ -761,6 +761,81 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, ...@@ -761,6 +761,81 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs,
} }
/*
Calculate difference between two datetime values as seconds + microseconds.
SYNOPSIS
calc_time_diff()
l_time1 - TIME/DATE/DATETIME value
l_time2 - TIME/DATE/DATETIME value
l_sign - 1 absolute values are substracted,
-1 absolute values are added.
seconds_out - Out parameter where difference between
l_time1 and l_time2 in seconds is stored.
microseconds_out- Out parameter where microsecond part of difference
between l_time1 and l_time2 is stored.
NOTE
This function calculates difference between l_time1 and l_time2 absolute
values. So one should set l_sign and correct result if he want to take
signs into account (i.e. for TIME values).
RETURN VALUES
Returns sign of difference.
1 means negative result
0 means positive result
*/
static bool calc_time_diff(TIME *l_time1, TIME *l_time2, int l_sign,
longlong *seconds_out, long *microseconds_out)
{
long days;
bool neg;
longlong microseconds;
/*
We suppose that if first argument is MYSQL_TIMESTAMP_TIME
the second argument should be TIMESTAMP_TIME also.
We should check it before calc_time_diff call.
*/
if (l_time1->time_type == MYSQL_TIMESTAMP_TIME) // Time value
days= l_time1->day - l_sign*l_time2->day;
else
{
days= calc_daynr((uint) l_time1->year,
(uint) l_time1->month,
(uint) l_time1->day);
if (l_time2->time_type == MYSQL_TIMESTAMP_TIME)
days-= l_sign*l_time2->day;
else
days-= l_sign*calc_daynr((uint) l_time2->year,
(uint) l_time2->month,
(uint) l_time2->day);
}
microseconds= ((longlong)days*LL(86400) +
(longlong)(l_time1->hour*3600L +
l_time1->minute*60L +
l_time1->second) -
l_sign*(longlong)(l_time2->hour*3600L +
l_time2->minute*60L +
l_time2->second)) * LL(1000000) +
(longlong)l_time1->second_part -
l_sign*(longlong)l_time2->second_part;
neg= 0;
if (microseconds < 0)
{
microseconds= -microseconds;
neg= 1;
}
*seconds_out= microseconds/1000000L;
*microseconds_out= (long) (microseconds%1000000L);
return neg;
}
longlong Item_func_period_add::val_int() longlong Item_func_period_add::val_int()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
...@@ -2332,11 +2407,11 @@ String *Item_func_add_time::val_str(String *str) ...@@ -2332,11 +2407,11 @@ String *Item_func_add_time::val_str(String *str)
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
TIME l_time1, l_time2, l_time3; TIME l_time1, l_time2, l_time3;
bool is_time= 0; bool is_time= 0;
long microseconds, seconds, days= 0; long days, microseconds;
longlong seconds;
int l_sign= sign; int l_sign= sign;
null_value=0; null_value=0;
l_time3.neg= 0;
if (is_date) // TIMESTAMP function if (is_date) // TIMESTAMP function
{ {
if (get_arg0_date(&l_time1,1) || if (get_arg0_date(&l_time1,1) ||
...@@ -2352,51 +2427,26 @@ String *Item_func_add_time::val_str(String *str) ...@@ -2352,51 +2427,26 @@ String *Item_func_add_time::val_str(String *str)
l_time2.time_type == MYSQL_TIMESTAMP_DATETIME) l_time2.time_type == MYSQL_TIMESTAMP_DATETIME)
goto null_date; goto null_date;
is_time= (l_time1.time_type == MYSQL_TIMESTAMP_TIME); is_time= (l_time1.time_type == MYSQL_TIMESTAMP_TIME);
if (is_time && (l_time2.neg == l_time1.neg && l_time1.neg))
l_time3.neg= 1;
} }
if (l_time1.neg != l_time2.neg) if (l_time1.neg != l_time2.neg)
l_sign= -l_sign; l_sign= -l_sign;
microseconds= l_time1.second_part + l_sign*l_time2.second_part; l_time3.neg= calc_time_diff(&l_time1, &l_time2, -l_sign,
seconds= (l_time1.hour*3600L + l_time1.minute*60L + l_time1.second + &seconds, &microseconds);
(l_time2.day*86400L + l_time2.hour*3600L +
l_time2.minute*60L + l_time2.second)*l_sign);
if (is_time)
seconds+= l_time1.day*86400L;
else
days+= calc_daynr((uint) l_time1.year,(uint) l_time1.month,
(uint) l_time1.day);
seconds= seconds + microseconds/1000000L;
microseconds= microseconds%1000000L;
days+= seconds/86400L;
seconds= seconds%86400L;
if (microseconds < 0) /*
{ If first argument was negative and diff between arguments
microseconds+= 1000000L; is non-zero we need to swap sign to get proper result.
seconds--; */
} if (l_time1.neg && (seconds || microseconds))
if (seconds < 0) l_time3.neg= 1-l_time3.neg; // Swap sign of result
{
days+= seconds/86400L - 1;
seconds+= 86400L;
}
if (days < 0)
{
if (!is_time)
goto null_date;
if (microseconds)
{
microseconds= 1000000L - microseconds;
seconds++;
}
seconds= 86400L - seconds;
days= -(++days);
l_time3.neg= 1;
}
calc_time_from_sec(&l_time3, seconds, microseconds); if (!is_time && l_time3.neg)
goto null_date;
days= (long)(seconds/86400L);
calc_time_from_sec(&l_time3, (long)(seconds%86400L), microseconds);
if (!is_time) if (!is_time)
{ {
get_date_from_daynr(days,&l_time3.year,&l_time3.month,&l_time3.day); get_date_from_daynr(days,&l_time3.year,&l_time3.month,&l_time3.day);
...@@ -2452,8 +2502,8 @@ void Item_func_add_time::print(String *str) ...@@ -2452,8 +2502,8 @@ void Item_func_add_time::print(String *str)
String *Item_func_timediff::val_str(String *str) String *Item_func_timediff::val_str(String *str)
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
longlong microseconds; longlong seconds;
long days; long microseconds;
int l_sign= 1; int l_sign= 1;
TIME l_time1 ,l_time2, l_time3; TIME l_time1 ,l_time2, l_time3;
...@@ -2466,33 +2516,18 @@ String *Item_func_timediff::val_str(String *str) ...@@ -2466,33 +2516,18 @@ String *Item_func_timediff::val_str(String *str)
if (l_time1.neg != l_time2.neg) if (l_time1.neg != l_time2.neg)
l_sign= -l_sign; l_sign= -l_sign;
if (l_time1.time_type == MYSQL_TIMESTAMP_TIME) // Time value l_time3.neg= calc_time_diff(&l_time1, &l_time2, l_sign,
days= l_time1.day - l_sign*l_time2.day; &seconds, &microseconds);
else // DateTime value
days= (calc_daynr((uint) l_time1.year, /*
(uint) l_time1.month, For MYSQL_TIMESTAMP_TIME only:
(uint) l_time1.day) - If first argument was negative and diff between arguments
l_sign*calc_daynr((uint) l_time2.year, is non-zero we need to swap sign to get proper result.
(uint) l_time2.month, */
(uint) l_time2.day)); if (l_time1.neg && (seconds || microseconds))
l_time3.neg= 1-l_time3.neg; // Swap sign of result
microseconds= ((longlong)days*86400L +
l_time1.hour*3600L + l_time1.minute*60L + l_time1.second -
(longlong)l_sign*(l_time2.hour*3600L + l_time2.minute*60L +
l_time2.second))*1000000 +
l_time1.second_part - l_sign*l_time2.second_part;
l_time3.neg= 0;
if (microseconds < 0)
{
microseconds= -microseconds;
l_time3.neg= 1;
}
if ((l_time2.neg == l_time1.neg) && l_time1.neg && microseconds)
l_time3.neg= l_time3.neg ? 0 : 1;
calc_time_from_sec(&l_time3, (long)(microseconds/1000000), calc_time_from_sec(&l_time3, (long) seconds, microseconds);
(long)(microseconds%1000000));
if (!make_datetime(l_time1.second_part || l_time2.second_part ? if (!make_datetime(l_time1.second_part || l_time2.second_part ?
TIME_MICROSECOND : TIME_ONLY, TIME_MICROSECOND : TIME_ONLY,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment