mySQL 5.0.11 sources for tomato
[tomato.git] / release / src / router / mysql / sql-common / my_time.c
blobbd60277d1bc467048124d438c35e1a1cb4b8a2d3
1 /*
2 Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
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.
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.
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-1301 USA
18 #include <my_time.h>
19 #include <m_string.h>
20 #include <m_ctype.h>
21 /* Windows version of localtime_r() is declared in my_ptrhead.h */
22 #include <my_pthread.h>
24 ulonglong log_10_int[20]=
26 1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL,
27 ULL(100000000), ULL(1000000000), ULL(10000000000), ULL(100000000000),
28 ULL(1000000000000), ULL(10000000000000), ULL(100000000000000),
29 ULL(1000000000000000), ULL(10000000000000000), ULL(100000000000000000),
30 ULL(1000000000000000000), ULL(10000000000000000000)
34 /* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */
36 static uchar internal_format_positions[]=
37 {0, 1, 2, 3, 4, 5, 6, (uchar) 255};
39 static char time_separator=':';
41 static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */
42 uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
45 Offset of system time zone from UTC in seconds used to speed up
46 work of my_system_gmt_sec() function.
48 static long my_time_zone=0;
51 /* Calc days in one year. works with 0 <= year <= 99 */
53 uint calc_days_in_year(uint year)
55 return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
56 366 : 365);
59 /**
60 @brief Check datetime value for validity according to flags.
62 @param[in] ltime Date to check.
63 @param[in] not_zero_date ltime is not the zero date
64 @param[in] flags flags to check
65 (see str_to_datetime() flags in my_time.h)
66 @param[out] was_cut set to 2 if value was invalid according to flags.
67 (Feb 29 in non-leap etc.) This remains unchanged
68 if value is not invalid.
70 @details Here we assume that year and month is ok!
71 If month is 0 we allow any date. (This only happens if we allow zero
72 date parts in str_to_datetime())
73 Disallow dates with zero year and non-zero month and/or day.
75 @return
76 0 OK
77 1 error
80 my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
81 ulong flags, int *was_cut)
83 if (not_zero_date)
85 if ((((flags & TIME_NO_ZERO_IN_DATE) || !(flags & TIME_FUZZY_DATE)) &&
86 (ltime->month == 0 || ltime->day == 0)) ||
87 (!(flags & TIME_INVALID_DATES) &&
88 ltime->month && ltime->day > days_in_month[ltime->month-1] &&
89 (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 ||
90 ltime->day != 29)))
92 *was_cut= 2;
93 return TRUE;
96 else if (flags & TIME_NO_ZERO_DATE)
99 We don't set *was_cut here to signal that the problem was a zero date
100 and not an invalid date
102 return TRUE;
104 return FALSE;
109 Convert a timestamp string to a MYSQL_TIME value.
111 SYNOPSIS
112 str_to_datetime()
113 str String to parse
114 length Length of string
115 l_time Date is stored here
116 flags Bitmap of following items
117 TIME_FUZZY_DATE Set if we should allow partial dates
118 TIME_DATETIME_ONLY Set if we only allow full datetimes.
119 TIME_NO_ZERO_IN_DATE Don't allow partial dates
120 TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
121 TIME_INVALID_DATES Allow 2000-02-31
122 was_cut 0 Value OK
123 1 If value was cut during conversion
124 2 check_date(date,flags) considers date invalid
126 DESCRIPTION
127 At least the following formats are recogniced (based on number of digits)
128 YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
129 YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS
130 YYYYMMDDTHHMMSS where T is a the character T (ISO8601)
131 Also dates where all parts are zero are allowed
133 The second part may have an optional .###### fraction part.
135 NOTES
136 This function should work with a format position vector as long as the
137 following things holds:
138 - All date are kept together and all time parts are kept together
139 - Date and time parts must be separated by blank
140 - Second fractions must come after second part and be separated
141 by a '.'. (The second fractions are optional)
142 - AM/PM must come after second fractions (or after seconds if no fractions)
143 - Year must always been specified.
144 - If time is before date, then we will use datetime format only if
145 the argument consist of two parts, separated by space.
146 Otherwise we will assume the argument is a date.
147 - The hour part must be specified in hour-minute-second order.
149 RETURN VALUES
150 MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like
151 [DD [HH:[MM:[SS]]]].fraction.
152 l_time is not changed.
153 MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
154 MYSQL_TIMESTAMP_DATETIME Full timestamp
155 MYSQL_TIMESTAMP_ERROR Timestamp with wrong values.
156 All elements in l_time is set to 0
159 #define MAX_DATE_PARTS 8
161 enum enum_mysql_timestamp_type
162 str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
163 uint flags, int *was_cut)
165 uint field_length, UNINIT_VAR(year_length), digits, i, number_of_fields;
166 uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS];
167 uint add_hours= 0, start_loop;
168 ulong not_zero_date, allow_space;
169 my_bool is_internal_format;
170 const char *pos, *UNINIT_VAR(last_field_pos);
171 const char *end=str+length;
172 const uchar *format_position;
173 my_bool found_delimitier= 0, found_space= 0;
174 uint frac_pos, frac_len;
175 DBUG_ENTER("str_to_datetime");
176 DBUG_PRINT("ENTER",("str: %.*s",length,str));
178 LINT_INIT(field_length);
180 *was_cut= 0;
182 /* Skip space at start */
183 for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
185 if (str == end || ! my_isdigit(&my_charset_latin1, *str))
187 *was_cut= 1;
188 DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
191 is_internal_format= 0;
192 /* This has to be changed if want to activate different timestamp formats */
193 format_position= internal_format_positions;
196 Calculate number of digits in first part.
197 If length= 8 or >= 14 then year is of format YYYY.
198 (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
200 for (pos=str;
201 pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T');
202 pos++)
205 digits= (uint) (pos-str);
206 start_loop= 0; /* Start of scan loop */
207 date_len[format_position[0]]= 0; /* Length of year field */
208 if (pos == end || *pos == '.')
210 /* Found date in internal format (only numbers like YYYYMMDD) */
211 year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
212 field_length= year_length;
213 is_internal_format= 1;
214 format_position= internal_format_positions;
216 else
218 if (format_position[0] >= 3) /* If year is after HHMMDD */
221 If year is not in first part then we have to determinate if we got
222 a date field or a datetime field.
223 We do this by checking if there is two numbers separated by
224 space in the input.
226 while (pos < end && !my_isspace(&my_charset_latin1, *pos))
227 pos++;
228 while (pos < end && !my_isdigit(&my_charset_latin1, *pos))
229 pos++;
230 if (pos == end)
232 if (flags & TIME_DATETIME_ONLY)
234 *was_cut= 1;
235 DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a full datetime */
237 /* Date field. Set hour, minutes and seconds to 0 */
238 date[0]= date[1]= date[2]= date[3]= date[4]= 0;
239 start_loop= 5; /* Start with first date part */
243 field_length= format_position[0] == 0 ? 4 : 2;
247 Only allow space in the first "part" of the datetime field and:
248 - after days, part seconds
249 - before and after AM/PM (handled by code later)
251 2003-03-03 20:00:20 AM
252 20:00:20.000000 AM 03-03-2000
254 i= max((uint) format_position[0], (uint) format_position[1]);
255 set_if_bigger(i, (uint) format_position[2]);
256 allow_space= ((1 << i) | (1 << format_position[6]));
257 allow_space&= (1 | 2 | 4 | 8);
259 not_zero_date= 0;
260 for (i = start_loop;
261 i < MAX_DATE_PARTS-1 && str != end &&
262 my_isdigit(&my_charset_latin1,*str);
263 i++)
265 const char *start= str;
266 ulong tmp_value= (uint) (uchar) (*str++ - '0');
269 Internal format means no delimiters; every field has a fixed
270 width. Otherwise, we scan until we find a delimiter and discard
271 leading zeroes -- except for the microsecond part, where leading
272 zeroes are significant, and where we never process more than six
273 digits.
275 my_bool scan_until_delim= !is_internal_format &&
276 ((i != format_position[6]));
278 while (str != end && my_isdigit(&my_charset_latin1,str[0]) &&
279 (scan_until_delim || --field_length))
281 tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0');
282 str++;
284 date_len[i]= (uint) (str - start);
285 if (tmp_value > 999999) /* Impossible date part */
287 *was_cut= 1;
288 DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
290 date[i]=tmp_value;
291 not_zero_date|= tmp_value;
293 /* Length of next field */
294 field_length= format_position[i+1] == 0 ? 4 : 2;
296 if ((last_field_pos= str) == end)
298 i++; /* Register last found part */
299 break;
301 /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
302 if (i == format_position[2] && *str == 'T')
304 str++; /* ISO8601: CCYYMMDDThhmmss */
305 continue;
307 if (i == format_position[5]) /* Seconds */
309 if (*str == '.') /* Followed by part seconds */
311 str++;
312 field_length= 6; /* 6 digits */
314 continue;
316 while (str != end &&
317 (my_ispunct(&my_charset_latin1,*str) ||
318 my_isspace(&my_charset_latin1,*str)))
320 if (my_isspace(&my_charset_latin1,*str))
322 if (!(allow_space & (1 << i)))
324 *was_cut= 1;
325 DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
327 found_space= 1;
329 str++;
330 found_delimitier= 1; /* Should be a 'normal' date */
332 /* Check if next position is AM/PM */
333 if (i == format_position[6]) /* Seconds, time for AM/PM */
335 i++; /* Skip AM/PM part */
336 if (format_position[7] != 255) /* If using AM/PM */
338 if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
340 if (str[0] == 'p' || str[0] == 'P')
341 add_hours= 12;
342 else if (str[0] != 'a' || str[0] != 'A')
343 continue; /* Not AM/PM */
344 str+= 2; /* Skip AM/PM */
345 /* Skip space after AM/PM */
346 while (str != end && my_isspace(&my_charset_latin1,*str))
347 str++;
351 last_field_pos= str;
353 if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
355 *was_cut= 1;
356 DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a datetime */
359 str= last_field_pos;
361 number_of_fields= i - start_loop;
362 while (i < MAX_DATE_PARTS)
364 date_len[i]= 0;
365 date[i++]= 0;
368 if (!is_internal_format)
370 year_length= date_len[(uint) format_position[0]];
371 if (!year_length) /* Year must be specified */
373 *was_cut= 1;
374 DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
377 l_time->year= date[(uint) format_position[0]];
378 l_time->month= date[(uint) format_position[1]];
379 l_time->day= date[(uint) format_position[2]];
380 l_time->hour= date[(uint) format_position[3]];
381 l_time->minute= date[(uint) format_position[4]];
382 l_time->second= date[(uint) format_position[5]];
384 frac_pos= (uint) format_position[6];
385 frac_len= date_len[frac_pos];
386 if (frac_len < 6)
387 date[frac_pos]*= (uint) log_10_int[6 - frac_len];
388 l_time->second_part= date[frac_pos];
390 if (format_position[7] != (uchar) 255)
392 if (l_time->hour > 12)
394 *was_cut= 1;
395 goto err;
397 l_time->hour= l_time->hour%12 + add_hours;
400 else
402 l_time->year= date[0];
403 l_time->month= date[1];
404 l_time->day= date[2];
405 l_time->hour= date[3];
406 l_time->minute= date[4];
407 l_time->second= date[5];
408 if (date_len[6] < 6)
409 date[6]*= (uint) log_10_int[6 - date_len[6]];
410 l_time->second_part=date[6];
412 l_time->neg= 0;
414 if (year_length == 2 && not_zero_date)
415 l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
417 if (number_of_fields < 3 ||
418 l_time->year > 9999 || l_time->month > 12 ||
419 l_time->day > 31 || l_time->hour > 23 ||
420 l_time->minute > 59 || l_time->second > 59)
422 /* Only give warning for a zero date if there is some garbage after */
423 if (!not_zero_date) /* If zero date */
425 for (; str != end ; str++)
427 if (!my_isspace(&my_charset_latin1, *str))
429 not_zero_date= 1; /* Give warning */
430 break;
434 *was_cut= test(not_zero_date);
435 goto err;
438 if (check_date(l_time, not_zero_date != 0, flags, was_cut))
439 goto err;
441 l_time->time_type= (number_of_fields <= 3 ?
442 MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
444 for (; str != end ; str++)
446 if (!my_isspace(&my_charset_latin1,*str))
448 *was_cut= 1;
449 break;
453 DBUG_RETURN(l_time->time_type);
455 err:
456 bzero((char*) l_time, sizeof(*l_time));
457 DBUG_RETURN(MYSQL_TIMESTAMP_ERROR);
462 Convert a time string to a MYSQL_TIME struct.
464 SYNOPSIS
465 str_to_time()
466 str A string in full TIMESTAMP format or
467 [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
468 [M]MSS or [S]S
469 There may be an optional [.second_part] after seconds
470 length Length of str
471 l_time Store result here
472 warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string
473 was cut during conversion, and/or
474 MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is
475 out of range.
477 NOTES
478 Because of the extra days argument, this function can only
479 work with times where the time arguments are in the above order.
481 RETURN
482 0 ok
483 1 error
486 my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
487 int *warning)
489 ulong date[5];
490 ulonglong value;
491 const char *end=str+length, *end_of_days;
492 my_bool found_days,found_hours;
493 uint state;
495 l_time->neg=0;
496 *warning= 0;
497 for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
498 length--;
499 if (str != end && *str == '-')
501 l_time->neg=1;
502 str++;
503 length--;
505 if (str == end)
506 return 1;
508 /* Check first if this is a full TIMESTAMP */
509 if (length >= 12)
510 { /* Probably full timestamp */
511 int was_cut;
512 enum enum_mysql_timestamp_type
513 res= str_to_datetime(str, length, l_time,
514 (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut);
515 if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR)
517 if (was_cut)
518 *warning|= MYSQL_TIME_WARN_TRUNCATED;
519 return res == MYSQL_TIMESTAMP_ERROR;
523 /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
524 for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
525 value=value*10L + (long) (*str - '0');
527 /* Skip all space after 'days' */
528 end_of_days= str;
529 for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++)
532 LINT_INIT(state);
533 found_days=found_hours=0;
534 if ((uint) (end-str) > 1 && str != end_of_days &&
535 my_isdigit(&my_charset_latin1, *str))
536 { /* Found days part */
537 date[0]= (ulong) value;
538 state= 1; /* Assume next is hours */
539 found_days= 1;
541 else if ((end-str) > 1 && *str == time_separator &&
542 my_isdigit(&my_charset_latin1, str[1]))
544 date[0]= 0; /* Assume we found hours */
545 date[1]= (ulong) value;
546 state=2;
547 found_hours=1;
548 str++; /* skip ':' */
550 else
552 /* String given as one number; assume HHMMSS format */
553 date[0]= 0;
554 date[1]= (ulong) (value/10000);
555 date[2]= (ulong) (value/100 % 100);
556 date[3]= (ulong) (value % 100);
557 state=4;
558 goto fractional;
561 /* Read hours, minutes and seconds */
562 for (;;)
564 for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
565 value=value*10L + (long) (*str - '0');
566 date[state++]= (ulong) value;
567 if (state == 4 || (end-str) < 2 || *str != time_separator ||
568 !my_isdigit(&my_charset_latin1,str[1]))
569 break;
570 str++; /* Skip time_separator (':') */
573 if (state != 4)
574 { /* Not HH:MM:SS */
575 /* Fix the date to assume that seconds was given */
576 if (!found_hours && !found_days)
578 bmove_upp((uchar*) (date+4), (uchar*) (date+state),
579 sizeof(long)*(state-1));
580 bzero((uchar*) date, sizeof(long)*(4-state));
582 else
583 bzero((uchar*) (date+state), sizeof(long)*(4-state));
586 fractional:
587 /* Get fractional second part */
588 if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1]))
590 int field_length= 5;
591 str++; value=(uint) (uchar) (*str - '0');
592 while (++str != end && my_isdigit(&my_charset_latin1, *str))
594 if (field_length-- > 0)
595 value= value*10 + (uint) (uchar) (*str - '0');
597 if (field_length > 0)
598 value*= (long) log_10_int[field_length];
599 else if (field_length < 0)
600 *warning|= MYSQL_TIME_WARN_TRUNCATED;
601 date[4]= (ulong) value;
603 else
604 date[4]=0;
606 /* Check for exponent part: E<gigit> | E<sign><digit> */
607 /* (may occur as result of %g formatting of time value) */
608 if ((end - str) > 1 &&
609 (*str == 'e' || *str == 'E') &&
610 (my_isdigit(&my_charset_latin1, str[1]) ||
611 ((str[1] == '-' || str[1] == '+') &&
612 (end - str) > 2 &&
613 my_isdigit(&my_charset_latin1, str[2]))))
614 return 1;
616 if (internal_format_positions[7] != 255)
618 /* Read a possible AM/PM */
619 while (str != end && my_isspace(&my_charset_latin1, *str))
620 str++;
621 if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
623 if (str[0] == 'p' || str[0] == 'P')
625 str+= 2;
626 date[1]= date[1]%12 + 12;
628 else if (str[0] == 'a' || str[0] == 'A')
629 str+=2;
633 /* Integer overflow checks */
634 if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
635 date[2] > UINT_MAX || date[3] > UINT_MAX ||
636 date[4] > UINT_MAX)
637 return 1;
639 l_time->year= 0; /* For protocol::store_time */
640 l_time->month= 0;
641 l_time->day= date[0];
642 l_time->hour= date[1];
643 l_time->minute= date[2];
644 l_time->second= date[3];
645 l_time->second_part= date[4];
646 l_time->time_type= MYSQL_TIMESTAMP_TIME;
648 /* Check if the value is valid and fits into MYSQL_TIME range */
649 if (check_time_range(l_time, warning))
650 return 1;
652 /* Check if there is garbage at end of the MYSQL_TIME specification */
653 if (str != end)
657 if (!my_isspace(&my_charset_latin1,*str))
659 *warning|= MYSQL_TIME_WARN_TRUNCATED;
660 break;
662 } while (++str != end);
664 return 0;
669 Check 'time' value to lie in the MYSQL_TIME range
671 SYNOPSIS:
672 check_time_range()
673 time pointer to MYSQL_TIME value
674 warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
676 DESCRIPTION
677 If the time value lies outside of the range [-838:59:59, 838:59:59],
678 set it to the closest endpoint of the range and set
679 MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
681 RETURN
682 0 time value is valid, but was possibly truncated
683 1 time value is invalid
686 int check_time_range(struct st_mysql_time *my_time, int *warning)
688 longlong hour;
690 if (my_time->minute >= 60 || my_time->second >= 60)
691 return 1;
693 hour= my_time->hour + (24*my_time->day);
694 if (hour <= TIME_MAX_HOUR &&
695 (hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE ||
696 my_time->second != TIME_MAX_SECOND || !my_time->second_part))
697 return 0;
699 my_time->day= 0;
700 my_time->hour= TIME_MAX_HOUR;
701 my_time->minute= TIME_MAX_MINUTE;
702 my_time->second= TIME_MAX_SECOND;
703 my_time->second_part= 0;
704 *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE;
705 return 0;
710 Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
712 SYNOPSIS
713 my_init_time()
715 void my_init_time(void)
717 time_t seconds;
718 struct tm *l_time,tm_tmp;
719 MYSQL_TIME my_time;
720 my_bool not_used;
722 seconds= (time_t) time((time_t*) 0);
723 localtime_r(&seconds,&tm_tmp);
724 l_time= &tm_tmp;
725 my_time_zone= 3600; /* Comp. for -3600 in my_gmt_sec */
726 my_time.year= (uint) l_time->tm_year+1900;
727 my_time.month= (uint) l_time->tm_mon+1;
728 my_time.day= (uint) l_time->tm_mday;
729 my_time.hour= (uint) l_time->tm_hour;
730 my_time.minute= (uint) l_time->tm_min;
731 my_time.second= (uint) l_time->tm_sec;
732 my_system_gmt_sec(&my_time, &my_time_zone, &not_used); /* Init my_time_zone */
737 Handle 2 digit year conversions
739 SYNOPSIS
740 year_2000_handling()
741 year 2 digit year
743 RETURN
744 Year between 1970-2069
747 uint year_2000_handling(uint year)
749 if ((year=year+1900) < 1900+YY_PART_YEAR)
750 year+=100;
751 return year;
756 Calculate nr of day since year 0 in new date-system (from 1615)
758 SYNOPSIS
759 calc_daynr()
760 year Year (exact 4 digit year, no year conversions)
761 month Month
762 day Day
764 NOTES: 0000-00-00 is a valid date, and will return 0
766 RETURN
767 Days since 0000-00-00
770 long calc_daynr(uint year,uint month,uint day)
772 long delsum;
773 int temp;
774 int y= year; /* may be < 0 temporarily */
775 DBUG_ENTER("calc_daynr");
777 if (y == 0 && month == 0)
778 DBUG_RETURN(0); /* Skip errors */
779 /* Cast to int to be able to handle month == 0 */
780 delsum= (long) (365 * y + 31 *((int) month - 1) + (int) day);
781 if (month <= 2)
782 y--;
783 else
784 delsum-= (long) ((int) month * 4 + 23) / 10;
785 temp=(int) ((y/100+1)*3)/4;
786 DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld",
787 y+(month <= 2),month,day,delsum+y/4-temp));
788 DBUG_ASSERT(delsum+(int) y/4-temp >= 0);
789 DBUG_RETURN(delsum+(int) y/4-temp);
790 } /* calc_daynr */
794 Convert time in MYSQL_TIME representation in system time zone to its
795 my_time_t form (number of seconds in UTC since begginning of Unix Epoch).
797 SYNOPSIS
798 my_system_gmt_sec()
799 t - time value to be converted
800 my_timezone - pointer to long where offset of system time zone
801 from UTC will be stored for caching
802 in_dst_time_gap - set to true if time falls into spring time-gap
804 NOTES
805 The idea is to cache the time zone offset from UTC (including daylight
806 saving time) for the next call to make things faster. But currently we
807 just calculate this offset during startup (by calling my_init_time()
808 function) and use it all the time.
809 Time value provided should be legal time value (e.g. '2003-01-01 25:00:00'
810 is not allowed).
812 RETURN VALUE
813 Time in UTC seconds since Unix Epoch representation.
815 my_time_t
816 my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone,
817 my_bool *in_dst_time_gap)
819 uint loop;
820 time_t tmp= 0;
821 int shift= 0;
822 MYSQL_TIME tmp_time;
823 MYSQL_TIME *t= &tmp_time;
824 struct tm *l_time,tm_tmp;
825 long diff, current_timezone;
828 Use temp variable to avoid trashing input data, which could happen in
829 case of shift required for boundary dates processing.
831 memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME));
833 if (!validate_timestamp_range(t))
834 return 0;
837 Calculate the gmt time based on current time and timezone
838 The -1 on the end is to ensure that if have a date that exists twice
839 (like 2002-10-27 02:00:0 MET), we will find the initial date.
841 By doing -3600 we will have to call localtime_r() several times, but
842 I couldn't come up with a better way to get a repeatable result :(
844 We can't use mktime() as it's buggy on many platforms and not thread safe.
846 Note: this code assumes that our time_t estimation is not too far away
847 from real value (we assume that localtime_r(tmp) will return something
848 within 24 hrs from t) which is probably true for all current time zones.
850 Note2: For the dates, which have time_t representation close to
851 MAX_INT32 (efficient time_t limit for supported platforms), we should
852 do a small trick to avoid overflow. That is, convert the date, which is
853 two days earlier, and then add these days to the final value.
855 The same trick is done for the values close to 0 in time_t
856 representation for platfroms with unsigned time_t (QNX).
858 To be more verbose, here is a sample (extracted from the code below):
859 (calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L
860 would return -2147480896 because of the long type overflow. In result
861 we would get 1901 year in localtime_r(), which is an obvious error.
863 Alike problem raises with the dates close to Epoch. E.g.
864 (calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L
865 will give -3600.
867 On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600)
868 wil give us a date around 2106 year. Which is no good.
870 Theoreticaly, there could be problems with the latter conversion:
871 there are at least two timezones, which had time switches near 1 Jan
872 of 1970 (because of political reasons). These are America/Hermosillo and
873 America/Mazatlan time zones. They changed their offset on
874 1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones
875 the code below will give incorrect results for dates close to
876 1970-01-01, in the case OS takes into account these historical switches.
877 Luckily, it seems that we support only one platform with unsigned
878 time_t. It's QNX. And QNX does not support historical timezone data at all.
879 E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply
880 historical information for localtime_r() etc. That is, the problem is not
881 relevant to QNX.
883 We are safe with shifts close to MAX_INT32, as there are no known
884 time switches on Jan 2038 yet :)
886 if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4))
889 Below we will pass (uint) (t->day - shift) to calc_daynr.
890 As we don't want to get an overflow here, we will shift
891 only safe dates. That's why we have (t->day > 4) above.
893 t->day-= 2;
894 shift= 2;
896 #ifdef TIME_T_UNSIGNED
897 else
900 We can get 0 in time_t representaion only on 1969, 31 of Dec or on
901 1970, 1 of Jan. For both dates we use shift, which is added
902 to t->day in order to step out a bit from the border.
903 This is required for platforms, where time_t is unsigned.
904 As far as I know, among the platforms we support it's only QNX.
905 Note: the order of below if-statements is significant.
908 if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1)
909 && (t->day <= 10))
911 t->day+= 2;
912 shift= -2;
915 if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12)
916 && (t->day == 31))
918 t->year++;
919 t->month= 1;
920 t->day= 2;
921 shift= -2;
924 #endif
926 tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) -
927 (long) days_at_timestart)*86400L + (long) t->hour*3600L +
928 (long) (t->minute*60 + t->second)) + (time_t) my_time_zone -
929 3600);
931 current_timezone= my_time_zone;
932 localtime_r(&tmp,&tm_tmp);
933 l_time=&tm_tmp;
934 for (loop=0;
935 loop < 2 &&
936 (t->hour != (uint) l_time->tm_hour ||
937 t->minute != (uint) l_time->tm_min ||
938 t->second != (uint) l_time->tm_sec);
939 loop++)
940 { /* One check should be enough ? */
941 /* Get difference in days */
942 int days= t->day - l_time->tm_mday;
943 if (days < -1)
944 days= 1; /* Month has wrapped */
945 else if (days > 1)
946 days= -1;
947 diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) +
948 (long) (60*((int) t->minute - (int) l_time->tm_min)) +
949 (long) ((int) t->second - (int) l_time->tm_sec));
950 current_timezone+= diff+3600; /* Compensate for -3600 above */
951 tmp+= (time_t) diff;
952 localtime_r(&tmp,&tm_tmp);
953 l_time=&tm_tmp;
956 Fix that if we are in the non existing daylight saving time hour
957 we move the start of the next real hour.
959 This code doesn't handle such exotical thing as time-gaps whose length
960 is more than one hour or non-integer (latter can theoretically happen
961 if one of seconds will be removed due leap correction, or because of
962 general time correction like it happened for Africa/Monrovia time zone
963 in year 1972).
965 if (loop == 2 && t->hour != (uint) l_time->tm_hour)
967 int days= t->day - l_time->tm_mday;
968 if (days < -1)
969 days=1; /* Month has wrapped */
970 else if (days > 1)
971 days= -1;
972 diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+
973 (long) (60*((int) t->minute - (int) l_time->tm_min)) +
974 (long) ((int) t->second - (int) l_time->tm_sec));
975 if (diff == 3600)
976 tmp+=3600 - t->minute*60 - t->second; /* Move to next hour */
977 else if (diff == -3600)
978 tmp-=t->minute*60 + t->second; /* Move to previous hour */
980 *in_dst_time_gap= 1;
982 *my_timezone= current_timezone;
985 /* shift back, if we were dealing with boundary dates */
986 tmp+= shift*86400L;
989 This is possible for dates, which slightly exceed boundaries.
990 Conversion will pass ok for them, but we don't allow them.
991 First check will pass for platforms with signed time_t.
992 instruction above (tmp+= shift*86400L) could exceed
993 MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen.
994 So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms
995 with unsigned time_t tmp+= shift*86400L might result in a number,
996 larger then TIMESTAMP_MAX_VALUE, so another check will work.
998 if (!IS_TIME_T_VALID_FOR_TIMESTAMP(tmp))
999 tmp= 0;
1001 return (my_time_t) tmp;
1002 } /* my_system_gmt_sec */
1005 /* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */
1007 void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
1009 bzero((void*) tm, sizeof(*tm));
1010 tm->time_type= time_type;
1015 Functions to convert time/date/datetime value to a string,
1016 using default format.
1017 This functions don't check that given MYSQL_TIME structure members are
1018 in valid range. If they are not, return value won't reflect any
1019 valid date either. Additionally, make_time doesn't take into
1020 account time->day member: it's assumed that days have been converted
1021 to hours already.
1023 RETURN
1024 number of characters written to 'to'
1027 int my_time_to_str(const MYSQL_TIME *l_time, char *to)
1029 uint extra_hours= 0;
1030 return sprintf(to, "%s%02u:%02u:%02u", (l_time->neg ? "-" : ""),
1031 extra_hours+ l_time->hour, l_time->minute, l_time->second);
1034 int my_date_to_str(const MYSQL_TIME *l_time, char *to)
1036 return sprintf(to, "%04u-%02u-%02u",
1037 l_time->year, l_time->month, l_time->day);
1040 int my_datetime_to_str(const MYSQL_TIME *l_time, char *to)
1042 return sprintf(to, "%04u-%02u-%02u %02u:%02u:%02u",
1043 l_time->year, l_time->month, l_time->day,
1044 l_time->hour, l_time->minute, l_time->second);
1049 Convert struct DATE/TIME/DATETIME value to string using built-in
1050 MySQL time conversion formats.
1052 SYNOPSIS
1053 my_TIME_to_string()
1055 NOTE
1056 The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved.
1059 int my_TIME_to_str(const MYSQL_TIME *l_time, char *to)
1061 switch (l_time->time_type) {
1062 case MYSQL_TIMESTAMP_DATETIME:
1063 return my_datetime_to_str(l_time, to);
1064 case MYSQL_TIMESTAMP_DATE:
1065 return my_date_to_str(l_time, to);
1066 case MYSQL_TIMESTAMP_TIME:
1067 return my_time_to_str(l_time, to);
1068 case MYSQL_TIMESTAMP_NONE:
1069 case MYSQL_TIMESTAMP_ERROR:
1070 to[0]='\0';
1071 return 0;
1072 default:
1073 DBUG_ASSERT(0);
1074 return 0;
1080 Convert datetime value specified as number to broken-down TIME
1081 representation and form value of DATETIME type as side-effect.
1083 SYNOPSIS
1084 number_to_datetime()
1085 nr - datetime value as number
1086 time_res - pointer for structure for broken-down representation
1087 flags - flags to use in validating date, as in str_to_datetime()
1088 was_cut 0 Value ok
1089 1 If value was cut during conversion
1090 2 check_date(date,flags) considers date invalid
1092 DESCRIPTION
1093 Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
1094 YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in
1095 YYYYMMDDHHMMSS format as side-effect.
1097 This function also checks if datetime value fits in DATETIME range.
1099 RETURN VALUE
1100 -1 Timestamp with wrong values
1101 anything else DATETIME as integer in YYYYMMDDHHMMSS format
1102 Datetime value in YYYYMMDDHHMMSS format.
1105 longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
1106 uint flags, int *was_cut)
1108 long part1,part2;
1110 *was_cut= 0;
1111 bzero((char*) time_res, sizeof(*time_res));
1112 time_res->time_type=MYSQL_TIMESTAMP_DATE;
1114 if (nr == LL(0) || nr >= LL(10000101000000))
1116 time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1117 goto ok;
1119 if (nr < 101)
1120 goto err;
1121 if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
1123 nr= (nr+20000000L)*1000000L; /* YYMMDD, year: 2000-2069 */
1124 goto ok;
1126 if (nr < (YY_PART_YEAR)*10000L+101L)
1127 goto err;
1128 if (nr <= 991231L)
1130 nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
1131 goto ok;
1133 if (nr < 10000101L)
1134 goto err;
1135 if (nr <= 99991231L)
1137 nr= nr*1000000L;
1138 goto ok;
1140 if (nr < 101000000L)
1141 goto err;
1143 time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1145 if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
1147 nr= nr+LL(20000000000000); /* YYMMDDHHMMSS, 2000-2069 */
1148 goto ok;
1150 if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
1151 goto err;
1152 if (nr <= LL(991231235959))
1153 nr= nr+LL(19000000000000); /* YYMMDDHHMMSS, 1970-1999 */
1156 part1=(long) (nr/LL(1000000));
1157 part2=(long) (nr - (longlong) part1*LL(1000000));
1158 time_res->year= (int) (part1/10000L); part1%=10000L;
1159 time_res->month= (int) part1 / 100;
1160 time_res->day= (int) part1 % 100;
1161 time_res->hour= (int) (part2/10000L); part2%=10000L;
1162 time_res->minute=(int) part2 / 100;
1163 time_res->second=(int) part2 % 100;
1165 if (time_res->year <= 9999 && time_res->month <= 12 &&
1166 time_res->day <= 31 && time_res->hour <= 23 &&
1167 time_res->minute <= 59 && time_res->second <= 59 &&
1168 !check_date(time_res, (nr != 0), flags, was_cut))
1169 return nr;
1171 /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
1172 if (!nr && (flags & TIME_NO_ZERO_DATE))
1173 return LL(-1);
1175 err:
1176 *was_cut= 1;
1177 return LL(-1);
1181 /* Convert time value to integer in YYYYMMDDHHMMSS format */
1183 ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)
1185 return ((ulonglong) (my_time->year * 10000UL +
1186 my_time->month * 100UL +
1187 my_time->day) * ULL(1000000) +
1188 (ulonglong) (my_time->hour * 10000UL +
1189 my_time->minute * 100UL +
1190 my_time->second));
1194 /* Convert MYSQL_TIME value to integer in YYYYMMDD format */
1196 ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *my_time)
1198 return (ulonglong) (my_time->year * 10000UL + my_time->month * 100UL +
1199 my_time->day);
1204 Convert MYSQL_TIME value to integer in HHMMSS format.
1205 This function doesn't take into account time->day member:
1206 it's assumed that days have been converted to hours already.
1209 ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *my_time)
1211 return (ulonglong) (my_time->hour * 10000UL +
1212 my_time->minute * 100UL +
1213 my_time->second);
1218 Convert struct MYSQL_TIME (date and time split into year/month/day/hour/...
1219 to a number in format YYYYMMDDHHMMSS (DATETIME),
1220 YYYYMMDD (DATE) or HHMMSS (TIME).
1222 SYNOPSIS
1223 TIME_to_ulonglong()
1225 DESCRIPTION
1226 The function is used when we need to convert value of time item
1227 to a number if it's used in numeric context, i. e.:
1228 SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
1229 SELECT ?+1;
1231 NOTE
1232 This function doesn't check that given MYSQL_TIME structure members are
1233 in valid range. If they are not, return value won't reflect any
1234 valid date either.
1237 ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time)
1239 switch (my_time->time_type) {
1240 case MYSQL_TIMESTAMP_DATETIME:
1241 return TIME_to_ulonglong_datetime(my_time);
1242 case MYSQL_TIMESTAMP_DATE:
1243 return TIME_to_ulonglong_date(my_time);
1244 case MYSQL_TIMESTAMP_TIME:
1245 return TIME_to_ulonglong_time(my_time);
1246 case MYSQL_TIMESTAMP_NONE:
1247 case MYSQL_TIMESTAMP_ERROR:
1248 return ULL(0);
1249 default:
1250 DBUG_ASSERT(0);
1252 return 0;