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