1 /* Copyright (c) 2000, 2010, Oracle and/or its affiliates.
2    Copyright (c) 2009, 2020, MariaDB Corporation.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; version 2 of the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
16 
17 
18 /* Functions to handle date and time */
19 
20 #include "mariadb.h"
21 #include "sql_time.h"
22 #include "tztime.h"                      // struct Time_zone
23 #include "sql_class.h"                   // THD
24 #include <m_ctype.h>
25 
26 
27 #define MAX_DAY_NUMBER 3652424L
28 
29 	/* Some functions to calculate dates */
30 
31 /*
32   Name description of interval names used in statements.
33 
34   'interval_type_to_name' is ordered and sorted on interval size and
35   interval complexity.
36   Order of elements in 'interval_type_to_name' should correspond to
37   the order of elements in 'interval_type' enum
38 
39   See also interval_type, interval_names, append_interval
40 */
41 
42 LEX_CSTRING interval_type_to_name[INTERVAL_LAST] = {
43   { STRING_WITH_LEN("YEAR")},
44   { STRING_WITH_LEN("QUARTER")},
45   { STRING_WITH_LEN("MONTH")},
46   { STRING_WITH_LEN("WEEK")},
47   { STRING_WITH_LEN("DAY")},
48   { STRING_WITH_LEN("HOUR")},
49   { STRING_WITH_LEN("MINUTE")},
50   { STRING_WITH_LEN("SECOND")},
51   { STRING_WITH_LEN("MICROSECOND")},
52   { STRING_WITH_LEN("YEAR_MONTH")},
53   { STRING_WITH_LEN("DAY_HOUR")},
54   { STRING_WITH_LEN("DAY_MINUTE")},
55   { STRING_WITH_LEN("DAY_SECOND")},
56   { STRING_WITH_LEN("HOUR_MINUTE")},
57   { STRING_WITH_LEN("HOUR_SECOND")},
58   { STRING_WITH_LEN("MINUTE_SECOND")},
59   { STRING_WITH_LEN("DAY_MICROSECOND")},
60   { STRING_WITH_LEN("HOUR_MICROSECOND")},
61   { STRING_WITH_LEN("MINUTE_MICROSECOND")},
62   { STRING_WITH_LEN("SECOND_MICROSECOND")}
63 };
64 
append_interval(String * str,interval_type int_type,const INTERVAL & interval)65 int append_interval(String *str, interval_type int_type, const INTERVAL &interval)
66 {
67   char buf[64];
68   size_t len;
69   switch (int_type) {
70   case INTERVAL_YEAR:
71     len= my_snprintf(buf,sizeof(buf),"%u", interval.year);
72     break;
73   case INTERVAL_QUARTER:
74   case INTERVAL_MONTH:
75     len= my_snprintf(buf,sizeof(buf),"%u", interval.month);
76     int_type=INTERVAL_MONTH;
77     break;
78   case INTERVAL_WEEK:
79   case INTERVAL_DAY:
80     len= my_snprintf(buf,sizeof(buf),"%u", interval.day);
81     int_type=INTERVAL_DAY;
82     break;
83   case INTERVAL_HOUR:
84     len= my_snprintf(buf,sizeof(buf),"%u", interval.hour);
85     break;
86   case INTERVAL_MINUTE:
87     len= my_snprintf(buf,sizeof(buf),"%u", interval.minute);
88     break;
89   case INTERVAL_SECOND:
90     len= my_snprintf(buf,sizeof(buf),"%u", interval.second);
91     break;
92   case INTERVAL_MICROSECOND:
93     len= my_snprintf(buf,sizeof(buf),"%u", interval.second_part);
94     break;
95   case INTERVAL_YEAR_MONTH:
96     len= my_snprintf(buf,sizeof(buf),"%u-%02u", interval.day, interval.month);
97     break;
98   case INTERVAL_DAY_HOUR:
99     len= my_snprintf(buf,sizeof(buf),"%u %u", interval.day, interval.hour);
100     break;
101   case INTERVAL_DAY_MINUTE:
102     len= my_snprintf(buf,sizeof(buf),"%u %u:%02u", interval.day, interval.hour, interval.minute);
103     break;
104   case INTERVAL_DAY_SECOND:
105     len= my_snprintf(buf,sizeof(buf),"%u %u:%02u:%02u", interval.day, interval.hour, interval.minute, interval.second);
106     break;
107   case INTERVAL_HOUR_MINUTE:
108     len= my_snprintf(buf,sizeof(buf),"%u:%02u", interval.hour, interval.minute);
109     break;
110   case INTERVAL_HOUR_SECOND:
111     len= my_snprintf(buf,sizeof(buf),"%u:%02u:%02u", interval.hour, interval.minute, interval.second);
112     break;
113   case INTERVAL_MINUTE_SECOND:
114     len= my_snprintf(buf,sizeof(buf),"%u:%02u", interval.minute, interval.second);
115     break;
116   case INTERVAL_DAY_MICROSECOND:
117     len= my_snprintf(buf,sizeof(buf),"%u %u:%02u:%02u.%06u", interval.day, interval.hour, interval.minute, interval.second, interval.second_part);
118     break;
119   case INTERVAL_HOUR_MICROSECOND:
120     len= my_snprintf(buf,sizeof(buf),"%u:%02u:%02u.%06u", interval.hour, interval.minute, interval.second, interval.second_part);
121     break;
122   case INTERVAL_MINUTE_MICROSECOND:
123     len= my_snprintf(buf,sizeof(buf),"%u:%02u.%06u", interval.minute, interval.second, interval.second_part);
124     break;
125   case INTERVAL_SECOND_MICROSECOND:
126     len= my_snprintf(buf,sizeof(buf),"%u.%06u", interval.second, interval.second_part);
127     break;
128   default:
129     DBUG_ASSERT(0);
130     len= 0;
131   }
132   return str->append(buf, len) || str->append(' ') ||
133          str->append(interval_type_to_name + int_type);
134 }
135 
136 
137 /*
138   Calc weekday from daynr
139   Returns 0 for monday, 1 for tuesday ...
140 */
141 
calc_weekday(long daynr,bool sunday_first_day_of_week)142 int calc_weekday(long daynr,bool sunday_first_day_of_week)
143 {
144   DBUG_ENTER("calc_weekday");
145   DBUG_RETURN ((int) ((daynr + 5L + (sunday_first_day_of_week ? 1L : 0L)) % 7));
146 }
147 
148 /*
149   The bits in week_format has the following meaning:
150    WEEK_MONDAY_FIRST (0)  If not set	Sunday is first day of week
151       		   	  If set	Monday is first day of week
152    WEEK_YEAR (1)	  If not set	Week is in range 0-53
153 
154    	Week 0 is returned for the the last week of the previous year (for
155 	a date at start of january) In this case one can get 53 for the
156 	first week of next year.  This flag ensures that the week is
157 	relevant for the given year. Note that this flag is only
158 	releveant if WEEK_JANUARY is not set.
159 
160 			  If set	 Week is in range 1-53.
161 
162 	In this case one may get week 53 for a date in January (when
163 	the week is that last week of previous year) and week 1 for a
164 	date in December.
165 
166   WEEK_FIRST_WEEKDAY (2)  If not set	Weeks are numbered according
167 			   		to ISO 8601:1988
168 			  If set	The week that contains the first
169 					'first-day-of-week' is week 1.
170 
171 	ISO 8601:1988 means that if the week containing January 1 has
172 	four or more days in the new year, then it is week 1;
173 	Otherwise it is the last week of the previous year, and the
174 	next week is week 1.
175 */
176 
calc_week(const MYSQL_TIME * l_time,uint week_behaviour,uint * year)177 uint calc_week(const MYSQL_TIME *l_time, uint week_behaviour, uint *year)
178 {
179   uint days;
180   ulong daynr=calc_daynr(l_time->year,l_time->month,l_time->day);
181   ulong first_daynr=calc_daynr(l_time->year,1,1);
182   bool monday_first= MY_TEST(week_behaviour & WEEK_MONDAY_FIRST);
183   bool week_year= MY_TEST(week_behaviour & WEEK_YEAR);
184   bool first_weekday= MY_TEST(week_behaviour & WEEK_FIRST_WEEKDAY);
185 
186   uint weekday=calc_weekday(first_daynr, !monday_first);
187   *year=l_time->year;
188 
189   if (l_time->month == 1 && l_time->day <= 7-weekday)
190   {
191     if (!week_year &&
192 	((first_weekday && weekday != 0) ||
193 	 (!first_weekday && weekday >= 4)))
194       return 0;
195     week_year= 1;
196     (*year)--;
197     first_daynr-= (days=calc_days_in_year(*year));
198     weekday= (weekday + 53*7- days) % 7;
199   }
200 
201   if ((first_weekday && weekday != 0) ||
202       (!first_weekday && weekday >= 4))
203     days= daynr - (first_daynr+ (7-weekday));
204   else
205     days= daynr - (first_daynr - weekday);
206 
207   if (week_year && days >= 52*7)
208   {
209     weekday= (weekday + calc_days_in_year(*year)) % 7;
210     if ((!first_weekday && weekday < 4) ||
211 	(first_weekday && weekday == 0))
212     {
213       (*year)++;
214       return 1;
215     }
216   }
217   return days/7+1;
218 }
219 
220 	/* Change a daynr to year, month and day */
221 	/* Daynr 0 is returned as date 00.00.00 */
222 
get_date_from_daynr(long daynr,uint * ret_year,uint * ret_month,uint * ret_day)223 bool get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month,
224 			 uint *ret_day)
225 {
226   uint year,temp,leap_day,day_of_year,days_in_year;
227   uchar *month_pos;
228   DBUG_ENTER("get_date_from_daynr");
229 
230   if (daynr < 366 || daynr > MAX_DAY_NUMBER)
231     DBUG_RETURN(1);
232 
233   year= (uint) (daynr*100 / 36525L);
234   temp=(((year-1)/100+1)*3)/4;
235   day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp;
236   while (day_of_year > (days_in_year= calc_days_in_year(year)))
237   {
238     day_of_year-=days_in_year;
239     (year)++;
240   }
241   leap_day=0;
242   if (days_in_year == 366)
243   {
244     if (day_of_year > 31+28)
245     {
246       day_of_year--;
247       if (day_of_year == 31+28)
248         leap_day=1;		/* Handle leapyears leapday */
249     }
250   }
251   *ret_month=1;
252   for (month_pos= days_in_month ;
253        day_of_year > (uint) *month_pos ;
254        day_of_year-= *(month_pos++), (*ret_month)++)
255     ;
256   *ret_year=year;
257   *ret_day=day_of_year+leap_day;
258   DBUG_RETURN(0);
259 }
260 
261 	/* Functions to handle periods */
262 
convert_period_to_month(ulong period)263 ulong convert_period_to_month(ulong period)
264 {
265   ulong a,b;
266   if (period == 0 || period > 999912)
267     return 0L;
268   if ((a=period/100) < YY_PART_YEAR)
269     a+=2000;
270   else if (a < 100)
271     a+=1900;
272   b=period%100;
273   return a*12+b-1;
274 }
275 
276 
convert_month_to_period(ulong month)277 ulong convert_month_to_period(ulong month)
278 {
279   ulong year;
280   if (month == 0L)
281     return 0L;
282   if ((year=month/12) < 100)
283   {
284     year+=(year < YY_PART_YEAR) ? 2000 : 1900;
285   }
286   return year*100+month%12+1;
287 }
288 
289 
290 bool
check_date_with_warn(THD * thd,const MYSQL_TIME * ltime,date_conv_mode_t fuzzydate,timestamp_type ts_type)291 check_date_with_warn(THD *thd, const MYSQL_TIME *ltime,
292                      date_conv_mode_t fuzzydate, timestamp_type ts_type)
293 {
294   int unused;
295   if (check_date(ltime, fuzzydate, &unused))
296   {
297     ErrConvTime str(ltime);
298     make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
299                                  &str, ts_type, nullptr, nullptr, nullptr);
300     return true;
301   }
302   return false;
303 }
304 
305 
306 bool
adjust_time_range_with_warn(THD * thd,MYSQL_TIME * ltime,uint dec)307 adjust_time_range_with_warn(THD *thd, MYSQL_TIME *ltime, uint dec)
308 {
309   MYSQL_TIME copy= *ltime;
310   ErrConvTime str(&copy);
311   int warnings= 0;
312   if (check_time_range(ltime, dec, &warnings))
313     return true;
314   if (warnings)
315     thd->push_warning_truncated_wrong_value("time", str.ptr());
316   return false;
317 }
318 
319 /*
320   Convert a string to 8-bit representation,
321   for use in str_to_time/str_to_date/str_to_date.
322 
323   In the future to_ascii() can be extended to convert
324   non-ASCII digits to ASCII digits
325   (for example, ARABIC-INDIC, DEVANAGARI, BENGALI, and so on)
326   so DATE/TIME/DATETIME values understand digits in the
327   respected scripts.
328 */
329 static uint
to_ascii(CHARSET_INFO * cs,const char * src,size_t src_length,char * dst,size_t dst_length)330 to_ascii(CHARSET_INFO *cs,
331          const char *src, size_t src_length,
332          char *dst, size_t dst_length)
333 
334 {
335   int cnvres;
336   my_wc_t wc;
337   const char *srcend= src + src_length;
338   char *dst0= dst, *dstend= dst + dst_length - 1;
339   while (dst < dstend &&
340          (cnvres= (cs->cset->mb_wc)(cs, &wc,
341                                     (const uchar*) src,
342                                     (const uchar*) srcend)) > 0 &&
343          wc < 128)
344   {
345     src+= cnvres;
346     *dst++= static_cast<char>(wc);
347   }
348   *dst= '\0';
349   return (uint)(dst - dst0);
350 }
351 
352 
353 class TemporalAsciiBuffer: public LEX_CSTRING
354 {
355   char cnv[32];
356 public:
TemporalAsciiBuffer(const char * str,size_t length,CHARSET_INFO * cs)357   TemporalAsciiBuffer(const char *str, size_t length, CHARSET_INFO *cs)
358   {
359     if ((cs->state & MY_CS_NONASCII) != 0)
360     {
361       LEX_CSTRING::str= cnv;
362       LEX_CSTRING::length= to_ascii(cs, str, length, cnv, sizeof(cnv));
363     }
364     else
365     {
366       LEX_CSTRING::str= str;
367       LEX_CSTRING::length= length;
368     }
369   }
370 };
371 
372 
373 /* Character set-aware version of ascii_to_datetime_or_date_or_time() */
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)374 bool Temporal::str_to_datetime_or_date_or_time(THD *thd, MYSQL_TIME_STATUS *st,
375                                                const char *str, size_t length,
376                                                CHARSET_INFO *cs,
377                                                date_mode_t fuzzydate)
378 {
379   TemporalAsciiBuffer tmp(str, length, cs);
380   return ascii_to_datetime_or_date_or_time(st, tmp.str, tmp.length, fuzzydate)||
381          add_nanoseconds(thd, &st->warnings, fuzzydate, st->nanoseconds);
382 }
383 
384 
385 /* Character set-aware version of str_to_datetime_or_date() */
str_to_datetime_or_date(THD * thd,MYSQL_TIME_STATUS * status,const char * str,size_t length,CHARSET_INFO * cs,date_mode_t flags)386 bool Temporal::str_to_datetime_or_date(THD *thd, MYSQL_TIME_STATUS *status,
387                                        const char *str, size_t length,
388                                        CHARSET_INFO *cs,
389                                        date_mode_t flags)
390 {
391   TemporalAsciiBuffer tmp(str, length, cs);
392   return ascii_to_datetime_or_date(status, tmp.str, tmp.length, flags) ||
393          add_nanoseconds(thd, &status->warnings, flags, status->nanoseconds);
394 }
395 
396 
397 /* Character set-aware version of ascii_to_temporal() */
str_to_temporal(THD * thd,MYSQL_TIME_STATUS * status,const char * str,size_t length,CHARSET_INFO * cs,date_mode_t flags)398 bool Temporal::str_to_temporal(THD *thd, MYSQL_TIME_STATUS *status,
399                                const char *str, size_t length, CHARSET_INFO *cs,
400                                date_mode_t flags)
401 {
402   TemporalAsciiBuffer tmp(str, length, cs);
403   return ascii_to_temporal(status, tmp.str, tmp.length, flags) ||
404          add_nanoseconds(thd, &status->warnings, flags, status->nanoseconds);
405 }
406 
407 
408 /* Character set-aware version of str_to_DDhhmmssff() */
str_to_DDhhmmssff(MYSQL_TIME_STATUS * status,const char * str,size_t length,CHARSET_INFO * cs,ulong max_hour)409 bool Interval_DDhhmmssff::str_to_DDhhmmssff(MYSQL_TIME_STATUS *status,
410                                             const char *str, size_t length,
411                                             CHARSET_INFO *cs, ulong max_hour)
412 {
413   TemporalAsciiBuffer tmp(str, length, cs);
414   bool rc= ::str_to_DDhhmmssff(tmp.str, tmp.length, this, UINT_MAX32, status);
415   DBUG_ASSERT(status->warnings || !rc);
416   return rc;
417 }
418 
419 
420 /*
421   Convert a timestamp string to a MYSQL_TIME value and produce a warning
422   if string was truncated during conversion.
423 
424   NOTE
425     See description of str_to_datetime_xxx() for more information.
426 */
427 
428 bool
str_to_datetime_with_warn(THD * thd,CHARSET_INFO * cs,const char * str,size_t length,MYSQL_TIME * to,date_mode_t mode)429 str_to_datetime_with_warn(THD *thd, CHARSET_INFO *cs,
430                           const char *str, size_t length, MYSQL_TIME *to,
431                           date_mode_t mode)
432 {
433   Temporal::Warn_push warn(thd, nullptr, nullptr, nullptr, to, mode);
434   Temporal_hybrid *t= new(to) Temporal_hybrid(thd, &warn, str, length, cs, mode);
435   return !t->is_valid_temporal();
436 }
437 
438 
double_to_datetime_with_warn(THD * thd,double value,MYSQL_TIME * ltime,date_mode_t fuzzydate,const TABLE_SHARE * s,const char * field_name)439 bool double_to_datetime_with_warn(THD *thd, double value, MYSQL_TIME *ltime,
440                                   date_mode_t fuzzydate,
441                                   const TABLE_SHARE *s, const char *field_name)
442 {
443   Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
444                            s ? s->table_name.str : nullptr,
445                            field_name, ltime, fuzzydate);
446   Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, value, fuzzydate);
447   return !t->is_valid_temporal();
448 }
449 
450 
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)451 bool decimal_to_datetime_with_warn(THD *thd, const my_decimal *value,
452                                    MYSQL_TIME *ltime,
453                                    date_mode_t fuzzydate,
454                                    const TABLE_SHARE *s, const char *field_name)
455 {
456   Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
457                            s ? s->table_name.str : nullptr,
458                            field_name, ltime, fuzzydate);
459   Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, value, fuzzydate);
460   return !t->is_valid_temporal();
461 }
462 
463 
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)464 bool int_to_datetime_with_warn(THD *thd, const Longlong_hybrid &nr,
465                                MYSQL_TIME *ltime,
466                                date_mode_t fuzzydate,
467                                const TABLE_SHARE *s, const char *field_name)
468 {
469   /*
470     Note: conversion from an integer to TIME can overflow to '838:59:59.999999',
471     so the conversion result can have fractional digits.
472   */
473   Temporal::Warn_push warn(thd, s ? s->db.str : nullptr,
474                            s ? s->table_name.str : nullptr,
475                            field_name, ltime, fuzzydate);
476   Temporal_hybrid *t= new (ltime) Temporal_hybrid(thd, &warn, nr, fuzzydate);
477   return !t->is_valid_temporal();
478 }
479 
480 
481 /*
482   Convert a datetime from broken-down MYSQL_TIME representation to
483   corresponding TIMESTAMP value.
484 
485   SYNOPSIS
486     TIME_to_timestamp()
487       thd             - current thread
488       t               - datetime in broken-down representation,
489       error_code      - 0, if the conversion was successful;
490                         ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
491                            which is out of TIMESTAMP range;
492                         ER_WARN_INVALID_TIMESTAMP, if t represents value which
493                            doesn't exists (falls into the spring time-gap).
494 
495   RETURN
496      Number seconds in UTC since start of Unix Epoch corresponding to t.
497      0 - in case of ER_WARN_DATA_OUT_OF_RANGE
498 */
499 
TIME_to_timestamp(THD * thd,const MYSQL_TIME * t,uint * error_code)500 my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code)
501 {
502   thd->time_zone_used= 1;
503   return thd->variables.time_zone->TIME_to_gmt_sec(t, error_code);
504 }
505 
506 
507 /*
508   Convert a system time structure to TIME
509 */
510 
localtime_to_TIME(MYSQL_TIME * to,struct tm * from)511 void localtime_to_TIME(MYSQL_TIME *to, struct tm *from)
512 {
513   to->neg=0;
514   to->second_part=0;
515   to->year=	(int) ((from->tm_year+1900) % 10000);
516   to->month=	(int) from->tm_mon+1;
517   to->day=	(int) from->tm_mday;
518   to->hour=	(int) from->tm_hour;
519   to->minute=	(int) from->tm_min;
520   to->second=   (int) from->tm_sec;
521 }
522 
523 
calc_time_from_sec(MYSQL_TIME * to,ulong seconds,ulong microseconds)524 void calc_time_from_sec(MYSQL_TIME *to, ulong seconds, ulong microseconds)
525 {
526   long t_seconds;
527   // to->neg is not cleared, it may already be set to a useful value
528   to->time_type= MYSQL_TIMESTAMP_TIME;
529   to->year= 0;
530   to->month= 0;
531   to->day= 0;
532   to->hour= seconds/3600L;
533   t_seconds= seconds%3600L;
534   to->minute= t_seconds/60L;
535   to->second= t_seconds%60L;
536   to->second_part= microseconds;
537 }
538 
539 
540 /*
541   Parse a format string specification
542 
543   SYNOPSIS
544     parse_date_time_format()
545     format_type		Format of string (time, date or datetime)
546     format_str		String to parse
547     format_length	Length of string
548     date_time_format	Format to fill in
549 
550   NOTES
551     Fills in date_time_format->positions for all date time parts.
552 
553     positions marks the position for a datetime element in the format string.
554     The position array elements are in the following order:
555     YYYY-DD-MM HH-MM-DD.FFFFFF AM
556     0    1  2  3  4  5  6      7
557 
558     If positions[0]= 5, it means that year will be the forth element to
559     read from the parsed date string.
560 
561   RETURN
562     0	ok
563     1	error
564 */
565 
parse_date_time_format(timestamp_type format_type,const char * format,uint format_length,DATE_TIME_FORMAT * date_time_format)566 bool parse_date_time_format(timestamp_type format_type,
567 			    const char *format, uint format_length,
568 			    DATE_TIME_FORMAT *date_time_format)
569 {
570   uint offset= 0, separators= 0;
571   const char *ptr= format, *format_str;
572   const char *end= ptr+format_length;
573   uchar *dt_pos= date_time_format->positions;
574   /* need_p is set if we are using AM/PM format */
575   bool need_p= 0, allow_separator= 0;
576   ulong part_map= 0, separator_map= 0;
577   const char *parts[16];
578 
579   date_time_format->time_separator= 0;
580   date_time_format->flag= 0;			// For future
581 
582   /*
583     Fill position with 'dummy' arguments to found out if a format tag is
584     used twice (This limit's the format to 255 characters, but this is ok)
585   */
586   dt_pos[0]= dt_pos[1]= dt_pos[2]= dt_pos[3]=
587     dt_pos[4]= dt_pos[5]= dt_pos[6]= dt_pos[7]= 255;
588 
589   for (; ptr != end; ptr++)
590   {
591     if (*ptr == '%' && ptr+1 != end)
592     {
593       uint UNINIT_VAR(position);
594       switch (*++ptr) {
595       case 'y':					// Year
596       case 'Y':
597 	position= 0;
598 	break;
599       case 'c':					// Month
600       case 'm':
601 	position= 1;
602 	break;
603       case 'd':
604       case 'e':
605 	position= 2;
606 	break;
607       case 'h':
608       case 'I':
609       case 'l':
610 	need_p= 1;				// Need AM/PM
611 	/* Fall through */
612       case 'k':
613       case 'H':
614 	position= 3;
615 	break;
616       case 'i':
617 	position= 4;
618 	break;
619       case 's':
620       case 'S':
621 	position= 5;
622 	break;
623       case 'f':
624 	position= 6;
625 	if (dt_pos[5] != offset-1 || ptr[-2] != '.')
626 	  return 1;				// Wrong usage of %f
627 	break;
628       case 'p':					// AM/PM
629 	if (offset == 0)			// Can't be first
630 	  return 0;
631 	position= 7;
632 	break;
633       default:
634 	return 1;				// Unknown controll char
635       }
636       if (dt_pos[position] != 255)		// Don't allow same tag twice
637 	return 1;
638       parts[position]= ptr-1;
639 
640       /*
641 	If switching from time to date, ensure that all time parts
642 	are used
643       */
644       if (part_map && position <= 2 && !(part_map & (1 | 2 | 4)))
645 	offset=5;
646       part_map|= (ulong) 1 << position;
647       dt_pos[position]= offset++;
648       allow_separator= 1;
649     }
650     else
651     {
652       /*
653 	Don't allow any characters in format as this could easily confuse
654 	the date reader
655       */
656       if (!allow_separator)
657 	return 1;				// No separator here
658       allow_separator= 0;			// Don't allow two separators
659       separators++;
660       /* Store in separator_map which parts are punct characters */
661       if (my_ispunct(&my_charset_latin1, *ptr))
662 	separator_map|= (ulong) 1 << (offset-1);
663       else if (!my_isspace(&my_charset_latin1, *ptr))
664 	return 1;
665     }
666   }
667 
668   /* If no %f, specify it after seconds.  Move %p up, if necessary */
669   if ((part_map & 32) && !(part_map & 64))
670   {
671     dt_pos[6]= dt_pos[5] +1;
672     parts[6]= parts[5];				// For later test in (need_p)
673     if (dt_pos[6] == dt_pos[7])			// Move %p one step up if used
674       dt_pos[7]++;
675   }
676 
677   /*
678     Check that we have not used a non legal format specifier and that all
679     format specifiers have been used
680 
681     The last test is to ensure that %p is used if and only if
682     it's needed.
683   */
684   if ((format_type == MYSQL_TIMESTAMP_DATETIME &&
685        !test_all_bits(part_map, (1 | 2 | 4 | 8 | 16 | 32))) ||
686       (format_type == MYSQL_TIMESTAMP_DATE && part_map != (1 | 2 | 4)) ||
687       (format_type == MYSQL_TIMESTAMP_TIME &&
688        !test_all_bits(part_map, 8 | 16 | 32)) ||
689       !allow_separator ||			// %option should be last
690       (need_p && dt_pos[6] +1 != dt_pos[7]) ||
691       (need_p ^ (dt_pos[7] != 255)))
692     return 1;
693 
694   if (dt_pos[6] != 255)				// If fractional seconds
695   {
696     /* remove fractional seconds from later tests */
697     uint pos= dt_pos[6] -1;
698     /* Remove separator before %f from sep map */
699     separator_map= ((separator_map & ((ulong) (1 << pos)-1)) |
700 		    ((separator_map & ~((ulong) (1 << pos)-1)) >> 1));
701     if (part_map & 64)
702     {
703       separators--;				// There is always a separator
704       need_p= 1;				// force use of separators
705     }
706   }
707 
708   /*
709     Remove possible separator before %p from sep_map
710     (This can either be at position 3, 4, 6 or 7) h.m.d.%f %p
711   */
712   if (dt_pos[7] != 255)
713   {
714     if (need_p && parts[7] != parts[6]+2)
715       separators--;
716   }
717   /*
718     Calculate if %p is in first or last part of the datetime field
719 
720     At this point we have either %H-%i-%s %p 'year parts' or
721     'year parts' &H-%i-%s %p" as %f was removed above
722   */
723   offset= dt_pos[6] <= 3 ? 3 : 6;
724   /* Remove separator before %p from sep map */
725   separator_map= ((separator_map & ((ulong) (1 << offset)-1)) |
726 		  ((separator_map & ~((ulong) (1 << offset)-1)) >> 1));
727 
728   format_str= 0;
729   switch (format_type) {
730   case MYSQL_TIMESTAMP_DATE:
731     format_str= known_date_time_formats[INTERNAL_FORMAT].date_format;
732     /* fall through */
733   case MYSQL_TIMESTAMP_TIME:
734     if (!format_str)
735       format_str=known_date_time_formats[INTERNAL_FORMAT].time_format;
736 
737     /*
738       If there is no separators, allow the internal format as we can read
739       this.  If separators are used, they must be between each part
740     */
741     if (format_length == 6 && !need_p &&
742 	!my_strnncoll(&my_charset_bin,
743 		      (const uchar *) format, 6,
744 		      (const uchar *) format_str, 6))
745       return 0;
746     if (separator_map == (1 | 2))
747     {
748       if (format_type == MYSQL_TIMESTAMP_TIME)
749       {
750 	if (*(format+2) != *(format+5))
751 	  break;				// Error
752 	/* Store the character used for time formats */
753 	date_time_format->time_separator= *(format+2);
754       }
755       return 0;
756     }
757     break;
758   case MYSQL_TIMESTAMP_DATETIME:
759     /*
760       If there is no separators, allow the internal format as we can read
761       this.  If separators are used, they must be between each part.
762       Between DATE and TIME we also allow space as separator
763     */
764     if ((format_length == 12 && !need_p &&
765 	 !my_strnncoll(&my_charset_bin,
766 		       (const uchar *) format, 12,
767 		       (const uchar*) known_date_time_formats[INTERNAL_FORMAT].datetime_format,
768 		       12)) ||
769 	(separators == 5 && separator_map == (1 | 2 | 8 | 16)))
770       return 0;
771     break;
772   default:
773     DBUG_ASSERT(0);
774     break;
775   }
776   return 1;					// Error
777 }
778 
779 
780 /*
781   Create a DATE_TIME_FORMAT object from a format string specification
782 
783   SYNOPSIS
784     date_time_format_make()
785     format_type		Format to parse (time, date or datetime)
786     format_str		String to parse
787     format_length	Length of string
788 
789   NOTES
790     The returned object should be freed with my_free()
791 
792   RETURN
793     NULL ponter:	Error
794     new object
795 */
796 
797 DATE_TIME_FORMAT
date_time_format_make(timestamp_type format_type,const char * format_str,uint format_length)798 *date_time_format_make(timestamp_type format_type,
799 		       const char *format_str, uint format_length)
800 {
801   DATE_TIME_FORMAT tmp;
802 
803   if (format_length && format_length < 255 &&
804       !parse_date_time_format(format_type, format_str,
805 			      format_length, &tmp))
806   {
807     tmp.format.str=    format_str;
808     tmp.format.length= format_length;
809     return date_time_format_copy((THD *)0, &tmp);
810   }
811   return 0;
812 }
813 
814 
815 /*
816   Create a copy of a DATE_TIME_FORMAT object
817 
818   SYNOPSIS
819     date_and_time_format_copy()
820     thd			Set if variable should be allocated in thread mem
821     format		format to copy
822 
823   NOTES
824     The returned object should be freed with my_free()
825 
826   RETURN
827     NULL ponter:	Error
828     new object
829 */
830 
date_time_format_copy(THD * thd,DATE_TIME_FORMAT * format)831 DATE_TIME_FORMAT *date_time_format_copy(THD *thd, DATE_TIME_FORMAT *format)
832 {
833   DATE_TIME_FORMAT *new_format;
834   size_t length= sizeof(*format) + format->format.length + 1;
835   char *format_pos;
836 
837   if (thd)
838     new_format= (DATE_TIME_FORMAT *) thd->alloc(length);
839   else
840     new_format=  (DATE_TIME_FORMAT *) my_malloc(length, MYF(MY_WME));
841   if (new_format)
842   {
843     /* Put format string after current pos */
844     new_format->format.str= format_pos= (char*) (new_format+1);
845     memcpy((char*) new_format->positions, (char*) format->positions,
846 	   sizeof(format->positions));
847     new_format->time_separator= format->time_separator;
848     /* We make the string null terminated for easy printf in SHOW VARIABLES */
849     memcpy(format_pos, format->format.str, format->format.length);
850     format_pos[format->format.length]= 0;
851     new_format->format.length= format->format.length;
852   }
853   return new_format;
854 }
855 
856 
857 KNOWN_DATE_TIME_FORMAT known_date_time_formats[6]=
858 {
859   {"USA", "%m.%d.%Y", "%Y-%m-%d %H.%i.%s", "%h:%i:%s %p" },
860   {"JIS", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
861   {"ISO", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
862   {"EUR", "%d.%m.%Y", "%Y-%m-%d %H.%i.%s", "%H.%i.%s" },
863   {"INTERNAL", "%Y%m%d",   "%Y%m%d%H%i%s", "%H%i%s" },
864   { 0, 0, 0, 0 }
865 };
866 
867 
get_date_time_format_str(KNOWN_DATE_TIME_FORMAT * format,timestamp_type type)868 const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format,
869 				     timestamp_type type)
870 {
871   switch (type) {
872   case MYSQL_TIMESTAMP_DATE:
873     return format->date_format;
874   case MYSQL_TIMESTAMP_DATETIME:
875     return format->datetime_format;
876   case MYSQL_TIMESTAMP_TIME:
877     return format->time_format;
878   default:
879     DBUG_ASSERT(0);				// Impossible
880     return 0;
881   }
882 }
883 
884 
885 /**
886   Convert TIME/DATE/DATETIME value to String.
887   @param l_time   DATE value
888   @param OUT str  String to convert to
889   @param dec      Number of fractional digits.
890 */
my_TIME_to_str(const MYSQL_TIME * ltime,String * str,uint dec)891 bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec)
892 {
893   if (str->alloc(MAX_DATE_STRING_REP_LENGTH))
894     return true;
895   str->set_charset(&my_charset_numeric);
896   str->length(my_TIME_to_str(ltime, const_cast<char*>(str->ptr()), dec));
897   return false;
898 }
899 
900 
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)901 void make_truncated_value_warning(THD *thd,
902                                   Sql_condition::enum_warning_level level,
903                                   const ErrConv *sval,
904 				  timestamp_type time_type,
905                                   const char *db_name, const char *table_name,
906                                   const char *field_name)
907 {
908   const char *type_str= Temporal::type_name_by_timestamp_type(time_type);
909   return thd->push_warning_wrong_or_truncated_value
910     (level, time_type <= MYSQL_TIMESTAMP_ERROR, type_str, sval->ptr(),
911      db_name, table_name, field_name);
912 }
913 
914 
915 /* Daynumber from year 0 to 9999-12-31 */
916 #define COMBINE(X)                                                      \
917                (((((X)->day * 24LL + (X)->hour) * 60LL +                \
918                    (X)->minute) * 60LL + (X)->second)*1000000LL +       \
919                    (X)->second_part)
920 #define GET_PART(X, N) X % N ## LL; X/= N ## LL
921 
date_add_interval(THD * thd,MYSQL_TIME * ltime,interval_type int_type,const INTERVAL & interval,bool push_warn)922 bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
923                        const INTERVAL &interval, bool push_warn)
924 {
925   long period, sign;
926 
927   sign= (interval.neg == (bool)ltime->neg ? 1 : -1);
928 
929   switch (int_type) {
930   case INTERVAL_SECOND:
931   case INTERVAL_SECOND_MICROSECOND:
932   case INTERVAL_MICROSECOND:
933   case INTERVAL_MINUTE:
934   case INTERVAL_HOUR:
935   case INTERVAL_MINUTE_MICROSECOND:
936   case INTERVAL_MINUTE_SECOND:
937   case INTERVAL_HOUR_MICROSECOND:
938   case INTERVAL_HOUR_SECOND:
939   case INTERVAL_HOUR_MINUTE:
940   case INTERVAL_DAY_MICROSECOND:
941   case INTERVAL_DAY_SECOND:
942   case INTERVAL_DAY_MINUTE:
943   case INTERVAL_DAY_HOUR:
944   case INTERVAL_DAY:
945   {
946     longlong usec, daynr;
947     my_bool neg= 0;
948     enum enum_mysql_timestamp_type time_type= ltime->time_type;
949 
950     if (((ulonglong) interval.day +
951          (ulonglong) interval.hour / 24 +
952          (ulonglong) interval.minute / 24 / 60 +
953          (ulonglong) interval.second / 24 / 60 / 60) > MAX_DAY_NUMBER)
954       goto invalid_date;
955 
956     if (time_type != MYSQL_TIMESTAMP_TIME)
957       ltime->day+= calc_daynr(ltime->year, ltime->month, 1) - 1;
958 
959     usec= COMBINE(ltime) + sign*COMBINE(&interval);
960 
961     if (usec < 0)
962     {
963       neg= 1;
964       usec= -usec;
965     }
966 
967     ltime->second_part= GET_PART(usec, 1000000);
968     ltime->second= GET_PART(usec, 60);
969     ltime->minute= GET_PART(usec, 60);
970     ltime->neg^= neg;
971 
972     if (time_type == MYSQL_TIMESTAMP_TIME)
973     {
974       if (usec > TIME_MAX_HOUR)
975         goto invalid_date;
976       ltime->hour= static_cast<uint>(usec);
977       ltime->day= 0;
978       return 0;
979     }
980     else if (ltime->neg)
981       goto invalid_date;
982 
983     if (int_type != INTERVAL_DAY)
984       ltime->time_type= MYSQL_TIMESTAMP_DATETIME; // Return full date
985 
986     ltime->hour= GET_PART(usec, 24);
987     daynr= usec;
988 
989     /* Day number from year 0 to 9999-12-31 */
990     if (get_date_from_daynr((long) daynr, &ltime->year, &ltime->month,
991                             &ltime->day))
992       goto invalid_date;
993     break;
994   }
995   case INTERVAL_WEEK:
996     period= (calc_daynr(ltime->year,ltime->month,ltime->day) +
997              sign * (long) interval.day);
998     /* Daynumber from year 0 to 9999-12-31 */
999     if (get_date_from_daynr((long) period,&ltime->year,&ltime->month,
1000                             &ltime->day))
1001       goto invalid_date;
1002     break;
1003   case INTERVAL_YEAR:
1004     ltime->year+= sign * (long) interval.year;
1005     if ((ulong) ltime->year >= 10000L)
1006       goto invalid_date;
1007     if (ltime->month == 2 && ltime->day == 29 &&
1008 	calc_days_in_year(ltime->year) != 366)
1009       ltime->day=28;				// Was leap-year
1010     break;
1011   case INTERVAL_YEAR_MONTH:
1012   case INTERVAL_QUARTER:
1013   case INTERVAL_MONTH:
1014     period= (ltime->year*12 + sign * (long) interval.year*12 +
1015 	     ltime->month-1 + sign * (long) interval.month);
1016     if ((ulong) period >= 120000L)
1017       goto invalid_date;
1018     ltime->year= (uint) (period / 12);
1019     ltime->month= (uint) (period % 12L)+1;
1020     /* Adjust day if the new month doesn't have enough days */
1021     if (ltime->day > days_in_month[ltime->month-1])
1022     {
1023       ltime->day = days_in_month[ltime->month-1];
1024       if (ltime->month == 2 && calc_days_in_year(ltime->year) == 366)
1025 	ltime->day++;				// Leap-year
1026     }
1027     break;
1028   default:
1029     goto null_date;
1030   }
1031 
1032   if (ltime->time_type != MYSQL_TIMESTAMP_TIME)
1033     return 0;                                   // Ok
1034 
1035 invalid_date:
1036   if (push_warn)
1037   {
1038     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
1039                         ER_DATETIME_FUNCTION_OVERFLOW,
1040                         ER_THD(thd, ER_DATETIME_FUNCTION_OVERFLOW),
1041                         ltime->time_type == MYSQL_TIMESTAMP_TIME ?
1042                         "time" : "datetime");
1043   }
1044 null_date:
1045   return 1;
1046 }
1047 
1048 
1049 /*
1050   Calculate difference between two datetime values as seconds + microseconds.
1051 
1052   SYNOPSIS
1053     calc_time_diff()
1054       l_time1         - TIME/DATE/DATETIME value
1055       l_time2         - TIME/DATE/DATETIME value
1056       l_sign          - 1 absolute values are substracted,
1057                         -1 absolute values are added.
1058       seconds_out     - Out parameter where difference between
1059                         l_time1 and l_time2 in seconds is stored.
1060       microseconds_out- Out parameter where microsecond part of difference
1061                         between l_time1 and l_time2 is stored.
1062 
1063   NOTE
1064     This function calculates difference between l_time1 and l_time2 absolute
1065     values. So one should set l_sign and correct result if he want to take
1066     signs into account (i.e. for MYSQL_TIME values).
1067 
1068   RETURN VALUES
1069     Returns sign of difference.
1070     1 means negative result
1071     0 means positive result
1072 
1073 */
1074 
1075 bool
calc_time_diff(const MYSQL_TIME * l_time1,const MYSQL_TIME * l_time2,int l_sign,ulonglong * seconds_out,ulong * microseconds_out)1076 calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
1077                int l_sign, ulonglong *seconds_out, ulong *microseconds_out)
1078 {
1079   long days;
1080   bool neg;
1081   longlong microseconds;
1082 
1083   /*
1084     We suppose that if first argument is MYSQL_TIMESTAMP_TIME
1085     the second argument should be TIMESTAMP_TIME also.
1086     We should check it before calc_time_diff call.
1087   */
1088   if (l_time1->time_type == MYSQL_TIMESTAMP_TIME)  // Time value
1089     days= (long)l_time1->day - l_sign * (long)l_time2->day;
1090   else
1091   {
1092     days= calc_daynr((uint) l_time1->year,
1093 		     (uint) l_time1->month,
1094 		     (uint) l_time1->day);
1095     if (l_time2->time_type == MYSQL_TIMESTAMP_TIME)
1096       days-= l_sign * (long)l_time2->day;
1097     else
1098       days-= l_sign*calc_daynr((uint) l_time2->year,
1099 			       (uint) l_time2->month,
1100 			       (uint) l_time2->day);
1101   }
1102 
1103   microseconds= ((longlong)days * SECONDS_IN_24H +
1104                  (longlong)(l_time1->hour*3600LL +
1105                             l_time1->minute*60L +
1106                             l_time1->second) -
1107                  l_sign*(longlong)(l_time2->hour*3600LL +
1108                                    l_time2->minute*60L +
1109                                    l_time2->second)) * 1000000LL +
1110                 (longlong)l_time1->second_part -
1111                 l_sign*(longlong)l_time2->second_part;
1112 
1113   neg= 0;
1114   if (microseconds < 0)
1115   {
1116     microseconds= -microseconds;
1117     neg= 1;
1118   }
1119   *seconds_out= (ulonglong) microseconds/1000000L;
1120   *microseconds_out= (ulong) (microseconds%1000000L);
1121   return neg;
1122 }
1123 
1124 
calc_time_diff(const MYSQL_TIME * l_time1,const MYSQL_TIME * l_time2,int l_sign,MYSQL_TIME * l_time3,date_mode_t fuzzydate)1125 bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
1126                     int l_sign, MYSQL_TIME *l_time3, date_mode_t fuzzydate)
1127 {
1128   ulonglong seconds;
1129   ulong microseconds;
1130   bzero((char *) l_time3, sizeof(*l_time3));
1131   l_time3->neg= calc_time_diff(l_time1, l_time2, l_sign,
1132 			       &seconds, &microseconds);
1133   /*
1134     For MYSQL_TIMESTAMP_TIME only:
1135       If first argument was negative and diff between arguments
1136       is non-zero we need to swap sign to get proper result.
1137   */
1138   if (l_time1->neg && (seconds || microseconds))
1139     l_time3->neg= 1 - l_time3->neg;         // Swap sign of result
1140 
1141   /*
1142     seconds is longlong, when casted to long it may become a small number
1143     even if the original seconds value was too large and invalid.
1144     as a workaround we limit seconds by a large invalid long number
1145     ("invalid" means > TIME_MAX_SECOND)
1146   */
1147   set_if_smaller(seconds, INT_MAX32);
1148   calc_time_from_sec(l_time3, (ulong) seconds, microseconds);
1149   return ((fuzzydate & TIME_NO_ZERO_DATE) && (seconds == 0) &&
1150           (microseconds == 0));
1151 }
1152 
1153 
1154 /*
1155   Compares 2 MYSQL_TIME structures
1156 
1157   SYNOPSIS
1158     my_time_compare()
1159 
1160       a - first time
1161       b - second time
1162 
1163   RETURN VALUE
1164    -1   - a < b
1165     0   - a == b
1166     1   - a > b
1167 
1168 */
1169 
my_time_compare(const MYSQL_TIME * a,const MYSQL_TIME * b)1170 int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b)
1171 {
1172   ulonglong a_t= pack_time(a);
1173   ulonglong b_t= pack_time(b);
1174 
1175   if (a_t < b_t)
1176     return -1;
1177   if (a_t > b_t)
1178     return 1;
1179 
1180   return 0;
1181 }
1182 
1183 
1184 /**
1185   Convert TIME to DATETIME.
1186   @param   ltime    The value to convert.
1187   @return  false on success, true of error (negative time).
1188 */
time_to_datetime(MYSQL_TIME * ltime)1189 bool time_to_datetime(MYSQL_TIME *ltime)
1190 {
1191   DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_TIME);
1192   DBUG_ASSERT(ltime->year == 0);
1193   DBUG_ASSERT(ltime->month == 0);
1194   DBUG_ASSERT(ltime->day == 0);
1195   if (ltime->neg)
1196     return true;
1197   uint day= ltime->hour / 24;
1198   ltime->hour%= 24;
1199   ltime->month= day / 31;
1200   ltime->day= day % 31;
1201   return false;
1202 }
1203 
1204 
1205 /*** Conversion from TIME to DATETIME ***/
1206 
1207 /*
1208   Simple case: TIME is within normal 24 hours internal.
1209   Mix DATE part of ldate and TIME part of ltime together.
1210 */
1211 static void
mix_date_and_time_simple(MYSQL_TIME * ldate,const MYSQL_TIME * ltime)1212 mix_date_and_time_simple(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
1213 {
1214   DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
1215               ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
1216   ldate->hour= ltime->hour;
1217   ldate->minute= ltime->minute;
1218   ldate->second= ltime->second;
1219   ldate->second_part= ltime->second_part;
1220   ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
1221 }
1222 
1223 
1224 /*
1225   Complex case: TIME is negative or outside of the 24 hour interval.
1226 */
1227 static void
mix_date_and_time_complex(MYSQL_TIME * ldate,const MYSQL_TIME * ltime)1228 mix_date_and_time_complex(MYSQL_TIME *ldate, const MYSQL_TIME *ltime)
1229 {
1230   DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
1231               ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
1232   ulonglong seconds;
1233   ulong days, useconds;
1234   int sign= ltime->neg ? 1 : -1;
1235   ldate->neg= calc_time_diff(ldate, ltime, sign, &seconds, &useconds);
1236 
1237   DBUG_ASSERT(!ldate->neg);
1238   DBUG_ASSERT(ldate->year > 0);
1239 
1240   days= (long) (seconds / SECONDS_IN_24H);
1241   calc_time_from_sec(ldate, seconds % SECONDS_IN_24H, useconds);
1242   get_date_from_daynr(days, &ldate->year, &ldate->month, &ldate->day);
1243   ldate->time_type= MYSQL_TIMESTAMP_DATETIME;
1244 }
1245 
1246 
1247 /**
1248   Mix a date value and a time value.
1249 
1250   @param  IN/OUT  ldate  Date value.
1251   @param          ltime  Time value.
1252 */
1253 static void
mix_date_and_time(MYSQL_TIME * to,const MYSQL_TIME * from)1254 mix_date_and_time(MYSQL_TIME *to, const MYSQL_TIME *from)
1255 {
1256   if (!from->neg && from->hour < 24)
1257     mix_date_and_time_simple(to, from);
1258   else
1259     mix_date_and_time_complex(to, from);
1260 }
1261 
1262 
1263 /**
1264   Get current date in DATE format
1265 */
set_current_date(THD * thd,MYSQL_TIME * to)1266 void set_current_date(THD *thd, MYSQL_TIME *to)
1267 {
1268   thd->variables.time_zone->gmt_sec_to_TIME(to, thd->query_start());
1269   thd->time_zone_used= 1;
1270   datetime_to_date(to);
1271 }
1272 
1273 
1274 /**
1275   5.5 compatible conversion from TIME to DATETIME
1276 */
1277 static bool
time_to_datetime_old(THD * thd,const MYSQL_TIME * from,MYSQL_TIME * to)1278 time_to_datetime_old(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
1279 {
1280   DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
1281 
1282   if (from->neg)
1283     return true;
1284 
1285   /* Set the date part */
1286   uint day= from->hour / 24;
1287   to->day= day % 31;
1288   to->month= day / 31;
1289   to->year= 0;
1290   /* Set the time part */
1291   to->hour= from->hour % 24;
1292   to->minute= from->minute;
1293   to->second= from->second;
1294   to->second_part= from->second_part;
1295   /* set sign and type */
1296   to->neg= 0;
1297   to->time_type= MYSQL_TIMESTAMP_DATETIME;
1298   return false;
1299 }
1300 
1301 
1302 /**
1303   Convert time to datetime.
1304 
1305   The time value is added to the current datetime value.
1306   @param  IN  ltime    Time value to convert from.
1307   @param  OUT ltime2   Datetime value to convert to.
1308 */
1309 bool
time_to_datetime(THD * thd,const MYSQL_TIME * from,MYSQL_TIME * to)1310 time_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to)
1311 {
1312   if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)
1313     return time_to_datetime_old(thd, from, to);
1314   set_current_date(thd, to);
1315   mix_date_and_time(to, from);
1316   return false;
1317 }
1318 
1319 
1320 bool
time_to_datetime_with_warn(THD * thd,const MYSQL_TIME * from,MYSQL_TIME * to,date_conv_mode_t fuzzydate)1321 time_to_datetime_with_warn(THD *thd,
1322                            const MYSQL_TIME *from, MYSQL_TIME *to,
1323                            date_conv_mode_t fuzzydate)
1324 {
1325   int warn= 0;
1326   DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME);
1327   /*
1328     After time_to_datetime() we need to do check_date(), as
1329     the caller may want TIME_NO_ZERO_DATE or TIME_NO_ZERO_IN_DATE.
1330     Note, the SQL standard time->datetime conversion mode always returns
1331     a valid date based on CURRENT_DATE. So we need to do check_date()
1332     only in the old mode.
1333   */
1334   if (time_to_datetime(thd, from, to) ||
1335       ((thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) &&
1336         check_date(to, fuzzydate, &warn)))
1337   {
1338     ErrConvTime str(from);
1339     thd->push_warning_truncated_wrong_value("datetime", str.ptr());
1340     return true;
1341   }
1342   return false;
1343 }
1344 
1345 
pack_time(const MYSQL_TIME * my_time)1346 longlong pack_time(const MYSQL_TIME *my_time)
1347 {
1348   return  ((((((my_time->year     * 13ULL +
1349                my_time->month)    * 32ULL +
1350                my_time->day)      * 24ULL +
1351                my_time->hour)     * 60ULL +
1352                my_time->minute)   * 60ULL +
1353                my_time->second)   * 1000000ULL +
1354                my_time->second_part) * (my_time->neg ? -1 : 1);
1355 }
1356 
1357 #define get_one(WHERE, FACTOR) WHERE= (ulong)(packed % FACTOR); packed/= FACTOR
1358 
unpack_time(longlong packed,MYSQL_TIME * my_time,enum_mysql_timestamp_type ts_type)1359 void unpack_time(longlong packed, MYSQL_TIME *my_time,
1360                      enum_mysql_timestamp_type ts_type)
1361 {
1362   if ((my_time->neg= packed < 0))
1363     packed= -packed;
1364   get_one(my_time->second_part, 1000000ULL);
1365   get_one(my_time->second,           60U);
1366   get_one(my_time->minute,           60U);
1367   get_one(my_time->hour,             24U);
1368   get_one(my_time->day,              32U);
1369   get_one(my_time->month,            13U);
1370   my_time->year= (uint)packed;
1371   my_time->time_type= ts_type;
1372   switch (ts_type) {
1373   case MYSQL_TIMESTAMP_TIME:
1374     my_time->hour+= (my_time->month * 32 + my_time->day) * 24;
1375     my_time->month= my_time->day= 0;
1376     break;
1377   case MYSQL_TIMESTAMP_DATE:
1378     my_time->hour= my_time->minute= my_time->second= my_time->second_part= 0;
1379     break;
1380   case MYSQL_TIMESTAMP_NONE:
1381   case MYSQL_TIMESTAMP_ERROR:
1382     DBUG_ASSERT(0);
1383   case MYSQL_TIMESTAMP_DATETIME:
1384     break;
1385   }
1386 }
1387