1/*
2 Copyright (c) 2004, 2012, Oracle and/or its affiliates.
3 Copyright (c) 2010, 2017, MariaDB Corporation.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
17
18#include <my_global.h>
19#include <my_time.h>
20#include <m_string.h>
21#include <m_ctype.h>
22/* Windows version of localtime_r() is declared in my_ptrhead.h */
23#include <my_pthread.h>
24
25
26ulonglong log_10_int[20]=
27{
28 1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL,
29 100000000ULL, 1000000000ULL, 10000000000ULL, 100000000000ULL,
30 1000000000000ULL, 10000000000000ULL, 100000000000000ULL,
31 1000000000000000ULL, 10000000000000000ULL, 100000000000000000ULL,
32 1000000000000000000ULL, 10000000000000000000ULL
33};
34
35
36/* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */
37
38static uchar internal_format_positions[]=
39{0, 1, 2, 3, 4, 5, 6, (uchar) 255};
40
41static char time_separator=':';
42
43static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */
44uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
45
46/*
47 Offset of system time zone from UTC in seconds used to speed up
48 work of my_system_gmt_sec() function.
49*/
50static long my_time_zone=0;
51
52
53/* Calc days in one year. works with 0 <= year <= 99 */
54
55uint calc_days_in_year(uint year)
56{
57 return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
58 366 : 365);
59}
60
61/**
62 @brief Check datetime value for validity according to flags.
63
64 @param[in] ltime Date to check.
65 @param[in] not_zero_date ltime is not the zero date
66 @param[in] flags flags to check
67 (see str_to_datetime() flags in my_time.h)
68 @param[out] was_cut set to 2 if value was invalid according to flags.
69 (Feb 29 in non-leap etc.) This remains unchanged
70 if value is not invalid.
71
72 @details Here we assume that year and month is ok!
73 If month is 0 we allow any date. (This only happens if we allow zero
74 date parts in str_to_datetime())
75 Disallow dates with zero year and non-zero month and/or day.
76
77 @return
78 0 OK
79 1 error
80*/
81
82my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
83 ulonglong flags, int *was_cut)
84{
85 if (ltime->time_type == MYSQL_TIMESTAMP_TIME)
86 return FALSE;
87 if (not_zero_date)
88 {
89 if (((flags & TIME_NO_ZERO_IN_DATE) &&
90 (ltime->month == 0 || ltime->day == 0)) || ltime->neg ||
91 (!(flags & TIME_INVALID_DATES) &&
92 ltime->month && ltime->day > days_in_month[ltime->month-1] &&
93 (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 ||
94 ltime->day != 29)))
95 {
96 *was_cut= 2;
97 return TRUE;
98 }
99 }
100 else if (flags & TIME_NO_ZERO_DATE)
101 {
102 /*
103 We don't set *was_cut here to signal that the problem was a zero date
104 and not an invalid date
105 */
106 return TRUE;
107 }
108 return FALSE;
109}
110
111static int get_number(uint *val, uint *number_of_fields, const char **str,
112 const char *end)
113{
114 const char *s = *str;
115
116 if (s >= end)
117 return 0;
118
119 if (!my_isdigit(&my_charset_latin1, *s))
120 return 1;
121 *val= *s++ - '0';
122
123 for (; s < end && my_isdigit(&my_charset_latin1, *s); s++)
124 *val= *val * 10 + *s - '0';
125 *str = s;
126 (*number_of_fields)++;
127 return 0;
128}
129
130static int get_digits(uint *val, uint *number_of_fields, const char **str,
131 const char *end, uint length)
132{
133 return get_number(val, number_of_fields, str, MY_MIN(end, *str + length));
134}
135
136static int get_punct(const char **str, const char *end)
137{
138 if (*str >= end)
139 return 0;
140 if (my_ispunct(&my_charset_latin1, **str))
141 {
142 (*str)++;
143 return 0;
144 }
145 return 1;
146}
147
148static int get_date_time_separator(uint *number_of_fields, ulonglong flags,
149 const char **str, const char *end)
150{
151 const char *s= *str;
152 if (s >= end)
153 return 0;
154
155 if (*s == 'T')
156 {
157 (*str)++;
158 return 0;
159 }
160
161 /*
162 now, this is tricky, for backward compatibility reasons.
163 cast("11:11:11.12.12.12" as datetime) should give 2011-11-11 12:12:12
164 but
165 cast("11:11:11.12.12.12" as time) should give 11:11:11.12
166 that is, a punctuation character can be accepted as a date/time separator
167 only if TIME_DATETIME_ONLY (see str_to_time) is not set.
168 */
169 if (my_ispunct(&my_charset_latin1, *s))
170 {
171 if (flags & TIME_DATETIME_ONLY)
172 {
173 /* see above, returning 1 is not enough, we need hard abort here */
174 *number_of_fields= 0;
175 return 1;
176 }
177
178 (*str)++;
179 return 0;
180 }
181
182 if (!my_isspace(&my_charset_latin1, *s))
183 return 1;
184
185 do
186 {
187 s++;
188 } while (my_isspace(&my_charset_latin1, *s));
189 *str= s;
190 return 0;
191}
192
193static int get_maybe_T(const char **str, const char *end)
194{
195 if (*str < end && **str == 'T')
196 (*str)++;
197 return 0;
198}
199
200static uint skip_digits(const char **str, const char *end)
201{
202 const char *start= *str, *s= *str;
203 while (s < end && my_isdigit(&my_charset_latin1, *s))
204 s++;
205 *str= s;
206 return (uint)(s - start);
207}
208
209
210/**
211 Check datetime, date, or normalized time (i.e. time without days) range.
212 @param ltime Datetime value.
213 @returns
214 @retval FALSE on success
215 @retval TRUE on error
216*/
217my_bool check_datetime_range(const MYSQL_TIME *ltime)
218{
219 /*
220 In case of MYSQL_TIMESTAMP_TIME hour value can be up to TIME_MAX_HOUR.
221 In case of MYSQL_TIMESTAMP_DATETIME it cannot be bigger than 23.
222 */
223 return
224 ltime->year > 9999 || ltime->month > 12 || ltime->day > 31 ||
225 ltime->minute > 59 || ltime->second > 59 ||
226 ltime->second_part > TIME_MAX_SECOND_PART ||
227 (ltime->hour >
228 (uint) (ltime->time_type == MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR : 23));
229}
230
231
232static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status,
233 uint *number_of_fields,
234 const char **str, const char *end)
235{
236 const char *start= *str;
237 uint tmp= 0; /* For the case '10:10:10.' */
238 if (get_digits(&tmp, number_of_fields, str, end, 6))
239 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
240 if ((status->precision= (uint)(*str - start)) < 6)
241 *val= (ulong) (tmp * log_10_int[6 - (*str - start)]);
242 else
243 *val= tmp;
244 if (skip_digits(str, end))
245 status->warnings|= MYSQL_TIME_NOTE_TRUNCATED;
246}
247
248
249/*
250 Convert a timestamp string to a MYSQL_TIME value.
251
252 SYNOPSIS
253 str_to_datetime()
254 str String to parse
255 length Length of string
256 l_time Date is stored here
257 flags Bitmap of following items
258 TIME_FUZZY_DATE
259 TIME_DATETIME_ONLY Set if we only allow full datetimes.
260 TIME_NO_ZERO_IN_DATE Don't allow partial dates
261 TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
262 TIME_INVALID_DATES Allow 2000-02-31
263 status Conversion status
264
265
266 DESCRIPTION
267 At least the following formats are recogniced (based on number of digits)
268 YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
269 YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS
270 YYYYMMDDTHHMMSS where T is a the character T (ISO8601)
271 Also dates where all parts are zero are allowed
272
273 The second part may have an optional .###### fraction part.
274
275 status->warnings is set to:
276 0 Value OK
277 MYSQL_TIME_WARN_TRUNCATED If value was cut during conversion
278 MYSQL_TIME_WARN_OUT_OF_RANGE check_date(date,flags) considers date invalid
279
280 l_time->time_type is set as follows:
281 MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like
282 [DD [HH:[MM:[SS]]]].fraction.
283 l_time is not changed.
284 MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
285 MYSQL_TIMESTAMP_DATETIME Full timestamp
286 MYSQL_TIMESTAMP_ERROR Timestamp with wrong values.
287 All elements in l_time is set to 0
288 RETURN VALUES
289 0 - Ok
290 1 - Error
291*/
292
293#define MAX_DATE_PARTS 8
294
295my_bool
296str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time,
297 ulonglong flags, MYSQL_TIME_STATUS *status)
298{
299 const char *end=str+length, *pos;
300 uint number_of_fields= 0, digits, year_length, not_zero_date;
301 DBUG_ENTER("str_to_datetime");
302 bzero(l_time, sizeof(*l_time));
303
304 if (flags & TIME_TIME_ONLY)
305 {
306 my_bool ret= str_to_time(str, length, l_time, flags, status);
307 DBUG_RETURN(ret);
308 }
309
310 my_time_status_init(status);
311
312 /* Skip space at start */
313 for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
314 ;
315 if (str == end || ! my_isdigit(&my_charset_latin1, *str))
316 {
317 status->warnings= MYSQL_TIME_WARN_TRUNCATED;
318 l_time->time_type= MYSQL_TIMESTAMP_NONE;
319 DBUG_RETURN(1);
320 }
321
322 /*
323 Calculate number of digits in first part.
324 If length= 8 or >= 14 then year is of format YYYY.
325 (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
326 */
327 pos= str;
328 digits= skip_digits(&pos, end);
329
330 if (pos < end && *pos == 'T') /* YYYYYMMDDHHMMSSThhmmss is supported too */
331 {
332 pos++;
333 digits+= skip_digits(&pos, end);
334 }
335 if (pos < end && *pos == '.' && digits >= 12) /* YYYYYMMDDHHMMSShhmmss.uuuuuu is supported too */
336 {
337 pos++;
338 skip_digits(&pos, end); // ignore the return value
339 }
340
341 if (pos == end)
342 {
343 /*
344 Found date in internal format
345 (only numbers like [YY]YYMMDD[T][hhmmss[.uuuuuu]])
346 */
347 year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
348 if (get_digits(&l_time->year, &number_of_fields, &str, end, year_length)
349 || get_digits(&l_time->month, &number_of_fields, &str, end, 2)
350 || get_digits(&l_time->day, &number_of_fields, &str, end, 2)
351 || get_maybe_T(&str, end)
352 || get_digits(&l_time->hour, &number_of_fields, &str, end, 2)
353 || get_digits(&l_time->minute, &number_of_fields, &str, end, 2)
354 || get_digits(&l_time->second, &number_of_fields, &str, end, 2))
355 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
356 }
357 else
358 {
359 const char *start= str;
360 if (get_number(&l_time->year, &number_of_fields, &str, end))
361 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
362 year_length= (uint)(str - start);
363
364 if (!status->warnings &&
365 (get_punct(&str, end)
366 || get_number(&l_time->month, &number_of_fields, &str, end)
367 || get_punct(&str, end)
368 || get_number(&l_time->day, &number_of_fields, &str, end)
369 || get_date_time_separator(&number_of_fields, flags, &str, end)
370 || get_number(&l_time->hour, &number_of_fields, &str, end)
371 || get_punct(&str, end)
372 || get_number(&l_time->minute, &number_of_fields, &str, end)
373 || get_punct(&str, end)
374 || get_number(&l_time->second, &number_of_fields, &str, end)))
375 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
376 }
377
378 /* we're ok if date part is correct. even if the rest is truncated */
379 if (number_of_fields < 3)
380 {
381 l_time->time_type= MYSQL_TIMESTAMP_NONE;
382 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
383 DBUG_RETURN(TRUE);
384 }
385
386 if (!status->warnings && str < end && *str == '.')
387 {
388 str++;
389 get_microseconds(&l_time->second_part, status,
390 &number_of_fields, &str, end);
391 }
392
393 not_zero_date = l_time->year || l_time->month || l_time->day ||
394 l_time->hour || l_time->minute || l_time->second ||
395 l_time->second_part;
396
397 if (year_length == 2 && not_zero_date)
398 l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
399
400 if (l_time->year > 9999 || l_time->month > 12 || l_time->day > 31 ||
401 l_time->hour > 23 || l_time->minute > 59 || l_time->second > 59)
402 {
403 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
404 goto err;
405 }
406
407 if (check_date(l_time, not_zero_date, flags, &status->warnings))
408 goto err;
409
410 l_time->time_type= (number_of_fields <= 3 ?
411 MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
412
413 for (; str != end ; str++)
414 {
415 if (!my_isspace(&my_charset_latin1,*str))
416 {
417 status->warnings= MYSQL_TIME_WARN_TRUNCATED;
418 break;
419 }
420 }
421
422 DBUG_RETURN(FALSE);
423
424err:
425 bzero((char*) l_time, sizeof(*l_time));
426 l_time->time_type= MYSQL_TIMESTAMP_ERROR;
427 DBUG_RETURN(TRUE);
428}
429
430
431/*
432 Convert a time string to a MYSQL_TIME struct.
433
434 SYNOPSIS
435 str_to_time()
436 str A string in full TIMESTAMP format or
437 [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
438 [M]MSS or [S]S
439 There may be an optional [.second_part] after seconds
440 length Length of str
441 l_time Store result here
442 status Conversion status
443
444
445 NOTES
446
447 Because of the extra days argument, this function can only
448 work with times where the time arguments are in the above order.
449
450 status->warnings is set as follows:
451 MYSQL_TIME_WARN_TRUNCATED if the input string was cut during conversion,
452 and/or
453 MYSQL_TIME_WARN_OUT_OF_RANGE flag is set if the value is out of range.
454
455 RETURN
456 FALSE on success
457 TRUE on error
458*/
459
460my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time,
461 ulonglong fuzzydate, MYSQL_TIME_STATUS *status)
462{
463 ulong date[5];
464 ulonglong value;
465 const char *end=str+length, *end_of_days;
466 my_bool found_days,found_hours, neg= 0;
467 uint UNINIT_VAR(state);
468
469 my_time_status_init(status);
470 for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
471 length--;
472 if (str != end && *str == '-')
473 {
474 neg=1;
475 str++;
476 length--;
477 }
478 if (str == end)
479 {
480 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
481 goto err;
482 }
483
484 /* Check first if this is a full TIMESTAMP */
485 if (length >= 12)
486 { /* Probably full timestamp */
487 (void) str_to_datetime(str, length, l_time,
488 (fuzzydate & ~TIME_TIME_ONLY) | TIME_DATETIME_ONLY,
489 status);
490 if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR)
491 return l_time->time_type == MYSQL_TIMESTAMP_ERROR;
492 my_time_status_init(status);
493 }
494
495 l_time->neg= neg;
496 /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
497 for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
498 value=value*10L + (long) (*str - '0');
499
500 /* Skip all space after 'days' */
501 end_of_days= str;
502 for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++)
503 ;
504
505 found_days=found_hours=0;
506 if ((uint) (end-str) > 1 && str != end_of_days &&
507 my_isdigit(&my_charset_latin1, *str))
508 { /* Found days part */
509 date[0]= (ulong) value;
510 state= 1; /* Assume next is hours */
511 found_days= 1;
512 }
513 else if ((end-str) > 1 && *str == time_separator &&
514 my_isdigit(&my_charset_latin1, str[1]))
515 {
516 date[0]= 0; /* Assume we found hours */
517 date[1]= (ulong) value;
518 state=2;
519 found_hours=1;
520 str++; /* skip ':' */
521 }
522 else
523 {
524 /* String given as one number; assume HHMMSS format */
525 date[0]= 0;
526 date[1]= (ulong) (value/10000);
527 date[2]= (ulong) (value/100 % 100);
528 date[3]= (ulong) (value % 100);
529 state=4;
530 goto fractional;
531 }
532
533 /* Read hours, minutes and seconds */
534 for (;;)
535 {
536 for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
537 value=value*10L + (long) (*str - '0');
538 date[state++]= (ulong) value;
539 if (state == 4 || (end-str) < 2 || *str != time_separator ||
540 !my_isdigit(&my_charset_latin1,str[1]))
541 break;
542 str++; /* Skip time_separator (':') */
543 }
544
545 if (state != 4)
546 { /* Not HH:MM:SS */
547 /* Fix the date to assume that seconds was given */
548 if (!found_hours && !found_days)
549 {
550 bmove_upp((uchar*) (date+4), (uchar*) (date+state),
551 sizeof(long)*(state-1));
552 bzero((uchar*) date, sizeof(long)*(4-state));
553 }
554 else
555 bzero((uchar*) (date+state), sizeof(long)*(4-state));
556 }
557
558fractional:
559 /* Get fractional second part */
560 if (!status->warnings && str < end && *str == '.')
561 {
562 uint number_of_fields= 0;
563 str++;
564 get_microseconds(&date[4], status, &number_of_fields, &str, end);
565 }
566 else
567 date[4]= 0;
568
569 /* Check for exponent part: E<gigit> | E<sign><digit> */
570 /* (may occur as result of %g formatting of time value) */
571 if ((end - str) > 1 &&
572 (*str == 'e' || *str == 'E') &&
573 (my_isdigit(&my_charset_latin1, str[1]) ||
574 ((str[1] == '-' || str[1] == '+') &&
575 (end - str) > 2 &&
576 my_isdigit(&my_charset_latin1, str[2]))))
577 {
578 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
579 goto err;
580 }
581
582 if (internal_format_positions[7] != 255)
583 {
584 /* Read a possible AM/PM */
585 while (str != end && my_isspace(&my_charset_latin1, *str))
586 str++;
587 if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
588 {
589 if (str[0] == 'p' || str[0] == 'P')
590 {
591 str+= 2;
592 date[1]= date[1]%12 + 12;
593 }
594 else if (str[0] == 'a' || str[0] == 'A')
595 str+=2;
596 }
597 }
598
599 /* Integer overflow checks */
600 if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
601 date[2] > UINT_MAX || date[3] > UINT_MAX ||
602 date[4] > UINT_MAX)
603 {
604 status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE;
605 goto err;
606 }
607
608 l_time->year= 0; /* For protocol::store_time */
609 l_time->month= 0;
610 l_time->day= 0;
611 l_time->hour= date[1] + date[0] * 24; /* Mix days and hours */
612 l_time->minute= date[2];
613 l_time->second= date[3];
614 l_time->second_part= date[4];
615 l_time->time_type= MYSQL_TIMESTAMP_TIME;
616
617 /* Check if the value is valid and fits into MYSQL_TIME range */
618 if (check_time_range(l_time, 6, &status->warnings))
619 return TRUE;
620
621 /* Check if there is garbage at end of the MYSQL_TIME specification */
622 if (str != end)
623 {
624 do
625 {
626 if (!my_isspace(&my_charset_latin1,*str))
627 {
628 status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
629 break;
630 }
631 } while (++str != end);
632 }
633 return FALSE;
634
635err:
636 bzero((char*) l_time, sizeof(*l_time));
637 l_time->time_type= MYSQL_TIMESTAMP_ERROR;
638 return TRUE;
639}
640
641
642/*
643 Check 'time' value to lie in the MYSQL_TIME range
644
645 SYNOPSIS:
646 check_time_range()
647 time pointer to MYSQL_TIME value
648 uint dec
649 warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
650
651 DESCRIPTION
652 If the time value lies outside of the range [-838:59:59, 838:59:59],
653 set it to the closest endpoint of the range and set
654 MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
655
656 RETURN
657 0 time value is valid, but was possibly truncated
658 1 time value is invalid
659*/
660
661int check_time_range(struct st_mysql_time *my_time, uint dec, int *warning)
662{
663 longlong hour;
664 static ulong max_sec_part[TIME_SECOND_PART_DIGITS+1]= {000000, 900000, 990000,
665 999000, 999900, 999990, 999999};
666
667 if (my_time->minute >= 60 || my_time->second >= 60)
668 {
669 *warning|= MYSQL_TIME_WARN_TRUNCATED;
670 return 1;
671 }
672
673 hour= my_time->hour + (24*my_time->day);
674
675 if (dec == AUTO_SEC_PART_DIGITS)
676 dec= TIME_SECOND_PART_DIGITS;
677
678 if (hour <= TIME_MAX_HOUR &&
679 (hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE ||
680 my_time->second != TIME_MAX_SECOND ||
681 my_time->second_part <= max_sec_part[dec]))
682 return 0;
683
684 my_time->day= 0;
685 my_time->hour= TIME_MAX_HOUR;
686 my_time->minute= TIME_MAX_MINUTE;
687 my_time->second= TIME_MAX_SECOND;
688 my_time->second_part= max_sec_part[dec];
689 *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE;
690 return 0;
691}
692
693
694/*
695 Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
696
697 SYNOPSIS
698 my_init_time()
699*/
700void my_init_time(void)
701{
702 time_t seconds;
703 struct tm *l_time,tm_tmp;
704 MYSQL_TIME my_time;
705 uint not_used;
706
707 seconds= (time_t) time((time_t*) 0);
708 localtime_r(&seconds,&tm_tmp);
709 l_time= &tm_tmp;
710 my_time_zone= 3600; /* Comp. for -3600 in my_gmt_sec */
711 my_time.year= (uint) l_time->tm_year+1900;
712 my_time.month= (uint) l_time->tm_mon+1;
713 my_time.day= (uint) l_time->tm_mday;
714 my_time.hour= (uint) l_time->tm_hour;
715 my_time.minute= (uint) l_time->tm_min;
716 my_time.second= (uint) l_time->tm_sec;
717 my_time.neg= 0;
718 my_time.second_part= 0;
719 my_time.time_type= MYSQL_TIMESTAMP_DATETIME;
720
721 my_system_gmt_sec(&my_time, &my_time_zone, &not_used); /* Init my_time_zone */
722}
723
724
725/*
726 Handle 2 digit year conversions
727
728 SYNOPSIS
729 year_2000_handling()
730 year 2 digit year
731
732 RETURN
733 Year between 1970-2069
734*/
735
736uint year_2000_handling(uint year)
737{
738 if ((year=year+1900) < 1900+YY_PART_YEAR)
739 year+=100;
740 return year;
741}
742
743
744/*
745 Calculate nr of day since year 0 in new date-system (from 1615)
746
747 SYNOPSIS
748 calc_daynr()
749 year Year (exact 4 digit year, no year conversions)
750 month Month
751 day Day
752
753 NOTES: 0000-00-00 is a valid date, and will return 0
754
755 RETURN
756 Days since 0000-00-00
757*/
758
759long calc_daynr(uint year,uint month,uint day)
760{
761 long delsum;
762 int temp;
763 int y= year; /* may be < 0 temporarily */
764 DBUG_ENTER("calc_daynr");
765
766 if (y == 0 && month == 0)
767 DBUG_RETURN(0); /* Skip errors */
768 /* Cast to int to be able to handle month == 0 */
769 delsum= (long) (365 * y + 31 *((int) month - 1) + (int) day);
770 if (month <= 2)
771 y--;
772 else
773 delsum-= (long) ((int) month * 4 + 23) / 10;
774 temp=(int) ((y/100+1)*3)/4;
775 DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld",
776 y+(month <= 2),month,day,delsum+y/4-temp));
777 DBUG_ASSERT(delsum+(int) y/4-temp >= 0);
778 DBUG_RETURN(delsum+(int) y/4-temp);
779} /* calc_daynr */
780
781/*
782 Convert time in MYSQL_TIME representation in system time zone to its
783 my_time_t form (number of seconds in UTC since begginning of Unix Epoch).
784
785 SYNOPSIS
786 my_system_gmt_sec()
787 t - time value to be converted
788 my_timezone - pointer to long where offset of system time zone
789 from UTC will be stored for caching
790 error_code - 0, if the conversion was successful;
791 ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
792 which is out of TIMESTAMP range;
793 ER_WARN_INVALID_TIMESTAMP, if t represents value which
794 doesn't exists (falls into the spring time-gap).
795
796 NOTES
797 The idea is to cache the time zone offset from UTC (including daylight
798 saving time) for the next call to make things faster. But currently we
799 just calculate this offset during startup (by calling my_init_time()
800 function) and use it all the time.
801 Time value provided should be legal time value (e.g. '2003-01-01 25:00:00'
802 is not allowed).
803
804 RETURN VALUE
805 Time in UTC seconds since Unix Epoch representation.
806*/
807my_time_t
808my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, uint *error_code)
809{
810 uint loop;
811 time_t tmp= 0;
812 int shift= 0;
813 MYSQL_TIME tmp_time;
814 MYSQL_TIME *t= &tmp_time;
815 struct tm *l_time,tm_tmp;
816 long diff, current_timezone;
817
818 /*
819 Use temp variable to avoid trashing input data, which could happen in
820 case of shift required for boundary dates processing.
821 */
822 memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME));
823
824 if (!validate_timestamp_range(t))
825 {
826 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
827 return 0;
828 }
829 *error_code= 0;
830
831 /*
832 Calculate the gmt time based on current time and timezone
833 The -1 on the end is to ensure that if have a date that exists twice
834 (like 2002-10-27 02:00:0 MET), we will find the initial date.
835
836 By doing -3600 we will have to call localtime_r() several times, but
837 I couldn't come up with a better way to get a repeatable result :(
838
839 We can't use mktime() as it's buggy on many platforms and not thread safe.
840
841 Note: this code assumes that our time_t estimation is not too far away
842 from real value (we assume that localtime_r(tmp) will return something
843 within 24 hrs from t) which is probably true for all current time zones.
844
845 Note2: For the dates, which have time_t representation close to
846 MAX_INT32 (efficient time_t limit for supported platforms), we should
847 do a small trick to avoid overflow. That is, convert the date, which is
848 two days earlier, and then add these days to the final value.
849
850 The same trick is done for the values close to 0 in time_t
851 representation for platfroms with unsigned time_t (QNX).
852
853 To be more verbose, here is a sample (extracted from the code below):
854 (calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L
855 would return -2147480896 because of the long type overflow. In result
856 we would get 1901 year in localtime_r(), which is an obvious error.
857
858 Alike problem raises with the dates close to Epoch. E.g.
859 (calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L
860 will give -3600.
861
862 On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600)
863 wil give us a date around 2106 year. Which is no good.
864
865 Theoreticaly, there could be problems with the latter conversion:
866 there are at least two timezones, which had time switches near 1 Jan
867 of 1970 (because of political reasons). These are America/Hermosillo and
868 America/Mazatlan time zones. They changed their offset on
869 1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones
870 the code below will give incorrect results for dates close to
871 1970-01-01, in the case OS takes into account these historical switches.
872 Luckily, it seems that we support only one platform with unsigned
873 time_t. It's QNX. And QNX does not support historical timezone data at all.
874 E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply
875 historical information for localtime_r() etc. That is, the problem is not
876 relevant to QNX.
877
878 We are safe with shifts close to MAX_INT32, as there are no known
879 time switches on Jan 2038 yet :)
880 */
881 if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4))
882 {
883 /*
884 Below we will pass (uint) (t->day - shift) to calc_daynr.
885 As we don't want to get an overflow here, we will shift
886 only safe dates. That's why we have (t->day > 4) above.
887 */
888 t->day-= 2;
889 shift= 2;
890 }
891#ifdef TIME_T_UNSIGNED
892 else
893 {
894 /*
895 We can get 0 in time_t representaion only on 1969, 31 of Dec or on
896 1970, 1 of Jan. For both dates we use shift, which is added
897 to t->day in order to step out a bit from the border.
898 This is required for platforms, where time_t is unsigned.
899 As far as I know, among the platforms we support it's only QNX.
900 Note: the order of below if-statements is significant.
901 */
902
903 if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1)
904 && (t->day <= 10))
905 {
906 t->day+= 2;
907 shift= -2;
908 }
909
910 if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12)
911 && (t->day == 31))
912 {
913 t->year++;
914 t->month= 1;
915 t->day= 2;
916 shift= -2;
917 }
918 }
919#endif
920
921 tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) -
922 (long) days_at_timestart) * SECONDS_IN_24H +
923 (long) t->hour*3600L +
924 (long) (t->minute*60 + t->second)) + (time_t) my_time_zone -
925 3600);
926
927 current_timezone= my_time_zone;
928 localtime_r(&tmp,&tm_tmp);
929 l_time=&tm_tmp;
930 for (loop=0;
931 loop < 2 &&
932 (t->hour != (uint) l_time->tm_hour ||
933 t->minute != (uint) l_time->tm_min ||
934 t->second != (uint) l_time->tm_sec);
935 loop++)
936 { /* One check should be enough ? */
937 /* Get difference in days */
938 int days= t->day - l_time->tm_mday;
939 if (days < -1)
940 days= 1; /* Month has wrapped */
941 else if (days > 1)
942 days= -1;
943 diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) +
944 (long) (60*((int) t->minute - (int) l_time->tm_min)) +
945 (long) ((int) t->second - (int) l_time->tm_sec));
946 current_timezone+= diff+3600; /* Compensate for -3600 above */
947 tmp+= (time_t) diff;
948 localtime_r(&tmp,&tm_tmp);
949 l_time=&tm_tmp;
950 }
951 /*
952 Fix that if we are in the non existing daylight saving time hour
953 we move the start of the next real hour.
954
955 This code doesn't handle such exotical thing as time-gaps whose length
956 is more than one hour or non-integer (latter can theoretically happen
957 if one of seconds will be removed due leap correction, or because of
958 general time correction like it happened for Africa/Monrovia time zone
959 in year 1972).
960 */
961 if (loop == 2 && t->hour != (uint) l_time->tm_hour)
962 {
963 int days= t->day - l_time->tm_mday;
964 if (days < -1)
965 days=1; /* Month has wrapped */
966 else if (days > 1)
967 days= -1;
968 diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+
969 (long) (60*((int) t->minute - (int) l_time->tm_min)) +
970 (long) ((int) t->second - (int) l_time->tm_sec));
971 if (diff == 3600)
972 tmp+=3600 - t->minute*60 - t->second; /* Move to next hour */
973 else if (diff == -3600)
974 tmp-=t->minute*60 + t->second; /* Move to previous hour */
975
976 *error_code= ER_WARN_INVALID_TIMESTAMP;
977 }
978 *my_timezone= current_timezone;
979
980
981 /* shift back, if we were dealing with boundary dates */
982 tmp+= shift * SECONDS_IN_24H;
983
984 /*
985 This is possible for dates, which slightly exceed boundaries.
986 Conversion will pass ok for them, but we don't allow them.
987 First check will pass for platforms with signed time_t.
988 instruction above (tmp+= shift*86400L) could exceed
989 MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen.
990 So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms
991 with unsigned time_t tmp+= shift*86400L might result in a number,
992 larger then TIMESTAMP_MAX_VALUE, so another check will work.
993 */
994 if (!IS_TIME_T_VALID_FOR_TIMESTAMP(tmp))
995 {
996 tmp= 0;
997 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
998 }
999
1000 return (my_time_t) tmp;
1001} /* my_system_gmt_sec */
1002
1003
1004/* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */
1005
1006void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
1007{
1008 bzero((void*) tm, sizeof(*tm));
1009 tm->time_type= time_type;
1010}
1011
1012
1013/*
1014 Helper function for datetime formatting.
1015 Format number as string, left-padded with 0.
1016
1017 The reason to use own formatting rather than sprintf() is performance - in a
1018 datetime benchmark it helped to reduced the datetime formatting overhead
1019 from ~30% down to ~4%.
1020*/
1021
1022static char* fmt_number(uint val, char *out, uint digits)
1023{
1024 uint i;
1025 for(i= 0; i < digits; i++)
1026 {
1027 out[digits-i-1]= '0' + val%10;
1028 val/=10;
1029 }
1030 return out + digits;
1031}
1032
1033
1034/*
1035 Functions to convert time/date/datetime value to a string,
1036 using default format.
1037 This functions don't check that given MYSQL_TIME structure members are
1038 in valid range. If they are not, return value won't reflect any
1039 valid date either.
1040
1041 RETURN
1042 number of characters written to 'to'
1043*/
1044
1045int my_time_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
1046{
1047 uint day= (l_time->year || l_time->month) ? 0 : l_time->day;
1048 uint hour= day * 24 + l_time->hour;
1049 char*pos= to;
1050
1051 if (digits == AUTO_SEC_PART_DIGITS)
1052 digits= l_time->second_part ? TIME_SECOND_PART_DIGITS : 0;
1053
1054 DBUG_ASSERT(digits <= TIME_SECOND_PART_DIGITS);
1055
1056 if(l_time->neg)
1057 *pos++= '-';
1058
1059 if(hour > 99)
1060 /* Need more than 2 digits for hours in string representation. */
1061 pos= longlong10_to_str((longlong)hour, pos, 10);
1062 else
1063 pos= fmt_number(hour, pos, 2);
1064
1065 *pos++= ':';
1066 pos= fmt_number(l_time->minute, pos, 2);
1067 *pos++= ':';
1068 pos= fmt_number(l_time->second, pos, 2);
1069
1070 if (digits)
1071 {
1072 *pos++= '.';
1073 pos= fmt_number((uint)sec_part_shift(l_time->second_part, digits),
1074 pos, digits);
1075 }
1076
1077 *pos= 0;
1078 return (int) (pos-to);
1079}
1080
1081
1082int my_date_to_str(const MYSQL_TIME *l_time, char *to)
1083{
1084 char *pos=to;
1085 pos= fmt_number(l_time->year, pos, 4);
1086 *pos++='-';
1087 pos= fmt_number(l_time->month, pos, 2);
1088 *pos++='-';
1089 pos= fmt_number(l_time->day, pos, 2);
1090 *pos= 0;
1091 return (int)(pos - to);
1092}
1093
1094
1095int my_datetime_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
1096{
1097 char *pos= to;
1098
1099 if (digits == AUTO_SEC_PART_DIGITS)
1100 digits= l_time->second_part ? TIME_SECOND_PART_DIGITS : 0;
1101
1102 DBUG_ASSERT(digits <= TIME_SECOND_PART_DIGITS);
1103
1104 pos= fmt_number(l_time->year, pos, 4);
1105 *pos++='-';
1106 pos= fmt_number(l_time->month, pos, 2);
1107 *pos++='-';
1108 pos= fmt_number(l_time->day, pos, 2);
1109 *pos++=' ';
1110 pos= fmt_number(l_time->hour, pos, 2);
1111 *pos++= ':';
1112 pos= fmt_number(l_time->minute, pos, 2);
1113 *pos++= ':';
1114 pos= fmt_number(l_time->second, pos, 2);
1115
1116 if (digits)
1117 {
1118 *pos++='.';
1119 pos= fmt_number((uint) sec_part_shift(l_time->second_part, digits), pos,
1120 digits);
1121 }
1122
1123 *pos= 0;
1124 return (int)(pos - to);
1125}
1126
1127
1128/*
1129 Convert struct DATE/TIME/DATETIME value to string using built-in
1130 MySQL time conversion formats.
1131
1132 SYNOPSIS
1133 my_TIME_to_string()
1134
1135 RETURN
1136 length of string
1137
1138 NOTE
1139 The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved.
1140*/
1141
1142int my_TIME_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
1143{
1144 switch (l_time->time_type) {
1145 case MYSQL_TIMESTAMP_DATETIME:
1146 return my_datetime_to_str(l_time, to, digits);
1147 case MYSQL_TIMESTAMP_DATE:
1148 return my_date_to_str(l_time, to);
1149 case MYSQL_TIMESTAMP_TIME:
1150 return my_time_to_str(l_time, to, digits);
1151 case MYSQL_TIMESTAMP_NONE:
1152 case MYSQL_TIMESTAMP_ERROR:
1153 to[0]='\0';
1154 return 0;
1155 default:
1156 DBUG_ASSERT(0);
1157 return 0;
1158 }
1159}
1160
1161
1162/**
1163 Print a timestamp with an optional fractional part: XXXXX[.YYYYY]
1164
1165 @param tm The timestamp value to print.
1166 @param OUT to The string pointer to print at.
1167 @param dec Precision, in the range 0..6.
1168 @return The length of the result string.
1169*/
1170int my_timeval_to_str(const struct timeval *tm, char *to, uint dec)
1171{
1172 char *pos= longlong10_to_str((longlong) tm->tv_sec, to, 10);
1173 if (dec)
1174 {
1175 *pos++= '.';
1176 pos= fmt_number((uint) sec_part_shift(tm->tv_usec, dec), pos, dec);
1177 }
1178 *pos= '\0';
1179 return (int) (pos - to);
1180}
1181
1182
1183/*
1184 Convert datetime value specified as number to broken-down TIME
1185 representation and form value of DATETIME type as side-effect.
1186
1187 SYNOPSIS
1188 number_to_datetime()
1189 nr - datetime value as number
1190 time_res - pointer for structure for broken-down representation
1191 flags - flags to use in validating date, as in str_to_datetime()
1192 was_cut 0 Value ok
1193 1 If value was cut during conversion
1194 2 check_date(date,flags) considers date invalid
1195
1196 DESCRIPTION
1197 Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
1198 YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in
1199 YYYYMMDDHHMMSS format as side-effect.
1200
1201 This function also checks if datetime value fits in DATETIME range.
1202
1203 RETURN VALUE
1204 -1 Timestamp with wrong values
1205 anything else DATETIME as integer in YYYYMMDDHHMMSS format
1206 Datetime value in YYYYMMDDHHMMSS format.
1207*/
1208
1209longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res,
1210 ulonglong flags, int *was_cut)
1211{
1212 long part1,part2;
1213
1214 *was_cut= 0;
1215 time_res->time_type=MYSQL_TIMESTAMP_DATE;
1216
1217 if (nr == 0 || nr >= 10000101000000LL)
1218 {
1219 time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1220 goto ok;
1221 }
1222 if (nr < 101)
1223 goto err;
1224 if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
1225 {
1226 nr= (nr+20000000L)*1000000L; /* YYMMDD, year: 2000-2069 */
1227 goto ok;
1228 }
1229 if (nr < (YY_PART_YEAR)*10000L+101L)
1230 goto err;
1231 if (nr <= 991231L)
1232 {
1233 nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
1234 goto ok;
1235 }
1236 if (nr < 10000101L)
1237 goto err;
1238 if (nr <= 99991231L)
1239 {
1240 nr= nr*1000000L;
1241 goto ok;
1242 }
1243 if (nr < 101000000L)
1244 goto err;
1245
1246 time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1247
1248 if (nr <= (YY_PART_YEAR-1)*10000000000LL+1231235959LL)
1249 {
1250 nr= nr+20000000000000LL; /* YYMMDDHHMMSS, 2000-2069 */
1251 goto ok;
1252 }
1253 if (nr < YY_PART_YEAR*10000000000LL+ 101000000LL)
1254 goto err;
1255 if (nr <= 991231235959LL)
1256 nr= nr+19000000000000LL; /* YYMMDDHHMMSS, 1970-1999 */
1257
1258 ok:
1259 part1=(long) (nr/1000000LL);
1260 part2=(long) (nr - (longlong) part1*1000000LL);
1261 time_res->year= (int) (part1/10000L); part1%=10000L;
1262 time_res->month= (int) part1 / 100;
1263 time_res->day= (int) part1 % 100;
1264 time_res->hour= (int) (part2/10000L); part2%=10000L;
1265 time_res->minute=(int) part2 / 100;
1266 time_res->second=(int) part2 % 100;
1267 time_res->second_part= sec_part;
1268 time_res->neg= 0;
1269
1270 if (time_res->year <= 9999 && time_res->month <= 12 &&
1271 time_res->day <= 31 && time_res->hour <= 23 &&
1272 time_res->minute <= 59 && time_res->second <= 59 &&
1273 sec_part <= TIME_MAX_SECOND_PART &&
1274 !check_date(time_res, nr || sec_part, flags, was_cut))
1275 {
1276 if (time_res->time_type == MYSQL_TIMESTAMP_DATE && sec_part != 0)
1277 *was_cut= MYSQL_TIME_NOTE_TRUNCATED;
1278 return nr;
1279 }
1280
1281 /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
1282 if (nr || !(flags & TIME_NO_ZERO_DATE))
1283 *was_cut= 1;
1284 return -1;
1285
1286 err:
1287 {
1288 /* reset everything except time_type */
1289 enum enum_mysql_timestamp_type save= time_res->time_type;
1290 bzero((char*) time_res, sizeof(*time_res));
1291 time_res->time_type= save; /* Restore range */
1292 *was_cut= 1; /* Found invalid date */
1293 }
1294 return -1;
1295}
1296
1297/*
1298 Convert a pair of integers to a MYSQL_TIME struct.
1299
1300 @param[in] nr a number to convert
1301 @param[out] ltime Date to check.
1302 @param[out] was_cut MYSQL_TIME_WARN_OUT_OF_RANGE if the value was
1303 modified to fit in the valid range. Otherwise 0.
1304
1305 @details
1306 Takes a number in the [-]HHHMMSS.uuuuuu,
1307 YYMMDDHHMMSS.uuuuuu, or in the YYYYMMDDHHMMSS.uuuuuu formats.
1308
1309 @return
1310 0 time value is valid, but was possibly truncated
1311 -1 time value is invalid
1312*/
1313int number_to_time(my_bool neg, ulonglong nr, ulong sec_part,
1314 MYSQL_TIME *ltime, int *was_cut)
1315{
1316 if (nr > 9999999 && nr < 99991231235959ULL && neg == 0)
1317 return number_to_datetime(nr, sec_part, ltime,
1318 TIME_INVALID_DATES, was_cut) < 0 ? -1 : 0;
1319
1320 *was_cut= 0;
1321 ltime->year= ltime->month= ltime->day= 0;
1322 ltime->time_type= MYSQL_TIMESTAMP_TIME;
1323
1324 ltime->neg= neg;
1325
1326 if (nr > TIME_MAX_VALUE)
1327 {
1328 nr= TIME_MAX_VALUE;
1329 sec_part= TIME_MAX_SECOND_PART;
1330 *was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE;
1331 }
1332 ltime->hour = (uint)(nr/100/100);
1333 ltime->minute= nr/100%100;
1334 ltime->second= nr%100;
1335 ltime->second_part= sec_part;
1336
1337 if (ltime->minute < 60 && ltime->second < 60 && sec_part <= TIME_MAX_SECOND_PART)
1338 return 0;
1339
1340 *was_cut= MYSQL_TIME_WARN_TRUNCATED;
1341 return -1;
1342}
1343
1344
1345/* Convert time value to integer in YYYYMMDDHHMMSS format */
1346
1347ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)
1348{
1349 return ((ulonglong) (my_time->year * 10000UL +
1350 my_time->month * 100UL +
1351 my_time->day) * 1000000ULL +
1352 (ulonglong) (my_time->hour * 10000UL +
1353 my_time->minute * 100UL +
1354 my_time->second));
1355}
1356
1357
1358/* Convert MYSQL_TIME value to integer in YYYYMMDD format */
1359
1360ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *my_time)
1361{
1362 return (ulonglong) (my_time->year * 10000UL + my_time->month * 100UL +
1363 my_time->day);
1364}
1365
1366
1367/*
1368 Convert MYSQL_TIME value to integer in HHMMSS format.
1369 This function doesn't take into account time->day member:
1370 it's assumed that days have been converted to hours already.
1371*/
1372
1373ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *my_time)
1374{
1375 return (ulonglong) (my_time->hour * 10000UL +
1376 my_time->minute * 100UL +
1377 my_time->second);
1378}
1379
1380
1381/*
1382 Convert struct MYSQL_TIME (date and time split into year/month/day/hour/...
1383 to a number in format YYYYMMDDHHMMSS (DATETIME),
1384 YYYYMMDD (DATE) or HHMMSS (TIME).
1385
1386 SYNOPSIS
1387 TIME_to_ulonglong()
1388
1389 DESCRIPTION
1390 The function is used when we need to convert value of time item
1391 to a number if it's used in numeric context, i. e.:
1392 SELECT NOW()+1, CURDATE()+0, CURTIME()+0;
1393 SELECT ?+1;
1394
1395 NOTE
1396 This function doesn't check that given MYSQL_TIME structure members are
1397 in valid range. If they are not, return value won't reflect any
1398 valid date either.
1399*/
1400
1401ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time)
1402{
1403 switch (my_time->time_type) {
1404 case MYSQL_TIMESTAMP_DATETIME:
1405 return TIME_to_ulonglong_datetime(my_time);
1406 case MYSQL_TIMESTAMP_DATE:
1407 return TIME_to_ulonglong_date(my_time);
1408 case MYSQL_TIMESTAMP_TIME:
1409 return TIME_to_ulonglong_time(my_time);
1410 case MYSQL_TIMESTAMP_NONE:
1411 case MYSQL_TIMESTAMP_ERROR:
1412 return 0;
1413 default:
1414 DBUG_ASSERT(0);
1415 }
1416 return 0;
1417}
1418
1419double TIME_to_double(const MYSQL_TIME *my_time)
1420{
1421 double d= (double)TIME_to_ulonglong(my_time);
1422
1423 if (my_time->time_type == MYSQL_TIMESTAMP_DATE)
1424 return d;
1425
1426 d+= my_time->second_part/(double)TIME_SECOND_PART_FACTOR;
1427 return my_time->neg ? -d : d;
1428}
1429