/* Copyright (c) 2000, 2010, Oracle and/or its affiliates.
Copyright ( c ) 2009 , 2020 , MariaDB Corporation .
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/* Functions to handle date and time */
#include "mariadb.h"
#include "sql_time.h"
#include "tztime.h" // struct Time_zone
#include "sql_class.h" // THD
#include <m_ctype.h>
#define MAX_DAY_NUMBER 3652424 L
/* Some functions to calculate dates */
/*
Name description of interval names used in statements .
' interval_type_to_name ' is ordered and sorted on interval size and
interval complexity .
Order of elements in ' interval_type_to_name ' should correspond to
the order of elements in ' interval_type ' enum
See also interval_type , interval_names , append_interval
*/
LEX_CSTRING interval_type_to_name[INTERVAL_LAST] = {
{ STRING_WITH_LEN("YEAR" )},
{ STRING_WITH_LEN("QUARTER" )},
{ STRING_WITH_LEN("MONTH" )},
{ STRING_WITH_LEN("WEEK" )},
{ STRING_WITH_LEN("DAY" )},
{ STRING_WITH_LEN("HOUR" )},
{ STRING_WITH_LEN("MINUTE" )},
{ STRING_WITH_LEN("SECOND" )},
{ STRING_WITH_LEN("MICROSECOND" )},
{ STRING_WITH_LEN("YEAR_MONTH" )},
{ STRING_WITH_LEN("DAY_HOUR" )},
{ STRING_WITH_LEN("DAY_MINUTE" )},
{ STRING_WITH_LEN("DAY_SECOND" )},
{ STRING_WITH_LEN("HOUR_MINUTE" )},
{ STRING_WITH_LEN("HOUR_SECOND" )},
{ STRING_WITH_LEN("MINUTE_SECOND" )},
{ STRING_WITH_LEN("DAY_MICROSECOND" )},
{ STRING_WITH_LEN("HOUR_MICROSECOND" )},
{ STRING_WITH_LEN("MINUTE_MICROSECOND" )},
{ STRING_WITH_LEN("SECOND_MICROSECOND" )}
};
int append_interval(String *str, interval_type int_type, const INTERVAL &interval)
{
char buf[64 ];
size_t len;
switch (int_type) {
case INTERVAL_YEAR:
len= my_snprintf(buf,sizeof (buf),"%lu" , interval.year);
break ;
case INTERVAL_QUARTER:
case INTERVAL_MONTH:
len= my_snprintf(buf,sizeof (buf),"%lu" , interval.month);
int_type=INTERVAL_MONTH;
break ;
case INTERVAL_WEEK:
case INTERVAL_DAY:
len= my_snprintf(buf,sizeof (buf),"%lu" , interval.day);
int_type=INTERVAL_DAY;
break ;
case INTERVAL_HOUR:
len= my_snprintf(buf,sizeof (buf),"%lu" , interval.hour);
break ;
case INTERVAL_MINUTE:
len= my_snprintf(buf,sizeof (buf),"%llu" , interval.minute);
break ;
case INTERVAL_SECOND:
len= my_snprintf(buf,sizeof (buf),"%llu" , interval.second);
break ;
case INTERVAL_MICROSECOND:
len= my_snprintf(buf,sizeof (buf),"%llu" , interval.second_part);
break ;
case INTERVAL_YEAR_MONTH:
len= my_snprintf(buf,sizeof (buf),"'%lu-%02lu'" ,
interval.year, interval.month);
break ;
case INTERVAL_DAY_HOUR:
len= my_snprintf(buf,sizeof (buf),"'%lu %lu'" , interval.day, interval.hour);
break ;
case INTERVAL_DAY_MINUTE:
len= my_snprintf(buf,sizeof (buf),"'%lu %lu:%02llu'" ,
interval.day, interval.hour, interval.minute);
break ;
case INTERVAL_DAY_SECOND:
len= my_snprintf(buf,sizeof (buf),"'%lu %lu:%02llu:%02llu'" ,
interval.day, interval.hour, interval.minute, interval.second);
break ;
case INTERVAL_HOUR_MINUTE:
len= my_snprintf(buf,sizeof (buf),"'%lu:%02llu'" , interval.hour, interval.minute);
break ;
case INTERVAL_HOUR_SECOND:
len= my_snprintf(buf,sizeof (buf),"'%lu:%02llu:%02llu'" ,
interval.hour, interval.minute, interval.second);
break ;
case INTERVAL_MINUTE_SECOND:
len= my_snprintf(buf,sizeof (buf),"'%llu:%02llu'" , interval.minute, interval.second);
break ;
case INTERVAL_DAY_MICROSECOND:
len= my_snprintf(buf,sizeof (buf),"'%lu %lu:%02llu:%02llu.%06llu'" ,
interval.day, interval.hour, interval.minute,
interval.second, interval.second_part);
break ;
case INTERVAL_HOUR_MICROSECOND:
len= my_snprintf(buf,sizeof (buf),"'%lu:%02llu:%02llu.%06llu'" ,
interval.hour, interval.minute, interval.second,
interval.second_part);
break ;
case INTERVAL_MINUTE_MICROSECOND:
len= my_snprintf(buf,sizeof (buf),"'%llu:%02llu.%06llu'" ,
interval.minute, interval.second, interval.second_part);
break ;
case INTERVAL_SECOND_MICROSECOND:
len= my_snprintf(buf,sizeof (buf),"%llu.%06llu" , interval.second, interval.second_part);
break ;
default :
DBUG_ASSERT(0 );
len= 0 ;
}
return str->append(buf, len) || str->append(' ' ) ||
str->append(interval_type_to_name + int_type);
}
/*
Calc weekday from daynr
Returns 0 for monday , 1 for tuesday . . .
*/
int calc_weekday(long daynr,bool sunday_first_day_of_week)
{
DBUG_ENTER("calc_weekday" );
DBUG_RETURN ((int ) ((daynr + 5 L + (sunday_first_day_of_week ? 1 L : 0 L)) % 7 ));
}
/*
The bits in week_format has the following meaning :
WEEK_MONDAY_FIRST ( 0 ) If not set Sunday is first day of week
If set Monday is first day of week
WEEK_YEAR ( 1 ) If not set Week is in range 0 - 53
Week 0 is returned for the last week of the previous year ( for
a date at start of january ) In this case one can get 53 for the
first week of next year . This flag ensures that the week is
relevant for the given year . Note that this flag is only
relevant if WEEK_JANUARY is not set .
If set Week is in range 1 - 53 .
In this case one may get week 53 for a date in January ( when
the week is that last week of previous year ) and week 1 for a
date in December .
WEEK_FIRST_WEEKDAY ( 2 ) If not set Weeks are numbered according
to ISO 8601 : 1988
If set The week that contains the first
' first - day - of - week ' is week 1 .
ISO 8601 : 1988 means that if the week containing January 1 has
four or more days in the new year , then it is week 1 ;
Otherwise it is the last week of the previous year , and the
next week is week 1 .
*/
uint calc_week(const MYSQL_TIME *l_time, uint week_behaviour, uint *year)
{
uint days;
ulong daynr=calc_daynr(l_time->year,l_time->month,l_time->day);
ulong first_daynr=calc_daynr(l_time->year,1 ,1 );
bool monday_first= MY_TEST(week_behaviour & WEEK_MONDAY_FIRST);
bool week_year= MY_TEST(week_behaviour & WEEK_YEAR);
bool first_weekday= MY_TEST(week_behaviour & WEEK_FIRST_WEEKDAY);
uint weekday=calc_weekday(first_daynr, !monday_first);
*year=l_time->year;
if (l_time->month == 1 && l_time->day <= 7 -weekday)
{
if (!week_year &&
((first_weekday && weekday != 0 ) ||
(!first_weekday && weekday >= 4 )))
return 0 ;
week_year= 1 ;
(*year)--;
first_daynr-= (days=calc_days_in_year(*year));
weekday= (weekday + 53 *7 - days) % 7 ;
}
if ((first_weekday && weekday != 0 ) ||
(!first_weekday && weekday >= 4 ))
days= daynr - (first_daynr+ (7 -weekday));
else
days= daynr - (first_daynr - weekday);
if (week_year && days >= 52 *7 )
{
weekday= (weekday + calc_days_in_year(*year)) % 7 ;
if ((!first_weekday && weekday < 4 ) ||
(first_weekday && weekday == 0 ))
{
(*year)++;
return 1 ;
}
}
return days/7 +1 ;
}
/* Change a daynr to year, month and day */
/* Daynr 0 is returned as date 00.00.00 */
bool get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month,
uint *ret_day)
{
uint year,temp,leap_day,day_of_year,days_in_year;
uchar *month_pos;
DBUG_ENTER("get_date_from_daynr" );
if (daynr < 366 || daynr > MAX_DAY_NUMBER)
DBUG_RETURN(1 );
year= (uint) (daynr*100 / 36525 L);
temp=(((year-1 )/100 +1 )*3 )/4 ;
day_of_year=(uint) (daynr - (long ) year * 365 L) - (year-1 )/4 +temp;
while (day_of_year > (days_in_year= calc_days_in_year(year)))
{
day_of_year-=days_in_year;
(year)++;
}
leap_day=0 ;
if (days_in_year == 366 )
{
if (day_of_year > 31 +28 )
{
day_of_year--;
if (day_of_year == 31 +28 )
leap_day=1 ; /* Handle leapyears leapday */
}
}
*ret_month=1 ;
for (month_pos= days_in_month ;
day_of_year > (uint) *month_pos ;
day_of_year-= *(month_pos++), (*ret_month)++)
;
*ret_year=year;
*ret_day=day_of_year+leap_day;
DBUG_RETURN(0 );
}
/* Functions to handle periods */
ulong convert_period_to_month(ulong period)
{
ulong a,b;
if (period == 0 || period > 999912 )
return 0 L;
if ((a=period/100 ) < YY_PART_YEAR)
a+=2000 ;
else if (a < 100 )
a+=1900 ;
b=period%100 ;
return a*12 +b-1 ;
}
ulong convert_month_to_period(ulong month)
{
ulong year;
if (month == 0 L)
return 0 L;
if ((year=month/12 ) < 100 )
{
year+=(year < YY_PART_YEAR) ? 2000 : 1900 ;
}
return year*100 +month%12 +1 ;
}
bool
check_date_with_warn(THD *thd, const MYSQL_TIME *ltime,
date_conv_mode_t fuzzydate, timestamp_type ts_type)
{
int unused;
if (check_date(ltime, fuzzydate, &unused))
{
ErrConvTime str(ltime);
make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
&str, ts_type, nullptr, nullptr, nullptr);
return true ;
}
return false ;
}
bool
adjust_time_range_with_warn(THD *thd, MYSQL_TIME *ltime, uint dec)
{
MYSQL_TIME copy= *ltime;
ErrConvTime str(©);
int warnings= 0 ;
if (check_time_range(ltime, dec, &warnings))
return true ;
if (warnings)
thd->push_warning_truncated_wrong_value("time" , str.ptr());
return false ;
}
/*
Convert a string to 8 - bit representation ,
for use in str_to_time / str_to_date / str_to_date .
In the future to_ascii ( ) can be extended to convert
non - ASCII digits to ASCII digits
( for example , ARABIC - INDIC , DEVANAGARI , BENGALI , and so on )
so DATE / TIME / DATETIME values understand digits in the
respected scripts .
*/
static uint
to_ascii(CHARSET_INFO *cs,
const char *src, size_t src_length,
char *dst, size_t dst_length)
{
int cnvres;
my_wc_t wc;
const char *srcend= src + src_length;
char *dst0= dst, *dstend= dst + dst_length - 1 ;
while (dst < dstend &&
(cnvres= cs->mb_wc(&wc,
(const uchar*) src,
(const uchar*) srcend)) > 0 &&
wc < 128 )
{
src+= cnvres;
*dst++= static_cast <char >(wc);
}
*dst= '\0' ;
return (uint)(dst - dst0);
}
class TemporalAsciiBuffer: public LEX_CSTRING
{
char cnv[32 ];
public :
TemporalAsciiBuffer(const char *str, size_t length, CHARSET_INFO *cs)
{
if ((cs->state & MY_CS_NONASCII) != 0 )
{
LEX_CSTRING::str= cnv;
LEX_CSTRING::length= to_ascii(cs, str, length, cnv, sizeof (cnv));
}
else
{
LEX_CSTRING::str= str;
LEX_CSTRING::length= length;
}
}
};
/* Character set-aware version of ascii_to_datetime_or_date_or_time() */
bool Temporal::str_to_datetime_or_date_or_time(THD *thd, MYSQL_TIME_STATUS *st,
const char *str, size_t length,
CHARSET_INFO *cs,
date_mode_t fuzzydate)
{
TemporalAsciiBuffer tmp(str, length, cs);
return ascii_to_datetime_or_date_or_time(st, tmp.str, tmp.length, fuzzydate)||
add_nanoseconds(thd, &st->warnings, fuzzydate, st->nanoseconds);
}
/* Character set-aware version of str_to_datetime_or_date() */
bool Temporal::str_to_datetime_or_date(THD *thd, MYSQL_TIME_STATUS *status,
const char *str, size_t length,
CHARSET_INFO *cs,
date_mode_t flags)
{
TemporalAsciiBuffer tmp(str, length, cs);
return ascii_to_datetime_or_date(status, tmp.str, tmp.length, flags) ||
add_nanoseconds(thd, &status->warnings, flags, status->nanoseconds);
}
/* Character set-aware version of ascii_to_temporal() */
bool Temporal::str_to_temporal(THD *thd, MYSQL_TIME_STATUS *status,
const char *str, size_t length, CHARSET_INFO *cs,
date_mode_t flags)
{
TemporalAsciiBuffer tmp(str, length, cs);
return ascii_to_temporal(status, tmp.str, tmp.length, flags) ||
add_nanoseconds(thd, &status->warnings, flags, status->nanoseconds);
}
/* Character set-aware version of str_to_DDhhmmssff() */
bool Interval_DDhhmmssff::str_to_DDhhmmssff(MYSQL_TIME_STATUS *status,
const char *str, size_t length,
CHARSET_INFO *cs, ulong max_hour)
{
TemporalAsciiBuffer tmp(str, length, cs);
bool rc= ::str_to_DDhhmmssff(tmp.str, tmp.length, this , UINT_MAX32, status);
DBUG_ASSERT(status->warnings || !rc);
return rc;
}
/*
Convert a timestamp string to a MYSQL_TIME value and produce a warning
if string was truncated during conversion .
NOTE
See description of str_to_datetime_xxx ( ) for more information .
*/
bool
str_to_datetime_with_warn(THD *thd, CHARSET_INFO *cs,
const char *str, size_t length, MYSQL_TIME *to,
date_mode_t mode)
{
Temporal::Warn_push warn(thd, nullptr, nullptr, nullptr, to, mode);
Temporal_hybrid *t= new (to) Temporal_hybrid(thd, &warn, str, length, cs, mode);
return !t->is_valid_temporal();
}
bool double_to_datetime_with_warn(THD *thd, double value, MYSQL_TIME *ltime,
date_mode_t fuzzydate,
const TABLE_SHARE *s, const char *field_name)
{
Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
s ? s->table_name.str : nullptr,
field_name, ltime, fuzzydate);
Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, value, fuzzydate);
return !t->is_valid_temporal();
}
bool decimal_to_datetime_with_warn(THD *thd, const my_decimal *value,
MYSQL_TIME *ltime,
date_mode_t fuzzydate,
const TABLE_SHARE *s, const char *field_name)
{
Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
s ? s->table_name.str : nullptr,
field_name, ltime, fuzzydate);
Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, value, fuzzydate);
return !t->is_valid_temporal();
}
bool int_to_datetime_with_warn(THD *thd, const Longlong_hybrid &nr,
MYSQL_TIME *ltime,
date_mode_t fuzzydate,
const TABLE_SHARE *s, const char *field_name)
{
/*
Note : conversion from an integer to TIME can overflow to ' 838 : 59 : 59 . 999999 ' ,
so the conversion result can have fractional digits .
*/
Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
s ? s->table_name.str : nullptr,
field_name, ltime, fuzzydate);
Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, nr, fuzzydate);
return !t->is_valid_temporal();
}
/*
Convert a datetime from broken - down MYSQL_TIME representation to
corresponding TIMESTAMP value .
SYNOPSIS
TIME_to_timestamp ( )
thd - current thread
t - datetime in broken - down representation ,
error_code - 0 , if the conversion was successful ;
ER_WARN_DATA_OUT_OF_RANGE , if t contains datetime value
which is out of TIMESTAMP range ;
ER_WARN_INVALID_TIMESTAMP , if t represents value which
doesn ' t exists ( falls into the spring time - gap ) .
RETURN
Number seconds in UTC since start of Unix Epoch corresponding to t .
0 - in case of ER_WARN_DATA_OUT_OF_RANGE
*/
my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code)
{
thd->used|= THD::TIME_ZONE_USED;
return thd->variables.time_zone->TIME_to_gmt_sec(t, error_code);
}
/*
Convert a system time structure to TIME
*/
void localtime_to_TIME(MYSQL_TIME *to, struct tm *from)
{
to->neg=0 ;
to->second_part=0 ;
to->year= (int ) ((from->tm_year+1900 ) % 10000 );
to->month= (int ) from->tm_mon+1 ;
to->day= (int ) from->tm_mday;
to->hour= (int ) from->tm_hour;
to->minute= (int ) from->tm_min;
to->second= (int ) from->tm_sec;
}
void calc_time_from_sec(MYSQL_TIME *to, ulong seconds, ulong microseconds)
{
long t_seconds;
// to->neg is not cleared, it may already be set to a useful value
to->time_type= MYSQL_TIMESTAMP_TIME;
to->year= 0 ;
to->month= 0 ;
to->day= 0 ;
to->hour= seconds/3600 L;
t_seconds= seconds%3600 L;
to->minute= t_seconds/60 L;
to->second= t_seconds%60 L;
to->second_part= microseconds;
}
KNOWN_DATE_TIME_FORMAT known_date_time_formats[6 ]=
{
{"USA" , "%m.%d.%Y" , "%Y-%m-%d %H.%i.%s" , "%h:%i:%s %p" },
{"JIS" , "%Y-%m-%d" , "%Y-%m-%d %H:%i:%s" , "%H:%i:%s" },
{"ISO" , "%Y-%m-%d" , "%Y-%m-%d %H:%i:%s" , "%H:%i:%s" },
{"EUR" , "%d.%m.%Y" , "%Y-%m-%d %H.%i.%s" , "%H.%i.%s" },
{"INTERNAL" , "%Y%m%d" , "%Y%m%d%H%i%s" , "%H%i%s" },
{ 0 , 0 , 0 , 0 }
};
const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format,
timestamp_type type)
{
switch (type) {
case MYSQL_TIMESTAMP_DATE:
return format->date_format;
case MYSQL_TIMESTAMP_DATETIME:
return format->datetime_format;
case MYSQL_TIMESTAMP_TIME:
return format->time_format;
default :
DBUG_ASSERT(0 ); // Impossible
return 0 ;
}
}
/**
Convert TIME / DATE / DATETIME value to String .
@ param l_time DATE value
@ param OUT str String to convert to
@ param dec Number of fractional digits .
*/
bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec)
{
if (str->alloc(MAX_DATE_STRING_REP_LENGTH))
return true ;
str->set_charset(&my_charset_numeric);
str->length(my_TIME_to_str(ltime, const_cast <char *>(str->ptr()), dec));
return false ;
}
void make_truncated_value_warning(THD *thd,
Sql_condition::enum_warning_level level,
const ErrConv *sval,
timestamp_type time_type,
const char *db_name, const char *table_name,
const char *field_name)
{
const char *type_str= Temporal::type_name_by_timestamp_type(time_type);
return thd->push_warning_wrong_or_truncated_value
(level, time_type <= MYSQL_TIMESTAMP_ERROR, type_str, sval->ptr(),
db_name, table_name, field_name);
}
/* Daynumber from year 0 to 9999-12-31 */
#define COMBINE(X) \
(((((X)->day * 24 LL + (X)->hour) * 60 LL + \
(X)->minute) * 60 LL + (X)->second)*1000000 LL + \
(X)->second_part)
#define GET_PART(X, N) X % N ## LL; X/= N ## LL
bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
const INTERVAL &interval, bool push_warn)
{
long sign;
sign= (interval.neg == (bool )ltime->neg ? 1 : -1 );
switch (int_type) {
case INTERVAL_SECOND:
case INTERVAL_SECOND_MICROSECOND:
case INTERVAL_MICROSECOND:
case INTERVAL_MINUTE:
case INTERVAL_HOUR:
case INTERVAL_MINUTE_MICROSECOND:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_HOUR_MICROSECOND:
case INTERVAL_HOUR_SECOND:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_DAY_MICROSECOND:
case INTERVAL_DAY_SECOND:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY:
{
longlong usec, daynr;
my_bool neg= 0 ;
enum enum_mysql_timestamp_type time_type= ltime->time_type;
if (((ulonglong) interval.day +
(ulonglong) interval.hour / 24 +
(ulonglong) interval.minute / 24 / 60 +
(ulonglong) interval.second / 24 / 60 / 60 ) > MAX_DAY_NUMBER)
goto invalid_date;
if (time_type != MYSQL_TIMESTAMP_TIME)
ltime->day+= calc_daynr(ltime->year, ltime->month, 1 ) - 1 ;
usec= COMBINE(ltime) + sign*COMBINE(&interval);
if (usec < 0 )
{
neg= 1 ;
usec= -usec;
}
ltime->second_part= GET_PART(usec, 1000000 );
ltime->second= GET_PART(usec, 60 );
ltime->minute= GET_PART(usec, 60 );
ltime->neg^= neg;
if (time_type == MYSQL_TIMESTAMP_TIME)
{
if (usec > TIME_MAX_HOUR)
goto invalid_date;
ltime->hour= static_cast <uint>(usec);
ltime->day= 0 ;
return 0 ;
}
else if (ltime->neg)
goto invalid_date;
if (int_type != INTERVAL_DAY)
ltime->time_type= MYSQL_TIMESTAMP_DATETIME; // Return full date
ltime->hour= GET_PART(usec, 24 );
daynr= usec;
/* Day number from year 0 to 9999-12-31 */
if (get_date_from_daynr((long ) daynr, <ime->year, <ime->month,
<ime->day))
goto invalid_date;
break ;
}
case INTERVAL_WEEK:
{
longlong period= calc_daynr(ltime->year, ltime->month, ltime->day) +
(longlong) sign * (longlong) interval.day;
if (period < 0 || period > 0 x7FFFFFFF)
goto invalid_date;
/* Daynumber from year 0 to 9999-12-31 */
if (get_date_from_daynr((long ) period,<ime->year,<ime->month,
<ime->day))
goto invalid_date;
break ;
}
case INTERVAL_YEAR:
ltime->year+= sign * (long ) interval.year;
if ((ulong) ltime->year >= 10000 L)
goto invalid_date;
if (ltime->month == 2 && ltime->day == 29 &&
calc_days_in_year(ltime->year) != 366 )
ltime->day=28 ; // Was leap-year
break ;
case INTERVAL_YEAR_MONTH:
case INTERVAL_QUARTER:
case INTERVAL_MONTH:
{
long period= (ltime->year*12 + sign * (long ) interval.year*12 +
ltime->month-1 + sign * (long ) interval.month);
if ((ulong) period >= 120000 L)
goto invalid_date;
ltime->year= (uint) (period / 12 );
ltime->month= (uint) (period % 12 L)+1 ;
/* Adjust day if the new month doesn't have enough days */
if (ltime->day > days_in_month[ltime->month-1 ])
{
ltime->day = days_in_month[ltime->month-1 ];
if (ltime->month == 2 && calc_days_in_year(ltime->year) == 366 )
ltime->day++; // Leap-year
}
break ;
}
default :
goto null_date;
}
if (ltime->time_type != MYSQL_TIMESTAMP_TIME)
return 0 ; // Ok
invalid_date:
if (push_warn)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_DATETIME_FUNCTION_OVERFLOW,
ER_THD(thd, ER_DATETIME_FUNCTION_OVERFLOW),
ltime->time_type == MYSQL_TIMESTAMP_TIME ?
"time" : "datetime" );
}
null_date:
return 1 ;
}
/*
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 MYSQL_TIME values ) .
RETURN VALUES
Returns sign of difference .
1 means negative result
0 means positive result
*/
bool
calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
int l_sign, ulonglong *seconds_out, ulong *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= (long )l_time1->day - l_sign * (long )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 * (long )l_time2->day;
else
days-= l_sign*calc_daynr((uint) l_time2->year,
(uint) l_time2->month,
(uint) l_time2->day);
}
microseconds= ((longlong)days * SECONDS_IN_24H +
(longlong)(l_time1->hour*3600 LL +
l_time1->minute*60 L +
l_time1->second) -
l_sign*(longlong)(l_time2->hour*3600 LL +
l_time2->minute*60 L +
l_time2->second)) * 1000000 LL +
(longlong)l_time1->second_part -
l_sign*(longlong)l_time2->second_part;
neg= 0 ;
if (microseconds < 0 )
{
microseconds= -microseconds;
neg= 1 ;
}
*seconds_out= (ulonglong) microseconds/1000000 L;
*microseconds_out= (ulong) (microseconds%1000000 L);
return neg;
}
bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
int l_sign, MYSQL_TIME *l_time3, date_mode_t fuzzydate)
{
ulonglong seconds;
ulong microseconds;
bzero((char *) l_time3, sizeof (*l_time3));
l_time3->neg= calc_time_diff(l_time1, l_time2, l_sign,
&seconds, µseconds);
/*
For MYSQL_TIMESTAMP_TIME only :
If first argument was negative and diff between arguments
is non - zero we need to swap sign to get proper result .
*/
if (l_time1->neg && (seconds || microseconds))
l_time3->neg= 1 - l_time3->neg; // Swap sign of result
/*
seconds is longlong , when casted to long it may become a small number
even if the original seconds value was too large and invalid .
as a workaround we limit seconds by a large invalid long number
( " invalid " means > TIME_MAX_SECOND )
*/
set_if_smaller(seconds, INT_MAX32);
calc_time_from_sec(l_time3, (ulong) seconds, microseconds);
return ((fuzzydate & TIME_NO_ZERO_DATE) && (seconds == 0 ) &&
(microseconds == 0 ));
}
/*
Compares 2 MYSQL_TIME structures
SYNOPSIS
my_time_compare ( )
a - first time
b - second time
RETURN VALUE
- 1 - a < b
0 - a = = b
1 - a > b
*/
int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b)
{
ulonglong a_t= pack_time(a);
ulonglong b_t= pack_time(b);
if (a_t < b_t)
return -1 ;
if (a_t > b_t)
return 1 ;
return 0 ;
}
/**
Convert TIME to DATETIME .
@ param ltime The value to convert .
@ return false on success , true of error ( negative time ) .
*/
bool time_to_datetime(MYSQL_TIME *ltime)
{
DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_TIME);
DBUG_ASSERT(ltime->year == 0 );
DBUG_ASSERT(ltime->month == 0 );
DBUG_ASSERT(ltime->day == 0 );
if (ltime->neg)
return true ;
uint day= ltime->hour / 24 ;
ltime->hour%= 24 ;
ltime->month= day / 31 ;
ltime->day= day % 31 ;
return false ;
}
/*** Conversion from TIME to DATETIME ***/
/*
Simple case : TIME is within normal 24 hours internal .
Mix DATE part of ldate and TIME part of ltime together .
*/
static void
mix_date_and_time_simple(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
{
DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
ldate->hour= ltime->hour;
ldate->minute= ltime->minute;
ldate->second= ltime->second;
ldate->second_part= ltime->second_part;
ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
}
/*
Complex case : TIME is negative or outside of the 24 hour interval .
*/
static void
mix_date_and_time_complex(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
{
DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
ulonglong seconds;
ulong days, useconds;
int sign= ltime->neg ? 1 : -1 ;
ldate->neg= calc_time_diff(ldate, ltime, sign, &seconds, &useconds);
DBUG_ASSERT(!ldate->neg);
DBUG_ASSERT(ldate->year > 0 );
days= (long ) (seconds / SECONDS_IN_24H);
calc_time_from_sec(ldate, seconds % SECONDS_IN_24H, useconds);
get_date_from_daynr(days, &ldate->year, &ldate->month, &ldate->day);
ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
}
/**
Mix a date value and a time value .
@ param IN / OUT ldate Date value .
@ param ltime Time value .
*/
static void
mix_date_and_time(MYSQL_TIME *to, const MYSQL_TIME *from)
{
if (!from->neg && from->hour < 24 )
mix_date_and_time_simple(to, from);
else
mix_date_and_time_complex(to, from);
}
/**
Get current date in DATE format
*/
void set_current_date(THD *thd, MYSQL_TIME *to)
{
thd->variables.time_zone->gmt_sec_to_TIME(to, thd->query_start());
thd->used|= THD::TIME_ZONE_USED;
datetime_to_date(to);
}
/**
5 . 5 compatible conversion from TIME to DATETIME
*/
static bool
time_to_datetime_old(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
{
DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
if (from->neg)
return true ;
/* Set the date part */
uint day= from->hour / 24 ;
to->day= day % 31 ;
to->month= day / 31 ;
to->year= 0 ;
/* Set the time part */
to->hour= from->hour % 24 ;
to->minute= from->minute;
to->second= from->second;
to->second_part= from->second_part;
/* set sign and type */
to->neg= 0 ;
to->time_type= MYSQL_TIMESTAMP_DATETIME;
return false ;
}
/**
Convert time to datetime .
The time value is added to the current datetime value .
@ param IN ltime Time value to convert from .
@ param OUT ltime2 Datetime value to convert to .
*/
bool
time_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
{
if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)
return time_to_datetime_old(thd, from, to);
set_current_date(thd, to);
mix_date_and_time(to, from);
return false ;
}
bool
time_to_datetime_with_warn(THD *thd,
const MYSQL_TIME *from, MYSQL_TIME *to,
date_conv_mode_t fuzzydate)
{
int warn= 0 ;
DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
/*
After time_to_datetime ( ) we need to do check_date ( ) , as
the caller may want TIME_NO_ZERO_DATE or TIME_NO_ZERO_IN_DATE .
Note , the SQL standard time - > datetime conversion mode always returns
a valid date based on CURRENT_DATE . So we need to do check_date ( )
only in the old mode .
*/
if (time_to_datetime(thd, from, to) ||
((thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) &&
check_date(to, fuzzydate, &warn)))
{
ErrConvTime str(from);
thd->push_warning_truncated_wrong_value("datetime" , str.ptr());
return true ;
}
return false ;
}
longlong pack_time(const MYSQL_TIME *my_time)
{
return ((((((my_time->year * 13 ULL +
my_time->month) * 32 ULL +
my_time->day) * 24 ULL +
my_time->hour) * 60 ULL +
my_time->minute) * 60 ULL +
my_time->second) * 1000000 ULL +
my_time->second_part) * (my_time->neg ? -1 : 1 );
}
#define get_one(WHERE, FACTOR) WHERE= (ulong)(packed % FACTOR); packed/= FACTOR
void unpack_time(longlong packed, MYSQL_TIME *my_time,
enum_mysql_timestamp_type ts_type)
{
if ((my_time->neg= packed < 0 ))
packed= -packed;
get_one(my_time->second_part, 1000000 ULL);
get_one(my_time->second, 60 U);
get_one(my_time->minute, 60 U);
get_one(my_time->hour, 24 U);
get_one(my_time->day, 32 U);
get_one(my_time->month, 13 U);
my_time->year= (uint)packed;
my_time->time_type= ts_type;
switch (ts_type) {
case MYSQL_TIMESTAMP_TIME:
my_time->hour+= (my_time->month * 32 + my_time->day) * 24 ;
my_time->month= my_time->day= 0 ;
break ;
case MYSQL_TIMESTAMP_DATE:
my_time->hour= my_time->minute= my_time->second= my_time->second_part= 0 ;
break ;
case MYSQL_TIMESTAMP_NONE:
case MYSQL_TIMESTAMP_ERROR:
DBUG_ASSERT(0 );
case MYSQL_TIMESTAMP_DATETIME:
break ;
}
}
Messung V0.5 in Prozent C=96 H=94 G=94
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland