1
/* Copyright (C) 2004-2006 MySQL AB
3
This program is free software; you can redistribute it and/or modify
4
it under the terms of the GNU General Public License as published by
5
the Free Software Foundation; version 2 of the License.
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
12
You should have received a copy of the GNU General Public License
13
along with this program; if not, write to the Free Software
14
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
19
/* Windows version of localtime_r() is declared in my_ptrhead.h */
20
#include <my_pthread.h>
22
ulonglong log_10_int[20]=
24
1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL,
25
ULL(100000000), ULL(1000000000), ULL(10000000000), ULL(100000000000),
26
ULL(1000000000000), ULL(10000000000000), ULL(100000000000000),
27
ULL(1000000000000000), ULL(10000000000000000), ULL(100000000000000000),
28
ULL(1000000000000000000), ULL(10000000000000000000)
32
/* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */
34
static uchar internal_format_positions[]=
35
{0, 1, 2, 3, 4, 5, 6, (uchar) 255};
37
static char time_separator=':';
39
static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */
40
uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
43
Offset of system time zone from UTC in seconds used to speed up
44
work of my_system_gmt_sec() function.
46
static long my_time_zone=0;
49
/* Calc days in one year. works with 0 <= year <= 99 */
51
uint calc_days_in_year(uint year)
53
return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
58
@brief Check datetime value for validity according to flags.
60
@param[in] ltime Date to check.
61
@param[in] not_zero_date ltime is not the zero date
62
@param[in] flags flags to check
63
(see str_to_datetime() flags in my_time.h)
64
@param[out] was_cut set to 2 if value was invalid according to flags.
65
(Feb 29 in non-leap etc.) This remains unchanged
66
if value is not invalid.
68
@details Here we assume that year and month is ok!
69
If month is 0 we allow any date. (This only happens if we allow zero
70
date parts in str_to_datetime())
71
Disallow dates with zero year and non-zero month and/or day.
78
my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
79
ulong flags, int *was_cut)
83
if ((((flags & TIME_NO_ZERO_IN_DATE) || !(flags & TIME_FUZZY_DATE)) &&
84
(ltime->month == 0 || ltime->day == 0)) ||
85
(!(flags & TIME_INVALID_DATES) &&
86
ltime->month && ltime->day > days_in_month[ltime->month-1] &&
87
(ltime->month != 2 || calc_days_in_year(ltime->year) != 366 ||
94
else if (flags & TIME_NO_ZERO_DATE)
97
We don't set *was_cut here to signal that the problem was a zero date
98
and not an invalid date
107
Convert a timestamp string to a MYSQL_TIME value.
112
length Length of string
113
l_time Date is stored here
114
flags Bitmap of following items
115
TIME_FUZZY_DATE Set if we should allow partial dates
116
TIME_DATETIME_ONLY Set if we only allow full datetimes.
117
TIME_NO_ZERO_IN_DATE Don't allow partial dates
118
TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
119
TIME_INVALID_DATES Allow 2000-02-31
121
1 If value was cut during conversion
122
2 check_date(date,flags) considers date invalid
125
At least the following formats are recogniced (based on number of digits)
126
YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
127
YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS
128
YYYYMMDDTHHMMSS where T is a the character T (ISO8601)
129
Also dates where all parts are zero are allowed
131
The second part may have an optional .###### fraction part.
134
This function should work with a format position vector as long as the
135
following things holds:
136
- All date are kept together and all time parts are kept together
137
- Date and time parts must be separated by blank
138
- Second fractions must come after second part and be separated
139
by a '.'. (The second fractions are optional)
140
- AM/PM must come after second fractions (or after seconds if no fractions)
141
- Year must always been specified.
142
- If time is before date, then we will use datetime format only if
143
the argument consist of two parts, separated by space.
144
Otherwise we will assume the argument is a date.
145
- The hour part must be specified in hour-minute-second order.
148
MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like
149
[DD [HH:[MM:[SS]]]].fraction.
150
l_time is not changed.
151
MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
152
MYSQL_TIMESTAMP_DATETIME Full timestamp
153
MYSQL_TIMESTAMP_ERROR Timestamp with wrong values.
154
All elements in l_time is set to 0
157
#define MAX_DATE_PARTS 8
159
enum enum_mysql_timestamp_type
160
str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
161
uint flags, int *was_cut)
163
uint field_length, UNINIT_VAR(year_length), digits, i, number_of_fields;
164
uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS];
165
uint add_hours= 0, start_loop;
166
ulong not_zero_date, allow_space;
167
my_bool is_internal_format;
168
const char *pos, *UNINIT_VAR(last_field_pos);
169
const char *end=str+length;
170
const uchar *format_position;
171
my_bool found_delimitier= 0, found_space= 0;
172
uint frac_pos, frac_len;
173
DBUG_ENTER("str_to_datetime");
174
DBUG_PRINT("ENTER",("str: %.*s",length,str));
176
LINT_INIT(field_length);
180
/* Skip space at start */
181
for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
183
if (str == end || ! my_isdigit(&my_charset_latin1, *str))
186
DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
189
is_internal_format= 0;
190
/* This has to be changed if want to activate different timestamp formats */
191
format_position= internal_format_positions;
194
Calculate number of digits in first part.
195
If length= 8 or >= 14 then year is of format YYYY.
196
(YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
199
pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T');
203
digits= (uint) (pos-str);
204
start_loop= 0; /* Start of scan loop */
205
date_len[format_position[0]]= 0; /* Length of year field */
206
if (pos == end || *pos == '.')
208
/* Found date in internal format (only numbers like YYYYMMDD) */
209
year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
210
field_length= year_length;
211
is_internal_format= 1;
212
format_position= internal_format_positions;
216
if (format_position[0] >= 3) /* If year is after HHMMDD */
219
If year is not in first part then we have to determinate if we got
220
a date field or a datetime field.
221
We do this by checking if there is two numbers separated by
224
while (pos < end && !my_isspace(&my_charset_latin1, *pos))
226
while (pos < end && !my_isdigit(&my_charset_latin1, *pos))
230
if (flags & TIME_DATETIME_ONLY)
233
DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a full datetime */
235
/* Date field. Set hour, minutes and seconds to 0 */
236
date[0]= date[1]= date[2]= date[3]= date[4]= 0;
237
start_loop= 5; /* Start with first date part */
241
field_length= format_position[0] == 0 ? 4 : 2;
245
Only allow space in the first "part" of the datetime field and:
246
- after days, part seconds
247
- before and after AM/PM (handled by code later)
249
2003-03-03 20:00:20 AM
250
20:00:20.000000 AM 03-03-2000
252
i= max((uint) format_position[0], (uint) format_position[1]);
253
set_if_bigger(i, (uint) format_position[2]);
254
allow_space= ((1 << i) | (1 << format_position[6]));
255
allow_space&= (1 | 2 | 4 | 8);
259
i < MAX_DATE_PARTS-1 && str != end &&
260
my_isdigit(&my_charset_latin1,*str);
263
const char *start= str;
264
ulong tmp_value= (uint) (uchar) (*str++ - '0');
267
Internal format means no delimiters; every field has a fixed
268
width. Otherwise, we scan until we find a delimiter and discard
269
leading zeroes -- except for the microsecond part, where leading
270
zeroes are significant, and where we never process more than six
273
my_bool scan_until_delim= !is_internal_format &&
274
((i != format_position[6]));
276
while (str != end && my_isdigit(&my_charset_latin1,str[0]) &&
277
(scan_until_delim || --field_length))
279
tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0');
282
date_len[i]= (uint) (str - start);
283
if (tmp_value > 999999) /* Impossible date part */
286
DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
289
not_zero_date|= tmp_value;
291
/* Length of next field */
292
field_length= format_position[i+1] == 0 ? 4 : 2;
294
if ((last_field_pos= str) == end)
296
i++; /* Register last found part */
299
/* Allow a 'T' after day to allow CCYYMMDDT type of fields */
300
if (i == format_position[2] && *str == 'T')
302
str++; /* ISO8601: CCYYMMDDThhmmss */
305
if (i == format_position[5]) /* Seconds */
307
if (*str == '.') /* Followed by part seconds */
310
field_length= 6; /* 6 digits */
315
(my_ispunct(&my_charset_latin1,*str) ||
316
my_isspace(&my_charset_latin1,*str)))
318
if (my_isspace(&my_charset_latin1,*str))
320
if (!(allow_space & (1 << i)))
323
DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
328
found_delimitier= 1; /* Should be a 'normal' date */
330
/* Check if next position is AM/PM */
331
if (i == format_position[6]) /* Seconds, time for AM/PM */
333
i++; /* Skip AM/PM part */
334
if (format_position[7] != 255) /* If using AM/PM */
336
if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
338
if (str[0] == 'p' || str[0] == 'P')
340
else if (str[0] != 'a' || str[0] != 'A')
341
continue; /* Not AM/PM */
342
str+= 2; /* Skip AM/PM */
343
/* Skip space after AM/PM */
344
while (str != end && my_isspace(&my_charset_latin1,*str))
351
if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
354
DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a datetime */
359
number_of_fields= i - start_loop;
360
while (i < MAX_DATE_PARTS)
366
if (!is_internal_format)
368
year_length= date_len[(uint) format_position[0]];
369
if (!year_length) /* Year must be specified */
372
DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
375
l_time->year= date[(uint) format_position[0]];
376
l_time->month= date[(uint) format_position[1]];
377
l_time->day= date[(uint) format_position[2]];
378
l_time->hour= date[(uint) format_position[3]];
379
l_time->minute= date[(uint) format_position[4]];
380
l_time->second= date[(uint) format_position[5]];
382
frac_pos= (uint) format_position[6];
383
frac_len= date_len[frac_pos];
385
date[frac_pos]*= (uint) log_10_int[6 - frac_len];
386
l_time->second_part= date[frac_pos];
388
if (format_position[7] != (uchar) 255)
390
if (l_time->hour > 12)
395
l_time->hour= l_time->hour%12 + add_hours;
400
l_time->year= date[0];
401
l_time->month= date[1];
402
l_time->day= date[2];
403
l_time->hour= date[3];
404
l_time->minute= date[4];
405
l_time->second= date[5];
407
date[6]*= (uint) log_10_int[6 - date_len[6]];
408
l_time->second_part=date[6];
412
if (year_length == 2 && not_zero_date)
413
l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
415
if (number_of_fields < 3 ||
416
l_time->year > 9999 || l_time->month > 12 ||
417
l_time->day > 31 || l_time->hour > 23 ||
418
l_time->minute > 59 || l_time->second > 59)
420
/* Only give warning for a zero date if there is some garbage after */
421
if (!not_zero_date) /* If zero date */
423
for (; str != end ; str++)
425
if (!my_isspace(&my_charset_latin1, *str))
427
not_zero_date= 1; /* Give warning */
432
*was_cut= test(not_zero_date);
436
if (check_date(l_time, not_zero_date != 0, flags, was_cut))
439
l_time->time_type= (number_of_fields <= 3 ?
440
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
442
for (; str != end ; str++)
444
if (!my_isspace(&my_charset_latin1,*str))
451
DBUG_RETURN(l_time->time_type);
454
bzero((char*) l_time, sizeof(*l_time));
455
DBUG_RETURN(MYSQL_TIMESTAMP_ERROR);
460
Convert a time string to a MYSQL_TIME struct.
464
str A string in full TIMESTAMP format or
465
[-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
467
There may be an optional [.second_part] after seconds
469
l_time Store result here
470
warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string
471
was cut during conversion, and/or
472
MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is
476
Because of the extra days argument, this function can only
477
work with times where the time arguments are in the above order.
484
my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
489
const char *end=str+length, *end_of_days;
490
my_bool found_days,found_hours;
495
for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
497
if (str != end && *str == '-')
506
/* Check first if this is a full TIMESTAMP */
508
{ /* Probably full timestamp */
510
enum enum_mysql_timestamp_type
511
res= str_to_datetime(str, length, l_time,
512
(TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut);
513
if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR)
516
*warning|= MYSQL_TIME_WARN_TRUNCATED;
517
return res == MYSQL_TIMESTAMP_ERROR;
521
/* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
522
for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
523
value=value*10L + (long) (*str - '0');
525
/* Skip all space after 'days' */
527
for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++)
531
found_days=found_hours=0;
532
if ((uint) (end-str) > 1 && str != end_of_days &&
533
my_isdigit(&my_charset_latin1, *str))
534
{ /* Found days part */
535
date[0]= (ulong) value;
536
state= 1; /* Assume next is hours */
539
else if ((end-str) > 1 && *str == time_separator &&
540
my_isdigit(&my_charset_latin1, str[1]))
542
date[0]= 0; /* Assume we found hours */
543
date[1]= (ulong) value;
546
str++; /* skip ':' */
550
/* String given as one number; assume HHMMSS format */
552
date[1]= (ulong) (value/10000);
553
date[2]= (ulong) (value/100 % 100);
554
date[3]= (ulong) (value % 100);
559
/* Read hours, minutes and seconds */
562
for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
563
value=value*10L + (long) (*str - '0');
564
date[state++]= (ulong) value;
565
if (state == 4 || (end-str) < 2 || *str != time_separator ||
566
!my_isdigit(&my_charset_latin1,str[1]))
568
str++; /* Skip time_separator (':') */
573
/* Fix the date to assume that seconds was given */
574
if (!found_hours && !found_days)
576
bmove_upp((uchar*) (date+4), (uchar*) (date+state),
577
sizeof(long)*(state-1));
578
bzero((uchar*) date, sizeof(long)*(4-state));
581
bzero((uchar*) (date+state), sizeof(long)*(4-state));
585
/* Get fractional second part */
586
if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1]))
589
str++; value=(uint) (uchar) (*str - '0');
590
while (++str != end && my_isdigit(&my_charset_latin1, *str))
592
if (field_length-- > 0)
593
value= value*10 + (uint) (uchar) (*str - '0');
595
if (field_length > 0)
596
value*= (long) log_10_int[field_length];
597
else if (field_length < 0)
598
*warning|= MYSQL_TIME_WARN_TRUNCATED;
599
date[4]= (ulong) value;
604
/* Check for exponent part: E<gigit> | E<sign><digit> */
605
/* (may occur as result of %g formatting of time value) */
606
if ((end - str) > 1 &&
607
(*str == 'e' || *str == 'E') &&
608
(my_isdigit(&my_charset_latin1, str[1]) ||
609
((str[1] == '-' || str[1] == '+') &&
611
my_isdigit(&my_charset_latin1, str[2]))))
614
if (internal_format_positions[7] != 255)
616
/* Read a possible AM/PM */
617
while (str != end && my_isspace(&my_charset_latin1, *str))
619
if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
621
if (str[0] == 'p' || str[0] == 'P')
624
date[1]= date[1]%12 + 12;
626
else if (str[0] == 'a' || str[0] == 'A')
631
/* Integer overflow checks */
632
if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
633
date[2] > UINT_MAX || date[3] > UINT_MAX ||
637
l_time->year= 0; /* For protocol::store_time */
639
l_time->day= date[0];
640
l_time->hour= date[1];
641
l_time->minute= date[2];
642
l_time->second= date[3];
643
l_time->second_part= date[4];
644
l_time->time_type= MYSQL_TIMESTAMP_TIME;
646
/* Check if the value is valid and fits into MYSQL_TIME range */
647
if (check_time_range(l_time, warning))
650
/* Check if there is garbage at end of the MYSQL_TIME specification */
655
if (!my_isspace(&my_charset_latin1,*str))
657
*warning|= MYSQL_TIME_WARN_TRUNCATED;
660
} while (++str != end);
667
Check 'time' value to lie in the MYSQL_TIME range
671
time pointer to MYSQL_TIME value
672
warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
675
If the time value lies outside of the range [-838:59:59, 838:59:59],
676
set it to the closest endpoint of the range and set
677
MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
680
0 time value is valid, but was possibly truncated
681
1 time value is invalid
684
int check_time_range(struct st_mysql_time *my_time, int *warning)
688
if (my_time->minute >= 60 || my_time->second >= 60)
691
hour= my_time->hour + (24*my_time->day);
692
if (hour <= TIME_MAX_HOUR &&
693
(hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE ||
694
my_time->second != TIME_MAX_SECOND || !my_time->second_part))
698
my_time->hour= TIME_MAX_HOUR;
699
my_time->minute= TIME_MAX_MINUTE;
700
my_time->second= TIME_MAX_SECOND;
701
my_time->second_part= 0;
702
*warning|= MYSQL_TIME_WARN_OUT_OF_RANGE;
708
Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
713
void my_init_time(void)
716
struct tm *l_time,tm_tmp;
720
seconds= (time_t) time((time_t*) 0);
721
localtime_r(&seconds,&tm_tmp);
723
my_time_zone= 3600; /* Comp. for -3600 in my_gmt_sec */
724
my_time.year= (uint) l_time->tm_year+1900;
725
my_time.month= (uint) l_time->tm_mon+1;
726
my_time.day= (uint) l_time->tm_mday;
727
my_time.hour= (uint) l_time->tm_hour;
728
my_time.minute= (uint) l_time->tm_min;
729
my_time.second= (uint) l_time->tm_sec;
730
my_system_gmt_sec(&my_time, &my_time_zone, ¬_used); /* Init my_time_zone */
735
Handle 2 digit year conversions
742
Year between 1970-2069
745
uint year_2000_handling(uint year)
747
if ((year=year+1900) < 1900+YY_PART_YEAR)
754
Calculate nr of day since year 0 in new date-system (from 1615)
758
year Year (exact 4 digit year, no year conversions)
762
NOTES: 0000-00-00 is a valid date, and will return 0
765
Days since 0000-00-00
768
long calc_daynr(uint year,uint month,uint day)
772
int y= year; /* may be < 0 temporarily */
773
DBUG_ENTER("calc_daynr");
775
if (y == 0 && month == 0 && day == 0)
776
DBUG_RETURN(0); /* Skip errors */
777
/* Cast to int to be able to handle month == 0 */
778
delsum= (long) (365 * y + 31 *((int) month - 1) + (int) day);
782
delsum-= (long) ((int) month * 4 + 23) / 10;
783
temp=(int) ((y/100+1)*3)/4;
784
DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld",
785
y+(month <= 2),month,day,delsum+y/4-temp));
786
DBUG_RETURN(delsum+(int) y/4-temp);
791
Convert time in MYSQL_TIME representation in system time zone to its
792
my_time_t form (number of seconds in UTC since begginning of Unix Epoch).
796
t - time value to be converted
797
my_timezone - pointer to long where offset of system time zone
798
from UTC will be stored for caching
799
in_dst_time_gap - set to true if time falls into spring time-gap
802
The idea is to cache the time zone offset from UTC (including daylight
803
saving time) for the next call to make things faster. But currently we
804
just calculate this offset during startup (by calling my_init_time()
805
function) and use it all the time.
806
Time value provided should be legal time value (e.g. '2003-01-01 25:00:00'
810
Time in UTC seconds since Unix Epoch representation.
813
my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone,
814
my_bool *in_dst_time_gap)
820
MYSQL_TIME *t= &tmp_time;
821
struct tm *l_time,tm_tmp;
822
long diff, current_timezone;
825
Use temp variable to avoid trashing input data, which could happen in
826
case of shift required for boundary dates processing.
828
memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME));
830
if (!validate_timestamp_range(t))
834
Calculate the gmt time based on current time and timezone
835
The -1 on the end is to ensure that if have a date that exists twice
836
(like 2002-10-27 02:00:0 MET), we will find the initial date.
838
By doing -3600 we will have to call localtime_r() several times, but
839
I couldn't come up with a better way to get a repeatable result :(
841
We can't use mktime() as it's buggy on many platforms and not thread safe.
843
Note: this code assumes that our time_t estimation is not too far away
844
from real value (we assume that localtime_r(tmp) will return something
845
within 24 hrs from t) which is probably true for all current time zones.
847
Note2: For the dates, which have time_t representation close to
848
MAX_INT32 (efficient time_t limit for supported platforms), we should
849
do a small trick to avoid overflow. That is, convert the date, which is
850
two days earlier, and then add these days to the final value.
852
The same trick is done for the values close to 0 in time_t
853
representation for platfroms with unsigned time_t (QNX).
855
To be more verbose, here is a sample (extracted from the code below):
856
(calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L
857
would return -2147480896 because of the long type overflow. In result
858
we would get 1901 year in localtime_r(), which is an obvious error.
860
Alike problem raises with the dates close to Epoch. E.g.
861
(calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L
864
On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600)
865
wil give us a date around 2106 year. Which is no good.
867
Theoreticaly, there could be problems with the latter conversion:
868
there are at least two timezones, which had time switches near 1 Jan
869
of 1970 (because of political reasons). These are America/Hermosillo and
870
America/Mazatlan time zones. They changed their offset on
871
1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones
872
the code below will give incorrect results for dates close to
873
1970-01-01, in the case OS takes into account these historical switches.
874
Luckily, it seems that we support only one platform with unsigned
875
time_t. It's QNX. And QNX does not support historical timezone data at all.
876
E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply
877
historical information for localtime_r() etc. That is, the problem is not
880
We are safe with shifts close to MAX_INT32, as there are no known
881
time switches on Jan 2038 yet :)
883
if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4))
886
Below we will pass (uint) (t->day - shift) to calc_daynr.
887
As we don't want to get an overflow here, we will shift
888
only safe dates. That's why we have (t->day > 4) above.
893
#ifdef TIME_T_UNSIGNED
897
We can get 0 in time_t representaion only on 1969, 31 of Dec or on
898
1970, 1 of Jan. For both dates we use shift, which is added
899
to t->day in order to step out a bit from the border.
900
This is required for platforms, where time_t is unsigned.
901
As far as I know, among the platforms we support it's only QNX.
902
Note: the order of below if-statements is significant.
905
if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1)
912
if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12)
923
tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) -
924
(long) days_at_timestart)*86400L + (long) t->hour*3600L +
925
(long) (t->minute*60 + t->second)) + (time_t) my_time_zone -
928
current_timezone= my_time_zone;
929
localtime_r(&tmp,&tm_tmp);
933
(t->hour != (uint) l_time->tm_hour ||
934
t->minute != (uint) l_time->tm_min ||
935
t->second != (uint) l_time->tm_sec);
937
{ /* One check should be enough ? */
938
/* Get difference in days */
939
int days= t->day - l_time->tm_mday;
941
days= 1; /* Month has wrapped */
944
diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) +
945
(long) (60*((int) t->minute - (int) l_time->tm_min)) +
946
(long) ((int) t->second - (int) l_time->tm_sec));
947
current_timezone+= diff+3600; /* Compensate for -3600 above */
949
localtime_r(&tmp,&tm_tmp);
953
Fix that if we are in the non existing daylight saving time hour
954
we move the start of the next real hour.
956
This code doesn't handle such exotical thing as time-gaps whose length
957
is more than one hour or non-integer (latter can theoretically happen
958
if one of seconds will be removed due leap correction, or because of
959
general time correction like it happened for Africa/Monrovia time zone
962
if (loop == 2 && t->hour != (uint) l_time->tm_hour)
964
int days= t->day - l_time->tm_mday;
966
days=1; /* Month has wrapped */
969
diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+
970
(long) (60*((int) t->minute - (int) l_time->tm_min)) +
971
(long) ((int) t->second - (int) l_time->tm_sec));
973
tmp+=3600 - t->minute*60 - t->second; /* Move to next hour */
974
else if (diff == -3600)
975
tmp-=t->minute*60 + t->second; /* Move to previous hour */
979
*my_timezone= current_timezone;
982
/* shift back, if we were dealing with boundary dates */
986
This is possible for dates, which slightly exceed boundaries.
987
Conversion will pass ok for them, but we don't allow them.
988
First check will pass for platforms with signed time_t.
989
instruction above (tmp+= shift*86400L) could exceed
990
MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen.
991
So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms
992
with unsigned time_t tmp+= shift*86400L might result in a number,
993
larger then TIMESTAMP_MAX_VALUE, so another check will work.
995
if ((tmp < TIMESTAMP_MIN_VALUE) || (tmp > TIMESTAMP_MAX_VALUE))
998
return (my_time_t) tmp;
999
} /* my_system_gmt_sec */
1002
/* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */
1004
void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
1006
bzero((void*) tm, sizeof(*tm));
1007
tm->time_type= time_type;
1012
Functions to convert time/date/datetime value to a string,
1013
using default format.
1014
This functions don't check that given MYSQL_TIME structure members are
1015
in valid range. If they are not, return value won't reflect any
1016
valid date either. Additionally, make_time doesn't take into
1017
account time->day member: it's assumed that days have been converted
1021
number of characters written to 'to'
1024
int my_time_to_str(const MYSQL_TIME *l_time, char *to)
1026
uint extra_hours= 0;
1027
return my_sprintf(to, (to, "%s%02u:%02u:%02u",
1028
(l_time->neg ? "-" : ""),
1029
extra_hours+ l_time->hour,
1034
int my_date_to_str(const MYSQL_TIME *l_time, char *to)
1036
return my_sprintf(to, (to, "%04u-%02u-%02u",
1042
int my_datetime_to_str(const MYSQL_TIME *l_time, char *to)
1044
return my_sprintf(to, (to, "%04u-%02u-%02u %02u:%02u:%02u",
1055
Convert struct DATE/TIME/DATETIME value to string using built-in
1056
MySQL time conversion formats.
1062
The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved.
1065
int my_TIME_to_str(const MYSQL_TIME *l_time, char *to)
1067
switch (l_time->time_type) {
1068
case MYSQL_TIMESTAMP_DATETIME:
1069
return my_datetime_to_str(l_time, to);
1070
case MYSQL_TIMESTAMP_DATE:
1071
return my_date_to_str(l_time, to);
1072
case MYSQL_TIMESTAMP_TIME:
1073
return my_time_to_str(l_time, to);
1074
case MYSQL_TIMESTAMP_NONE:
1075
case MYSQL_TIMESTAMP_ERROR:
1086
Convert datetime value specified as number to broken-down TIME
1087
representation and form value of DATETIME type as side-effect.
1090
number_to_datetime()
1091
nr - datetime value as number
1092
time_res - pointer for structure for broken-down representation
1093
flags - flags to use in validating date, as in str_to_datetime()
1095
1 If value was cut during conversion
1096
2 check_date(date,flags) considers date invalid
1099
Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
1100
YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in
1101
YYYYMMDDHHMMSS format as side-effect.
1103
This function also checks if datetime value fits in DATETIME range.
1106
-1 Timestamp with wrong values
1107
anything else DATETIME as integer in YYYYMMDDHHMMSS format
1108
Datetime value in YYYYMMDDHHMMSS format.
1111
longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
1112
uint flags, int *was_cut)
1117
bzero((char*) time_res, sizeof(*time_res));
1118
time_res->time_type=MYSQL_TIMESTAMP_DATE;
1120
if (nr == LL(0) || nr >= LL(10000101000000))
1122
time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1127
if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
1129
nr= (nr+20000000L)*1000000L; /* YYMMDD, year: 2000-2069 */
1132
if (nr < (YY_PART_YEAR)*10000L+101L)
1136
nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
1141
if (nr <= 99991231L)
1146
if (nr < 101000000L)
1149
time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1151
if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
1153
nr= nr+LL(20000000000000); /* YYMMDDHHMMSS, 2000-2069 */
1156
if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
1158
if (nr <= LL(991231235959))
1159
nr= nr+LL(19000000000000); /* YYMMDDHHMMSS, 1970-1999 */
1162
part1=(long) (nr/LL(1000000));
1163
part2=(long) (nr - (longlong) part1*LL(1000000));
1164
time_res->year= (int) (part1/10000L); part1%=10000L;
1165
time_res->month= (int) part1 / 100;
1166
time_res->day= (int) part1 % 100;
1167
time_res->hour= (int) (part2/10000L); part2%=10000L;
1168
time_res->minute=(int) part2 / 100;
1169
time_res->second=(int) part2 % 100;
1171
if (time_res->year <= 9999 && time_res->month <= 12 &&
1172
time_res->day <= 31 && time_res->hour <= 23 &&
1173
time_res->minute <= 59 && time_res->second <= 59 &&
1174
!check_date(time_res, (nr != 0), flags, was_cut))
1177
/* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
1178
if (!nr && (flags & TIME_NO_ZERO_DATE))
1187
/* Convert time value to integer in YYYYMMDDHHMMSS format */
1189
ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)
1191
return ((ulonglong) (my_time->year * 10000UL +
1192
my_time->month * 100UL +
1193
my_time->day) * ULL(1000000) +
1194
(ulonglong) (my_time->hour * 10000UL +
1195
my_time->minute * 100UL +
1200
/* Convert MYSQL_TIME value to integer in YYYYMMDD format */
1202
ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *my_time)
1204
return (ulonglong) (my_time->year * 10000UL + my_time->month * 100UL +
1210
Convert MYSQL_TIME value to integer in HHMMSS format.
1211
This function doesn't take into account time->day member:
1212
it's assumed that days have been converted to hours already.
1215
ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *my_time)
1217
return (ulonglong) (my_time->hour * 10000UL +
1218
my_time->minute * 100UL +
1224
Convert struct MYSQL_TIME (date and time split into year/month/day/hour/...
1225
to a number in format YYYYMMDDHHMMSS (DATETIME),
1226
YYYYMMDD (DATE) or HHMMSS (TIME).
1232
The function is used when we need to convert value of time item
1233
to a number if it's used in numeric context, i. e.:
1234
SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
1238
This function doesn't check that given MYSQL_TIME structure members are
1239
in valid range. If they are not, return value won't reflect any
1243
ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time)
1245
switch (my_time->time_type) {
1246
case MYSQL_TIMESTAMP_DATETIME:
1247
return TIME_to_ulonglong_datetime(my_time);
1248
case MYSQL_TIMESTAMP_DATE:
1249
return TIME_to_ulonglong_date(my_time);
1250
case MYSQL_TIMESTAMP_TIME:
1251
return TIME_to_ulonglong_time(my_time);
1252
case MYSQL_TIMESTAMP_NONE:
1253
case MYSQL_TIMESTAMP_ERROR: