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 | |
26 | ulonglong 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 | |
38 | static uchar internal_format_positions[]= |
39 | {0, 1, 2, 3, 4, 5, 6, (uchar) 255}; |
40 | |
41 | static char time_separator=':'; |
42 | |
43 | static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */ |
44 | uchar 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 | */ |
50 | static long my_time_zone=0; |
51 | |
52 | |
53 | /* Calc days in one year. works with 0 <= year <= 99 */ |
54 | |
55 | uint 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 | |
82 | my_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 | |
111 | static 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 | |
130 | static 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 | |
136 | static 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 | |
148 | static 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 | |
193 | static int get_maybe_T(const char **str, const char *end) |
194 | { |
195 | if (*str < end && **str == 'T') |
196 | (*str)++; |
197 | return 0; |
198 | } |
199 | |
200 | static 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 | */ |
217 | my_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 | |
232 | static 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 | |
295 | my_bool |
296 | str_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 | |
424 | err: |
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 | |
460 | my_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 | |
558 | fractional: |
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 | |
635 | err: |
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 | |
661 | int 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 | */ |
700 | void 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, ¬_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 | |
736 | uint 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 | |
759 | long 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 | */ |
807 | my_time_t |
808 | my_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 | |
1006 | void 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 | |
1022 | static 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 | |
1045 | int 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 | |
1082 | int 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 | |
1095 | int 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 | |
1142 | int 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 | */ |
1170 | int 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 | |
1209 | longlong 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 | */ |
1313 | int 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 | |
1347 | ulonglong 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 | |
1360 | ulonglong 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 | |
1373 | ulonglong 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 | |
1401 | ulonglong 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 | |
1419 | double 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 | |