1 /* Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 Without limiting anything contained in the foregoing, this file,
15 which is part of C Driver for MySQL (Connector/C), is also subject to the
16 Universal FOSS Exception, version 1.0, a copy of which can be found at
17 http://oss.oracle.com/licenses/universal-foss-exception.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License, version 2.0, for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
27
28 /**
29 @defgroup MY_TIME Mysys time utilities
30 @ingroup MYSYS
31 @{
32
33 @file mysys/my_time.cc
34
35 Implementation of low level time utilities.
36 */
37
38 /**
39 @ingroup MY_TIME
40 @page LOW_LEVEL_FORMATS Low-level memory and disk formats
41
42 - @subpage datetime_and_date_low_level_rep
43 - @subpage time_low_level_rep
44 */
45
46 #include "my_time.h"
47
48 #include <assert.h> // assert
49 #include <algorithm> // std::max
50 #include <cctype> // std::isspace
51 #include <climits> // UINT_MAX
52 #include <cstdio> // std::sprintf
53 #include <cstring> // std::memset
54
55 #include "field_types.h" // enum_field_types
56 #include "integer_digits.h" // count_digits, write_digits, write_two_digits
57 #include "my_byteorder.h" // int3store
58 #include "my_systime.h" // localtime_r
59 #include "myisampack.h" // mi_int2store
60 #include "template_utils.h" // pointer_cast
61
62 const ulonglong log_10_int[20] = {1,
63 10,
64 100,
65 1000,
66 10000UL,
67 100000UL,
68 1000000UL,
69 10000000UL,
70 100000000ULL,
71 1000000000ULL,
72 10000000000ULL,
73 100000000000ULL,
74 1000000000000ULL,
75 10000000000000ULL,
76 100000000000000ULL,
77 1000000000000000ULL,
78 10000000000000000ULL,
79 100000000000000000ULL,
80 1000000000000000000ULL,
81 10000000000000000000ULL};
82
83 const char my_zero_datetime6[] = "0000-00-00 00:00:00.000000";
84
85 /**
86 Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format.
87 */
88 static constexpr const uchar internal_format_positions[] = {0, 1, 2, 3,
89 4, 5, 6, 255};
90
91 static constexpr const char time_separator = ':';
92
93 /** Day number with 1970-01-01 as base. */
94 static constexpr ulong const days_at_timestart = 719528;
95 const uchar days_in_month[] = {31, 28, 31, 30, 31, 30, 31,
96 31, 30, 31, 30, 31, 0};
97
98 /**
99 Offset of system time zone from UTC in seconds used to speed up
100 work of my_system_gmt_sec() function.
101 */
102 static long my_time_zone = 0;
103
104 // Right-shift of a negative value is implementation-defined
105 // Assert that we have arithmetic shift of negative numbers
106 static_assert((-2 >> 1) == -1, "Right shift of negative numbers is arithmetic");
my_packed_time_get_int_part(longlong i)107 static longlong my_packed_time_get_int_part(longlong i) { return (i >> 24); }
108
my_packed_time_make(longlong i,longlong f)109 static longlong my_packed_time_make(longlong i, longlong f) {
110 assert(std::abs(f) <= 0xffffffLL);
111 return (static_cast<ulonglong>(i) << 24) + f;
112 }
113
my_packed_time_make_int(longlong i)114 static longlong my_packed_time_make_int(longlong i) {
115 return (static_cast<ulonglong>(i) << 24);
116 }
117
118 // The behavior of <cctype> functions is undefined if the argument's value is
119 // neither representable as unsigned char nor equal to EOF. To use these
120 // functions safely with plain chars, cast to unsigned char.
isspace_char(char ch)121 static inline int isspace_char(char ch) {
122 return std::isspace(static_cast<unsigned char>(ch));
123 }
124
isdigit_char(char ch)125 static inline int isdigit_char(char ch) {
126 return std::isdigit(static_cast<unsigned char>(ch));
127 }
128
ispunct_char(char ch)129 static inline int ispunct_char(char ch) {
130 return std::ispunct(static_cast<unsigned char>(ch));
131 }
132
133 /**
134 Calc days in one year.
135 @note Works with both two and four digit years.
136
137 @return number of days in that year
138 */
calc_days_in_year(uint year)139 uint calc_days_in_year(uint year) {
140 return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)) ? 366
141 : 365);
142 }
143
144 /**
145 Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000
146 @param [out] tm The value to set.
147 @param time_type Timestasmp type
148 */
set_zero_time(MYSQL_TIME * tm,enum enum_mysql_timestamp_type time_type)149 void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type) {
150 memset(tm, 0, sizeof(*tm));
151 tm->time_type = time_type;
152 }
153
154 /**
155 Set hour, minute and second of a MYSQL_TIME variable to maximum time value.
156 Unlike set_max_time(), does not touch the other structure members.
157 */
set_max_hhmmss(MYSQL_TIME * tm)158 void set_max_hhmmss(MYSQL_TIME *tm) {
159 tm->hour = TIME_MAX_HOUR;
160 tm->minute = TIME_MAX_MINUTE;
161 tm->second = TIME_MAX_SECOND;
162 }
163
164 /**
165 Set MYSQL_TIME variable to maximum time value
166 @param tm OUT The variable to set.
167 @param neg Sign: 1 if negative, 0 if positive.
168 */
set_max_time(MYSQL_TIME * tm,bool neg)169 void set_max_time(MYSQL_TIME *tm, bool neg) {
170 set_zero_time(tm, MYSQL_TIMESTAMP_TIME);
171 set_max_hhmmss(tm);
172 tm->neg = neg;
173 }
174
175 /**
176 @brief Check datetime value for validity according to flags.
177
178 @param[in] my_time Date to check.
179 @param[in] not_zero_date my_time is not the zero date
180 @param[in] flags flags to check
181 (see str_to_datetime() flags in my_time.h)
182 @param[out] was_cut set to 2 if value was invalid according to flags.
183 (Feb 29 in non-leap etc.). This remains unchanged
184 if value is not invalid.
185
186 @details Here we assume that year and month is ok!
187 If month is 0 we allow any date. (This only happens if we allow zero
188 date parts in str_to_datetime())
189 Disallow dates with zero year and non-zero month and/or day.
190
191 @retval false OK
192 @retval true error
193 */
check_date(const MYSQL_TIME & my_time,bool not_zero_date,my_time_flags_t flags,int * was_cut)194 bool check_date(const MYSQL_TIME &my_time, bool not_zero_date,
195 my_time_flags_t flags, int *was_cut) {
196 if (not_zero_date) {
197 if (((flags & TIME_NO_ZERO_IN_DATE) || !(flags & TIME_FUZZY_DATE)) &&
198 (my_time.month == 0 || my_time.day == 0)) {
199 *was_cut = MYSQL_TIME_WARN_ZERO_IN_DATE;
200 return true;
201 } else if ((!(flags & TIME_INVALID_DATES) && my_time.month &&
202 my_time.day > days_in_month[my_time.month - 1] &&
203 (my_time.month != 2 || calc_days_in_year(my_time.year) != 366 ||
204 my_time.day != 29))) {
205 *was_cut = MYSQL_TIME_WARN_OUT_OF_RANGE;
206 return true;
207 }
208 } else if (flags & TIME_NO_ZERO_DATE) {
209 *was_cut = MYSQL_TIME_WARN_ZERO_DATE;
210 return true;
211 }
212 return false;
213 }
214
215 /**
216 Check if TIME fields can be adjusted to make the time value valid.
217
218 @param my_time Time value.
219 @retval true if the value cannot be made valid.
220 @retval false if the value is already valid or can be adjusted to
221 become valid.
222 */
check_time_mmssff_range(const MYSQL_TIME & my_time)223 bool check_time_mmssff_range(const MYSQL_TIME &my_time) {
224 return my_time.minute >= 60 || my_time.second >= 60 ||
225 my_time.second_part > 999999;
226 }
227
228 /**
229 Check TIME range. The value can include day part,
230 for example: '1 10:20:30.123456'.
231
232 minute, second and second_part values are not checked
233 unless hour is equal TIME_MAX_HOUR.
234
235 @param my_time Time value.
236 @returns Test result.
237 @retval false if value is Ok.
238 @retval true if value is out of range.
239 */
check_time_range_quick(const MYSQL_TIME & my_time)240 bool check_time_range_quick(const MYSQL_TIME &my_time) {
241 longlong hour = static_cast<longlong>(my_time.hour) + 24LL * my_time.day;
242 /* The input value should not be fatally bad */
243 assert(!check_time_mmssff_range(my_time));
244 if (hour <= TIME_MAX_HOUR &&
245 (hour != TIME_MAX_HOUR || my_time.minute != TIME_MAX_MINUTE ||
246 my_time.second != TIME_MAX_SECOND || !my_time.second_part))
247 return false;
248 return true;
249 }
250
251 /**
252 Check datetime, date, or normalized time (i.e. time without days) range.
253 @param my_time Datetime value.
254 @retval false on success
255 @retval true on error
256 */
check_datetime_range(const MYSQL_TIME & my_time)257 bool check_datetime_range(const MYSQL_TIME &my_time) {
258 /*
259 In case of MYSQL_TIMESTAMP_TIME hour value can be up to TIME_MAX_HOUR.
260 In case of MYSQL_TIMESTAMP_DATETIME it cannot be bigger than 23.
261 */
262 return my_time.year > 9999U || my_time.month > 12U || my_time.day > 31U ||
263 my_time.minute > 59U || my_time.second > 59U ||
264 my_time.second_part > 999999U ||
265 (my_time.hour >
266 (my_time.time_type == MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR : 23U));
267 }
268
269 #define MAX_DATE_PARTS 8
270
271 /**
272 Parses a time zone displacement string on the form `{+-}HH:MM`, converting
273 to seconds.
274
275 @param[in] str Time zone displacement string.
276 @param[in] length Length of said string.
277 @param[out] result Calculated displacement in seconds.
278
279 @retval false Ok.
280 @retval true Not a valid time zone displacement string.
281 */
time_zone_displacement_to_seconds(const char * str,size_t length,int * result)282 bool time_zone_displacement_to_seconds(const char *str, size_t length,
283 int *result) {
284 if (length < 6) return true;
285
286 int sign = str[0] == '+' ? 1 : (str[0] == '-' ? -1 : 0);
287 if (sign == 0) return true;
288
289 if (!(std::isdigit(str[1]) && std::isdigit(str[2]))) return true;
290 int hours = (str[1] - '0') * 10 + str[2] - '0';
291
292 if (str[3] != ':') return true;
293
294 if (!(std::isdigit(str[4]) && std::isdigit(str[5]))) return true;
295 int minutes = (str[4] - '0') * 10 + str[5] - '0';
296 if (minutes >= MINS_PER_HOUR) return true;
297 int seconds = hours * SECS_PER_HOUR + minutes * SECS_PER_MIN;
298
299 if (seconds > MAX_TIME_ZONE_HOURS * SECS_PER_HOUR) return true;
300
301 // The SQL standard forbids -00:00.
302 if (sign == -1 && hours == 0 && minutes == 0) return true;
303
304 for (size_t i = 6; i < length; ++i)
305 if (!std::isspace(str[i])) return true;
306
307 *result = seconds * sign;
308 return false;
309 }
310
311 /**
312 Convert a timestamp string to a MYSQL_TIME value.
313
314 DESCRIPTION
315 At least the following formats are recogniced (based on number of digits)
316 YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
317 YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS
318 YYYYMMDDTHHMMSS where T is a the character T (ISO8601)
319 Also dates where all parts are zero are allowed
320
321 The second part may have an optional .###### fraction part.
322 The datetime value may be followed by a time zone displacement +/-HH:MM.
323
324 NOTES
325 This function should work with a format position vector as long as the
326 following things holds:
327 - All date are kept together and all time parts are kept together
328 - Date and time parts must be separated by blank
329 - Second fractions must come after second part and be separated
330 by a '.'. (The second fractions are optional)
331 - AM/PM must come after second fractions (or after seconds if no fractions)
332 - Year must always been specified.
333 - If time is before date, then we will use datetime format only if
334 the argument consist of two parts, separated by space.
335 Otherwise we will assume the argument is a date.
336 - The hour part must be specified in hour-minute-second order.
337
338 status->warnings is set to:
339 0 Value OK
340 MYSQL_TIME_WARN_TRUNCATED If value was cut during conversion
341 MYSQL_TIME_WARN_OUT_OF_RANGE check_date(date,flags) considers date invalid
342
343 l_time->time_type is set as follows:
344 MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like
345 [DD [HH:[MM:[SS]]]].fraction.
346 l_time is not changed.
347 MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
348 MYSQL_TIMESTAMP_DATETIME Full timestamp
349 MYSQL_TIMESTAMP_ERROR Timestamp with wrong values.
350 All elements in l_time is set to 0
351
352 flags is a bit field with the follwing possible values:
353 TIME_FUZZY_DATE
354 TIME_DATETIME_ONLY
355 TIME_NO_ZERO_IN_DATE
356 TIME_NO_ZERO_DATE
357 TIME_INVALID_DATES
358
359 @param str String to parse
360 @param length Length of string
361 @param[out] l_time Date is stored here
362 @param flags Bitfield
363 TIME_FUZZY_DATE|TIME_DATETIME_ONLY|TIME_NO_ZERO_IN_DATE|TIME_NO_ZERO_DATE|TIME_INVALID_DATES
364 (described above)
365 @param status Conversion status and warnings
366
367 @retval false Ok
368 @retval true Error
369 */
str_to_datetime(const char * str,std::size_t length,MYSQL_TIME * l_time,my_time_flags_t flags,MYSQL_TIME_STATUS * status)370 bool str_to_datetime(const char *str, std::size_t length, MYSQL_TIME *l_time,
371 my_time_flags_t flags, MYSQL_TIME_STATUS *status) {
372 uint field_length = 0;
373 uint year_length = 0;
374 uint digits;
375 uint i;
376 uint number_of_fields;
377 uint date[MAX_DATE_PARTS];
378 uint date_len[MAX_DATE_PARTS];
379 uint add_hours = 0;
380 uint start_loop;
381 ulong not_zero_date;
382 ulong allow_space;
383 bool is_internal_format = false;
384 const char *pos;
385 const char *last_field_pos = nullptr;
386 const char *end = str + length;
387 const uchar *format_position;
388 bool found_delimiter = false;
389 bool found_space = false;
390 bool found_displacement = false;
391 uint frac_pos;
392 uint frac_len;
393 int displacement = 0;
394
395 assert(status->warnings == 0 && status->fractional_digits == 0 &&
396 status->nanoseconds == 0);
397
398 /* Skip space at start */
399 for (; str != end && isspace_char(*str); str++)
400 ;
401 if (str == end || !isdigit_char(*str)) {
402 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
403 l_time->time_type = MYSQL_TIMESTAMP_NONE;
404 return true;
405 }
406
407 is_internal_format = false;
408 /* This has to be changed if want to activate different timestamp formats */
409 format_position = internal_format_positions;
410
411 /*
412 Calculate number of digits in first part.
413 If length= 8 or >= 14 then year is of format YYYY.
414 (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
415 */
416 for (pos = str; pos != end && (isdigit_char(*pos) || *pos == 'T'); pos++)
417 ;
418
419 digits = static_cast<uint>(pos - str);
420 start_loop = 0; /* Start of scan loop */
421 date_len[format_position[0]] = 0; /* Length of year field */
422 if (pos == end || *pos == '.') {
423 /* Found date in internal format (only numbers like YYYYMMDD) */
424 year_length = (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
425 field_length = year_length;
426 is_internal_format = true;
427 format_position = internal_format_positions;
428 } else {
429 if (format_position[0] >= 3) /* If year is after HHMMDD */
430 {
431 /*
432 If year is not in first part then we have to determinate if we got
433 a date field or a datetime field.
434 We do this by checking if there is two numbers separated by
435 space in the input.
436 */
437 while (pos < end && !isspace_char(*pos)) pos++;
438 while (pos < end && !isdigit_char(*pos)) pos++;
439 if (pos == end) {
440 if (flags & TIME_DATETIME_ONLY) {
441 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
442 l_time->time_type = MYSQL_TIMESTAMP_NONE;
443 return true; /* Can't be a full datetime */
444 }
445 /* Date field. Set hour, minutes and seconds to 0 */
446 date[0] = 0;
447 date[1] = 0;
448 date[2] = 0;
449 date[3] = 0;
450 date[4] = 0;
451 start_loop = 5; /* Start with first date part */
452 }
453 }
454
455 field_length = format_position[0] == 0 ? 4 : 2;
456 }
457
458 /*
459 Only allow space in the first "part" of the datetime field and:
460 - after days, part seconds
461 - before and after AM/PM (handled by code later)
462
463 2003-03-03 20:00:20 AM
464 20:00:20.000000 AM 03-03-2000
465 */
466 i = *std::max_element(format_position, format_position + 3);
467
468 allow_space = ((1 << i) | (1 << format_position[6]));
469 allow_space &= (1 | 2 | 4 | 8 | 64);
470
471 not_zero_date = 0;
472 for (i = start_loop;
473 i < MAX_DATE_PARTS - 1 && str != end && isdigit_char(*str); i++) {
474 const char *start = str;
475 ulong tmp_value = static_cast<uchar>(*str++ - '0');
476
477 /*
478 Internal format means no delimiters; every field has a fixed
479 width. Otherwise, we scan until we find a delimiter and discard
480 leading zeroes -- except for the microsecond part, where leading
481 zeroes are significant, and where we never process more than six
482 digits.
483 */
484 bool scan_until_delim = !is_internal_format && (i != format_position[6]);
485
486 while (str != end && isdigit_char(str[0]) &&
487 (scan_until_delim || --field_length)) {
488 tmp_value =
489 tmp_value * 10 + static_cast<ulong>(static_cast<uchar>(*str - '0'));
490 str++;
491 if (tmp_value > 999999) /* Impossible date part */
492 {
493 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
494 l_time->time_type = MYSQL_TIMESTAMP_NONE;
495 return true;
496 }
497 }
498 date_len[i] = static_cast<uint>(str - start);
499 date[i] = tmp_value;
500 not_zero_date |= tmp_value;
501
502 /* Length of next field */
503 field_length = format_position[i + 1] == 0 ? 4 : 2;
504
505 if ((last_field_pos = str) == end) {
506 i++; /* Register last found part */
507 break;
508 }
509 /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
510 if (i == format_position[2] && *str == 'T') {
511 str++; /* ISO8601: CCYYMMDDThhmmss */
512 continue;
513 }
514 if (i == format_position[5]) /* Seconds */
515 {
516 if (*str == '.') /* Followed by part seconds */
517 {
518 str++;
519 /*
520 Shift last_field_pos, so '2001-01-01 00:00:00.'
521 is treated as a valid value
522 */
523 last_field_pos = str;
524 field_length = 6; /* 6 digits */
525 } else if (isdigit_char(str[0])) {
526 /*
527 We do not see a decimal point which would have indicated a
528 fractional second part in further read. So we skip the further
529 processing of digits.
530 */
531 i++;
532 break;
533 } else if (str[0] == '+' || str[0] == '-') {
534 if (!time_zone_displacement_to_seconds(str, end - str, &displacement)) {
535 found_displacement = true;
536 str += end - str;
537 last_field_pos = str;
538 } else {
539 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
540 l_time->time_type = MYSQL_TIMESTAMP_NONE;
541 return true;
542 }
543 }
544 continue;
545 }
546 if (i == format_position[6] && (str[0] == '+' || str[0] == '-')) {
547 if (!time_zone_displacement_to_seconds(str, end - str, &displacement)) {
548 found_displacement = true;
549 str += end - str;
550 last_field_pos = str;
551 } else {
552 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
553 l_time->time_type = MYSQL_TIMESTAMP_NONE;
554 return true;
555 }
556 }
557
558 while (str != end && (ispunct_char(*str) || isspace_char(*str))) {
559 if (isspace_char(*str)) {
560 if (!(allow_space & (1 << i))) {
561 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
562 l_time->time_type = MYSQL_TIMESTAMP_NONE;
563 return true;
564 }
565 found_space = true;
566 }
567 str++;
568 found_delimiter = true; /* Should be a 'normal' date */
569 }
570 /* Check if next position is AM/PM */
571 if (i == format_position[6]) /* Seconds, time for AM/PM */
572 {
573 i++; /* Skip AM/PM part */
574 if (format_position[7] != 255) /* If using AM/PM */
575 {
576 if (str + 2 <= end && (str[1] == 'M' || str[1] == 'm')) {
577 if (str[0] == 'p' || str[0] == 'P')
578 add_hours = 12;
579 else if (str[0] != 'a' && str[0] != 'A')
580 continue; /* Not AM/PM */
581 str += 2; /* Skip AM/PM */
582 /* Skip space after AM/PM */
583 while (str != end && isspace_char(*str)) str++;
584 }
585 }
586 }
587 last_field_pos = str;
588 }
589 if (found_delimiter && !found_space && (flags & TIME_DATETIME_ONLY)) {
590 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
591 l_time->time_type = MYSQL_TIMESTAMP_NONE;
592 return true; /* Can't be a datetime */
593 }
594
595 str = last_field_pos;
596
597 number_of_fields = i - start_loop;
598 while (i < MAX_DATE_PARTS) {
599 date_len[i] = 0;
600 date[i++] = 0;
601 }
602
603 if (!is_internal_format) {
604 year_length = date_len[static_cast<uint>(format_position[0])];
605 if (!year_length) /* Year must be specified */
606 {
607 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
608 l_time->time_type = MYSQL_TIMESTAMP_NONE;
609 return true;
610 }
611
612 l_time->year = date[static_cast<uint>(format_position[0])];
613 l_time->month = date[static_cast<uint>(format_position[1])];
614 l_time->day = date[static_cast<uint>(format_position[2])];
615 l_time->hour = date[static_cast<uint>(format_position[3])];
616 l_time->minute = date[static_cast<uint>(format_position[4])];
617 l_time->second = date[static_cast<uint>(format_position[5])];
618 l_time->time_zone_displacement = displacement;
619
620 frac_pos = static_cast<uint>(format_position[6]);
621 frac_len = date_len[frac_pos];
622 status->fractional_digits = frac_len;
623 if (frac_len < 6)
624 date[frac_pos] *=
625 static_cast<uint>(log_10_int[DATETIME_MAX_DECIMALS - frac_len]);
626 l_time->second_part = date[frac_pos];
627
628 if (format_position[7] != static_cast<uchar>(255)) {
629 if (l_time->hour > 12) {
630 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
631 goto err;
632 }
633 l_time->hour = l_time->hour % 12 + add_hours;
634 }
635 } else {
636 l_time->year = date[0];
637 l_time->month = date[1];
638 l_time->day = date[2];
639 l_time->hour = date[3];
640 l_time->minute = date[4];
641 l_time->second = date[5];
642 if (date_len[6] < 6)
643 date[6] *=
644 static_cast<uint>(log_10_int[DATETIME_MAX_DECIMALS - date_len[6]]);
645 l_time->second_part = date[6];
646 l_time->time_zone_displacement = displacement;
647 status->fractional_digits = date_len[6];
648 }
649 l_time->neg = false;
650
651 if (year_length == 2 && not_zero_date)
652 l_time->year += (l_time->year < YY_PART_YEAR ? 2000 : 1900);
653
654 /*
655 Set time_type before check_datetime_range(),
656 as the latter relies on initialized time_type value.
657 */
658 l_time->time_type =
659 (number_of_fields <= 3 ? MYSQL_TIMESTAMP_DATE
660 : (found_displacement ? MYSQL_TIMESTAMP_DATETIME_TZ
661 : MYSQL_TIMESTAMP_DATETIME));
662
663 if (number_of_fields < 3 || check_datetime_range(*l_time)) {
664 /* Only give warning for a zero date if there is some garbage after */
665 if (!not_zero_date) /* If zero date */
666 {
667 for (; str != end; str++) {
668 if (!isspace_char(*str)) {
669 not_zero_date = 1; /* Give warning */
670 break;
671 }
672 }
673 }
674 status->warnings |=
675 not_zero_date ? MYSQL_TIME_WARN_TRUNCATED : MYSQL_TIME_WARN_ZERO_DATE;
676 goto err;
677 }
678
679 if (check_date(*l_time, not_zero_date != 0, flags, &status->warnings))
680 goto err;
681
682 /* Scan all digits left after microseconds */
683 if (status->fractional_digits == 6 && str != end) {
684 if (isdigit_char(*str)) {
685 /*
686 We don't need the exact nanoseconds value.
687 Knowing the first digit is enough for rounding.
688 */
689 status->nanoseconds = 100 * (*str++ - '0');
690 for (; str != end && isdigit_char(*str); str++) {
691 }
692 }
693 }
694
695 if (str != end && (str[0] == '+' || str[0] == '-')) {
696 if (time_zone_displacement_to_seconds(str, end - str, &displacement)) {
697 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
698 l_time->time_type = MYSQL_TIMESTAMP_NONE;
699 return true;
700 } else {
701 l_time->time_type = MYSQL_TIMESTAMP_DATETIME_TZ;
702 l_time->time_zone_displacement = displacement;
703 return false;
704 }
705 }
706
707 for (; str != end; str++) {
708 if (!isspace_char(*str)) {
709 status->warnings = MYSQL_TIME_WARN_TRUNCATED;
710 break;
711 }
712 }
713
714 return false;
715
716 err:
717 set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR);
718 return true;
719 }
720
721 /**
722 Convert a time string to a MYSQL_TIME struct.
723
724 status.warning is set to:
725 MYSQL_TIME_WARN_TRUNCATED flag if the input string
726 was cut during conversion, and/or
727 MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is out of range.
728
729 @note
730 Because of the extra days argument, this function can only
731 work with times where the time arguments are in the above order.
732
733 @param str A string in full TIMESTAMP format or
734 [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
735 [M]MSS or [S]S
736 @param length Length of str
737 @param[out] l_time Store result here
738 @param[out] status Conversion status, including warnings.
739 @param flags Optional flags to control conversion
740
741 @retval false Ok
742 @retval true Error
743 */
str_to_time(const char * str,std::size_t length,MYSQL_TIME * l_time,MYSQL_TIME_STATUS * status,my_time_flags_t flags)744 bool str_to_time(const char *str, std::size_t length, MYSQL_TIME *l_time,
745 MYSQL_TIME_STATUS *status, my_time_flags_t flags) {
746 ulong date[5];
747 ulonglong value;
748 const char *end = str + length;
749 const char *end_of_days;
750 bool found_days;
751 bool found_hours;
752 uint state;
753 const char *start;
754 bool seen_colon = false;
755
756 assert(status->warnings == 0 && status->fractional_digits == 0 &&
757 status->nanoseconds == 0);
758
759 l_time->neg = false;
760 for (; str != end && isspace_char(*str); str++) length--;
761 if (str != end && *str == '-') {
762 l_time->neg = true;
763 str++;
764 length--;
765 }
766 if (str == end) return true;
767
768 // Remember beginning of first non-space/- char.
769 start = str;
770
771 /* Check first if this is a full TIMESTAMP */
772 if (length >= 12) { /* Probably full timestamp */
773 MYSQL_TIME_STATUS tmpstatus;
774 (void)str_to_datetime(str, length, l_time,
775 (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &tmpstatus);
776 if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR) {
777 *status = tmpstatus;
778 return l_time->time_type == MYSQL_TIMESTAMP_ERROR;
779 }
780 assert(status->warnings == 0 && status->fractional_digits == 0 &&
781 status->nanoseconds == 0);
782 }
783
784 /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
785 for (value = 0; str != end && isdigit_char(*str); str++)
786 value = value * 10L + static_cast<long>(*str - '0');
787
788 if (value > UINT_MAX) return true;
789
790 /* Skip all space after 'days' */
791 end_of_days = str;
792 for (; str != end && isspace_char(str[0]); str++)
793 ;
794
795 state = 0;
796 found_days = found_hours = false;
797 if (static_cast<uint>(end - str) > 1 && str != end_of_days &&
798 isdigit_char(*str)) { /* Found days part */
799 date[0] = static_cast<ulong>(value);
800 state = 1; /* Assume next is hours */
801 found_days = true;
802 } else if ((end - str) > 1 && *str == time_separator &&
803 isdigit_char(str[1])) {
804 date[0] = 0; /* Assume we found hours */
805 date[1] = static_cast<ulong>(value);
806 state = 2;
807 found_hours = true;
808 str++; /* skip ':' */
809 seen_colon = true;
810 } else {
811 /* String given as one number; assume HHMMSS format */
812 date[0] = 0;
813 date[1] = static_cast<ulong>(value / 10000);
814 date[2] = static_cast<ulong>(value / 100 % 100);
815 date[3] = static_cast<ulong>(value % 100);
816 state = 4;
817 goto fractional;
818 }
819
820 /* Read hours, minutes and seconds */
821 for (;;) {
822 for (value = 0; str != end && isdigit_char(*str); str++)
823 value = value * 10L + static_cast<long>(*str - '0');
824 date[state++] = static_cast<ulong>(value);
825 if (state == 4 || (end - str) < 2 || *str != time_separator ||
826 !isdigit_char(str[1]))
827 break;
828 str++; /* Skip time_separator (':') */
829 seen_colon = true;
830 }
831
832 if (state != 4) { /* Not HH:MM:SS */
833 /* Fix the date to assume that seconds was given */
834 if (!found_hours && !found_days) {
835 std::size_t len = sizeof(long) * (state - 1);
836 memmove(pointer_cast<uchar *>(date + 4) - len,
837 pointer_cast<uchar *>(date + state) - len, len);
838 memset(date, 0, sizeof(long) * (4 - state));
839 } else
840 memset((date + state), 0, sizeof(long) * (4 - state));
841 }
842
843 fractional:
844 /* Get fractional second part */
845 if ((end - str) >= 2 && *str == '.' && isdigit_char(str[1])) {
846 int field_length = 5;
847 str++;
848 value = static_cast<uint>(static_cast<uchar>(*str - '0'));
849 while (++str != end && isdigit_char(*str)) {
850 if (field_length-- > 0)
851 value = value * 10 + static_cast<uint>(static_cast<uchar>(*str - '0'));
852 }
853 if (field_length >= 0) {
854 status->fractional_digits = DATETIME_MAX_DECIMALS - field_length;
855 if (field_length > 0)
856 value *= static_cast<long>(log_10_int[field_length]);
857 } else {
858 /* Scan digits left after microseconds */
859 status->fractional_digits = 6;
860 status->nanoseconds = 100 * (str[-1] - '0');
861 for (; str != end && isdigit_char(*str); str++) {
862 }
863 }
864 date[4] = static_cast<ulong>(value);
865 } else if ((end - str) == 1 && *str == '.') {
866 str++;
867 date[4] = 0;
868 } else
869 date[4] = 0;
870
871 /* Check for exponent part: E<gigit> | E<sign><digit> */
872 /* (may occur as result of %g formatting of time value) */
873 if ((end - str) > 1 && (*str == 'e' || *str == 'E') &&
874 (isdigit_char(str[1]) || ((str[1] == '-' || str[1] == '+') &&
875 (end - str) > 2 && isdigit_char(str[2]))))
876 return true;
877
878 if (internal_format_positions[7] != 255) {
879 /* Read a possible AM/PM */
880 while (str != end && isspace_char(*str)) str++;
881 if (str + 2 <= end && (str[1] == 'M' || str[1] == 'm')) {
882 if (str[0] == 'p' || str[0] == 'P') {
883 str += 2;
884 date[1] = date[1] % 12 + 12;
885 } else if (str[0] == 'a' || str[0] == 'A')
886 str += 2;
887 }
888 }
889
890 /* Integer overflow checks */
891 if (date[0] > UINT_MAX || date[1] > UINT_MAX || date[2] > UINT_MAX ||
892 date[3] > UINT_MAX || date[4] > UINT_MAX)
893 return true;
894
895 if (!seen_colon && (flags & TIME_STRICT_COLON)) {
896 memset(l_time, 0, sizeof(*l_time));
897 status->warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
898 return true;
899 }
900
901 l_time->year = 0; /* For protocol::store_time */
902 l_time->month = 0;
903
904 l_time->day = 0;
905 l_time->hour = date[1] + date[0] * 24; /* Mix days and hours */
906
907 l_time->minute = date[2];
908 l_time->second = date[3];
909 l_time->second_part = date[4];
910 l_time->time_type = MYSQL_TIMESTAMP_TIME;
911 l_time->time_zone_displacement = 0;
912
913 if (check_time_mmssff_range(*l_time)) {
914 status->warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
915 return true;
916 }
917
918 /* Adjust the value into supported MYSQL_TIME range */
919 adjust_time_range(l_time, &status->warnings);
920
921 /* Check if there is garbage at end of the MYSQL_TIME specification */
922 if (str != end) {
923 do {
924 if (!isspace_char(*str)) {
925 status->warnings |= MYSQL_TIME_WARN_TRUNCATED;
926 // No char was actually used in conversion - bad value
927 if (str == start) {
928 l_time->time_type = MYSQL_TIMESTAMP_NONE;
929 return true;
930 }
931 break;
932 }
933 } while (++str != end);
934 }
935 return false;
936 }
937
938 /**
939 Convert number to TIME
940 @param nr Number to convert.
941 @param [out] ltime Variable to convert to.
942 @param [out] warnings Warning vector.
943
944 @retval false OK
945 @retval true No. is out of range
946 */
number_to_time(longlong nr,MYSQL_TIME * ltime,int * warnings)947 bool number_to_time(longlong nr, MYSQL_TIME *ltime, int *warnings) {
948 if (nr > TIME_MAX_VALUE) {
949 /* For huge numbers try full DATETIME, like str_to_time does. */
950 if (nr >= 10000000000LL) /* '0001-00-00 00-00-00' */
951 {
952 int warnings_backup = *warnings;
953 if (number_to_datetime(nr, ltime, 0, warnings) != -1LL) return false;
954 *warnings = warnings_backup;
955 }
956 set_max_time(ltime, false);
957 *warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
958 return true;
959 } else if (nr < -TIME_MAX_VALUE) {
960 set_max_time(ltime, true);
961 *warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
962 return true;
963 }
964 if ((ltime->neg = (nr < 0))) nr = -nr;
965 if (nr % 100 >= 60 || nr / 100 % 100 >= 60) /* Check hours and minutes */
966 {
967 set_zero_time(ltime, MYSQL_TIMESTAMP_TIME);
968 *warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
969 return true;
970 }
971 ltime->time_type = MYSQL_TIMESTAMP_TIME;
972 ltime->year = ltime->month = ltime->day = 0;
973 TIME_set_hhmmss(ltime, static_cast<uint>(nr));
974 ltime->second_part = 0;
975 return false;
976 }
977
978 /**
979 Adjust 'time' value to lie in the MYSQL_TIME range.
980 If the time value lies outside of the range [-838:59:59, 838:59:59],
981 set it to the closest endpoint of the range and set
982 MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
983
984 @param[in,out] my_time pointer to MYSQL_TIME value
985 @param[out] warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is
986 out of range
987 */
adjust_time_range(MYSQL_TIME * my_time,int * warning)988 void adjust_time_range(MYSQL_TIME *my_time, int *warning) {
989 assert(!check_time_mmssff_range(*my_time));
990 if (check_time_range_quick(*my_time)) {
991 my_time->day = my_time->second_part = 0;
992 set_max_hhmmss(my_time);
993 *warning |= MYSQL_TIME_WARN_OUT_OF_RANGE;
994 }
995 }
996
997 /**
998 Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
999 */
my_init_time()1000 void my_init_time() {
1001 time_t seconds;
1002 struct tm *l_time;
1003 struct tm tm_tmp;
1004 MYSQL_TIME my_time;
1005 bool not_used;
1006
1007 seconds = time(nullptr);
1008 localtime_r(&seconds, &tm_tmp);
1009 l_time = &tm_tmp;
1010 my_time_zone = 3600; /* Comp. for -3600 in my_gmt_sec */
1011 my_time.year = static_cast<uint>(l_time->tm_year) + 1900;
1012 my_time.month = static_cast<uint>(l_time->tm_mon) + 1;
1013 my_time.day = static_cast<uint>(l_time->tm_mday);
1014 my_time.hour = static_cast<uint>(l_time->tm_hour);
1015 my_time.minute = static_cast<uint>(l_time->tm_min);
1016 my_time.second = static_cast<uint>(l_time->tm_sec);
1017 my_time.time_type = MYSQL_TIMESTAMP_DATETIME;
1018 my_time.neg = false;
1019 my_time.second_part = 0;
1020 my_system_gmt_sec(my_time, &my_time_zone, ¬_used); /* Init my_time_zone */
1021 }
1022
1023 /**
1024 Handle 2 digit year conversions.
1025
1026 @param year 2 digit year
1027 @return Year between 1970-2069
1028 */
year_2000_handling(uint year)1029 uint year_2000_handling(uint year) {
1030 if ((year = year + 1900) < 1900 + YY_PART_YEAR) year += 100;
1031 return year;
1032 }
1033
1034 /**
1035 Calculate nr of day since year 0 in new date-system (from 1615).
1036
1037 @param year Year (exact 4 digit year, no year conversions)
1038 @param month Month
1039 @param day Day
1040
1041 @note 0000-00-00 is a valid date, and will return 0
1042
1043 @return Days since 0000-00-00
1044 */
calc_daynr(uint year,uint month,uint day)1045 long calc_daynr(uint year, uint month, uint day) {
1046 long delsum;
1047 int temp;
1048 int y = year; /* may be < 0 temporarily */
1049
1050 if (y == 0 && month == 0) return 0; /* Skip errors */
1051 /* Cast to int to be able to handle month == 0 */
1052 delsum = static_cast<long>(365 * y + 31 * (static_cast<int>(month) - 1) +
1053 static_cast<int>(day));
1054 if (month <= 2)
1055 y--;
1056 else
1057 delsum -= static_cast<long>(static_cast<int>(month) * 4 + 23) / 10;
1058 temp = ((y / 100 + 1) * 3) / 4;
1059 assert(delsum + static_cast<int>(y) / 4 - temp >= 0);
1060 return (delsum + static_cast<int>(y) / 4 - temp);
1061 } /* calc_daynr */
1062
1063 /**
1064 Convert time in MYSQL_TIME representation in system time zone to its
1065 my_time_t form (number of seconds in UTC since begginning of Unix Epoch).
1066
1067 @param my_time - time value to be converted
1068 @param my_timezone - pointer to long where offset of system time zone
1069 from UTC will be stored for caching
1070 @param in_dst_time_gap - set to true if time falls into spring time-gap
1071
1072 @note
1073 The idea is to cache the time zone offset from UTC (including daylight
1074 saving time) for the next call to make things faster. But currently we
1075 just calculate this offset during startup (by calling my_init_time()
1076 function) and use it all the time.
1077 Time value provided should be legal time value (e.g. '2003-01-01 25:00:00'
1078 is not allowed).
1079
1080 @return Time in UTC seconds since Unix Epoch representation.
1081 */
my_system_gmt_sec(const MYSQL_TIME & my_time,long * my_timezone,bool * in_dst_time_gap)1082 my_time_t my_system_gmt_sec(const MYSQL_TIME &my_time, long *my_timezone,
1083 bool *in_dst_time_gap) {
1084 uint loop;
1085 time_t tmp = 0;
1086 int shift = 0;
1087 MYSQL_TIME tmp_time;
1088 MYSQL_TIME *t = &tmp_time;
1089 struct tm *l_time;
1090 struct tm tm_tmp;
1091 long diff, current_timezone;
1092
1093 /*
1094 Use temp variable to avoid trashing input data, which could happen in
1095 case of shift required for boundary dates processing.
1096 */
1097 // memcpy(&tmp_time, &my_time, sizeof(MYSQL_TIME));
1098 tmp_time = my_time;
1099
1100 if (!validate_timestamp_range(*t)) return 0;
1101
1102 /*
1103 Calculate the gmt time based on current time and timezone
1104 The -1 on the end is to ensure that if have a date that exists twice
1105 (like 2002-10-27 02:00:0 MET), we will find the initial date.
1106
1107 By doing -3600 we will have to call localtime_r() several times, but
1108 I couldn't come up with a better way to get a repeatable result :(
1109
1110 We can't use mktime() as it's buggy on many platforms and not thread safe.
1111
1112 Note: this code assumes that our time_t estimation is not too far away
1113 from real value (we assume that localtime_r(tmp) will return something
1114 within 24 hrs from t) which is probably true for all current time zones.
1115
1116 Note2: For the dates, which have time_t representation close to
1117 MAX_INT32 (efficient time_t limit for supported platforms), we should
1118 do a small trick to avoid overflow. That is, convert the date, which is
1119 two days earlier, and then add these days to the final value.
1120
1121 The same trick is done for the values close to 0 in time_t
1122 representation for platfroms with unsigned time_t (QNX).
1123
1124 To be more verbose, here is a sample (extracted from the code below):
1125 (calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L
1126 would return -2147480896 because of the long type overflow. In result
1127 we would get 1901 year in localtime_r(), which is an obvious error.
1128
1129 Alike problem raises with the dates close to Epoch. E.g.
1130 (calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L
1131 will give -3600.
1132
1133 On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600)
1134 wil give us a date around 2106 year. Which is no good.
1135
1136 Theoreticaly, there could be problems with the latter conversion:
1137 there are at least two timezones, which had time switches near 1 Jan
1138 of 1970 (because of political reasons). These are America/Hermosillo and
1139 America/Mazatlan time zones. They changed their offset on
1140 1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones
1141 the code below will give incorrect results for dates close to
1142 1970-01-01, in the case OS takes into account these historical switches.
1143 Luckily, it seems that we support only one platform with unsigned
1144 time_t. It's QNX. And QNX does not support historical timezone data at all.
1145 E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply
1146 historical information for localtime_r() etc. That is, the problem is not
1147 relevant to QNX.
1148
1149 We are safe with shifts close to MAX_INT32, as there are no known
1150 time switches on Jan 2038 yet :)
1151 */
1152 if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4)) {
1153 /*
1154 Below we will pass static_cast<uint>(t->day - shift) to calc_daynr.
1155 As we don't want to get an overflow here, we will shift
1156 only safe dates. That's why we have (t->day > 4) above.
1157 */
1158 t->day -= 2;
1159 shift = 2;
1160 }
1161
1162 tmp = static_cast<time_t>(
1163 ((calc_daynr(static_cast<uint>(t->year), static_cast<uint>(t->month),
1164 static_cast<uint>(t->day)) -
1165 static_cast<long>(days_at_timestart)) *
1166 SECONDS_IN_24H +
1167 static_cast<long>(t->hour) * 3600L +
1168 static_cast<long>(t->minute * 60 + t->second)) +
1169 static_cast<time_t>(my_time_zone) - 3600);
1170
1171 current_timezone = my_time_zone;
1172 localtime_r(&tmp, &tm_tmp);
1173 l_time = &tm_tmp;
1174 for (loop = 0; loop < 2 && (t->hour != static_cast<uint>(l_time->tm_hour) ||
1175 t->minute != static_cast<uint>(l_time->tm_min) ||
1176 t->second != static_cast<uint>(l_time->tm_sec));
1177 loop++) { /* One check should be enough ? */
1178 /* Get difference in days */
1179 int days = t->day - l_time->tm_mday;
1180 if (days < -1)
1181 days = 1; /* Month has wrapped */
1182 else if (days > 1)
1183 days = -1;
1184 diff = (3600L * static_cast<long>(days * 24 + (static_cast<int>(t->hour) -
1185 l_time->tm_hour)) +
1186 static_cast<long>(60 *
1187 (static_cast<int>(t->minute) - l_time->tm_min)) +
1188 static_cast<long>(static_cast<int>(t->second) - l_time->tm_sec));
1189 current_timezone += diff + 3600; /* Compensate for -3600 above */
1190 tmp += static_cast<time_t>(diff);
1191 localtime_r(&tmp, &tm_tmp);
1192 l_time = &tm_tmp;
1193 }
1194 /*
1195 Fix that if we are in the non existing daylight saving time hour
1196 we move the start of the next real hour.
1197
1198 This code doesn't handle such exotical thing as time-gaps whose length
1199 is more than one hour or non-integer (latter can theoretically happen
1200 if one of seconds will be removed due leap correction, or because of
1201 general time correction like it happened for Africa/Monrovia time zone
1202 in year 1972).
1203 */
1204 if (loop == 2 && t->hour != static_cast<uint>(l_time->tm_hour)) {
1205 int days = t->day - l_time->tm_mday;
1206 if (days < -1)
1207 days = 1; /* Month has wrapped */
1208 else if (days > 1)
1209 days = -1;
1210 diff = (3600L * static_cast<long>(days * 24 + (static_cast<int>(t->hour) -
1211 l_time->tm_hour)) +
1212 static_cast<long>(60 *
1213 (static_cast<int>(t->minute) - l_time->tm_min)) +
1214 static_cast<long>(static_cast<int>(t->second) - l_time->tm_sec));
1215 if (diff == 3600)
1216 tmp += 3600 - t->minute * 60 - t->second; /* Move to next hour */
1217 else if (diff == -3600)
1218 tmp -= t->minute * 60 + t->second; /* Move to previous hour */
1219
1220 *in_dst_time_gap = true;
1221 }
1222 *my_timezone = current_timezone;
1223
1224 /* shift back, if we were dealing with boundary dates */
1225 tmp += shift * SECONDS_IN_24H;
1226
1227 /*
1228 This is possible for dates, which slightly exceed boundaries.
1229 Conversion will pass ok for them, but we don't allow them.
1230 First check will pass for platforms with signed time_t.
1231 instruction above (tmp+= shift*86400L) could exceed
1232 MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen.
1233 So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms
1234 with unsigned time_t tmp+= shift*86400L might result in a number,
1235 larger then TIMESTAMP_MAX_VALUE, so another check will work.
1236 */
1237 if (!is_time_t_valid_for_timestamp(tmp)) tmp = 0;
1238
1239 return static_cast<my_time_t>(tmp);
1240 } /* my_system_gmt_sec */
1241
1242 /**
1243 Writes a two-digit number to a string, padded with zero if it is less than 10.
1244 If the number is greater than or equal to 100, "00" is written to the string.
1245 The number should be less than 100 for valid temporal values, but the
1246 formatting functions need to handle invalid values too, since they are used
1247 for formatting the values in error/warning messages when invalid values have
1248 been given by the user.
1249 */
format_two_digits(int value,char * to)1250 static char *format_two_digits(int value, char *to) {
1251 if (value < 0 || value >= 100) value = 0;
1252 return write_two_digits(value, to);
1253 }
1254
1255 /**
1256 Print the microsecond part with the specified precision.
1257
1258 @param[out] to The string pointer to print at
1259 @param useconds The microseconds value
1260 @param dec Precision, between 1 and 6
1261
1262 @return The length of the result string
1263 */
my_useconds_to_str(char * to,unsigned useconds,unsigned dec)1264 static int my_useconds_to_str(char *to, unsigned useconds, unsigned dec) {
1265 assert(dec <= DATETIME_MAX_DECIMALS);
1266
1267 // Write the decimal point and the terminating zero character.
1268 to[0] = '.';
1269 to[dec + 1] = '\0';
1270
1271 // Write the dec most significant digits of the microsecond value.
1272 for (int i = DATETIME_MAX_DECIMALS - dec; i > 0; --i) useconds /= 10;
1273 write_digits(useconds, dec, to + 1);
1274 return dec + 1;
1275 }
1276
1277 /**
1278 Converts a time value to a string with the format HH:MM:SS[.fraction].
1279
1280 This function doesn't check that the given MYSQL_TIME structure members
1281 are in the valid range. If they are not, the returned value won't reflect
1282 any valid time either. Additionally, it doesn't take into
1283 account time->day member: it's assumed that days have been converted
1284 to hours already.
1285
1286 @param my_time Source time value
1287 @param[out] to Destnation char array
1288 @param dec Precision, in the range 0..6
1289
1290 @return number of characters written to 'to'
1291 */
1292
my_time_to_str(const MYSQL_TIME & my_time,char * to,uint dec)1293 int my_time_to_str(const MYSQL_TIME &my_time, char *to, uint dec) {
1294 const char *const start = to;
1295 if (my_time.neg) *to++ = '-';
1296
1297 // Hours should be zero-padded up to two digits. It might have more digits.
1298 to = write_digits(my_time.hour, std::max(2, count_digits(my_time.hour)), to);
1299
1300 *to++ = ':';
1301 to = format_two_digits(my_time.minute, to);
1302 *to++ = ':';
1303 to = format_two_digits(my_time.second, to);
1304
1305 const int length = to - start;
1306 if (dec) return length + my_useconds_to_str(to, my_time.second_part, dec);
1307 *to = '\0';
1308 return length;
1309 }
1310
1311 /**
1312 Converts a date value to a string with the format 'YYYY-MM-DD'.
1313
1314 This function doesn't check that the given MYSQL_TIME structure members are
1315 in the valid range. If they are not, the returned value won't reflect any
1316 valid date either.
1317
1318 @param my_time Source time value
1319 @param[out] to Destination character array
1320
1321 @return number of characters written to 'to'
1322 */
my_date_to_str(const MYSQL_TIME & my_time,char * to)1323 int my_date_to_str(const MYSQL_TIME &my_time, char *to) {
1324 const char *const start = to;
1325 to = format_two_digits(my_time.year / 100, to);
1326 to = format_two_digits(my_time.year % 100, to);
1327 *to++ = '-';
1328 to = format_two_digits(my_time.month, to);
1329 *to++ = '-';
1330 to = format_two_digits(my_time.day, to);
1331 *to = '\0';
1332 return to - start;
1333 }
1334
1335 /**
1336 Convert datetime to a string 'YYYY-MM-DD hh:mm:ss'.
1337 Open coded for better performance.
1338 This code previously resided in field.cc, in Field_timestamp::val_str().
1339
1340 @param my_time The src MYSQL_TIME value.
1341 @param[out] to The string pointer to print at.
1342 @return The length of the result string.
1343 */
TIME_to_datetime_str(const MYSQL_TIME & my_time,char * to)1344 static int TIME_to_datetime_str(const MYSQL_TIME &my_time, char *to) {
1345 /* Year */
1346 to = format_two_digits(my_time.year / 100, to);
1347 to = format_two_digits(my_time.year % 100, to);
1348 *to++ = '-';
1349 /* Month */
1350 to = format_two_digits(my_time.month, to);
1351 *to++ = '-';
1352 /* Day */
1353 to = format_two_digits(my_time.day, to);
1354 *to++ = ' ';
1355 /* Hour */
1356 to = format_two_digits(my_time.hour, to);
1357 *to++ = ':';
1358 /* Minute */
1359 to = format_two_digits(my_time.minute, to);
1360 *to++ = ':';
1361 /* Second */
1362 format_two_digits(my_time.second, to);
1363 return 19;
1364 }
1365
1366 /**
1367 Print a datetime value with an optional fractional part.
1368
1369 @param my_time The MYSQL_TIME value to print
1370 @param [out] to The string pointer to print at
1371 @param dec Precision, in the range 0..6
1372
1373 @return The length of the result string.
1374 */
my_datetime_to_str(const MYSQL_TIME & my_time,char * to,uint dec)1375 int my_datetime_to_str(const MYSQL_TIME &my_time, char *to, uint dec) {
1376 int len = TIME_to_datetime_str(my_time, to);
1377 if (dec) len += my_useconds_to_str(to + len, my_time.second_part, dec);
1378 if (my_time.time_type == MYSQL_TIMESTAMP_DATETIME_TZ) {
1379 int tzd = my_time.time_zone_displacement;
1380 len += sprintf(to + len, "%+02i:%02i", tzd / SECS_PER_HOUR,
1381 std::abs(tzd) / SECS_PER_MIN % MINS_PER_HOUR);
1382 } else
1383 to[len] = '\0';
1384 return len;
1385 }
1386
1387 /**
1388 Convert struct DATE/TIME/DATETIME value to string using built-in
1389 MySQL time conversion formats.
1390
1391 @note The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved.
1392 @param my_time The MYSQL_TIME value to print
1393 @param [out] to The string pointer to print at
1394 @param dec Precision, in the range 0..6
1395
1396 @return number of bytes written
1397 */
my_TIME_to_str(const MYSQL_TIME & my_time,char * to,uint dec)1398 int my_TIME_to_str(const MYSQL_TIME &my_time, char *to, uint dec) {
1399 switch (my_time.time_type) {
1400 case MYSQL_TIMESTAMP_DATETIME:
1401 case MYSQL_TIMESTAMP_DATETIME_TZ:
1402 return my_datetime_to_str(my_time, to, dec);
1403 case MYSQL_TIMESTAMP_DATE:
1404 return my_date_to_str(my_time, to);
1405 case MYSQL_TIMESTAMP_TIME:
1406 return my_time_to_str(my_time, to, dec);
1407 case MYSQL_TIMESTAMP_NONE:
1408 case MYSQL_TIMESTAMP_ERROR:
1409 to[0] = '\0';
1410 return 0;
1411 default:
1412 assert(false);
1413 return 0;
1414 }
1415 }
1416
1417 /**
1418 Print a timestamp with an oprional fractional part: XXXXX[.YYYYY]
1419
1420 @param tm The timestamp value to print.
1421 @param [out] to The string pointer to print at.
1422 @param dec Precision, in the range 0..6.
1423 @return The length of the result string.
1424 */
my_timeval_to_str(const struct timeval * tm,char * to,uint dec)1425 int my_timeval_to_str(const struct timeval *tm, char *to, uint dec) {
1426 int len = sprintf(to, "%d", static_cast<int>(tm->tv_sec));
1427 if (dec) len += my_useconds_to_str(to + len, tm->tv_usec, dec);
1428 return len;
1429 }
1430
1431 /**
1432 Convert datetime value specified as number to broken-down TIME
1433 representation and form value of DATETIME type as side-effect.
1434
1435 Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
1436 YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in
1437 YYYYMMDDHHMMSS format as side-effect.
1438
1439 This function also checks if datetime value fits in DATETIME range.
1440
1441 Datetime value in YYYYMMDDHHMMSS format.
1442
1443 was_cut if return value -1: one of
1444 - MYSQL_TIME_WARN_OUT_OF_RANGE
1445 - MYSQL_TIME_WARN_ZERO_DATE
1446 - MYSQL_TIME_WARN_TRUNCATED
1447 otherwise 0.
1448
1449 @param nr datetime value as number
1450 @param[in,out] time_res pointer for structure for broken-down
1451 representation
1452 @param flags TIME_NO_ZERO_DATE and flags used by check_date()
1453 @param[out] was_cut 0 Value ok
1454 1 If value was cut during conversion
1455 2 check_date(date,flags) considers date invalid
1456
1457 @retval -1 Timestamp with wrong values, e.g. nr == 0 with
1458 TIME_NO_ZERO_DATE
1459 @retval anything else DATETIME as integer in YYYYMMDDHHMMSS format
1460 */
number_to_datetime(longlong nr,MYSQL_TIME * time_res,my_time_flags_t flags,int * was_cut)1461 longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
1462 my_time_flags_t flags, int *was_cut) {
1463 long part1;
1464 long part2;
1465
1466 *was_cut = 0;
1467 memset(time_res, 0, sizeof(*time_res));
1468 time_res->time_type = MYSQL_TIMESTAMP_DATE;
1469
1470 if (nr == 0LL || nr >= 10000101000000LL) {
1471 time_res->time_type = MYSQL_TIMESTAMP_DATETIME;
1472 if (nr > 99999999999999LL) /* 9999-99-99 99:99:99 */
1473 {
1474 *was_cut = MYSQL_TIME_WARN_OUT_OF_RANGE;
1475 return -1LL;
1476 }
1477 goto ok;
1478 }
1479 if (nr < 101) goto err;
1480 if (nr <= (YY_PART_YEAR - 1) * 10000L + 1231L) {
1481 nr = (nr + 20000000L) * 1000000L; /* YYMMDD, year: 2000-2069 */
1482 goto ok;
1483 }
1484 if (nr < (YY_PART_YEAR)*10000L + 101L) goto err;
1485 if (nr <= 991231L) {
1486 nr = (nr + 19000000L) * 1000000L; /* YYMMDD, year: 1970-1999 */
1487 goto ok;
1488 }
1489 /*
1490 Though officially we support DATE values from 1000-01-01 only, one can
1491 easily insert a value like 1-1-1. So, for consistency reasons such dates
1492 are allowed when TIME_FUZZY_DATE is set.
1493 */
1494 if (nr < 10000101L && !(flags & TIME_FUZZY_DATE)) goto err;
1495 if (nr <= 99991231L) {
1496 nr = nr * 1000000L;
1497 goto ok;
1498 }
1499 if (nr < 101000000L) goto err;
1500
1501 time_res->time_type = MYSQL_TIMESTAMP_DATETIME;
1502
1503 if (nr <= (YY_PART_YEAR - 1) * 10000000000LL + 1231235959LL) {
1504 nr = nr + 20000000000000LL; /* YYMMDDHHMMSS, 2000-2069 */
1505 goto ok;
1506 }
1507 if (nr < YY_PART_YEAR * 10000000000LL + 101000000LL) goto err;
1508 if (nr <= 991231235959LL)
1509 nr = nr + 19000000000000LL; /* YYMMDDHHMMSS, 1970-1999 */
1510
1511 ok:
1512 part1 = static_cast<long>(nr / 1000000LL);
1513 part2 = static_cast<long>(nr - static_cast<longlong>(part1) * 1000000LL);
1514 time_res->year = static_cast<int>(part1 / 10000L);
1515 part1 %= 10000L;
1516 time_res->month = static_cast<int>(part1) / 100;
1517 time_res->day = static_cast<int>(part1) % 100;
1518 time_res->hour = static_cast<int>(part2 / 10000L);
1519 part2 %= 10000L;
1520 time_res->minute = static_cast<int>(part2) / 100;
1521 time_res->second = static_cast<int>(part2) % 100;
1522
1523 if (!check_datetime_range(*time_res) &&
1524 !check_date(*time_res, (nr != 0), flags, was_cut))
1525 return nr;
1526
1527 /* Don't want to have was_cut get set if TIME_NO_ZERO_DATE was violated. */
1528 if (!nr && (flags & TIME_NO_ZERO_DATE)) return -1LL;
1529
1530 err:
1531 *was_cut = MYSQL_TIME_WARN_TRUNCATED;
1532 return -1LL;
1533 }
1534
1535 /**
1536 Convert time value to integer in YYYYMMDDHHMMSS.
1537
1538 @param my_time The MYSQL_TIME value to convert.
1539
1540 @return A number in format YYYYMMDDHHMMSS.
1541 */
TIME_to_ulonglong_datetime(const MYSQL_TIME & my_time)1542 ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME &my_time) {
1543 return (static_cast<ulonglong>(my_time.year * 10000UL +
1544 my_time.month * 100UL + my_time.day) *
1545 1000000ULL +
1546 static_cast<ulonglong>(my_time.hour * 10000UL +
1547 my_time.minute * 100UL + my_time.second));
1548 }
1549
1550 /**
1551 Convert MYSQL_TIME value to integer in YYYYMMDD format
1552
1553 @param my_time The MYSQL_TIME value to convert.
1554 @return A number in format YYYYMMDD.
1555 */
TIME_to_ulonglong_date(const MYSQL_TIME & my_time)1556 ulonglong TIME_to_ulonglong_date(const MYSQL_TIME &my_time) {
1557 return static_cast<ulonglong>(my_time.year * 10000UL + my_time.month * 100UL +
1558 my_time.day);
1559 }
1560
1561 /**
1562 Convert MYSQL_TIME value to integer in HHMMSS format.
1563 This function doesn't take into account time->day member:
1564 it's assumed that days have been converted to hours already.
1565
1566 @param my_time The TIME value to convert.
1567 @return The number in HHMMSS format.
1568 */
TIME_to_ulonglong_time(const MYSQL_TIME & my_time)1569 ulonglong TIME_to_ulonglong_time(const MYSQL_TIME &my_time) {
1570 return static_cast<ulonglong>(my_time.hour * 10000UL +
1571 my_time.minute * 100UL + my_time.second);
1572 }
1573
1574 /**
1575 Set day, month and year from a number.
1576
1577 @param ltime MYSQL_TIME variable
1578 @param yymmdd Number in YYYYMMDD format
1579 */
TIME_set_yymmdd(MYSQL_TIME * ltime,uint yymmdd)1580 void TIME_set_yymmdd(MYSQL_TIME *ltime, uint yymmdd) {
1581 ltime->day = static_cast<int>(yymmdd % 100);
1582 ltime->month = static_cast<int>(yymmdd / 100) % 100;
1583 ltime->year = static_cast<int>(yymmdd / 10000);
1584 }
1585
1586 /**
1587 Set hour, minute and secondr from a number.
1588
1589 @param ltime MYSQL_TIME variable
1590 @param hhmmss Number in HHMMSS format
1591 */
TIME_set_hhmmss(MYSQL_TIME * ltime,uint hhmmss)1592 void TIME_set_hhmmss(MYSQL_TIME *ltime, uint hhmmss) {
1593 ltime->second = static_cast<int>(hhmmss % 100);
1594 ltime->minute = static_cast<int>(hhmmss / 100) % 100;
1595 ltime->hour = static_cast<int>(hhmmss / 10000);
1596 }
1597
1598 /**
1599 Convert struct MYSQL_TIME (date and time split into year/month/day/hour/...
1600 to a number in format YYYYMMDDHHMMSS (DATETIME),
1601 YYYYMMDD (DATE) or HHMMSS (TIME).
1602
1603 The function is used when we need to convert value of time item
1604 to a number if it's used in numeric context, i. e.:
1605 SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
1606 SELECT ?+1;
1607
1608 @param my_time Source time value
1609
1610 @retval 0 in case of errors!
1611 @retval number in format YYYYMMDDHHMMSS (DATETIME), YYYYMMDD (DATE) or HHMMSS
1612 (TIME), otherwise.
1613
1614 @note
1615 This function doesn't check that given MYSQL_TIME structure members are
1616 in valid range. If they are not, return value won't reflect any
1617 valid date either.
1618 */
TIME_to_ulonglong(const MYSQL_TIME & my_time)1619 ulonglong TIME_to_ulonglong(const MYSQL_TIME &my_time) {
1620 switch (my_time.time_type) {
1621 case MYSQL_TIMESTAMP_DATETIME:
1622 return TIME_to_ulonglong_datetime(my_time);
1623 case MYSQL_TIMESTAMP_DATE:
1624 return TIME_to_ulonglong_date(my_time);
1625 case MYSQL_TIMESTAMP_TIME:
1626 return TIME_to_ulonglong_time(my_time);
1627 case MYSQL_TIMESTAMP_NONE:
1628 case MYSQL_TIMESTAMP_ERROR:
1629 return 0ULL;
1630 default:
1631 assert(false);
1632 }
1633 return 0;
1634 }
1635
1636 /**
1637 Round MYSQL_TIME datetime value and convert to ulonglong representation.
1638
1639 @param my_time input time
1640 @param[out] warnings warning vector
1641
1642 @return time in (u)longlong format
1643 */
TIME_to_ulonglong_datetime_round(const MYSQL_TIME & my_time,int * warnings)1644 ulonglong TIME_to_ulonglong_datetime_round(const MYSQL_TIME &my_time,
1645 int *warnings) {
1646 // Catch simple cases
1647 if (my_time.second_part < 500000) return TIME_to_ulonglong_datetime(my_time);
1648 if (my_time.second < 59) return TIME_to_ulonglong_datetime(my_time) + 1;
1649 // Corner case e.g. 'YYYY-MM-DD hh:mm:59.5'. Proceed with slower method.
1650 MYSQL_TIME tmp = my_time;
1651 my_datetime_adjust_frac(&tmp, 0, warnings, false);
1652 return TIME_to_ulonglong_datetime(tmp); // + TIME_microseconds_round(ltime);
1653 }
1654
1655 /**
1656 Round MYSQL_TIME time value and convert to to ulonglong representation.
1657
1658 @param my_time input time
1659 @return time in (u)longlong format
1660 */
TIME_to_ulonglong_time_round(const MYSQL_TIME & my_time)1661 ulonglong TIME_to_ulonglong_time_round(const MYSQL_TIME &my_time) {
1662 if (my_time.second_part < 500000) return TIME_to_ulonglong_time(my_time);
1663 if (my_time.second < 59) return TIME_to_ulonglong_time(my_time) + 1;
1664 // Corner case e.g. 'hh:mm:59.5'. Proceed with slower method.
1665 MYSQL_TIME tmp = my_time;
1666 my_time_adjust_frac(&tmp, 0, false);
1667 return TIME_to_ulonglong_time(tmp);
1668 }
1669
1670 /**
1671 @page time_low_level_rep TIME
1672
1673 In-memory format:
1674
1675 | Bits | Field | Value range |
1676 | ----: | :---- | :---- |
1677 | 1 | sign |(Used for sign, when on disk) |
1678 | 1 | unused |(Reserved for wider hour range, e.g. for intervals) |
1679 | 10 | hour |(0-838) |
1680 | 6 | minute |(0-59) |
1681 | 6 | second |(0-59) |
1682 | 24 | microseconds |(0-999999) |
1683
1684 Total: 48 bits = 6 bytes
1685
1686 @verbatim
1687 Format: Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
1688 @endverbatim
1689 */
1690
1691 /**
1692 Convert time value to numeric packed representation.
1693
1694 @param my_time The value to convert.
1695 @return Numeric packed representation.
1696 */
TIME_to_longlong_time_packed(const MYSQL_TIME & my_time)1697 longlong TIME_to_longlong_time_packed(const MYSQL_TIME &my_time) {
1698 /* If month is 0, we mix day with hours: "1 00:10:10" -> "24:00:10" */
1699 long hms = (((my_time.month ? 0 : my_time.day * 24) + my_time.hour) << 12) |
1700 (my_time.minute << 6) | my_time.second;
1701 longlong tmp = my_packed_time_make(hms, my_time.second_part);
1702 return my_time.neg ? -tmp : tmp;
1703 }
1704
1705 /**
1706 Convert time packed numeric representation to time.
1707
1708 @param [out] ltime The MYSQL_TIME variable to set.
1709 @param tmp The packed numeric representation.
1710 */
TIME_from_longlong_time_packed(MYSQL_TIME * ltime,longlong tmp)1711 void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp) {
1712 longlong hms;
1713 if ((ltime->neg = (tmp < 0))) tmp = -tmp;
1714 hms = my_packed_time_get_int_part(tmp);
1715 ltime->year = static_cast<uint>(0);
1716 ltime->month = static_cast<uint>(0);
1717 ltime->day = static_cast<uint>(0);
1718 ltime->hour =
1719 static_cast<uint>(hms >> 12) % (1 << 10); /* 10 bits starting at 12th */
1720 ltime->minute =
1721 static_cast<uint>(hms >> 6) % (1 << 6); /* 6 bits starting at 6th */
1722 ltime->second =
1723 static_cast<uint>(hms) % (1 << 6); /* 6 bits starting at 0th */
1724 ltime->second_part = my_packed_time_get_frac_part(tmp);
1725 ltime->time_type = MYSQL_TIMESTAMP_TIME;
1726 }
1727
1728 /**
1729 On disk we convert from signed representation to unsigned
1730 representation using TIMEF_OFS, so all values become binary comparable.
1731 */
1732 #define TIMEF_OFS 0x800000000000LL
1733 #define TIMEF_INT_OFS 0x800000LL
1734
1735 /**
1736 Convert in-memory numeric time representation to on-disk representation
1737
1738 @param nr Value in packed numeric time format.
1739 @param [out] ptr The buffer to put value at.
1740 @param dec Precision.
1741 */
my_time_packed_to_binary(longlong nr,uchar * ptr,uint dec)1742 void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec) {
1743 assert(dec <= DATETIME_MAX_DECIMALS);
1744 /* Make sure the stored value was previously properly rounded or truncated */
1745 assert((my_packed_time_get_frac_part(nr) %
1746 static_cast<int>(log_10_int[DATETIME_MAX_DECIMALS - dec])) == 0);
1747
1748 switch (dec) {
1749 case 0:
1750 default:
1751 mi_int3store(ptr, TIMEF_INT_OFS + my_packed_time_get_int_part(nr));
1752 break;
1753
1754 case 1:
1755 case 2:
1756 mi_int3store(ptr, TIMEF_INT_OFS + my_packed_time_get_int_part(nr));
1757 ptr[3] = static_cast<unsigned char>(
1758 static_cast<char>(my_packed_time_get_frac_part(nr) / 10000));
1759 break;
1760
1761 case 4:
1762 case 3:
1763 mi_int3store(ptr, TIMEF_INT_OFS + my_packed_time_get_int_part(nr));
1764 mi_int2store(ptr + 3, my_packed_time_get_frac_part(nr) / 100);
1765 break;
1766
1767 case 5:
1768 case 6:
1769 mi_int6store(ptr, nr + TIMEF_OFS);
1770 break;
1771 }
1772 }
1773
1774 /**
1775 Convert on-disk time representation to in-memory packed numeric
1776 representation.
1777
1778 @param ptr The pointer to read the value at.
1779 @param dec Precision.
1780 @return Packed numeric time representation.
1781 */
my_time_packed_from_binary(const uchar * ptr,uint dec)1782 longlong my_time_packed_from_binary(const uchar *ptr, uint dec) {
1783 assert(dec <= DATETIME_MAX_DECIMALS);
1784
1785 switch (dec) {
1786 case 0:
1787 default: {
1788 longlong intpart = mi_uint3korr(ptr) - TIMEF_INT_OFS;
1789 return my_packed_time_make_int(intpart);
1790 }
1791 case 1:
1792 case 2: {
1793 longlong intpart = mi_uint3korr(ptr) - TIMEF_INT_OFS;
1794 int frac = static_cast<uint>(ptr[3]);
1795 if (intpart < 0 && frac) {
1796 /*
1797 Negative values are stored with
1798 reverse fractional part order,
1799 for binary sort compatibility.
1800
1801 Disk value intpart frac Time value Memory value
1802 800000.00 0 0 00:00:00.00 0000000000.000000
1803 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0
1804 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0
1805 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000
1806 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0
1807 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960
1808
1809 Formula to convert fractional part from disk format
1810 (now stored in "frac" variable) to absolute value: "0x100 - frac".
1811 To reconstruct in-memory value, we shift
1812 to the next integer value and then substruct fractional part.
1813 */
1814 intpart++; /* Shift to the next integer value */
1815 frac -= 0x100; /* -(0x100 - frac) */
1816 }
1817 return my_packed_time_make(intpart, frac * 10000);
1818 }
1819
1820 case 3:
1821 case 4: {
1822 longlong intpart = mi_uint3korr(ptr) - TIMEF_INT_OFS;
1823 int frac = mi_uint2korr(ptr + 3);
1824 if (intpart < 0 && frac) {
1825 /*
1826 Fix reverse fractional part order: "0x10000 - frac".
1827 See comments for FSP=1 and FSP=2 above.
1828 */
1829 intpart++; /* Shift to the next integer value */
1830 frac -= 0x10000; /* -(0x10000-frac) */
1831 }
1832 return my_packed_time_make(intpart, frac * 100);
1833 }
1834
1835 case 5:
1836 case 6:
1837 return (static_cast<longlong>(mi_uint6korr(ptr))) - TIMEF_OFS;
1838 }
1839 }
1840
1841 /**
1842 @page datetime_and_date_low_level_rep DATETIME and DATE
1843
1844 | Bits | Field | Value |
1845 | ----: | :---- | :---- |
1846 | 1 | sign |(used when on disk) |
1847 | 17 | year*13+month |(year 0-9999, month 0-12) |
1848 | 5 | day |(0-31)|
1849 | 5 | hour |(0-23)|
1850 | 6 | minute |(0-59)|
1851 | 6 | second |(0-59)|
1852 | 24 | microseconds |(0-999999)|
1853
1854 Total: 64 bits = 8 bytes
1855
1856 @verbatim
1857 Format: SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
1858 @endverbatim
1859
1860 */
1861
1862 /**
1863 Convert datetime to packed numeric datetime representation.
1864
1865 @param my_time The value to convert.
1866 @return Packed numeric representation of my_time.
1867 */
TIME_to_longlong_datetime_packed(const MYSQL_TIME & my_time)1868 longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME &my_time) {
1869 longlong ymd = ((my_time.year * 13 + my_time.month) << 5) | my_time.day;
1870 longlong hms = (my_time.hour << 12) | (my_time.minute << 6) | my_time.second;
1871 longlong tmp = my_packed_time_make(((ymd << 17) | hms), my_time.second_part);
1872 assert(!check_datetime_range(my_time)); /* Make sure no overflow */
1873 return my_time.neg ? -tmp : tmp;
1874 }
1875
1876 /**
1877 Convert date to packed numeric date representation.
1878 Numeric packed date format is similar to numeric packed datetime
1879 representation, with zero hhmmss part.
1880
1881 @param my_time The value to convert.
1882 @return Packed numeric representation of ltime.
1883 */
TIME_to_longlong_date_packed(const MYSQL_TIME & my_time)1884 longlong TIME_to_longlong_date_packed(const MYSQL_TIME &my_time) {
1885 longlong ymd = ((my_time.year * 13 + my_time.month) << 5) | my_time.day;
1886 return my_packed_time_make_int(ymd << 17);
1887 }
1888
1889 /**
1890 Convert year to packed numeric date representation.
1891 Packed value for YYYY is the same to packed value for date YYYY-00-00.
1892
1893 @return packed value for date YYYY-00-00.
1894 */
year_to_longlong_datetime_packed(long year)1895 longlong year_to_longlong_datetime_packed(long year) {
1896 longlong ymd = ((year * 13) << 5);
1897 return my_packed_time_make_int(ymd << 17);
1898 }
1899
1900 /**
1901 Convert packed numeric datetime representation to MYSQL_TIME.
1902
1903 @param [out] ltime The datetime variable to convert to.
1904 @param tmp The packed numeric datetime value.
1905 */
TIME_from_longlong_datetime_packed(MYSQL_TIME * ltime,longlong tmp)1906 void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp) {
1907 longlong ymd;
1908 longlong hms;
1909 longlong ymdhms;
1910 longlong ym;
1911
1912 if ((ltime->neg = (tmp < 0))) tmp = -tmp;
1913
1914 ltime->second_part = my_packed_time_get_frac_part(tmp);
1915 ymdhms = my_packed_time_get_int_part(tmp);
1916
1917 ymd = ymdhms >> 17;
1918 ym = ymd >> 5;
1919 hms = ymdhms % (1 << 17);
1920
1921 ltime->day = ymd % (1 << 5);
1922 ltime->month = ym % 13;
1923 ltime->year = static_cast<uint>(ym / 13);
1924
1925 ltime->second = hms % (1 << 6);
1926 ltime->minute = (hms >> 6) % (1 << 6);
1927 ltime->hour = static_cast<uint>(hms >> 12);
1928
1929 ltime->time_type = MYSQL_TIMESTAMP_DATETIME;
1930 ltime->time_zone_displacement = 0;
1931 }
1932
1933 /**
1934 Convert packed numeric date representation to MYSQL_TIME.
1935
1936 @param [out] ltime The date variable to convert to.
1937 @param tmp The packed numeric date value.
1938 */
TIME_from_longlong_date_packed(MYSQL_TIME * ltime,longlong tmp)1939 void TIME_from_longlong_date_packed(MYSQL_TIME *ltime, longlong tmp) {
1940 TIME_from_longlong_datetime_packed(ltime, tmp);
1941 ltime->time_type = MYSQL_TIMESTAMP_DATE;
1942 }
1943
1944 /**
1945 On disk we store as unsigned number with DATETIMEF_INT_OFS offset,
1946 for HA_KETYPE_BINARY compatibilty purposes.
1947 */
1948 #define DATETIMEF_INT_OFS 0x8000000000LL
1949
1950 /**
1951 Convert on-disk datetime representation
1952 to in-memory packed numeric representation.
1953
1954 @param ptr The pointer to read value at.
1955 @param dec Precision.
1956 @return In-memory packed numeric datetime representation.
1957 */
my_datetime_packed_from_binary(const uchar * ptr,uint dec)1958 longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec) {
1959 longlong intpart = mi_uint5korr(ptr) - DATETIMEF_INT_OFS;
1960 int frac;
1961 assert(dec <= DATETIME_MAX_DECIMALS);
1962 switch (dec) {
1963 case 0:
1964 default:
1965 return my_packed_time_make_int(intpart);
1966 case 1:
1967 case 2:
1968 frac = (static_cast<int>(static_cast<signed char>(ptr[5]))) * 10000;
1969 break;
1970 case 3:
1971 case 4:
1972 frac = mi_sint2korr(ptr + 5) * 100;
1973 break;
1974 case 5:
1975 case 6:
1976 frac = mi_sint3korr(ptr + 5);
1977 break;
1978 }
1979 return my_packed_time_make(intpart, frac);
1980 }
1981
1982 /**
1983 Store in-memory numeric packed datetime representation to disk.
1984
1985 @param nr In-memory numeric packed datetime representation.
1986 @param [out] ptr The pointer to store at.
1987 @param dec Precision, 1-6.
1988 */
my_datetime_packed_to_binary(longlong nr,uchar * ptr,uint dec)1989 void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec) {
1990 assert(dec <= DATETIME_MAX_DECIMALS);
1991 /* The value being stored must have been properly rounded or truncated */
1992 assert((my_packed_time_get_frac_part(nr) %
1993 static_cast<int>(log_10_int[DATETIME_MAX_DECIMALS - dec])) == 0);
1994
1995 mi_int5store(ptr, my_packed_time_get_int_part(nr) + DATETIMEF_INT_OFS);
1996 switch (dec) {
1997 case 0:
1998 default:
1999 break;
2000 case 1:
2001 case 2:
2002 ptr[5] = static_cast<unsigned char>(
2003 static_cast<char>(my_packed_time_get_frac_part(nr) / 10000));
2004 break;
2005 case 3:
2006 case 4:
2007 mi_int2store(ptr + 5, my_packed_time_get_frac_part(nr) / 100);
2008 break;
2009 case 5:
2010 case 6:
2011 mi_int3store(ptr + 5, my_packed_time_get_frac_part(nr));
2012 }
2013 }
2014
2015 /*** TIMESTAMP low-level memory and disk representation routines ***/
2016
2017 /**
2018 Convert binary timestamp representation to in-memory representation.
2019
2020 @param [out] tm The variable to convert to.
2021 @param ptr The pointer to read the value from.
2022 @param dec Precision.
2023 */
my_timestamp_from_binary(struct timeval * tm,const uchar * ptr,uint dec)2024 void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec) {
2025 assert(dec <= DATETIME_MAX_DECIMALS);
2026 tm->tv_sec = mi_uint4korr(ptr);
2027 switch (dec) {
2028 case 0:
2029 default:
2030 tm->tv_usec = 0;
2031 break;
2032 case 1:
2033 case 2:
2034 tm->tv_usec = (static_cast<int>(ptr[4])) * 10000;
2035 break;
2036 case 3:
2037 case 4:
2038 tm->tv_usec = mi_sint2korr(ptr + 4) * 100;
2039 break;
2040 case 5:
2041 case 6:
2042 tm->tv_usec = mi_sint3korr(ptr + 4);
2043 }
2044 }
2045
2046 /**
2047 Convert in-memory timestamp representation to on-disk representation.
2048
2049 @param tm The value to convert.
2050 @param [out] ptr The pointer to store the value to.
2051 @param dec Precision.
2052 */
my_timestamp_to_binary(const struct timeval * tm,uchar * ptr,uint dec)2053 void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec) {
2054 assert(dec <= DATETIME_MAX_DECIMALS);
2055 /* Stored value must have been previously properly rounded or truncated */
2056 assert((tm->tv_usec %
2057 static_cast<int>(log_10_int[DATETIME_MAX_DECIMALS - dec])) == 0);
2058 mi_int4store(ptr, tm->tv_sec);
2059 switch (dec) {
2060 case 0:
2061 default:
2062 break;
2063 case 1:
2064 case 2:
2065 ptr[4] =
2066 static_cast<unsigned char>(static_cast<char>(tm->tv_usec / 10000));
2067 break;
2068 case 3:
2069 case 4:
2070 mi_int2store(ptr + 4, tm->tv_usec / 100);
2071 break;
2072 /* Impossible second precision. Fall through */
2073 case 5:
2074 case 6:
2075 mi_int3store(ptr + 4, tm->tv_usec);
2076 }
2077 }
2078 /**
2079 Convert in-memory date representation to on-disk representation.
2080
2081 @param ltime The value to convert.
2082 @param [out] ptr The pointer to store the value to.
2083 */
my_date_to_binary(const MYSQL_TIME * ltime,uchar * ptr)2084 void my_date_to_binary(const MYSQL_TIME *ltime, uchar *ptr) {
2085 long tmp = ltime->day + ltime->month * 32 + ltime->year * 16 * 32;
2086 int3store(ptr, tmp);
2087 }
2088
2089 /**
2090 Convert a temporal value to packed numeric temporal representation,
2091 depending on its time_type.
2092
2093 @param my_time The value to convert.
2094 @return Packed numeric time/date/datetime representation.
2095 */
TIME_to_longlong_packed(const MYSQL_TIME & my_time)2096 longlong TIME_to_longlong_packed(const MYSQL_TIME &my_time) {
2097 switch (my_time.time_type) {
2098 case MYSQL_TIMESTAMP_DATE:
2099 return TIME_to_longlong_date_packed(my_time);
2100 case MYSQL_TIMESTAMP_DATETIME_TZ:
2101 assert(false); // Should not be this type at this point.
2102 case MYSQL_TIMESTAMP_DATETIME:
2103 return TIME_to_longlong_datetime_packed(my_time);
2104 case MYSQL_TIMESTAMP_TIME:
2105 return TIME_to_longlong_time_packed(my_time);
2106 case MYSQL_TIMESTAMP_NONE:
2107 case MYSQL_TIMESTAMP_ERROR:
2108 return 0;
2109 }
2110 assert(false);
2111 return 0;
2112 }
2113
2114 /**
2115 Change a daynr to year, month and day. Daynr 0 is returned as date
2116 00.00.00
2117 */
get_date_from_daynr(int64_t daynr,uint * ret_year,uint * ret_month,uint * ret_day)2118 void get_date_from_daynr(int64_t daynr, uint *ret_year, uint *ret_month,
2119 uint *ret_day) {
2120 uint year;
2121 uint temp;
2122 uint leap_day;
2123 uint day_of_year;
2124 uint days_in_year;
2125 const uchar *month_pos;
2126
2127 if (daynr <= 365L || daynr >= 3652500) { /* Fix if wrong daynr */
2128 *ret_year = *ret_month = *ret_day = 0;
2129 } else {
2130 year = static_cast<uint>(daynr * 100 / 36525L);
2131 temp = (((year - 1) / 100 + 1) * 3) / 4;
2132 day_of_year = static_cast<uint>(daynr - static_cast<long>(year) * 365L) -
2133 (year - 1) / 4 + temp;
2134 while (day_of_year > (days_in_year = calc_days_in_year(year))) {
2135 day_of_year -= days_in_year;
2136 (year)++;
2137 }
2138 leap_day = 0;
2139 if (days_in_year == 366) {
2140 if (day_of_year > 31 + 28) {
2141 day_of_year--;
2142 if (day_of_year == 31 + 28) leap_day = 1; /* Handle leapyears leapday */
2143 }
2144 }
2145 *ret_month = 1;
2146 for (month_pos = days_in_month; day_of_year > static_cast<uint>(*month_pos);
2147 day_of_year -= *(month_pos++), (*ret_month)++)
2148 ;
2149 *ret_year = year;
2150 *ret_day = day_of_year + leap_day;
2151 }
2152 }
2153
2154 /**
2155 Calc weekday from daynr.
2156
2157 @retval 0 for Monday
2158 @retval 6 for Sunday
2159 */
calc_weekday(long daynr,bool sunday_first_day_of_week)2160 int calc_weekday(long daynr, bool sunday_first_day_of_week) {
2161 return (static_cast<int>((daynr + 5L + (sunday_first_day_of_week ? 1L : 0L)) %
2162 7));
2163 }
2164
2165 /**
2166 Calculate the week number from a MYSQL_TIME value.
2167
2168 The bits in week_format has the following meaning:
2169 WEEK_MONDAY_FIRST (0) If not set Sunday is first day of week
2170 If set Monday is first day of week
2171 WEEK_YEAR (1) If not set Week is in range 0-53
2172
2173 Week 0 is returned for the the last week of the previous year (for
2174 a date at start of january) In this case one can get 53 for the
2175 first week of next year. This flag ensures that the week is
2176 relevant for the given year. Note that this flag is only
2177 releveant if WEEK_JANUARY is not set.
2178
2179 If set Week is in range 1-53.
2180
2181 In this case one may get week 53 for a date in January (when
2182 the week is that last week of previous year) and week 1 for a
2183 date in December.
2184
2185 WEEK_FIRST_WEEKDAY (2) If not set Weeks are numbered according
2186 to ISO 8601:1988
2187 If set The week that contains the first
2188 'first-day-of-week' is week 1.
2189
2190 ISO 8601:1988 means that if the week containing January 1 has
2191 four or more days in the new year, then it is week 1;
2192 Otherwise it is the last week of the previous year, and the
2193 next week is week 1.
2194
2195 @param my_time Source time value
2196 @param week_behaviour Parameter controlling how weeks are counted
2197 @param[out] year The year of the week number (which may be different
2198 from my_time.year as descibed above)
2199
2200 @return week number
2201 */
calc_week(const MYSQL_TIME & my_time,uint week_behaviour,uint * year)2202 uint calc_week(const MYSQL_TIME &my_time, uint week_behaviour, uint *year) {
2203 uint days;
2204 ulong daynr = calc_daynr(my_time.year, my_time.month, my_time.day);
2205 ulong first_daynr = calc_daynr(my_time.year, 1, 1);
2206 bool monday_first = (week_behaviour & WEEK_MONDAY_FIRST);
2207 bool week_year = (week_behaviour & WEEK_YEAR);
2208 bool first_weekday = (week_behaviour & WEEK_FIRST_WEEKDAY);
2209
2210 uint weekday = calc_weekday(first_daynr, !monday_first);
2211 *year = my_time.year;
2212
2213 if (my_time.month == 1 && my_time.day <= 7 - weekday) {
2214 if (!week_year &&
2215 ((first_weekday && weekday != 0) || (!first_weekday && weekday >= 4)))
2216 return 0;
2217 week_year = true;
2218 (*year)--;
2219 first_daynr -= (days = calc_days_in_year(*year));
2220 weekday = (weekday + 53 * 7 - days) % 7;
2221 }
2222
2223 if ((first_weekday && weekday != 0) || (!first_weekday && weekday >= 4))
2224 days = daynr - (first_daynr + (7 - weekday));
2225 else
2226 days = daynr - (first_daynr - weekday);
2227
2228 if (week_year && days >= 52 * 7) {
2229 weekday = (weekday + calc_days_in_year(*year)) % 7;
2230 if ((!first_weekday && weekday < 4) || (first_weekday && weekday == 0)) {
2231 (*year)++;
2232 return 1;
2233 }
2234 }
2235 return days / 7 + 1;
2236 }
2237
2238 /**
2239 Predicate for the validity of a period.
2240 */
valid_period(long long period)2241 bool valid_period(long long period) {
2242 if (period <= 0) return false;
2243 if ((period % 100) == 0) return false;
2244 if ((period % 100) > 12) return false;
2245 return true;
2246 }
2247
2248 /**
2249 Calculate month from period.
2250
2251 @return month
2252 */
convert_period_to_month(ulong period)2253 ulong convert_period_to_month(ulong period) {
2254 ulong a;
2255 ulong b;
2256 if (period == 0) return 0L;
2257 if ((a = period / 100) < YY_PART_YEAR)
2258 a += 2000;
2259 else if (a < 100)
2260 a += 1900;
2261 b = period % 100;
2262 return a * 12 + b - 1;
2263 }
2264
2265 /**
2266 Convert month to period.
2267
2268 @return period
2269 */
convert_month_to_period(ulong month)2270 ulong convert_month_to_period(ulong month) {
2271 ulong year;
2272 if (month == 0L) return 0L;
2273 if ((year = month / 12) < 100) {
2274 year += (year < YY_PART_YEAR) ? 2000 : 1900;
2275 }
2276 return year * 100 + month % 12 + 1;
2277 }
2278
2279 /**
2280 Add an interval to a MYSQL_TIME struct.
2281
2282 @retval true if error
2283 @retval false otherwise
2284 */
date_add_interval(MYSQL_TIME * ltime,interval_type int_type,Interval interval,int * warnings)2285 bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type,
2286 Interval interval, int *warnings) {
2287 ltime->neg = false;
2288
2289 long long sign = (interval.neg ? -1 : 1);
2290
2291 switch (int_type) {
2292 case INTERVAL_SECOND:
2293 case INTERVAL_SECOND_MICROSECOND:
2294 case INTERVAL_MICROSECOND:
2295 case INTERVAL_MINUTE:
2296 case INTERVAL_HOUR:
2297 case INTERVAL_MINUTE_MICROSECOND:
2298 case INTERVAL_MINUTE_SECOND:
2299 case INTERVAL_HOUR_MICROSECOND:
2300 case INTERVAL_HOUR_SECOND:
2301 case INTERVAL_HOUR_MINUTE:
2302 case INTERVAL_DAY_MICROSECOND:
2303 case INTERVAL_DAY_SECOND:
2304 case INTERVAL_DAY_MINUTE:
2305 case INTERVAL_DAY_HOUR: {
2306 longlong sec, days, daynr, microseconds, extra_sec;
2307 ltime->time_type = MYSQL_TIMESTAMP_DATETIME; // Return full date
2308 microseconds = ltime->second_part + sign * interval.second_part;
2309 extra_sec = microseconds / 1000000L;
2310 microseconds = microseconds % 1000000L;
2311
2312 if (interval.day > MAX_DAY_NUMBER) goto invalid_date;
2313 if (interval.hour > MAX_DAY_NUMBER * 24ULL) goto invalid_date;
2314 if (interval.minute > MAX_DAY_NUMBER * 24ULL * 60ULL) goto invalid_date;
2315 if (interval.second > MAX_DAY_NUMBER * 24ULL * 60ULL * 60ULL)
2316 goto invalid_date;
2317 sec =
2318 ((ltime->day - 1) * 3600LL * 24LL + ltime->hour * 3600LL +
2319 ltime->minute * 60LL + ltime->second +
2320 sign * static_cast<longlong>(
2321 interval.day * 3600ULL * 24ULL + interval.hour * 3600ULL +
2322 interval.minute * 60ULL + interval.second)) +
2323 extra_sec;
2324 if (microseconds < 0) {
2325 microseconds += 1000000LL;
2326 sec--;
2327 }
2328 days = sec / (3600 * 24LL);
2329 sec -= days * 3600 * 24LL;
2330 if (sec < 0) {
2331 days--;
2332 sec += 3600 * 24LL;
2333 }
2334 ltime->second_part = static_cast<uint>(microseconds);
2335 ltime->second = static_cast<uint>(sec % 60);
2336 ltime->minute = static_cast<uint>(sec / 60 % 60);
2337 ltime->hour = static_cast<uint>(sec / 3600);
2338 daynr = calc_daynr(ltime->year, ltime->month, 1) + days;
2339 /* Day number from year 0 to 9999-12-31 */
2340 if (daynr < 0 || daynr > MAX_DAY_NUMBER) goto invalid_date;
2341 get_date_from_daynr(daynr, <ime->year, <ime->month, <ime->day);
2342 break;
2343 }
2344 case INTERVAL_DAY:
2345 case INTERVAL_WEEK: {
2346 unsigned long period;
2347 period = calc_daynr(ltime->year, ltime->month, ltime->day);
2348 if (interval.neg) {
2349 if (period < interval.day) // Before 0.
2350 goto invalid_date;
2351 period -= interval.day;
2352 } else {
2353 if (period + interval.day < period) // Overflow.
2354 goto invalid_date;
2355 if (period + interval.day > MAX_DAY_NUMBER) // After 9999-12-31.
2356 goto invalid_date;
2357 period += interval.day;
2358 }
2359 get_date_from_daynr(period, <ime->year, <ime->month, <ime->day);
2360 } break;
2361 case INTERVAL_YEAR:
2362 if (interval.year > 10000UL) goto invalid_date;
2363 ltime->year += sign * static_cast<long>(interval.year);
2364 if (static_cast<ulong>(ltime->year) >= 10000L) goto invalid_date;
2365 if (ltime->month == 2 && ltime->day == 29 &&
2366 calc_days_in_year(ltime->year) != 366)
2367 ltime->day = 28; // Was leap-year
2368 break;
2369 case INTERVAL_YEAR_MONTH:
2370 case INTERVAL_QUARTER:
2371 case INTERVAL_MONTH: {
2372 unsigned long long period;
2373
2374 // Simple guards against arithmetic overflow when calculating period.
2375 if (interval.month >= UINT_MAX / 2) goto invalid_date;
2376 if (interval.year >= UINT_MAX / 12) goto invalid_date;
2377
2378 period = (ltime->year * 12ULL +
2379 sign * static_cast<unsigned long long>(interval.year) * 12ULL +
2380 ltime->month - 1ULL +
2381 sign * static_cast<unsigned long long>(interval.month));
2382 if (period >= 120000LL) goto invalid_date;
2383 ltime->year = period / 12;
2384 ltime->month = (period % 12L) + 1;
2385 /* Adjust day if the new month doesn't have enough days */
2386 if (ltime->day > days_in_month[ltime->month - 1]) {
2387 ltime->day = days_in_month[ltime->month - 1];
2388 if (ltime->month == 2 && calc_days_in_year(ltime->year) == 366)
2389 ltime->day++; // Leap-year
2390 }
2391 } break;
2392 default:
2393 fprintf(stderr, "Unexpected interval type: %u\n",
2394 static_cast<unsigned int>(int_type));
2395 assert(false);
2396 goto null_date;
2397 }
2398
2399 return false; // Ok
2400
2401 invalid_date:
2402 if (warnings) {
2403 *warnings |= MYSQL_TIME_WARN_DATETIME_OVERFLOW;
2404 }
2405
2406 null_date:
2407
2408 return true;
2409 }
2410
2411 /**
2412 Add nanoseconds to a time value with truncation.
2413
2414 @param [in,out] ltime MYSQL_TIME variable to add to.
2415 @param nanoseconds Nanoseconds value.
2416 @param [in,out] warnings Warning flag vector.
2417 @retval False on success. No real failure case here.
2418 */
time_add_nanoseconds_with_truncate(MYSQL_TIME * ltime,uint nanoseconds,int * warnings)2419 bool time_add_nanoseconds_with_truncate(MYSQL_TIME *ltime, uint nanoseconds,
2420 int *warnings) {
2421 /*
2422 If second_part is not set then only add nanoseconds to it.
2423 If second_part is already set and then nanoseconds just represent
2424 additional numbers which help rounding, so we can ignore them.
2425 */
2426 if (ltime->second_part == 0) ltime->second_part = nanoseconds / 1000;
2427
2428 adjust_time_range(ltime, warnings);
2429 return false;
2430 }
2431
2432 /**
2433 Add nanoseconds to a datetime value with truncation.
2434
2435 @param [in,out] ltime MYSQL_TIME variable to add to.
2436 @param nanoseconds Nanoseconds value.
2437 @retval False on success. No real failure case here.
2438 */
datetime_add_nanoseconds_with_truncate(MYSQL_TIME * ltime,uint nanoseconds)2439 bool datetime_add_nanoseconds_with_truncate(MYSQL_TIME *ltime,
2440 uint nanoseconds) {
2441 /*
2442 If second_part is not set then only add nanoseconds to it.
2443 If second_part is already set and then nanoseconds just represent
2444 additional numbers which help rounding, so we can ignore them.
2445 */
2446 if (ltime->second_part == 0) ltime->second_part = nanoseconds / 1000;
2447 return false;
2448 }
2449
2450 /**
2451 Add nanoseconds to a time value with rounding.
2452
2453 @param [in,out] ltime MYSQL_TIME variable to add to.
2454 @param nanoseconds Nanoseconds value.
2455 @param [in,out] warnings Warning flag vector.
2456 @retval False on success, true on error.
2457 */
time_add_nanoseconds_with_round(MYSQL_TIME * ltime,uint nanoseconds,int * warnings)2458 bool time_add_nanoseconds_with_round(MYSQL_TIME *ltime, uint nanoseconds,
2459 int *warnings) {
2460 /* We expect correct input data */
2461 assert(nanoseconds < 1000000000);
2462 assert(!check_time_mmssff_range(*ltime));
2463
2464 if (nanoseconds < 500) return false;
2465
2466 ltime->second_part += (nanoseconds + 500) / 1000;
2467 if (ltime->second_part < 1000000) goto ret;
2468
2469 ltime->second_part %= 1000000;
2470 if (ltime->second < 59) {
2471 ltime->second++;
2472 goto ret;
2473 }
2474
2475 ltime->second = 0;
2476 if (ltime->minute < 59) {
2477 ltime->minute++;
2478 goto ret;
2479 }
2480 ltime->minute = 0;
2481 ltime->hour++;
2482
2483 ret:
2484 /*
2485 We can get '838:59:59.000001' at this point, which
2486 is bigger than the maximum possible value '838:59:59.000000'.
2487 Checking only "hour > 838" is not enough.
2488 Do full adjust_time_range().
2489 */
2490 adjust_time_range(ltime, warnings);
2491 return false;
2492 }
2493
2494 /**
2495 Add nanoseconds to a datetime value with rounding.
2496
2497 @param [in,out] ltime MYSQL_TIME variable to add to.
2498 @param nanoseconds Nanoseconds value.
2499 @param [in,out] warnings Warning flag vector.
2500 @retval False on success, true on error.
2501 */
datetime_add_nanoseconds_with_round(MYSQL_TIME * ltime,uint nanoseconds,int * warnings)2502 bool datetime_add_nanoseconds_with_round(MYSQL_TIME *ltime, uint nanoseconds,
2503 int *warnings) {
2504 assert(nanoseconds < 1000000000);
2505 if (nanoseconds < 500) return false;
2506
2507 ltime->second_part += (nanoseconds + 500) / 1000;
2508 if (ltime->second_part < 1000000) return false;
2509
2510 ltime->second_part %= 1000000;
2511 Interval interval;
2512 memset(&interval, 0, sizeof(interval));
2513 interval.second = 1;
2514 /* date_add_interval cannot handle bad dates */
2515 if (check_date(*ltime, non_zero_date(*ltime),
2516 (TIME_NO_ZERO_IN_DATE | TIME_NO_ZERO_DATE), warnings))
2517 return true;
2518
2519 if (date_add_interval(ltime, INTERVAL_SECOND, interval, warnings)) {
2520 *warnings |= MYSQL_TIME_WARN_OUT_OF_RANGE;
2521 return true;
2522 }
2523 return false;
2524 }
2525
2526 /**
2527 Add nanoseconds to time and round or tuncate as indicated by argument.
2528
2529 @param [in,out] ltime MYSQL_TIME variable to add to.
2530 @param nanoseconds Nanosecons value.
2531 @param [in,out] warnings Warning flag vector.
2532 @param truncate Decides whether fractional part of seconds will
2533 be truncated/rounded.
2534 @retval False on success. No real failure case here.
2535 */
time_add_nanoseconds_adjust_frac(MYSQL_TIME * ltime,uint nanoseconds,int * warnings,bool truncate)2536 bool time_add_nanoseconds_adjust_frac(MYSQL_TIME *ltime, uint nanoseconds,
2537 int *warnings, bool truncate) {
2538 if (truncate)
2539 return time_add_nanoseconds_with_truncate(ltime, nanoseconds, warnings);
2540 else
2541 return time_add_nanoseconds_with_round(ltime, nanoseconds, warnings);
2542 }
2543
2544 /**
2545 Add nanoseconds to datetime and round or tuncate as indicated by argument.
2546
2547 @param [in,out] ltime MYSQL_TIME variable to add to.
2548 @param nanoseconds Nanoseconds value.
2549 @param [in,out] warnings Warning flag vector.
2550 @param truncate Decides whether fractional part of seconds will
2551 be truncated/rounded.
2552 @retval False on success, true on error.
2553 */
datetime_add_nanoseconds_adjust_frac(MYSQL_TIME * ltime,uint nanoseconds,int * warnings,bool truncate)2554 bool datetime_add_nanoseconds_adjust_frac(MYSQL_TIME *ltime, uint nanoseconds,
2555 int *warnings, bool truncate) {
2556 if (truncate)
2557 return datetime_add_nanoseconds_with_truncate(ltime, nanoseconds);
2558 else
2559 return datetime_add_nanoseconds_with_round(ltime, nanoseconds, warnings);
2560 }
2561
2562 /** Rounding functions */
2563 static constexpr const uint msec_round_add[7] = {
2564 500000000, 50000000, 5000000, 500000, 50000, 5000, 0};
2565
2566 /**
2567 Round/Truncate time value to the given precision.
2568
2569 @param [in,out] ltime The value to round.
2570 @param dec Precision.
2571 @param truncate Decides whether fractional part of seconds will be
2572 truncated/rounded.
2573 @return False on success, true on error.
2574 */
my_time_adjust_frac(MYSQL_TIME * ltime,uint dec,bool truncate)2575 bool my_time_adjust_frac(MYSQL_TIME *ltime, uint dec, bool truncate) {
2576 int warnings = 0;
2577 assert(dec <= DATETIME_MAX_DECIMALS);
2578 /* Add half away from zero */
2579 bool rc = time_add_nanoseconds_adjust_frac(ltime, msec_round_add[dec],
2580 &warnings, truncate);
2581
2582 /* Truncate non-significant digits */
2583 my_time_trunc(ltime, dec);
2584 return rc;
2585 }
2586
2587 /**
2588 Round/Truncate datetime value to the given precision.
2589
2590 @param [in,out] ltime The value to round.
2591 @param dec Precision.
2592 @param [in,out] warnings Warning flag vector
2593 @param truncate Decides whether fractional part of seconds will be
2594 truncated/rounded.
2595 @return False on success, true on error.
2596 */
my_datetime_adjust_frac(MYSQL_TIME * ltime,uint dec,int * warnings,bool truncate)2597 bool my_datetime_adjust_frac(MYSQL_TIME *ltime, uint dec, int *warnings,
2598 bool truncate) {
2599 assert(dec <= DATETIME_MAX_DECIMALS);
2600 /* Add half away from zero */
2601 bool rc = datetime_add_nanoseconds_adjust_frac(ltime, msec_round_add[dec],
2602 warnings, truncate);
2603 /* Truncate non-significant digits */
2604 my_time_trunc(ltime, dec);
2605 return rc;
2606 }
2607
2608 /**
2609 Round timeval value to the given precision.
2610
2611 @param [in,out] tv The value to round.
2612 @param decimals Precision.
2613 @return False on success, true on error.
2614 */
my_timeval_round(struct timeval * tv,uint decimals)2615 bool my_timeval_round(struct timeval *tv, uint decimals) {
2616 assert(decimals <= DATETIME_MAX_DECIMALS);
2617 uint nanoseconds = msec_round_add[decimals];
2618 tv->tv_usec += (nanoseconds + 500) / 1000;
2619 if (tv->tv_usec < 1000000) goto ret;
2620
2621 tv->tv_usec = 0;
2622 tv->tv_sec++;
2623 if (!is_time_t_valid_for_timestamp(tv->tv_sec)) {
2624 tv->tv_sec = TIMESTAMP_MAX_VALUE;
2625 return true;
2626 }
2627
2628 ret:
2629 my_timeval_trunc(tv, decimals);
2630 return false;
2631 }
2632
2633 /**
2634 Mix a date value and a time value.
2635
2636 @param [in,out] ldate Date value.
2637 @param my_time Time value.
2638 */
mix_date_and_time(MYSQL_TIME * ldate,const MYSQL_TIME & my_time)2639 void mix_date_and_time(MYSQL_TIME *ldate, const MYSQL_TIME &my_time) {
2640 assert(ldate->time_type == MYSQL_TIMESTAMP_DATE ||
2641 ldate->time_type == MYSQL_TIMESTAMP_DATETIME);
2642
2643 if (!my_time.neg && my_time.hour < 24) {
2644 /*
2645 Simple case: TIME is within normal 24 hours internal.
2646 Mix DATE part of ltime2 and TIME part of ltime together.
2647 */
2648 ldate->hour = my_time.hour;
2649 ldate->minute = my_time.minute;
2650 ldate->second = my_time.second;
2651 ldate->second_part = my_time.second_part;
2652 } else {
2653 /* Complex case: TIME is negative or outside of 24 hours internal. */
2654 longlong seconds;
2655 long days, useconds;
2656 int sign = my_time.neg ? 1 : -1;
2657 ldate->neg = calc_time_diff(*ldate, my_time, sign, &seconds, &useconds);
2658 assert(!ldate->neg);
2659
2660 /*
2661 We pass current date to mix_date_and_time. If we want to use
2662 this function with arbitrary dates, this code will need
2663 to cover cases when ltime is negative and "ldate < -ltime".
2664 */
2665 assert(ldate->year > 0);
2666
2667 days = static_cast<long>(seconds / SECONDS_IN_24H);
2668 calc_time_from_sec(ldate, seconds % SECONDS_IN_24H, useconds);
2669 get_date_from_daynr(days, &ldate->year, &ldate->month, &ldate->day);
2670 }
2671 ldate->time_type = MYSQL_TIMESTAMP_DATETIME;
2672 }
2673
2674 /**
2675 Converts a timepoint in a posix tm struct to a MYSQL_TIME struct.
2676
2677 @param [out] to store converted timepoint here
2678 @param from posix tm struct holding a valid timepoint
2679 */
localtime_to_TIME(MYSQL_TIME * to,const struct tm * from)2680 void localtime_to_TIME(MYSQL_TIME *to, const struct tm *from) {
2681 to->neg = false;
2682 to->second_part = 0;
2683 to->year = ((from->tm_year + 1900) % 10000);
2684 to->month = from->tm_mon + 1;
2685 to->day = from->tm_mday;
2686 to->hour = from->tm_hour;
2687 to->minute = from->tm_min;
2688 to->second = from->tm_sec;
2689 to->time_zone_displacement = 0;
2690 }
2691
2692 /**
2693 Initialize MYSQL_TIME with MYSQL_TIMESTAMP_TIME from given number
2694 of seconds and microseconds.
2695 */
calc_time_from_sec(MYSQL_TIME * to,longlong seconds,long microseconds)2696 void calc_time_from_sec(MYSQL_TIME *to, longlong seconds, long microseconds) {
2697 long t_seconds;
2698 // to->neg is not cleared, it may already be set to a useful value
2699 to->time_type = MYSQL_TIMESTAMP_TIME;
2700 to->year = 0;
2701 to->month = 0;
2702 to->day = 0;
2703 assert(seconds < (0xFFFFFFFFLL * 3600LL));
2704 to->hour = static_cast<long>(seconds / 3600L);
2705 t_seconds = static_cast<long>(seconds % 3600L);
2706 to->minute = t_seconds / 60L;
2707 to->second = t_seconds % 60L;
2708 to->second_part = microseconds;
2709 }
2710
2711 /**
2712 Calculate difference between two datetime values as seconds + microseconds.
2713
2714 @param my_time1 - TIME/DATE/DATETIME value
2715 @param my_time2 - TIME/DATE/DATETIME value
2716 @param l_sign - 1 absolute values are substracted, -1 absolute
2717 values are added.
2718 @param[out] seconds_out - where difference between my_time1 and my_time2
2719 in seconds is stored.
2720 @param[out] microseconds_out - where microsecond part of difference between
2721 my_time1 and my_time2 is stored.
2722
2723 @note This function calculates difference between my_time1 and
2724 my_time2 absolute values. So one should set l_sign and correct
2725 result if he want to take signs into account (i.e. for MYSQL_TIME
2726 values).
2727
2728 @returns Sign of difference.
2729 @retval 1 means negative result
2730 @retval 0 means positive result
2731 */
calc_time_diff(const MYSQL_TIME & my_time1,const MYSQL_TIME & my_time2,int l_sign,longlong * seconds_out,long * microseconds_out)2732 bool calc_time_diff(const MYSQL_TIME &my_time1, const MYSQL_TIME &my_time2,
2733 int l_sign, longlong *seconds_out, long *microseconds_out) {
2734 long days;
2735 bool neg;
2736 longlong microseconds;
2737
2738 /*
2739 We suppose that if first argument is MYSQL_TIMESTAMP_TIME
2740 the second argument should be TIMESTAMP_TIME also.
2741 We should check it before calc_time_diff call.
2742 */
2743 if (my_time1.time_type == MYSQL_TIMESTAMP_TIME) // Time value
2744 days = static_cast<long>(my_time1.day) -
2745 l_sign * static_cast<long>(my_time2.day);
2746 else {
2747 days = calc_daynr(static_cast<uint>(my_time1.year),
2748 static_cast<uint>(my_time1.month),
2749 static_cast<uint>(my_time1.day));
2750 if (my_time2.time_type == MYSQL_TIMESTAMP_TIME)
2751 days -= l_sign * static_cast<long>(my_time2.day);
2752 else
2753 days -= l_sign * calc_daynr(static_cast<uint>(my_time2.year),
2754 static_cast<uint>(my_time2.month),
2755 static_cast<uint>(my_time2.day));
2756 }
2757
2758 microseconds =
2759 (static_cast<longlong>(days) * SECONDS_IN_24H +
2760 static_cast<longlong>(my_time1.hour * 3600L + my_time1.minute * 60L +
2761 my_time1.second) -
2762 l_sign *
2763 static_cast<longlong>(my_time2.hour * 3600L + my_time2.minute * 60L +
2764 my_time2.second)) *
2765 1000000LL +
2766 static_cast<longlong>(my_time1.second_part) -
2767 l_sign * static_cast<longlong>(my_time2.second_part);
2768
2769 neg = false;
2770 if (microseconds < 0) {
2771 microseconds = -microseconds;
2772 neg = true;
2773 }
2774 *seconds_out = microseconds / 1000000L;
2775 *microseconds_out = static_cast<long>(microseconds % 1000000L);
2776 return neg;
2777 }
2778
2779 /**
2780 Compare tow MYSQL_TIME objects.
2781
2782 @retval 0 if a and b are equal
2783 @retval -1 if a comes before b
2784 @retval 1 if b comes before a
2785 */
my_time_compare(const MYSQL_TIME & my_time_a,const MYSQL_TIME & my_time_b)2786 int my_time_compare(const MYSQL_TIME &my_time_a, const MYSQL_TIME &my_time_b) {
2787 ulonglong a_t = TIME_to_ulonglong_datetime(my_time_a);
2788 ulonglong b_t = TIME_to_ulonglong_datetime(my_time_b);
2789
2790 if (a_t < b_t) return -1;
2791 if (a_t > b_t) return 1;
2792
2793 if (my_time_a.second_part < my_time_b.second_part) return -1;
2794 if (my_time_a.second_part > my_time_b.second_part) return 1;
2795
2796 return 0;
2797 }
2798
2799 /**
2800 Convert MYSQL_TIME value to its packed numeric representation,
2801 using field type.
2802
2803 @param my_time The time value to convert.
2804 @param type MySQL field type.
2805 @return Packed numeric representation.
2806 */
TIME_to_longlong_packed(const MYSQL_TIME & my_time,enum enum_field_types type)2807 longlong TIME_to_longlong_packed(const MYSQL_TIME &my_time,
2808 enum enum_field_types type) {
2809 switch (type) {
2810 case MYSQL_TYPE_TIME:
2811 return TIME_to_longlong_time_packed(my_time);
2812 case MYSQL_TYPE_DATETIME:
2813 case MYSQL_TYPE_TIMESTAMP:
2814 return TIME_to_longlong_datetime_packed(my_time);
2815 case MYSQL_TYPE_DATE:
2816 return TIME_to_longlong_date_packed(my_time);
2817 default:
2818 return TIME_to_longlong_packed(my_time);
2819 }
2820 }
2821
2822 /**
2823 Convert packed numeric temporal representation to time, date or datetime,
2824 using field type.
2825
2826 @param[out] ltime The variable to write to.
2827 @param type MySQL field type.
2828 @param packed_value Numeric datetype representation.
2829 */
TIME_from_longlong_packed(MYSQL_TIME * ltime,enum enum_field_types type,longlong packed_value)2830 void TIME_from_longlong_packed(MYSQL_TIME *ltime, enum enum_field_types type,
2831 longlong packed_value) {
2832 switch (type) {
2833 case MYSQL_TYPE_TIME:
2834 TIME_from_longlong_time_packed(ltime, packed_value);
2835 break;
2836 case MYSQL_TYPE_DATE:
2837 TIME_from_longlong_date_packed(ltime, packed_value);
2838 break;
2839 case MYSQL_TYPE_DATETIME:
2840 case MYSQL_TYPE_TIMESTAMP:
2841 TIME_from_longlong_datetime_packed(ltime, packed_value);
2842 break;
2843 default:
2844 assert(false);
2845 set_zero_time(ltime, MYSQL_TIMESTAMP_ERROR);
2846 break;
2847 }
2848 }
2849
2850 /**
2851 Convert packed numeric representation to
2852 unpacked numeric representation.
2853
2854 @param type MySQL field type.
2855 @param packed_value Packed numeric temporal value.
2856 @return Number in one of the following formats,
2857 depending on type: YYMMDD, YYMMDDhhmmss, hhmmss.
2858 */
longlong_from_datetime_packed(enum enum_field_types type,longlong packed_value)2859 longlong longlong_from_datetime_packed(enum enum_field_types type,
2860 longlong packed_value) {
2861 MYSQL_TIME ltime;
2862 switch (type) {
2863 case MYSQL_TYPE_TIME:
2864 TIME_from_longlong_time_packed(<ime, packed_value);
2865 return TIME_to_ulonglong_time(ltime);
2866 case MYSQL_TYPE_DATE:
2867 TIME_from_longlong_date_packed(<ime, packed_value);
2868 return TIME_to_ulonglong_date(ltime);
2869 case MYSQL_TYPE_DATETIME:
2870 case MYSQL_TYPE_TIMESTAMP:
2871 TIME_from_longlong_datetime_packed(<ime, packed_value);
2872 return TIME_to_ulonglong_datetime(ltime);
2873 default:
2874 assert(false);
2875 return 0;
2876 }
2877 }
2878
2879 /**
2880 Convert packed numeric temporal representation to unpacked numeric
2881 representation.
2882
2883 @param type MySQL field type.
2884 @param packed_value Numeric packed temporal representation.
2885 @return A double value in on of the following formats,
2886 depending on type:
2887 YYYYMMDD, hhmmss.ffffff or YYMMDDhhmmss.ffffff.
2888 */
double_from_datetime_packed(enum enum_field_types type,longlong packed_value)2889 double double_from_datetime_packed(enum enum_field_types type,
2890 longlong packed_value) {
2891 longlong result = longlong_from_datetime_packed(type, packed_value);
2892 return result +
2893 (static_cast<double>(my_packed_time_get_frac_part(packed_value))) /
2894 1000000;
2895 }
2896
2897 /**
2898 @} (end of defgroup MY_TIME)
2899 */
2900
2901 // Non-static driver functions for unit tests
2902 namespace mysys_my_time {
DRV_my_packed_time_get_int_part(longlong i)2903 longlong DRV_my_packed_time_get_int_part(longlong i) {
2904 return my_packed_time_get_int_part(i);
2905 }
DRV_my_packed_time_make(longlong i,longlong f)2906 longlong DRV_my_packed_time_make(longlong i, longlong f) {
2907 return my_packed_time_make(i, f);
2908 }
DRV_my_packed_time_make_int(longlong i)2909 longlong DRV_my_packed_time_make_int(longlong i) {
2910 return my_packed_time_make_int(i);
2911 }
2912 } // namespace mysys_my_time
2913