1 /*
2 Copyright (c) 2004, 2010, Oracle and/or its affiliates.
3
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.
7
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.
12
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-1335 USA */
16
17 /*
18 Most of the following code and structures were derived from
19 public domain code from ftp://elsie.nci.nih.gov/pub
20 (We will refer to this code as to elsie-code further.)
21 */
22
23 /*
24 We should not include sql_priv.h in mysql_tzinfo_to_sql utility since
25 it creates unsolved link dependencies on some platforms.
26 */
27
28 #ifdef USE_PRAGMA_IMPLEMENTATION
29 #pragma implementation // gcc: Class implementation
30 #endif
31
32 #include "mariadb.h"
33 #if !defined(TZINFO2SQL) && !defined(TESTTIME)
34 #include "sql_priv.h"
35 #include "unireg.h"
36 #include "sql_time.h" // localtime_to_TIME
37 #include "sql_base.h" // open_system_tables_for_read,
38 // close_system_tables
39 #else
40 #include <my_time.h>
41 #include <my_sys.h>
42 #include <mysql_version.h>
43 #include <my_getopt.h>
44 #endif
45
46 #include "tztime.h"
47 #include "tzfile.h"
48 #include <m_string.h>
49 #include <my_dir.h>
50 #include <mysql/psi/mysql_file.h>
51 #include "lock.h" // MYSQL_LOCK_IGNORE_FLUSH,
52 // MYSQL_LOCK_IGNORE_TIMEOUT
53
54 /*
55 Now we don't use abbreviations in server but we will do this in future.
56 */
57 #if defined(TZINFO2SQL) || defined(TESTTIME)
58 #define ABBR_ARE_USED
59 #else
60 #if !defined(DBUG_OFF)
61 /* Let use abbreviations for debug purposes */
62 #undef ABBR_ARE_USED
63 #define ABBR_ARE_USED
64 #endif /* !defined(DBUG_OFF) */
65 #endif /* defined(TZINFO2SQL) || defined(TESTTIME) */
66
67 #define PROGRAM_VERSION "1.1"
68
69 /* Structure describing local time type (e.g. Moscow summer time (MSD)) */
70 typedef struct ttinfo
71 {
72 long tt_gmtoff; // Offset from UTC in seconds
73 uint tt_isdst; // Is daylight saving time or not. Used to set tm_isdst
74 #ifdef ABBR_ARE_USED
75 uint tt_abbrind; // Index of start of abbreviation for this time type.
76 #endif
77 /*
78 We don't use tt_ttisstd and tt_ttisgmt members of original elsie-code
79 struct since we don't support POSIX-style TZ descriptions in variables.
80 */
81 } TRAN_TYPE_INFO;
82
83 /* Structure describing leap-second corrections. */
84 typedef struct lsinfo
85 {
86 my_time_t ls_trans; // Transition time
87 long ls_corr; // Correction to apply
88 } LS_INFO;
89
90 /*
91 Structure with information describing ranges of my_time_t shifted to local
92 time (my_time_t + offset). Used for local MYSQL_TIME -> my_time_t conversion.
93 See comments for TIME_to_gmt_sec() for more info.
94 */
95 typedef struct revtinfo
96 {
97 long rt_offset; // Offset of local time from UTC in seconds
98 uint rt_type; // Type of period 0 - Normal period. 1 - Spring time-gap
99 } REVT_INFO;
100
101 #ifdef TZNAME_MAX
102 #define MY_TZNAME_MAX TZNAME_MAX
103 #endif
104 #ifndef TZNAME_MAX
105 #define MY_TZNAME_MAX 255
106 #endif
107
108 /*
109 Structure which fully describes time zone which is
110 described in our db or in zoneinfo files.
111 */
112 typedef struct st_time_zone_info
113 {
114 uint leapcnt; // Number of leap-second corrections
115 uint timecnt; // Number of transitions between time types
116 uint typecnt; // Number of local time types
117 uint charcnt; // Number of characters used for abbreviations
118 uint revcnt; // Number of transition descr. for TIME->my_time_t conversion
119 /* The following are dynamical arrays are allocated in MEM_ROOT */
120 my_time_t *ats; // Times of transitions between time types
121 uchar *types; // Local time types for transitions
122 TRAN_TYPE_INFO *ttis; // Local time types descriptions
123 #ifdef ABBR_ARE_USED
124 /* Storage for local time types abbreviations. They are stored as ASCIIZ */
125 char *chars;
126 #endif
127 /*
128 Leap seconds corrections descriptions, this array is shared by
129 all time zones who use leap seconds.
130 */
131 LS_INFO *lsis;
132 /*
133 Starting points and descriptions of shifted my_time_t (my_time_t + offset)
134 ranges on which shifted my_time_t -> my_time_t mapping is linear or undefined.
135 Used for tm -> my_time_t conversion.
136 */
137 my_time_t *revts;
138 REVT_INFO *revtis;
139 /*
140 Time type which is used for times smaller than first transition or if
141 there are no transitions at all.
142 */
143 TRAN_TYPE_INFO *fallback_tti;
144
145 } TIME_ZONE_INFO;
146
147
148 static my_bool prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage);
149
150 my_bool opt_leap, opt_verbose, opt_skip_write_binlog;
151
152 #if defined(TZINFO2SQL) || defined(TESTTIME)
153
154 /*
155 Load time zone description from zoneinfo (TZinfo) file.
156
157 SYNOPSIS
158 tz_load()
159 name - path to zoneinfo file
160 sp - TIME_ZONE_INFO structure to fill
161
162 RETURN VALUES
163 0 - Ok
164 1 - Error
165 */
166 static my_bool
tz_load(const char * name,TIME_ZONE_INFO * sp,MEM_ROOT * storage)167 tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage)
168 {
169 uchar *p;
170 ssize_t read_from_file;
171 uint i;
172 MYSQL_FILE *file;
173
174 if (!(file= mysql_file_fopen(0, name, O_RDONLY|O_BINARY, MYF(MY_WME))))
175 return 1;
176 {
177 union
178 {
179 struct tzhead tzhead;
180 uchar buf[sizeof(struct tzhead) + sizeof(my_time_t) * TZ_MAX_TIMES +
181 TZ_MAX_TIMES + sizeof(TRAN_TYPE_INFO) * TZ_MAX_TYPES +
182 #ifdef ABBR_ARE_USED
183 MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) +
184 #endif
185 sizeof(LS_INFO) * TZ_MAX_LEAPS];
186 } u;
187 uint ttisstdcnt;
188 uint ttisgmtcnt;
189 char *tzinfo_buf;
190
191 read_from_file= (ssize_t)mysql_file_fread(file, u.buf, sizeof(u.buf), MYF(MY_WME));
192
193 if (mysql_file_fclose(file, MYF(MY_WME)) != 0)
194 return 1;
195
196 if (read_from_file < (int)sizeof(struct tzhead))
197 return 1;
198
199 ttisstdcnt= int4net(u.tzhead.tzh_ttisgmtcnt);
200 ttisgmtcnt= int4net(u.tzhead.tzh_ttisstdcnt);
201 sp->leapcnt= int4net(u.tzhead.tzh_leapcnt);
202 sp->timecnt= int4net(u.tzhead.tzh_timecnt);
203 sp->typecnt= int4net(u.tzhead.tzh_typecnt);
204 sp->charcnt= int4net(u.tzhead.tzh_charcnt);
205 p= u.tzhead.tzh_charcnt + sizeof(u.tzhead.tzh_charcnt);
206 if (sp->leapcnt > TZ_MAX_LEAPS ||
207 sp->typecnt == 0 || sp->typecnt > TZ_MAX_TYPES ||
208 sp->timecnt > TZ_MAX_TIMES ||
209 sp->charcnt > TZ_MAX_CHARS ||
210 (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
211 (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
212 return 1;
213 if ((uint)(read_from_file - (p - u.buf)) <
214 sp->timecnt * 4 + /* ats */
215 sp->timecnt + /* types */
216 sp->typecnt * (4 + 2) + /* ttinfos */
217 sp->charcnt + /* chars */
218 sp->leapcnt * (4 + 4) + /* lsinfos */
219 ttisstdcnt + /* ttisstds */
220 ttisgmtcnt) /* ttisgmts */
221 return 1;
222
223 if (!(tzinfo_buf= (char *)alloc_root(storage,
224 ALIGN_SIZE(sp->timecnt *
225 sizeof(my_time_t)) +
226 ALIGN_SIZE(sp->timecnt) +
227 ALIGN_SIZE(sp->typecnt *
228 sizeof(TRAN_TYPE_INFO)) +
229 #ifdef ABBR_ARE_USED
230 ALIGN_SIZE(sp->charcnt+1) +
231 #endif
232 sp->leapcnt * sizeof(LS_INFO))))
233 return 1;
234
235 sp->ats= (my_time_t *)tzinfo_buf;
236 tzinfo_buf+= ALIGN_SIZE(sp->timecnt * sizeof(my_time_t));
237 sp->types= (uchar *)tzinfo_buf;
238 tzinfo_buf+= ALIGN_SIZE(sp->timecnt);
239 sp->ttis= (TRAN_TYPE_INFO *)tzinfo_buf;
240 tzinfo_buf+= ALIGN_SIZE(sp->typecnt * sizeof(TRAN_TYPE_INFO));
241 #ifdef ABBR_ARE_USED
242 sp->chars= tzinfo_buf;
243 tzinfo_buf+= ALIGN_SIZE(sp->charcnt+1);
244 #endif
245 sp->lsis= (LS_INFO *)tzinfo_buf;
246
247 for (i= 0; i < sp->timecnt; i++, p+= 4)
248 sp->ats[i]= int4net(p);
249
250 for (i= 0; i < sp->timecnt; i++)
251 {
252 sp->types[i]= (uchar) *p++;
253 if (sp->types[i] >= sp->typecnt)
254 return 1;
255 }
256 for (i= 0; i < sp->typecnt; i++)
257 {
258 TRAN_TYPE_INFO * ttisp;
259
260 ttisp= &sp->ttis[i];
261 ttisp->tt_gmtoff= int4net(p);
262 p+= 4;
263 ttisp->tt_isdst= (uchar) *p++;
264 if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
265 return 1;
266 ttisp->tt_abbrind= (uchar) *p++;
267 if (ttisp->tt_abbrind > sp->charcnt)
268 return 1;
269 }
270 for (i= 0; i < sp->charcnt; i++)
271 sp->chars[i]= *p++;
272 sp->chars[i]= '\0'; /* ensure '\0' at end */
273 for (i= 0; i < sp->leapcnt; i++)
274 {
275 LS_INFO *lsisp;
276
277 lsisp= &sp->lsis[i];
278 lsisp->ls_trans= int4net(p);
279 p+= 4;
280 lsisp->ls_corr= int4net(p);
281 p+= 4;
282 }
283 /*
284 Since we don't support POSIX style TZ definitions in variables we
285 don't read further like glibc or elsie code.
286 */
287 }
288
289 return prepare_tz_info(sp, storage);
290 }
291 #endif /* defined(TZINFO2SQL) || defined(TESTTIME) */
292
293
294 /*
295 Finish preparation of time zone description for use in TIME_to_gmt_sec()
296 and gmt_sec_to_TIME() functions.
297
298 SYNOPSIS
299 prepare_tz_info()
300 sp - pointer to time zone description
301 storage - pointer to MEM_ROOT where arrays for map allocated
302
303 DESCRIPTION
304 First task of this function is to find fallback time type which will
305 be used if there are no transitions or we have moment in time before
306 any transitions.
307 Second task is to build "shifted my_time_t" -> my_time_t map used in
308 MYSQL_TIME -> my_time_t conversion.
309 Note: See description of TIME_to_gmt_sec() function first.
310 In order to perform MYSQL_TIME -> my_time_t conversion we need to build table
311 which defines "shifted by tz offset and leap seconds my_time_t" ->
312 my_time_t function which is almost the same (except ranges of ambiguity)
313 as reverse function to piecewise linear function used for my_time_t ->
314 "shifted my_time_t" conversion and which is also specified as table in
315 zoneinfo file or in our db (It is specified as start of time type ranges
316 and time type offsets). So basic idea is very simple - let us iterate
317 through my_time_t space from one point of discontinuity of my_time_t ->
318 "shifted my_time_t" function to another and build our approximation of
319 reverse function. (Actually we iterate through ranges on which
320 my_time_t -> "shifted my_time_t" is linear function).
321
322 RETURN VALUES
323 0 Ok
324 1 Error
325 */
326 static my_bool
prepare_tz_info(TIME_ZONE_INFO * sp,MEM_ROOT * storage)327 prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage)
328 {
329 my_time_t cur_t= MY_TIME_T_MIN;
330 my_time_t cur_l, end_t, UNINIT_VAR(end_l);
331 my_time_t cur_max_seen_l= MY_TIME_T_MIN;
332 long cur_offset, cur_corr, cur_off_and_corr;
333 uint next_trans_idx, next_leap_idx;
334 uint i;
335 /*
336 Temporary arrays where we will store tables. Needed because
337 we don't know table sizes ahead. (Well we can estimate their
338 upper bound but this will take extra space.)
339 */
340 my_time_t revts[TZ_MAX_REV_RANGES];
341 REVT_INFO revtis[TZ_MAX_REV_RANGES];
342
343 /*
344 Let us setup fallback time type which will be used if we have not any
345 transitions or if we have moment of time before first transition.
346 We will find first non-DST local time type and use it (or use first
347 local time type if all of them are DST types).
348 */
349 for (i= 0; i < sp->typecnt && sp->ttis[i].tt_isdst; i++)
350 /* no-op */ ;
351 if (i == sp->typecnt)
352 i= 0;
353 sp->fallback_tti= &(sp->ttis[i]);
354
355
356 /*
357 Let us build shifted my_time_t -> my_time_t map.
358 */
359 sp->revcnt= 0;
360
361 /* Let us find initial offset */
362 if (sp->timecnt == 0 || cur_t < sp->ats[0])
363 {
364 /*
365 If we have not any transitions or t is before first transition we are using
366 already found fallback time type which index is already in i.
367 */
368 next_trans_idx= 0;
369 }
370 else
371 {
372 /* cur_t == sp->ats[0] so we found transition */
373 i= sp->types[0];
374 next_trans_idx= 1;
375 }
376
377 cur_offset= sp->ttis[i].tt_gmtoff;
378
379
380 /* let us find leap correction... unprobable, but... */
381 for (next_leap_idx= 0; next_leap_idx < sp->leapcnt &&
382 cur_t >= sp->lsis[next_leap_idx].ls_trans;
383 ++next_leap_idx)
384 continue;
385
386 if (next_leap_idx > 0)
387 cur_corr= sp->lsis[next_leap_idx - 1].ls_corr;
388 else
389 cur_corr= 0;
390
391 /* Iterate trough t space */
392 while (sp->revcnt < TZ_MAX_REV_RANGES - 1)
393 {
394 cur_off_and_corr= cur_offset - cur_corr;
395
396 /*
397 We assuming that cur_t could be only overflowed downwards,
398 we also assume that end_t won't be overflowed in this case.
399 */
400 if (cur_off_and_corr < 0 &&
401 cur_t < MY_TIME_T_MIN - cur_off_and_corr)
402 cur_t= MY_TIME_T_MIN - cur_off_and_corr;
403
404 cur_l= cur_t + cur_off_and_corr;
405
406 /*
407 Let us choose end_t as point before next time type change or leap
408 second correction.
409 */
410 end_t= MY_MIN((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1:
411 MY_TIME_T_MAX,
412 (next_leap_idx < sp->leapcnt) ?
413 sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX);
414 /*
415 again assuming that end_t can be overlowed only in positive side
416 we also assume that end_t won't be overflowed in this case.
417 */
418 if (cur_off_and_corr > 0 &&
419 end_t > MY_TIME_T_MAX - cur_off_and_corr)
420 end_t= MY_TIME_T_MAX - cur_off_and_corr;
421
422 end_l= end_t + cur_off_and_corr;
423
424
425 if (end_l > cur_max_seen_l)
426 {
427 /* We want special handling in the case of first range */
428 if (cur_max_seen_l == MY_TIME_T_MIN)
429 {
430 revts[sp->revcnt]= cur_l;
431 revtis[sp->revcnt].rt_offset= cur_off_and_corr;
432 revtis[sp->revcnt].rt_type= 0;
433 sp->revcnt++;
434 cur_max_seen_l= end_l;
435 }
436 else
437 {
438 if (cur_l > cur_max_seen_l + 1)
439 {
440 /* We have a spring time-gap and we are not at the first range */
441 revts[sp->revcnt]= cur_max_seen_l + 1;
442 revtis[sp->revcnt].rt_offset= revtis[sp->revcnt-1].rt_offset;
443 revtis[sp->revcnt].rt_type= 1;
444 sp->revcnt++;
445 if (sp->revcnt == TZ_MAX_TIMES + TZ_MAX_LEAPS + 1)
446 break; /* That was too much */
447 cur_max_seen_l= cur_l - 1;
448 }
449
450 /* Assume here end_l > cur_max_seen_l (because end_l>=cur_l) */
451
452 revts[sp->revcnt]= cur_max_seen_l + 1;
453 revtis[sp->revcnt].rt_offset= cur_off_and_corr;
454 revtis[sp->revcnt].rt_type= 0;
455 sp->revcnt++;
456 cur_max_seen_l= end_l;
457 }
458 }
459
460 if (end_t == MY_TIME_T_MAX ||
461 ((cur_off_and_corr > 0) &&
462 (end_t >= MY_TIME_T_MAX - cur_off_and_corr)))
463 /* end of t space */
464 break;
465
466 cur_t= end_t + 1;
467
468 /*
469 Let us find new offset and correction. Because of our choice of end_t
470 cur_t can only be point where new time type starts or/and leap
471 correction is performed.
472 */
473 if (sp->timecnt != 0 && cur_t >= sp->ats[0]) /* else reuse old offset */
474 if (next_trans_idx < sp->timecnt &&
475 cur_t == sp->ats[next_trans_idx])
476 {
477 /* We are at offset point */
478 cur_offset= sp->ttis[sp->types[next_trans_idx]].tt_gmtoff;
479 ++next_trans_idx;
480 }
481
482 if (next_leap_idx < sp->leapcnt &&
483 cur_t == sp->lsis[next_leap_idx].ls_trans)
484 {
485 /* we are at leap point */
486 cur_corr= sp->lsis[next_leap_idx].ls_corr;
487 ++next_leap_idx;
488 }
489 }
490
491 /* check if we have had enough space */
492 if (sp->revcnt == TZ_MAX_REV_RANGES - 1)
493 return 1;
494
495 /* set maximum end_l as finisher */
496 revts[sp->revcnt]= end_l;
497
498 /* Allocate arrays of proper size in sp and copy result there */
499 if (!(sp->revts= (my_time_t *)alloc_root(storage,
500 sizeof(my_time_t) * (sp->revcnt + 1))) ||
501 !(sp->revtis= (REVT_INFO *)alloc_root(storage,
502 sizeof(REVT_INFO) * sp->revcnt)))
503 return 1;
504
505 memcpy(sp->revts, revts, sizeof(my_time_t) * (sp->revcnt + 1));
506 memcpy(sp->revtis, revtis, sizeof(REVT_INFO) * sp->revcnt);
507
508 return 0;
509 }
510
511
512 #if !defined(TZINFO2SQL)
513
514 static const uint mon_lengths[2][MONS_PER_YEAR]=
515 {
516 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
517 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
518 };
519
520 static const uint mon_starts[2][MONS_PER_YEAR]=
521 {
522 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
523 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
524 };
525
526 static const uint year_lengths[2]=
527 {
528 DAYS_PER_NYEAR, DAYS_PER_LYEAR
529 };
530
531 #define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400)
532
533
534 /*
535 Converts time from my_time_t representation (seconds in UTC since Epoch)
536 to broken down representation using given local time zone offset.
537
538 SYNOPSIS
539 sec_to_TIME()
540 tmp - pointer to structure for broken down representation
541 t - my_time_t value to be converted
542 offset - local time zone offset
543
544 DESCRIPTION
545 Convert my_time_t with offset to MYSQL_TIME struct. Differs from timesub
546 (from elsie code) because doesn't contain any leap correction and
547 TM_GMTOFF and is_dst setting and contains some MySQL specific
548 initialization. Funny but with removing of these we almost have
549 glibc's offtime function.
550 */
551 static void
sec_to_TIME(MYSQL_TIME * tmp,my_time_t t,long offset)552 sec_to_TIME(MYSQL_TIME * tmp, my_time_t t, long offset)
553 {
554 long days;
555 long rem;
556 int y;
557 int yleap;
558 const uint *ip;
559
560 days= (long) (t / SECS_PER_DAY);
561 rem= (long) (t % SECS_PER_DAY);
562
563 /*
564 We do this as separate step after dividing t, because this
565 allows us handle times near my_time_t bounds without overflows.
566 */
567 rem+= offset;
568 while (rem < 0)
569 {
570 rem+= SECS_PER_DAY;
571 days--;
572 }
573 while (rem >= SECS_PER_DAY)
574 {
575 rem -= SECS_PER_DAY;
576 days++;
577 }
578 tmp->hour= (uint)(rem / SECS_PER_HOUR);
579 rem= rem % SECS_PER_HOUR;
580 tmp->minute= (uint)(rem / SECS_PER_MIN);
581 /*
582 A positive leap second requires a special
583 representation. This uses "... ??:59:60" et seq.
584 */
585 tmp->second= (uint)(rem % SECS_PER_MIN);
586
587 y= EPOCH_YEAR;
588 while (days < 0 || days >= (long)year_lengths[yleap= isleap(y)])
589 {
590 int newy;
591
592 newy= y + days / DAYS_PER_NYEAR;
593 if (days < 0)
594 newy--;
595 days-= (newy - y) * DAYS_PER_NYEAR +
596 LEAPS_THRU_END_OF(newy - 1) -
597 LEAPS_THRU_END_OF(y - 1);
598 y= newy;
599 }
600 tmp->year= y;
601
602 ip= mon_lengths[yleap];
603 for (tmp->month= 0; days >= (long) ip[tmp->month]; tmp->month++)
604 days= days - (long) ip[tmp->month];
605 tmp->month++;
606 tmp->day= (uint)(days + 1);
607
608 /* filling MySQL specific MYSQL_TIME members */
609 tmp->neg= 0; tmp->second_part= 0;
610 tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
611 }
612
613
614 /*
615 Find time range which contains given my_time_t value
616
617 SYNOPSIS
618 find_time_range()
619 t - my_time_t value for which we looking for range
620 range_boundaries - sorted array of range starts.
621 higher_bound - number of ranges
622
623 DESCRIPTION
624 Performs binary search for range which contains given my_time_t value.
625 It has sense if number of ranges is greater than zero and my_time_t value
626 is greater or equal than beginning of first range. It also assumes that
627 t belongs to some range specified or end of last is MY_TIME_T_MAX.
628
629 With this localtime_r on real data may takes less time than with linear
630 search (I've seen 30% speed up).
631
632 RETURN VALUE
633 Index of range to which t belongs
634 */
635 static uint
find_time_range(my_time_t t,const my_time_t * range_boundaries,uint higher_bound)636 find_time_range(my_time_t t, const my_time_t *range_boundaries,
637 uint higher_bound)
638 {
639 uint i, lower_bound= 0;
640
641 /*
642 Function will work without this assertion but result would be meaningless.
643 */
644 DBUG_ASSERT(higher_bound > 0 && t >= range_boundaries[0]);
645
646 /*
647 Do binary search for minimal interval which contain t. We preserve:
648 range_boundaries[lower_bound] <= t < range_boundaries[higher_bound]
649 invariant and decrease this higher_bound - lower_bound gap twice
650 times on each step.
651 */
652
653 while (higher_bound - lower_bound > 1)
654 {
655 i= (lower_bound + higher_bound) >> 1;
656 if (range_boundaries[i] <= t)
657 lower_bound= i;
658 else
659 higher_bound= i;
660 }
661 return lower_bound;
662 }
663
664 /*
665 Find local time transition for given my_time_t.
666
667 SYNOPSIS
668 find_transition_type()
669 t - my_time_t value to be converted
670 sp - pointer to struct with time zone description
671
672 RETURN VALUE
673 Pointer to structure in time zone description describing
674 local time type for given my_time_t.
675 */
676 static
677 const TRAN_TYPE_INFO *
find_transition_type(my_time_t t,const TIME_ZONE_INFO * sp)678 find_transition_type(my_time_t t, const TIME_ZONE_INFO *sp)
679 {
680 if (unlikely(sp->timecnt == 0 || t < sp->ats[0]))
681 {
682 /*
683 If we have not any transitions or t is before first transition let
684 us use fallback time type.
685 */
686 return sp->fallback_tti;
687 }
688
689 /*
690 Do binary search for minimal interval between transitions which
691 contain t. With this localtime_r on real data may takes less
692 time than with linear search (I've seen 30% speed up).
693 */
694 return &(sp->ttis[sp->types[find_time_range(t, sp->ats, sp->timecnt)]]);
695 }
696
697
698 /*
699 Converts time in my_time_t representation (seconds in UTC since Epoch) to
700 broken down MYSQL_TIME representation in local time zone.
701
702 SYNOPSIS
703 gmt_sec_to_TIME()
704 tmp - pointer to structure for broken down represenatation
705 sec_in_utc - my_time_t value to be converted
706 sp - pointer to struct with time zone description
707
708 TODO
709 We can improve this function by creating joined array of transitions and
710 leap corrections. This will require adding extra field to TRAN_TYPE_INFO
711 for storing number of "extra" seconds to minute occurred due to correction
712 (60th and 61st second, look how we calculate them as "hit" in this
713 function).
714 Under realistic assumptions about frequency of transitions the same array
715 can be used fot MYSQL_TIME -> my_time_t conversion. For this we need to
716 implement tweaked binary search which will take into account that some
717 MYSQL_TIME has two matching my_time_t ranges and some of them have none.
718 */
719 static void
gmt_sec_to_TIME(MYSQL_TIME * tmp,my_time_t sec_in_utc,const TIME_ZONE_INFO * sp)720 gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t sec_in_utc, const TIME_ZONE_INFO *sp)
721 {
722 const TRAN_TYPE_INFO *ttisp;
723 const LS_INFO *lp;
724 long corr= 0;
725 int hit= 0;
726 int i;
727
728 /*
729 Find proper transition (and its local time type) for our sec_in_utc value.
730 Funny but again by separating this step in function we receive code
731 which very close to glibc's code. No wonder since they obviously use
732 the same base and all steps are sensible.
733 */
734 ttisp= find_transition_type(sec_in_utc, sp);
735
736 /*
737 Let us find leap correction for our sec_in_utc value and number of extra
738 secs to add to this minute.
739 This loop is rarely used because most users will use time zones without
740 leap seconds, and even in case when we have such time zone there won't
741 be many iterations (we have about 22 corrections at this moment (2004)).
742 */
743 for ( i= sp->leapcnt; i-- > 0; )
744 {
745 lp= &sp->lsis[i];
746 if (sec_in_utc >= lp->ls_trans)
747 {
748 if (sec_in_utc == lp->ls_trans)
749 {
750 hit= ((i == 0 && lp->ls_corr > 0) ||
751 lp->ls_corr > sp->lsis[i - 1].ls_corr);
752 if (hit)
753 {
754 while (i > 0 &&
755 sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 &&
756 sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1)
757 {
758 hit++;
759 i--;
760 }
761 }
762 }
763 corr= lp->ls_corr;
764 break;
765 }
766 }
767
768 sec_to_TIME(tmp, sec_in_utc, ttisp->tt_gmtoff - corr);
769
770 tmp->second+= hit;
771 }
772
773
774 /*
775 Converts local time in broken down representation to local
776 time zone analog of my_time_t represenation.
777
778 SYNOPSIS
779 sec_since_epoch()
780 year, mon, mday, hour, min, sec - broken down representation.
781
782 DESCRIPTION
783 Converts time in broken down representation to my_time_t representation
784 ignoring time zone. Note that we cannot convert back some valid _local_
785 times near ends of my_time_t range because of my_time_t overflow. But we
786 ignore this fact now since MySQL will never pass such argument.
787
788 RETURN VALUE
789 Seconds since epoch time representation.
790 */
791 static my_time_t
sec_since_epoch(int year,int mon,int mday,int hour,int min,int sec)792 sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec)
793 {
794 /* Guard against my_time_t overflow(on system with 32 bit my_time_t) */
795 DBUG_ASSERT(!(year == TIMESTAMP_MAX_YEAR && mon == 1 && mday > 17));
796 #ifndef WE_WANT_TO_HANDLE_UNORMALIZED_DATES
797 /*
798 It turns out that only whenever month is normalized or unnormalized
799 plays role.
800 */
801 DBUG_ASSERT(mon > 0 && mon < 13);
802 long days= year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
803 LEAPS_THRU_END_OF(year - 1) -
804 LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
805 days+= mon_starts[isleap(year)][mon - 1];
806 #else
807 long norm_month= (mon - 1) % MONS_PER_YEAR;
808 long a_year= year + (mon - 1)/MONS_PER_YEAR - (int)(norm_month < 0);
809 long days= a_year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
810 LEAPS_THRU_END_OF(a_year - 1) -
811 LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
812 days+= mon_starts[isleap(a_year)]
813 [norm_month + (norm_month < 0 ? MONS_PER_YEAR : 0)];
814 #endif
815 days+= mday - 1;
816
817 return ((days * HOURS_PER_DAY + hour) * MINS_PER_HOUR + min) *
818 SECS_PER_MIN + sec;
819 }
820
821 /*
822 Converts local time in broken down MYSQL_TIME representation to my_time_t
823 representation.
824
825 SYNOPSIS
826 TIME_to_gmt_sec()
827 t - pointer to structure for broken down represenatation
828 sp - pointer to struct with time zone description
829 error_code - 0, if the conversion was successful;
830 ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
831 which is out of TIMESTAMP range;
832 ER_WARN_INVALID_TIMESTAMP, if t represents value which
833 doesn't exists (falls into the spring time-gap).
834
835 DESCRIPTION
836 This is mktime analog for MySQL. It is essentially different
837 from mktime (or hypotetical my_mktime) because:
838 - It has no idea about tm_isdst member so if it
839 has two answers it will give the smaller one
840 - If we are in spring time gap then it will return
841 beginning of the gap
842 - It can give wrong results near the ends of my_time_t due to
843 overflows, but we are safe since in MySQL we will never
844 call this function for such dates (its restriction for year
845 between 1970 and 2038 gives us several days of reserve).
846 - By default it doesn't support un-normalized input. But if
847 sec_since_epoch() function supports un-normalized dates
848 then this function should handle un-normalized input right,
849 altough it won't normalize structure TIME.
850
851 Traditional approach to problem of conversion from broken down
852 representation to time_t is iterative. Both elsie's and glibc
853 implementation try to guess what time_t value should correspond to
854 this broken-down value. They perform localtime_r function on their
855 guessed value and then calculate the difference and try to improve
856 their guess. Elsie's code guesses time_t value in bit by bit manner,
857 Glibc's code tries to add difference between broken-down value
858 corresponding to guess and target broken-down value to current guess.
859 It also uses caching of last found correction... So Glibc's approach
860 is essentially faster but introduces some undetermenism (in case if
861 is_dst member of broken-down representation (tm struct) is not known
862 and we have two possible answers).
863
864 We use completely different approach. It is better since it is both
865 faster than iterative implementations and fully determenistic. If you
866 look at my_time_t to MYSQL_TIME conversion then you'll find that it consist
867 of two steps:
868 The first is calculating shifted my_time_t value and the second - TIME
869 calculation from shifted my_time_t value (well it is a bit simplified
870 picture). The part in which we are interested in is my_time_t -> shifted
871 my_time_t conversion. It is piecewise linear function which is defined
872 by combination of transition times as break points and times offset
873 as changing function parameter. The possible inverse function for this
874 converison would be ambiguos but with MySQL's restrictions we can use
875 some function which is the same as inverse function on unambigiuos
876 ranges and coincides with one of branches of inverse function in
877 other ranges. Thus we just need to build table which will determine
878 this shifted my_time_t -> my_time_t conversion similar to existing
879 (my_time_t -> shifted my_time_t table). We do this in
880 prepare_tz_info function.
881
882 TODO
883 If we can even more improve this function. For doing this we will need to
884 build joined map of transitions and leap corrections for gmt_sec_to_TIME()
885 function (similar to revts/revtis). Under realistic assumptions about
886 frequency of transitions we can use the same array for TIME_to_gmt_sec().
887 We need to implement special version of binary search for this. Such step
888 will be beneficial to CPU cache since we will decrease data-set used for
889 conversion twice.
890
891 RETURN VALUE
892 Seconds in UTC since Epoch.
893 0 in case of error.
894 */
895
896 static my_time_t
TIME_to_gmt_sec(const MYSQL_TIME * t,const TIME_ZONE_INFO * sp,uint * error_code)897 TIME_to_gmt_sec(const MYSQL_TIME *t, const TIME_ZONE_INFO *sp, uint *error_code)
898 {
899 my_time_t local_t;
900 uint saved_seconds;
901 uint i;
902 int shift= 0;
903 DBUG_ENTER("TIME_to_gmt_sec");
904
905 if (!validate_timestamp_range(t))
906 {
907 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
908 DBUG_RETURN(0);
909 }
910
911 *error_code= 0;
912
913 /* We need this for correct leap seconds handling */
914 if (t->second < SECS_PER_MIN)
915 saved_seconds= 0;
916 else
917 saved_seconds= t->second;
918
919 /*
920 NOTE: to convert full my_time_t range we do a shift of the
921 boundary dates here to avoid overflow of my_time_t.
922 We use alike approach in my_system_gmt_sec().
923
924 However in that function we also have to take into account
925 overflow near 0 on some platforms. That's because my_system_gmt_sec
926 uses localtime_r(), which doesn't work with negative values correctly
927 on platforms with unsigned time_t (QNX). Here we don't use localtime()
928 => we negative values of local_t are ok.
929 */
930
931 if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
932 {
933 /*
934 We will pass (t->day - shift) to sec_since_epoch(), and
935 want this value to be a positive number, so we shift
936 only dates > 4.01.2038 (to avoid owerflow).
937 */
938 shift= 2;
939 }
940
941
942 local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
943 t->hour, t->minute,
944 saved_seconds ? 0 : t->second);
945
946 /* We have at least one range */
947 DBUG_ASSERT(sp->revcnt >= 1);
948
949 if (local_t < sp->revts[0] || local_t > sp->revts[sp->revcnt])
950 {
951 /*
952 This means that source time can't be represented as my_time_t due to
953 limited my_time_t range.
954 */
955 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
956 DBUG_RETURN(0);
957 }
958
959 /* binary search for our range */
960 i= find_time_range(local_t, sp->revts, sp->revcnt);
961
962 /*
963 As there are no offset switches at the end of TIMESTAMP range,
964 we could simply check for overflow here (and don't need to bother
965 about DST gaps etc)
966 */
967 if (shift)
968 {
969 if (local_t > (my_time_t) (TIMESTAMP_MAX_VALUE - shift * SECS_PER_DAY +
970 sp->revtis[i].rt_offset - saved_seconds))
971 {
972 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
973 DBUG_RETURN(0); /* my_time_t overflow */
974 }
975 local_t+= shift * SECS_PER_DAY;
976 }
977
978 if (sp->revtis[i].rt_type)
979 {
980 /*
981 Oops! We are in spring time gap.
982 May be we should return error here?
983 Now we are returning my_time_t value corresponding to the
984 beginning of the gap.
985 */
986 *error_code= ER_WARN_INVALID_TIMESTAMP;
987 local_t= sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds;
988 }
989 else
990 local_t= local_t - sp->revtis[i].rt_offset + saved_seconds;
991
992 /* check for TIMESTAMP_MAX_VALUE was already done above */
993 if (local_t < TIMESTAMP_MIN_VALUE)
994 {
995 local_t= 0;
996 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
997 }
998
999 DBUG_RETURN(local_t);
1000 }
1001
1002
1003 /*
1004 End of elsie derived code.
1005 */
1006 #endif /* !defined(TZINFO2SQL) */
1007
1008
1009 #if !defined(TESTTIME) && !defined(TZINFO2SQL)
1010
1011 /*
1012 String with names of SYSTEM time zone.
1013 */
1014 static const String tz_SYSTEM_name("SYSTEM", 6, &my_charset_latin1);
1015
1016
1017 /*
1018 Instance of this class represents local time zone used on this system
1019 (specified by TZ environment variable or via any other system mechanism).
1020 It uses system functions (localtime_r, my_system_gmt_sec) for conversion
1021 and is always available. Because of this it is used by default - if there
1022 were no explicit time zone specified. On the other hand because of this
1023 conversion methods provided by this class is significantly slower and
1024 possibly less multi-threaded-friendly than corresponding Time_zone_db
1025 methods so the latter should be preffered there it is possible.
1026 */
1027 class Time_zone_system : public Time_zone
1028 {
1029 public:
Time_zone_system()1030 Time_zone_system() {} /* Remove gcc warning */
1031 virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
1032 virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1033 virtual const String * get_name() const;
1034 };
1035
1036
1037 /*
1038 Converts local time in system time zone in MYSQL_TIME representation
1039 to its my_time_t representation.
1040
1041 SYNOPSIS
1042 TIME_to_gmt_sec()
1043 t - pointer to MYSQL_TIME structure with local time in
1044 broken-down representation.
1045 error_code - 0, if the conversion was successful;
1046 ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
1047 which is out of TIMESTAMP range;
1048 ER_WARN_INVALID_TIMESTAMP, if t represents value which
1049 doesn't exists (falls into the spring time-gap).
1050
1051 DESCRIPTION
1052 This method uses system function (localtime_r()) for conversion
1053 local time in system time zone in MYSQL_TIME structure to its my_time_t
1054 representation. Unlike the same function for Time_zone_db class
1055 it it won't handle unnormalized input properly. Still it will
1056 return lowest possible my_time_t in case of ambiguity or if we
1057 provide time corresponding to the time-gap.
1058
1059 You should call my_init_time() function before using this function.
1060
1061 RETURN VALUE
1062 Corresponding my_time_t value or 0 in case of error
1063 */
1064 my_time_t
TIME_to_gmt_sec(const MYSQL_TIME * t,uint * error_code) const1065 Time_zone_system::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
1066 {
1067 long not_used;
1068 return my_system_gmt_sec(t, ¬_used, error_code);
1069 }
1070
1071
1072 /*
1073 Converts time from UTC seconds since Epoch (my_time_t) representation
1074 to system local time zone broken-down representation.
1075
1076 SYNOPSIS
1077 gmt_sec_to_TIME()
1078 tmp - pointer to MYSQL_TIME structure to fill-in
1079 t - my_time_t value to be converted
1080
1081 NOTE
1082 We assume that value passed to this function will fit into time_t range
1083 supported by localtime_r. This conversion is putting restriction on
1084 TIMESTAMP range in MySQL. If we can get rid of SYSTEM time zone at least
1085 for interaction with client then we can extend TIMESTAMP range down to
1086 the 1902 easily.
1087 */
1088 void
gmt_sec_to_TIME(MYSQL_TIME * tmp,my_time_t t) const1089 Time_zone_system::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1090 {
1091 struct tm tmp_tm;
1092 time_t tmp_t= (time_t)t;
1093
1094 localtime_r(&tmp_t, &tmp_tm);
1095 localtime_to_TIME(tmp, &tmp_tm);
1096 tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
1097 adjust_leap_second(tmp);
1098 }
1099
1100
1101 /*
1102 Get name of time zone
1103
1104 SYNOPSIS
1105 get_name()
1106
1107 RETURN VALUE
1108 Name of time zone as String
1109 */
1110 const String *
get_name() const1111 Time_zone_system::get_name() const
1112 {
1113 return &tz_SYSTEM_name;
1114 }
1115
1116
1117 /*
1118 Instance of this class represents UTC time zone. It uses system gmtime_r
1119 function for conversions and is always available. It is used only for
1120 my_time_t -> MYSQL_TIME conversions in various UTC_... functions, it is not
1121 intended for MYSQL_TIME -> my_time_t conversions and shouldn't be exposed to user.
1122 */
1123 class Time_zone_utc : public Time_zone
1124 {
1125 public:
Time_zone_utc()1126 Time_zone_utc() {} /* Remove gcc warning */
1127 virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1128 uint *error_code) const;
1129 virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1130 virtual const String * get_name() const;
1131 };
1132
1133
1134 /*
1135 Convert UTC time from MYSQL_TIME representation to its my_time_t representation.
1136
1137 DESCRIPTION
1138 Since Time_zone_utc is used only internally for my_time_t -> TIME
1139 conversions, this function of Time_zone interface is not implemented for
1140 this class and should not be called.
1141
1142 RETURN VALUE
1143 0
1144 */
1145 my_time_t
TIME_to_gmt_sec(const MYSQL_TIME * t,uint * error_code) const1146 Time_zone_utc::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
1147 {
1148 /* Should be never called */
1149 DBUG_ASSERT(0);
1150 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
1151 return 0;
1152 }
1153
1154
1155 /*
1156 Converts time from UTC seconds since Epoch (my_time_t) representation
1157 to broken-down representation (also in UTC).
1158
1159 SYNOPSIS
1160 gmt_sec_to_TIME()
1161 tmp - pointer to MYSQL_TIME structure to fill-in
1162 t - my_time_t value to be converted
1163
1164 NOTE
1165 See note for apropriate Time_zone_system method.
1166 */
1167 void
gmt_sec_to_TIME(MYSQL_TIME * tmp,my_time_t t) const1168 Time_zone_utc::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1169 {
1170 struct tm tmp_tm;
1171 time_t tmp_t= (time_t)t;
1172 gmtime_r(&tmp_t, &tmp_tm);
1173 localtime_to_TIME(tmp, &tmp_tm);
1174 tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
1175 adjust_leap_second(tmp);
1176 }
1177
1178
1179 /*
1180 Get name of time zone
1181
1182 SYNOPSIS
1183 get_name()
1184
1185 DESCRIPTION
1186 Since Time_zone_utc is used only internally by SQL's UTC_* functions it
1187 is not accessible directly, and hence this function of Time_zone
1188 interface is not implemented for this class and should not be called.
1189
1190 RETURN VALUE
1191 0
1192 */
1193 const String *
get_name() const1194 Time_zone_utc::get_name() const
1195 {
1196 /* Should be never called */
1197 DBUG_ASSERT(0);
1198 return 0;
1199 }
1200
1201
1202 /*
1203 Instance of this class represents some time zone which is
1204 described in mysql.time_zone family of tables.
1205 */
1206 class Time_zone_db : public Time_zone
1207 {
1208 public:
1209 Time_zone_db(TIME_ZONE_INFO *tz_info_arg, const String * tz_name_arg);
1210 virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
1211 virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1212 virtual const String * get_name() const;
1213 private:
1214 TIME_ZONE_INFO *tz_info;
1215 const String *tz_name;
1216 };
1217
1218
1219 /*
1220 Initializes object representing time zone described by mysql.time_zone
1221 tables.
1222
1223 SYNOPSIS
1224 Time_zone_db()
1225 tz_info_arg - pointer to TIME_ZONE_INFO structure which is filled
1226 according to db or other time zone description
1227 (for example by my_tz_init()).
1228 Several Time_zone_db instances can share one
1229 TIME_ZONE_INFO structure.
1230 tz_name_arg - name of time zone.
1231 */
Time_zone_db(TIME_ZONE_INFO * tz_info_arg,const String * tz_name_arg)1232 Time_zone_db::Time_zone_db(TIME_ZONE_INFO *tz_info_arg,
1233 const String *tz_name_arg):
1234 tz_info(tz_info_arg), tz_name(tz_name_arg)
1235 {
1236 }
1237
1238
1239 /*
1240 Converts local time in time zone described from TIME
1241 representation to its my_time_t representation.
1242
1243 SYNOPSIS
1244 TIME_to_gmt_sec()
1245 t - pointer to MYSQL_TIME structure with local time
1246 in broken-down representation.
1247 error_code - 0, if the conversion was successful;
1248 ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
1249 which is out of TIMESTAMP range;
1250 ER_WARN_INVALID_TIMESTAMP, if t represents value which
1251 doesn't exists (falls into the spring time-gap).
1252
1253 DESCRIPTION
1254 Please see ::TIME_to_gmt_sec for function description and
1255 parameter restrictions.
1256
1257 RETURN VALUE
1258 Corresponding my_time_t value or 0 in case of error
1259 */
1260 my_time_t
TIME_to_gmt_sec(const MYSQL_TIME * t,uint * error_code) const1261 Time_zone_db::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
1262 {
1263 return ::TIME_to_gmt_sec(t, tz_info, error_code);
1264 }
1265
1266
1267 /*
1268 Converts time from UTC seconds since Epoch (my_time_t) representation
1269 to local time zone described in broken-down representation.
1270
1271 SYNOPSIS
1272 gmt_sec_to_TIME()
1273 tmp - pointer to MYSQL_TIME structure to fill-in
1274 t - my_time_t value to be converted
1275 */
1276 void
gmt_sec_to_TIME(MYSQL_TIME * tmp,my_time_t t) const1277 Time_zone_db::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1278 {
1279 ::gmt_sec_to_TIME(tmp, t, tz_info);
1280 adjust_leap_second(tmp);
1281 }
1282
1283
1284 /*
1285 Get name of time zone
1286
1287 SYNOPSIS
1288 get_name()
1289
1290 RETURN VALUE
1291 Name of time zone as ASCIIZ-string
1292 */
1293 const String *
get_name() const1294 Time_zone_db::get_name() const
1295 {
1296 return tz_name;
1297 }
1298
1299
1300 /*
1301 Instance of this class represents time zone which
1302 was specified as offset from UTC.
1303 */
1304 class Time_zone_offset : public Time_zone
1305 {
1306 public:
1307 Time_zone_offset(long tz_offset_arg);
1308 virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1309 uint *error_code) const;
1310 virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1311 virtual const String * get_name() const;
1312 /*
1313 This have to be public because we want to be able to access it from
1314 my_offset_tzs_get_key() function
1315 */
1316 long offset;
1317 private:
1318 /* Extra reserve because of snprintf */
1319 char name_buff[7+16];
1320 String name;
1321 };
1322
1323
1324 /*
1325 Initializes object representing time zone described by its offset from UTC.
1326
1327 SYNOPSIS
1328 Time_zone_offset()
1329 tz_offset_arg - offset from UTC in seconds.
1330 Positive for direction to east.
1331 */
Time_zone_offset(long tz_offset_arg)1332 Time_zone_offset::Time_zone_offset(long tz_offset_arg):
1333 offset(tz_offset_arg)
1334 {
1335 uint hours= abs((int)(offset / SECS_PER_HOUR));
1336 uint minutes= abs((int)(offset % SECS_PER_HOUR / SECS_PER_MIN));
1337 size_t length= my_snprintf(name_buff, sizeof(name_buff), "%s%02d:%02d",
1338 (offset>=0) ? "+" : "-", hours, minutes);
1339 name.set(name_buff, length, &my_charset_latin1);
1340 }
1341
1342
1343 /*
1344 Converts local time in time zone described as offset from UTC
1345 from MYSQL_TIME representation to its my_time_t representation.
1346
1347 SYNOPSIS
1348 TIME_to_gmt_sec()
1349 t - pointer to MYSQL_TIME structure with local time
1350 in broken-down representation.
1351 error_code - 0, if the conversion was successful;
1352 ER_WARN_DATA_OUT_OF_RANGE, if t contains datetime value
1353 which is out of TIMESTAMP range;
1354 ER_WARN_INVALID_TIMESTAMP, if t represents value which
1355 doesn't exists (falls into the spring time-gap).
1356
1357 RETURN VALUE
1358 Corresponding my_time_t value or 0 in case of error.
1359 */
1360
1361 my_time_t
TIME_to_gmt_sec(const MYSQL_TIME * t,uint * error_code) const1362 Time_zone_offset::TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const
1363 {
1364 my_time_t local_t;
1365 int shift= 0;
1366
1367 /*
1368 Check timestamp range.we have to do this as calling function relies on
1369 us to make all validation checks here.
1370 */
1371 if (!validate_timestamp_range(t))
1372 {
1373 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
1374 return 0;
1375 }
1376 *error_code= 0;
1377
1378 /*
1379 Do a temporary shift of the boundary dates to avoid
1380 overflow of my_time_t if the time value is near it's
1381 maximum range
1382 */
1383 if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
1384 shift= 2;
1385
1386 local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
1387 t->hour, t->minute, t->second) -
1388 offset;
1389
1390 if (shift)
1391 {
1392 /* Add back the shifted time */
1393 local_t+= shift * SECS_PER_DAY;
1394 }
1395
1396 if (local_t >= TIMESTAMP_MIN_VALUE && local_t <= TIMESTAMP_MAX_VALUE)
1397 return local_t;
1398
1399 /* range error*/
1400 *error_code= ER_WARN_DATA_OUT_OF_RANGE;
1401 return 0;
1402 }
1403
1404
1405 /*
1406 Converts time from UTC seconds since Epoch (my_time_t) representation
1407 to local time zone described as offset from UTC and in broken-down
1408 representation.
1409
1410 SYNOPSIS
1411 gmt_sec_to_TIME()
1412 tmp - pointer to MYSQL_TIME structure to fill-in
1413 t - my_time_t value to be converted
1414 */
1415 void
gmt_sec_to_TIME(MYSQL_TIME * tmp,my_time_t t) const1416 Time_zone_offset::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1417 {
1418 sec_to_TIME(tmp, t, offset);
1419 }
1420
1421
1422 /*
1423 Get name of time zone
1424
1425 SYNOPSIS
1426 get_name()
1427
1428 RETURN VALUE
1429 Name of time zone as pointer to String object
1430 */
1431 const String *
get_name() const1432 Time_zone_offset::get_name() const
1433 {
1434 return &name;
1435 }
1436
1437
1438 static Time_zone_utc tz_UTC;
1439 static Time_zone_system tz_SYSTEM;
1440 static Time_zone_offset tz_OFFSET0(0);
1441
1442 Time_zone *my_tz_OFFSET0= &tz_OFFSET0;
1443 Time_zone *my_tz_UTC= &tz_UTC;
1444 Time_zone *my_tz_SYSTEM= &tz_SYSTEM;
1445
1446 static HASH tz_names;
1447 static HASH offset_tzs;
1448 static MEM_ROOT tz_storage;
1449
1450 /*
1451 These mutex protects offset_tzs and tz_storage.
1452 These protection needed only when we are trying to set
1453 time zone which is specified as offset, and searching for existing
1454 time zone in offset_tzs or creating if it didn't existed before in
1455 tz_storage. So contention is low.
1456 */
1457 static mysql_mutex_t tz_LOCK;
1458 static bool tz_inited= 0;
1459
1460 /*
1461 This two static variables are inteded for holding info about leap seconds
1462 shared by all time zones.
1463 */
1464 static uint tz_leapcnt= 0;
1465 static LS_INFO *tz_lsis= 0;
1466
1467 /*
1468 Shows whenever we have found time zone tables during start-up.
1469 Used for avoiding of putting those tables to global table list
1470 for queries that use time zone info.
1471 */
1472 static bool time_zone_tables_exist= 1;
1473
1474
1475 /*
1476 Names of tables (with their lengths) that are needed
1477 for dynamical loading of time zone descriptions.
1478 */
1479
1480 static const LEX_CSTRING tz_tables_names[MY_TZ_TABLES_COUNT]=
1481 {
1482 { STRING_WITH_LEN("time_zone_name")},
1483 { STRING_WITH_LEN("time_zone")},
1484 { STRING_WITH_LEN("time_zone_transition_type")},
1485 { STRING_WITH_LEN("time_zone_transition")}
1486 };
1487
1488 class Tz_names_entry: public Sql_alloc
1489 {
1490 public:
1491 String name;
1492 Time_zone *tz;
1493 };
1494
1495
1496 /*
1497 We are going to call both of these functions from C code so
1498 they should obey C calling conventions.
1499 */
1500
1501 extern "C" uchar *
my_tz_names_get_key(Tz_names_entry * entry,size_t * length,my_bool not_used)1502 my_tz_names_get_key(Tz_names_entry *entry, size_t *length,
1503 my_bool not_used __attribute__((unused)))
1504 {
1505 *length= entry->name.length();
1506 return (uchar*) entry->name.ptr();
1507 }
1508
1509 extern "C" uchar *
my_offset_tzs_get_key(Time_zone_offset * entry,size_t * length,my_bool not_used)1510 my_offset_tzs_get_key(Time_zone_offset *entry,
1511 size_t *length,
1512 my_bool not_used __attribute__((unused)))
1513 {
1514 *length= sizeof(long);
1515 return (uchar*) &entry->offset;
1516 }
1517
1518
1519 /*
1520 Prepare table list with time zone related tables from preallocated array.
1521
1522 SYNOPSIS
1523 tz_init_table_list()
1524 tz_tabs - pointer to preallocated array of MY_TZ_TABLES_COUNT
1525 TABLE_LIST objects
1526
1527 DESCRIPTION
1528 This function prepares list of TABLE_LIST objects which can be used
1529 for opening of time zone tables from preallocated array.
1530 */
1531
1532 static void
tz_init_table_list(TABLE_LIST * tz_tabs)1533 tz_init_table_list(TABLE_LIST *tz_tabs)
1534 {
1535 for (int i= 0; i < MY_TZ_TABLES_COUNT; i++)
1536 {
1537 tz_tabs[i].init_one_table(&MYSQL_SCHEMA_NAME, tz_tables_names + i,
1538 NULL, TL_READ);
1539 if (i != MY_TZ_TABLES_COUNT - 1)
1540 tz_tabs[i].next_global= tz_tabs[i].next_local= &tz_tabs[i+1];
1541 if (i != 0)
1542 tz_tabs[i].prev_global= &tz_tabs[i-1].next_global;
1543 }
1544 }
1545
1546 #ifdef HAVE_PSI_INTERFACE
1547 static PSI_mutex_key key_tz_LOCK;
1548
1549 static PSI_mutex_info all_tz_mutexes[]=
1550 {
1551 { & key_tz_LOCK, "tz_LOCK", PSI_FLAG_GLOBAL}
1552 };
1553
init_tz_psi_keys(void)1554 static void init_tz_psi_keys(void)
1555 {
1556 const char* category= "sql";
1557 int count;
1558
1559 if (PSI_server == NULL)
1560 return;
1561
1562 count= array_elements(all_tz_mutexes);
1563 PSI_server->register_mutex(category, all_tz_mutexes, count);
1564 }
1565 #endif /* HAVE_PSI_INTERFACE */
1566
1567
1568 /*
1569 Initialize time zone support infrastructure.
1570
1571 SYNOPSIS
1572 my_tz_init()
1573 thd - current thread object
1574 default_tzname - default time zone or 0 if none.
1575 bootstrap - indicates whenever we are in bootstrap mode
1576
1577 DESCRIPTION
1578 This function will init memory structures needed for time zone support,
1579 it will register mandatory SYSTEM time zone in them. It will try to open
1580 mysql.time_zone* tables and load information about default time zone and
1581 information which further will be shared among all time zones loaded.
1582 If system tables with time zone descriptions don't exist it won't fail
1583 (unless default_tzname is time zone from tables). If bootstrap parameter
1584 is true then this routine assumes that we are in bootstrap mode and won't
1585 load time zone descriptions unless someone specifies default time zone
1586 which is supposedly stored in those tables.
1587 It'll also set default time zone if it is specified.
1588
1589 RETURN VALUES
1590 0 - ok
1591 1 - Error
1592 */
1593 my_bool
my_tz_init(THD * org_thd,const char * default_tzname,my_bool bootstrap)1594 my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap)
1595 {
1596 THD *thd;
1597 TABLE_LIST tz_tables[1+MY_TZ_TABLES_COUNT];
1598 TABLE *table;
1599 const LEX_CSTRING tmp_table_name= { STRING_WITH_LEN("time_zone_leap_second") };
1600 Tz_names_entry *tmp_tzname;
1601 my_bool return_val= 1;
1602 int res;
1603 DBUG_ENTER("my_tz_init");
1604
1605 #ifdef HAVE_PSI_INTERFACE
1606 init_tz_psi_keys();
1607 #endif
1608
1609 /*
1610 To be able to run this from boot, we allocate a temporary THD
1611 */
1612 if (!(thd= new THD(0)))
1613 DBUG_RETURN(1);
1614 thd->thread_stack= (char*) &thd;
1615 thd->store_globals();
1616
1617 /* Init all memory structures that require explicit destruction */
1618 if (my_hash_init(&tz_names, &my_charset_latin1, 20,
1619 0, 0, (my_hash_get_key) my_tz_names_get_key, 0, 0))
1620 {
1621 sql_print_error("Fatal error: OOM while initializing time zones");
1622 goto end;
1623 }
1624 if (my_hash_init(&offset_tzs, &my_charset_latin1, 26, 0, 0,
1625 (my_hash_get_key)my_offset_tzs_get_key, 0, 0))
1626 {
1627 sql_print_error("Fatal error: OOM while initializing time zones");
1628 my_hash_free(&tz_names);
1629 goto end;
1630 }
1631 init_sql_alloc(&tz_storage, "timezone_storage", 32 * 1024, 0, MYF(0));
1632 mysql_mutex_init(key_tz_LOCK, &tz_LOCK, MY_MUTEX_INIT_FAST);
1633 tz_inited= 1;
1634
1635 /* Add 'SYSTEM' time zone to tz_names hash */
1636 if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()))
1637 {
1638 sql_print_error("Fatal error: OOM while initializing time zones");
1639 goto end_with_cleanup;
1640 }
1641 tmp_tzname->name.set(STRING_WITH_LEN("SYSTEM"), &my_charset_latin1);
1642 tmp_tzname->tz= my_tz_SYSTEM;
1643 if (my_hash_insert(&tz_names, (const uchar *)tmp_tzname))
1644 {
1645 sql_print_error("Fatal error: OOM while initializing time zones");
1646 goto end_with_cleanup;
1647 }
1648
1649 if (bootstrap)
1650 {
1651 /* If we are in bootstrap mode we should not load time zone tables */
1652 return_val= time_zone_tables_exist= 0;
1653 goto end_with_cleanup;
1654 }
1655
1656 /*
1657 After this point all memory structures are inited and we even can live
1658 without time zone description tables. Now try to load information about
1659 leap seconds shared by all time zones.
1660 */
1661
1662 thd->set_db(&MYSQL_SCHEMA_NAME);
1663 bzero((char*) &tz_tables[0], sizeof(TABLE_LIST));
1664 tz_tables[0].alias= tz_tables[0].table_name= tmp_table_name;
1665 tz_tables[0].db= MYSQL_SCHEMA_NAME;
1666 tz_tables[0].lock_type= TL_READ;
1667
1668 tz_init_table_list(tz_tables+1);
1669 tz_tables[0].next_global= tz_tables[0].next_local= &tz_tables[1];
1670 tz_tables[1].prev_global= &tz_tables[0].next_global;
1671 init_mdl_requests(tz_tables);
1672
1673 /*
1674 We need to open only mysql.time_zone_leap_second, but we try to
1675 open all time zone tables to see if they exist.
1676 */
1677 if (open_and_lock_tables(thd, tz_tables, FALSE,
1678 MYSQL_OPEN_IGNORE_FLUSH | MYSQL_LOCK_IGNORE_TIMEOUT))
1679 {
1680 sql_print_warning("Can't open and lock time zone table: %s "
1681 "trying to live without them",
1682 thd->get_stmt_da()->message());
1683 /* We will try emulate that everything is ok */
1684 return_val= time_zone_tables_exist= 0;
1685 goto end_with_setting_default_tz;
1686 }
1687
1688 for (TABLE_LIST *tl= tz_tables; tl; tl= tl->next_global)
1689 {
1690 tl->table->use_all_columns();
1691 /* Force close at the end of the function to free memory. */
1692 tl->table->mark_table_for_reopen();
1693 }
1694
1695 /*
1696 Now we are going to load leap seconds descriptions that are shared
1697 between all time zones that use them. We are using index for getting
1698 records in proper order. Since we share the same MEM_ROOT between
1699 all time zones we just allocate enough memory for it first.
1700 */
1701 if (!(tz_lsis= (LS_INFO*) alloc_root(&tz_storage,
1702 sizeof(LS_INFO) * TZ_MAX_LEAPS)))
1703 {
1704 sql_print_error("Fatal error: Out of memory while loading "
1705 "mysql.time_zone_leap_second table");
1706 goto end_with_close;
1707 }
1708
1709 table= tz_tables[0].table;
1710
1711 if (table->file->ha_index_init(0, 1))
1712 goto end_with_close;
1713
1714 table->use_all_columns();
1715 tz_leapcnt= 0;
1716
1717 res= table->file->ha_index_first(table->record[0]);
1718
1719 while (!res)
1720 {
1721 if (tz_leapcnt + 1 > TZ_MAX_LEAPS)
1722 {
1723 sql_print_error("Fatal error: While loading mysql.time_zone_leap_second"
1724 " table: too much leaps");
1725 table->file->ha_index_end();
1726 goto end_with_close;
1727 }
1728
1729 tz_lsis[tz_leapcnt].ls_trans= (my_time_t)table->field[0]->val_int();
1730 tz_lsis[tz_leapcnt].ls_corr= (long)table->field[1]->val_int();
1731
1732 tz_leapcnt++;
1733
1734 DBUG_PRINT("info",
1735 ("time_zone_leap_second table: tz_leapcnt: %u tt_time: %lu offset: %ld",
1736 tz_leapcnt, (ulong) tz_lsis[tz_leapcnt-1].ls_trans,
1737 tz_lsis[tz_leapcnt-1].ls_corr));
1738
1739 res= table->file->ha_index_next(table->record[0]);
1740 }
1741
1742 (void)table->file->ha_index_end();
1743
1744 if (res != HA_ERR_END_OF_FILE)
1745 {
1746 sql_print_error("Fatal error: Error while loading "
1747 "mysql.time_zone_leap_second table");
1748 goto end_with_close;
1749 }
1750
1751 /*
1752 Loading of info about leap seconds succeeded
1753 */
1754
1755 return_val= 0;
1756
1757
1758 end_with_setting_default_tz:
1759 /* If we have default time zone try to load it */
1760 if (default_tzname)
1761 {
1762 String tmp_tzname2(default_tzname, &my_charset_latin1);
1763 /*
1764 Time zone tables may be open here, and my_tz_find() may open
1765 most of them once more, but this is OK for system tables open
1766 for READ.
1767 */
1768 if (unlikely(!(global_system_variables.time_zone=
1769 my_tz_find(thd, &tmp_tzname2))))
1770 {
1771 sql_print_error("Fatal error: Illegal or unknown default time zone '%s'",
1772 default_tzname);
1773 return_val= 1;
1774 }
1775 }
1776
1777 end_with_close:
1778 if (time_zone_tables_exist)
1779 close_mysql_tables(thd);
1780
1781 end_with_cleanup:
1782
1783 /* if there were error free time zone describing structs */
1784 if (unlikely(return_val))
1785 my_tz_free();
1786 end:
1787 delete thd;
1788 if (org_thd)
1789 org_thd->store_globals(); /* purecov: inspected */
1790
1791 default_tz= default_tz_name ? global_system_variables.time_zone
1792 : my_tz_SYSTEM;
1793
1794 DBUG_RETURN(return_val);
1795 }
1796
1797
1798 /*
1799 Free resources used by time zone support infrastructure.
1800
1801 SYNOPSIS
1802 my_tz_free()
1803 */
1804
my_tz_free()1805 void my_tz_free()
1806 {
1807 if (tz_inited)
1808 {
1809 tz_inited= 0;
1810 mysql_mutex_destroy(&tz_LOCK);
1811 my_hash_free(&offset_tzs);
1812 my_hash_free(&tz_names);
1813 free_root(&tz_storage, MYF(0));
1814 }
1815 }
1816
1817
1818 /*
1819 Load time zone description from system tables.
1820
1821 SYNOPSIS
1822 tz_load_from_open_tables()
1823 tz_name - name of time zone that should be loaded.
1824 tz_tables - list of tables from which time zone description
1825 should be loaded
1826
1827 DESCRIPTION
1828 This function will try to load information about time zone specified
1829 from the list of the already opened and locked tables (first table in
1830 tz_tables should be time_zone_name, next time_zone, then
1831 time_zone_transition_type and time_zone_transition should be last).
1832 It will also update information in hash used for time zones lookup.
1833
1834 RETURN VALUES
1835 Returns pointer to newly created Time_zone object or 0 in case of error.
1836
1837 */
1838
1839 static Time_zone*
tz_load_from_open_tables(const String * tz_name,TABLE_LIST * tz_tables)1840 tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables)
1841 {
1842 TABLE *table= 0;
1843 TIME_ZONE_INFO *tz_info= NULL;
1844 Tz_names_entry *tmp_tzname;
1845 Time_zone *return_val= 0;
1846 int res;
1847 uint tzid, ttid;
1848 my_time_t ttime;
1849 char buff[MAX_FIELD_WIDTH];
1850 uchar keybuff[32];
1851 Field *field;
1852 String abbr(buff, sizeof(buff), &my_charset_latin1);
1853 char *alloc_buff= NULL;
1854 char *tz_name_buff= NULL;
1855 /*
1856 Temporary arrays that are used for loading of data for filling
1857 TIME_ZONE_INFO structure
1858 */
1859 my_time_t ats[TZ_MAX_TIMES];
1860 uchar types[TZ_MAX_TIMES];
1861 TRAN_TYPE_INFO ttis[TZ_MAX_TYPES];
1862 #ifdef ABBR_ARE_USED
1863 char chars[MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))];
1864 #endif
1865 /*
1866 Used as a temporary tz_info until we decide that we actually want to
1867 allocate and keep the tz info and tz name in tz_storage.
1868 */
1869 TIME_ZONE_INFO tmp_tz_info;
1870 memset(&tmp_tz_info, 0, sizeof(TIME_ZONE_INFO));
1871
1872 DBUG_ENTER("tz_load_from_open_tables");
1873
1874 /*
1875 Let us find out time zone id by its name (there is only one index
1876 and it is specifically for this purpose).
1877 */
1878 table= tz_tables->table;
1879 tz_tables= tz_tables->next_local;
1880 table->field[0]->store(tz_name->ptr(), tz_name->length(),
1881 &my_charset_latin1);
1882 if (table->file->ha_index_init(0, 1))
1883 goto end;
1884
1885 if (table->file->ha_index_read_map(table->record[0], table->field[0]->ptr,
1886 HA_WHOLE_KEY, HA_READ_KEY_EXACT))
1887 {
1888 #ifdef EXTRA_DEBUG
1889 /*
1890 Most probably user has mistyped time zone name, so no need to bark here
1891 unless we need it for debugging.
1892 */
1893 sql_print_error("Can't find description of time zone '%.*s'",
1894 tz_name->length(), tz_name->ptr());
1895 #endif
1896 goto end;
1897 }
1898
1899 tzid= (uint)table->field[1]->val_int();
1900
1901 (void)table->file->ha_index_end();
1902
1903 /*
1904 Now we need to lookup record in mysql.time_zone table in order to
1905 understand whenever this timezone uses leap seconds (again we are
1906 using the only index in this table).
1907 */
1908 table= tz_tables->table;
1909 tz_tables= tz_tables->next_local;
1910 field= table->field[0];
1911 field->store((longlong) tzid, TRUE);
1912 DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
1913 field->get_key_image(keybuff,
1914 MY_MIN(field->key_length(), sizeof(keybuff)),
1915 Field::itRAW);
1916 if (table->file->ha_index_init(0, 1))
1917 goto end;
1918
1919 if (table->file->ha_index_read_map(table->record[0], keybuff,
1920 HA_WHOLE_KEY, HA_READ_KEY_EXACT))
1921 {
1922 sql_print_error("Can't find description of time zone '%u'", tzid);
1923 goto end;
1924 }
1925
1926 /* If Uses_leap_seconds == 'Y' */
1927 if (table->field[1]->val_int() == 1)
1928 {
1929 tmp_tz_info.leapcnt= tz_leapcnt;
1930 tmp_tz_info.lsis= tz_lsis;
1931 }
1932
1933 (void)table->file->ha_index_end();
1934
1935 /*
1936 Now we will iterate through records for out time zone in
1937 mysql.time_zone_transition_type table. Because we want records
1938 only for our time zone guess what are we doing?
1939 Right - using special index.
1940 */
1941 table= tz_tables->table;
1942 tz_tables= tz_tables->next_local;
1943 field= table->field[0];
1944 field->store((longlong) tzid, TRUE);
1945 DBUG_ASSERT(field->key_length() <= sizeof(keybuff));
1946 field->get_key_image(keybuff,
1947 MY_MIN(field->key_length(), sizeof(keybuff)),
1948 Field::itRAW);
1949 if (table->file->ha_index_init(0, 1))
1950 goto end;
1951
1952 res= table->file->ha_index_read_map(table->record[0], keybuff,
1953 (key_part_map)1, HA_READ_KEY_EXACT);
1954 while (!res)
1955 {
1956 ttid= (uint)table->field[1]->val_int();
1957
1958 if (ttid >= TZ_MAX_TYPES)
1959 {
1960 sql_print_error("Error while loading time zone description from "
1961 "mysql.time_zone_transition_type table: too big "
1962 "transition type id");
1963 goto end;
1964 }
1965
1966 ttis[ttid].tt_gmtoff= (long)table->field[2]->val_int();
1967 ttis[ttid].tt_isdst= (table->field[3]->val_int() > 0);
1968
1969 #ifdef ABBR_ARE_USED
1970 // FIXME should we do something with duplicates here ?
1971 table->field[4]->val_str(&abbr, &abbr);
1972 if (tmp_tz_info.charcnt + abbr.length() + 1 > sizeof(chars))
1973 {
1974 sql_print_error("Error while loading time zone description from "
1975 "mysql.time_zone_transition_type table: not enough "
1976 "room for abbreviations");
1977 goto end;
1978 }
1979 ttis[ttid].tt_abbrind= tmp_tz_info.charcnt;
1980 memcpy(chars + tmp_tz_info.charcnt, abbr.ptr(), abbr.length());
1981 tmp_tz_info.charcnt+= abbr.length();
1982 chars[tmp_tz_info.charcnt]= 0;
1983 tmp_tz_info.charcnt++;
1984
1985 DBUG_PRINT("info",
1986 ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
1987 "abbr='%s' tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff,
1988 chars + ttis[ttid].tt_abbrind, ttis[ttid].tt_isdst));
1989 #else
1990 DBUG_PRINT("info",
1991 ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
1992 "tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, ttis[ttid].tt_isdst));
1993 #endif
1994
1995 /* ttid is increasing because we are reading using index */
1996 DBUG_ASSERT(ttid >= tmp_tz_info.typecnt);
1997
1998 tmp_tz_info.typecnt= ttid + 1;
1999
2000 res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
2001 }
2002
2003 if (res != HA_ERR_END_OF_FILE)
2004 {
2005 sql_print_error("Error while loading time zone description from "
2006 "mysql.time_zone_transition_type table");
2007 goto end;
2008 }
2009
2010 (void)table->file->ha_index_end();
2011
2012
2013 /*
2014 At last we are doing the same thing for records in
2015 mysql.time_zone_transition table. Here we additionally need records
2016 in ascending order by index scan also satisfies us.
2017 */
2018 table= tz_tables->table;
2019 table->field[0]->store((longlong) tzid, TRUE);
2020 if (table->file->ha_index_init(0, 1))
2021 goto end;
2022
2023 res= table->file->ha_index_read_map(table->record[0], keybuff,
2024 (key_part_map)1, HA_READ_KEY_EXACT);
2025 while (!res)
2026 {
2027 ttime= (my_time_t)table->field[1]->val_int();
2028 ttid= (uint)table->field[2]->val_int();
2029
2030 if (tmp_tz_info.timecnt + 1 > TZ_MAX_TIMES)
2031 {
2032 sql_print_error("Error while loading time zone description from "
2033 "mysql.time_zone_transition table: "
2034 "too much transitions");
2035 goto end;
2036 }
2037 if (ttid + 1 > tmp_tz_info.typecnt)
2038 {
2039 sql_print_error("Error while loading time zone description from "
2040 "mysql.time_zone_transition table: "
2041 "bad transition type id");
2042 goto end;
2043 }
2044
2045 ats[tmp_tz_info.timecnt]= ttime;
2046 types[tmp_tz_info.timecnt]= ttid;
2047 tmp_tz_info.timecnt++;
2048
2049 DBUG_PRINT("info",
2050 ("time_zone_transition table: tz_id: %u tt_time: %lu tt_id: %u",
2051 tzid, (ulong) ttime, ttid));
2052
2053 res= table->file->ha_index_next_same(table->record[0], keybuff, 4);
2054 }
2055
2056 /*
2057 We have to allow HA_ERR_KEY_NOT_FOUND because some time zones
2058 for example UTC have no transitons.
2059 */
2060 if (res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND)
2061 {
2062 sql_print_error("Error while loading time zone description from "
2063 "mysql.time_zone_transition table");
2064 goto end;
2065 }
2066
2067 (void)table->file->ha_index_end();
2068 table= 0;
2069
2070 /*
2071 Let us check how correct our time zone description is. We don't check for
2072 tz->timecnt < 1 since it is ok for GMT.
2073 */
2074 if (tmp_tz_info.typecnt < 1)
2075 {
2076 sql_print_error("loading time zone without transition types");
2077 goto end;
2078 }
2079
2080 /* Allocate memory for the timezone info and timezone name in tz_storage. */
2081 if (!(alloc_buff= (char*) alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) +
2082 tz_name->length() + 1)))
2083 {
2084 sql_print_error("Out of memory while loading time zone description");
2085 return 0;
2086 }
2087
2088 /* Move the temporary tz_info into the allocated area */
2089 tz_info= (TIME_ZONE_INFO *)alloc_buff;
2090 memcpy(tz_info, &tmp_tz_info, sizeof(TIME_ZONE_INFO));
2091 tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO);
2092 /*
2093 By writing zero to the end we guarantee that we can call ptr()
2094 instead of c_ptr() for time zone name.
2095 */
2096 strmake(tz_name_buff, tz_name->ptr(), tz_name->length());
2097
2098 /*
2099 Now we will allocate memory and init TIME_ZONE_INFO structure.
2100 */
2101 if (!(alloc_buff= (char*) alloc_root(&tz_storage,
2102 ALIGN_SIZE(sizeof(my_time_t) *
2103 tz_info->timecnt) +
2104 ALIGN_SIZE(tz_info->timecnt) +
2105 #ifdef ABBR_ARE_USED
2106 ALIGN_SIZE(tz_info->charcnt) +
2107 #endif
2108 sizeof(TRAN_TYPE_INFO) *
2109 tz_info->typecnt)))
2110 {
2111 sql_print_error("Out of memory while loading time zone description");
2112 goto end;
2113 }
2114
2115 tz_info->ats= (my_time_t *) alloc_buff;
2116 memcpy(tz_info->ats, ats, tz_info->timecnt * sizeof(my_time_t));
2117 alloc_buff+= ALIGN_SIZE(sizeof(my_time_t) * tz_info->timecnt);
2118 tz_info->types= (uchar *)alloc_buff;
2119 memcpy(tz_info->types, types, tz_info->timecnt);
2120 alloc_buff+= ALIGN_SIZE(tz_info->timecnt);
2121 #ifdef ABBR_ARE_USED
2122 tz_info->chars= alloc_buff;
2123 memcpy(tz_info->chars, chars, tz_info->charcnt);
2124 alloc_buff+= ALIGN_SIZE(tz_info->charcnt);
2125 #endif
2126 tz_info->ttis= (TRAN_TYPE_INFO *)alloc_buff;
2127 memcpy(tz_info->ttis, ttis, tz_info->typecnt * sizeof(TRAN_TYPE_INFO));
2128
2129 /* Build reversed map. */
2130 if (prepare_tz_info(tz_info, &tz_storage))
2131 {
2132 sql_print_error("Unable to build mktime map for time zone");
2133 goto end;
2134 }
2135
2136
2137 if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()) ||
2138 !(tmp_tzname->tz= new (&tz_storage) Time_zone_db(tz_info,
2139 &(tmp_tzname->name))) ||
2140 (tmp_tzname->name.set(tz_name_buff, tz_name->length(),
2141 &my_charset_latin1),
2142 my_hash_insert(&tz_names, (const uchar *)tmp_tzname)))
2143 {
2144 sql_print_error("Out of memory while loading time zone");
2145 goto end;
2146 }
2147
2148 /*
2149 Loading of time zone succeeded
2150 */
2151 return_val= tmp_tzname->tz;
2152
2153 end:
2154
2155 if (table && table->file->inited)
2156 (void) table->file->ha_index_end();
2157
2158 DBUG_RETURN(return_val);
2159 }
2160
2161
2162 /*
2163 Parse string that specifies time zone as offset from UTC.
2164
2165 SYNOPSIS
2166 str_to_offset()
2167 str - pointer to string which contains offset
2168 length - length of string
2169 offset - out parameter for storing found offset in seconds.
2170
2171 DESCRIPTION
2172 This function parses string which contains time zone offset
2173 in form similar to '+10:00' and converts found value to
2174 seconds from UTC form (east is positive).
2175
2176 RETURN VALUE
2177 0 - Ok
2178 1 - String doesn't contain valid time zone offset
2179 */
2180 my_bool
str_to_offset(const char * str,uint length,long * offset)2181 str_to_offset(const char *str, uint length, long *offset)
2182 {
2183 const char *end= str + length;
2184 my_bool negative;
2185 ulong number_tmp;
2186 long offset_tmp;
2187
2188 if (length < 4)
2189 return 1;
2190
2191 if (*str == '+')
2192 negative= 0;
2193 else if (*str == '-')
2194 negative= 1;
2195 else
2196 return 1;
2197 str++;
2198
2199 number_tmp= 0;
2200
2201 while (str < end && my_isdigit(&my_charset_latin1, *str))
2202 {
2203 number_tmp= number_tmp*10 + *str - '0';
2204 str++;
2205 }
2206
2207 if (str + 1 >= end || *str != ':')
2208 return 1;
2209 str++;
2210
2211 offset_tmp = number_tmp * MINS_PER_HOUR; number_tmp= 0;
2212
2213 while (str < end && my_isdigit(&my_charset_latin1, *str))
2214 {
2215 number_tmp= number_tmp * 10 + *str - '0';
2216 str++;
2217 }
2218
2219 if (str != end)
2220 return 1;
2221
2222 offset_tmp= (offset_tmp + number_tmp) * SECS_PER_MIN;
2223
2224 if (negative)
2225 offset_tmp= -offset_tmp;
2226
2227 /*
2228 Check if offset is in range prescribed by standard
2229 (from -12:59 to 13:00).
2230 */
2231
2232 if (number_tmp > 59 || offset_tmp < -13 * SECS_PER_HOUR + 1 ||
2233 offset_tmp > 13 * SECS_PER_HOUR)
2234 return 1;
2235
2236 *offset= offset_tmp;
2237
2238 return 0;
2239 }
2240
2241
2242 /*
2243 Get Time_zone object for specified time zone.
2244
2245 SYNOPSIS
2246 my_tz_find()
2247 thd - pointer to thread THD structure
2248 name - time zone specification
2249
2250 DESCRIPTION
2251 This function checks if name is one of time zones described in db,
2252 predefined SYSTEM time zone or valid time zone specification as
2253 offset from UTC (In last case it will create proper Time_zone_offset
2254 object if there were not any.). If name is ok it returns corresponding
2255 Time_zone object.
2256
2257 Clients of this function are not responsible for releasing resources
2258 occupied by returned Time_zone object so they can just forget pointers
2259 to Time_zone object if they are not needed longer.
2260
2261 Other important property of this function: if some Time_zone found once
2262 it will be for sure found later, so this function can also be used for
2263 checking if proper Time_zone object exists (and if there will be error
2264 it will be reported during first call).
2265
2266 If name pointer is 0 then this function returns 0 (this allows to pass 0
2267 values as parameter without additional external check and this property
2268 is used by @@time_zone variable handling code).
2269
2270 It will perform lookup in system tables (mysql.time_zone*),
2271 opening and locking them, and closing afterwards. It won't perform
2272 such lookup if no time zone describing tables were found during
2273 server start up.
2274
2275 RETURN VALUE
2276 Pointer to corresponding Time_zone object. 0 - in case of bad time zone
2277 specification or other error.
2278
2279 */
2280 Time_zone *
my_tz_find(THD * thd,const String * name)2281 my_tz_find(THD *thd, const String *name)
2282 {
2283 Tz_names_entry *tmp_tzname;
2284 Time_zone *result_tz= 0;
2285 long offset;
2286 DBUG_ENTER("my_tz_find");
2287 DBUG_PRINT("enter", ("time zone name='%s'",
2288 name ? ((String *)name)->c_ptr_safe() : "NULL"));
2289
2290 if (!name || name->is_empty())
2291 DBUG_RETURN(0);
2292
2293 mysql_mutex_lock(&tz_LOCK);
2294
2295 if (!str_to_offset(name->ptr(), name->length(), &offset))
2296 {
2297 if (!(result_tz= (Time_zone_offset *)my_hash_search(&offset_tzs,
2298 (const uchar *)&offset,
2299 sizeof(long))))
2300 {
2301 DBUG_PRINT("info", ("Creating new Time_zone_offset object"));
2302
2303 if (!(result_tz= new (&tz_storage) Time_zone_offset(offset)) ||
2304 my_hash_insert(&offset_tzs, (const uchar *) result_tz))
2305 {
2306 result_tz= 0;
2307 sql_print_error("Fatal error: Out of memory "
2308 "while setting new time zone");
2309 }
2310 }
2311 }
2312 else
2313 {
2314 result_tz= 0;
2315 if ((tmp_tzname= (Tz_names_entry *)my_hash_search(&tz_names,
2316 (const uchar *)
2317 name->ptr(),
2318 name->length())))
2319 result_tz= tmp_tzname->tz;
2320 else if (time_zone_tables_exist)
2321 {
2322 TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT];
2323 Open_tables_backup open_tables_state_backup;
2324
2325 tz_init_table_list(tz_tables);
2326 init_mdl_requests(tz_tables);
2327 if (!open_system_tables_for_read(thd, tz_tables,
2328 &open_tables_state_backup))
2329 {
2330 result_tz= tz_load_from_open_tables(name, tz_tables);
2331 close_system_tables(thd, &open_tables_state_backup);
2332 }
2333 }
2334 }
2335
2336 mysql_mutex_unlock(&tz_LOCK);
2337
2338 if (result_tz && result_tz != my_tz_SYSTEM && result_tz != my_tz_UTC)
2339 status_var_increment(thd->status_var.feature_timezone);
2340
2341 DBUG_RETURN(result_tz);
2342 }
2343
2344
2345 /**
2346 Convert leap seconds into non-leap
2347
2348 This function will convert the leap seconds added by the OS to
2349 non-leap seconds, e.g. 23:59:59, 23:59:60 -> 23:59:59, 00:00:01 ...
2350 This check is not checking for years on purpose : although it's not a
2351 complete check this way it doesn't require looking (and having installed)
2352 the leap seconds table.
2353
2354 @param[in,out] broken down time structure as filled in by the OS
2355 */
2356
adjust_leap_second(MYSQL_TIME * t)2357 void Time_zone::adjust_leap_second(MYSQL_TIME *t)
2358 {
2359 if (t->second == 60 || t->second == 61)
2360 t->second= 59;
2361 }
2362
2363 #endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */
2364
2365
2366 #ifdef TZINFO2SQL
2367 /*
2368 This code belongs to mysql_tzinfo_to_sql converter command line utility.
2369 This utility should be used by db admin for populating mysql.time_zone
2370 tables.
2371 */
2372
2373 /*
2374 Print info about time zone described by TIME_ZONE_INFO struct as
2375 SQL statements populating mysql.time_zone* tables.
2376
2377 SYNOPSIS
2378 print_tz_as_sql()
2379 tz_name - name of time zone
2380 sp - structure describing time zone
2381 */
2382 void
print_tz_as_sql(const char * tz_name,const TIME_ZONE_INFO * sp)2383 print_tz_as_sql(const char* tz_name, const TIME_ZONE_INFO *sp)
2384 {
2385 uint i;
2386
2387 /* Here we assume that all time zones have same leap correction tables */
2388 printf("INSERT INTO time_zone (Use_leap_seconds) VALUES ('%s');\n",
2389 sp->leapcnt ? "Y" : "N");
2390 printf("SET @time_zone_id= LAST_INSERT_ID();\n");
2391 printf("INSERT INTO time_zone_name (Name, Time_zone_id) VALUES \
2392 ('%s', @time_zone_id);\n", tz_name);
2393
2394 if (sp->timecnt)
2395 {
2396 printf("INSERT INTO time_zone_transition \
2397 (Time_zone_id, Transition_time, Transition_type_id) VALUES\n");
2398 for (i= 0; i < sp->timecnt; i++)
2399 printf("%s(@time_zone_id, %ld, %u)\n", (i == 0 ? " " : ","), sp->ats[i],
2400 (uint)sp->types[i]);
2401 printf(";\n");
2402 }
2403
2404 printf("INSERT INTO time_zone_transition_type \
2405 (Time_zone_id, Transition_type_id, Offset, Is_DST, Abbreviation) VALUES\n");
2406
2407 for (i= 0; i < sp->typecnt; i++)
2408 printf("%s(@time_zone_id, %u, %ld, %d, '%s')\n", (i == 0 ? " " : ","), i,
2409 sp->ttis[i].tt_gmtoff, sp->ttis[i].tt_isdst,
2410 sp->chars + sp->ttis[i].tt_abbrind);
2411 printf(";\n");
2412 }
2413
2414
2415 /*
2416 Print info about leap seconds in time zone as SQL statements
2417 populating mysql.time_zone_leap_second table.
2418
2419 SYNOPSIS
2420 print_tz_leaps_as_sql()
2421 sp - structure describing time zone
2422 */
2423 void
print_tz_leaps_as_sql(const TIME_ZONE_INFO * sp)2424 print_tz_leaps_as_sql(const TIME_ZONE_INFO *sp)
2425 {
2426 uint i;
2427
2428 /*
2429 We are assuming that there are only one list of leap seconds
2430 For all timezones.
2431 */
2432 if (!opt_skip_write_binlog)
2433 printf("\\d |\n"
2434 "IF (select count(*) from information_schema.global_variables where\n"
2435 "variable_name='wsrep_on' and variable_value='ON') = 1 THEN\n"
2436 "ALTER TABLE time_zone_leap_second ENGINE=InnoDB;\n"
2437 "END IF|\n"
2438 "\\d ;\n");
2439
2440 printf("TRUNCATE TABLE time_zone_leap_second;\n");
2441
2442 if (sp->leapcnt)
2443 {
2444 printf("INSERT INTO time_zone_leap_second \
2445 (Transition_time, Correction) VALUES\n");
2446 for (i= 0; i < sp->leapcnt; i++)
2447 printf("%s(%ld, %ld)\n", (i == 0 ? " " : ","),
2448 sp->lsis[i].ls_trans, sp->lsis[i].ls_corr);
2449 printf(";\n");
2450 }
2451
2452 if (!opt_skip_write_binlog)
2453 printf("\\d |\n"
2454 "IF (select count(*) from information_schema.global_variables where\n"
2455 "variable_name='wsrep_on' and variable_value='ON') = 1 THEN\n"
2456 "ALTER TABLE time_zone_leap_second ENGINE=Aria;\n"
2457 "END IF|\n"
2458 "\\d ;\n");
2459
2460 printf("ALTER TABLE time_zone_leap_second ORDER BY Transition_time;\n");
2461 }
2462
2463
2464 /*
2465 Some variables used as temporary or as parameters
2466 in recursive scan_tz_dir() code.
2467 */
2468 TIME_ZONE_INFO tz_info;
2469 MEM_ROOT tz_storage;
2470 char fullname[FN_REFLEN + 1];
2471 char *root_name_end;
2472
2473 /*
2474 known file types that exist in the zoneinfo directory that are safe to
2475 silently skip
2476 */
2477 const char *known_extensions[]= {
2478 ".tab",
2479 NullS
2480 };
2481
2482
2483 /*
2484 Recursively scan zoneinfo directory and print all found time zone
2485 descriptions as SQL.
2486
2487 SYNOPSIS
2488 scan_tz_dir()
2489 name_end - pointer to end of path to directory to be searched.
2490 symlink_recursion_level How many symlink directory levels are used
2491 verbose >0 if we should print warnings
2492
2493 DESCRIPTION
2494 This auxiliary recursive function also uses several global
2495 variables as in parameters and for storing temporary values.
2496
2497 fullname - path to directory that should be scanned.
2498 root_name_end - pointer to place in fullname where part with
2499 path to initial directory ends.
2500 current_tz_id - last used time zone id
2501
2502 RETURN VALUE
2503 0 - Ok, 1 - Fatal error
2504
2505 */
2506 my_bool
scan_tz_dir(char * name_end,uint symlink_recursion_level,uint verbose)2507 scan_tz_dir(char * name_end, uint symlink_recursion_level, uint verbose)
2508 {
2509 MY_DIR *cur_dir;
2510 char *name_end_tmp;
2511 uint i;
2512
2513 /* Sort directory data, to pass mtr tests on different platforms. */
2514 if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT|MY_WANT_SORT))))
2515 return 1;
2516
2517 name_end= strmake(name_end, "/", FN_REFLEN - (name_end - fullname));
2518
2519 for (i= 0; i < cur_dir->number_of_files; i++)
2520 {
2521 if (cur_dir->dir_entry[i].name[0] != '.' &&
2522 strcmp(cur_dir->dir_entry[i].name, "Factory"))
2523 {
2524 name_end_tmp= strmake(name_end, cur_dir->dir_entry[i].name,
2525 FN_REFLEN - (name_end - fullname));
2526
2527 if (MY_S_ISDIR(cur_dir->dir_entry[i].mystat->st_mode))
2528 {
2529 my_bool is_symlink;
2530 if ((is_symlink= my_is_symlink(fullname)) &&
2531 symlink_recursion_level > 0)
2532 {
2533 /*
2534 The timezone definition data in some Linux distributions
2535 (e.g. the "timezone-data-2013f" package in Gentoo)
2536 may have synlimks like:
2537 /usr/share/zoneinfo/posix/ -> /usr/share/zoneinfo/,
2538 so the same timezone files are available under two names
2539 (e.g. "CET" and "posix/CET").
2540
2541 We allow one level of symlink recursion for backward
2542 compatibility with earlier timezone data packages that have
2543 duplicate copies of the same timezone files inside the root
2544 directory and the "posix" subdirectory (instead of symlinking).
2545 This makes "posix/CET" still available, but helps to avoid
2546 following such symlinks infinitely:
2547 /usr/share/zoneinfo/posix/posix/posix/.../posix/
2548 */
2549
2550 /*
2551 This is a normal case and not critical. only print warning if
2552 verbose mode is choosen.
2553 */
2554 if (verbose > 0)
2555 {
2556 fflush(stdout);
2557 fprintf(stderr, "Warning: Skipping directory '%s': "
2558 "to avoid infinite symlink recursion.\n", fullname);
2559 }
2560 continue;
2561 }
2562 if (scan_tz_dir(name_end_tmp, symlink_recursion_level + is_symlink,
2563 verbose))
2564 {
2565 my_dirend(cur_dir);
2566 return 1;
2567 }
2568 }
2569 else if (MY_S_ISREG(cur_dir->dir_entry[i].mystat->st_mode))
2570 {
2571 init_alloc_root(&tz_storage, "timezone_storage", 32768, 0,
2572 MYF(MY_THREAD_SPECIFIC));
2573 if (!tz_load(fullname, &tz_info, &tz_storage))
2574 print_tz_as_sql(root_name_end + 1, &tz_info);
2575 else
2576 {
2577 /*
2578 Some systems (like debian, opensuse etc) have description
2579 files (.tab). We skip these silently if verbose is > 0
2580 */
2581 const char *current_ext= fn_ext(fullname);
2582 my_bool known_ext= 0;
2583
2584 for (const char **ext= known_extensions ; *ext ; ext++)
2585 {
2586 if (!strcmp(*ext, current_ext))
2587 {
2588 known_ext= 1;
2589 break;
2590 }
2591 }
2592 if (verbose > 0 || !known_ext)
2593 {
2594 fflush(stdout);
2595 fprintf(stderr,
2596 "Warning: Unable to load '%s' as time zone. Skipping it.\n",
2597 fullname);
2598 }
2599 }
2600 free_root(&tz_storage, MYF(0));
2601 }
2602 else
2603 {
2604 fflush(stdout);
2605 fprintf(stderr, "Warning: '%s' is not regular file or directory\n",
2606 fullname);
2607 }
2608 }
2609 }
2610
2611 my_dirend(cur_dir);
2612
2613 return 0;
2614 }
2615
2616
2617 static const char *load_default_groups[]=
2618 { "mysql_tzinfo_to_sql", 0};
2619
2620 static struct my_option my_long_options[] =
2621 {
2622 {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG,
2623 0, 0, 0, 0, 0, 0},
2624 #ifdef DBUG_OFF
2625 {"debug", '#', "This is a non-debug version. Catch this and exit.",
2626 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
2627 #else
2628 {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.",
2629 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
2630 #endif
2631 {"leap", 'l', "Print the leap second information from the given time zone file. By convention, when --leap is used the next argument is the timezonefile.",
2632 &opt_leap, &opt_leap, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
2633 {"verbose", 'v', "Write non critical warnings.",
2634 &opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
2635 {"version", 'V', "Output version information and exit.",
2636 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
2637 {"skip-write-binlog", 'S', "Do not replicate changes to time zone tables to the binary log, or to other nodes in a Galera cluster (if wsrep_on=ON).",
2638 &opt_skip_write_binlog,&opt_skip_write_binlog, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
2639 { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
2640 };
2641
2642
2643 static char **default_argv;
2644
free_allocated_data()2645 static void free_allocated_data()
2646 {
2647 free_defaults(default_argv);
2648 my_end(0);
2649 }
2650
2651
2652 C_MODE_START
2653 static my_bool get_one_option(int optid, const struct my_option *,
2654 char *argument);
2655 C_MODE_END
2656
print_version(void)2657 static void print_version(void)
2658 {
2659 printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname, PROGRAM_VERSION,
2660 MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE);
2661 }
2662
2663 static const char *default_timezone_dir= "/usr/share/zoneinfo/";
2664
2665
print_usage(void)2666 static void print_usage(void)
2667 {
2668 fprintf(stdout, "Create SQL commands for loading system timezeone data for "
2669 "MariaDB\n\n");
2670 fprintf(stdout, "Usage:\n");
2671 fprintf(stdout, " %s [options] timezonedir\n", my_progname);
2672 fprintf(stdout, "or\n");
2673 fprintf(stdout, " %s [options] timezonefile timezonename\n", my_progname);
2674
2675 fprintf(stdout, "\nA typical place for the system timezone directory is "
2676 "\"%s\"\n", default_timezone_dir);
2677
2678 print_defaults("my",load_default_groups);
2679 puts("");
2680 my_print_help(my_long_options);
2681 my_print_variables(my_long_options);
2682 }
2683
2684
2685 static my_bool
get_one_option(int optid,const struct my_option * opt,char * argument)2686 get_one_option(int optid, const struct my_option *opt, char *argument)
2687 {
2688 switch(optid) {
2689 case '#':
2690 #ifndef DBUG_OFF
2691 DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysq_tzinfo_to_sql.trace");
2692 #endif
2693 break;
2694 case '?':
2695 print_version();
2696 puts("");
2697 print_usage();
2698 free_allocated_data();
2699 exit(0);
2700 case 'V':
2701 print_version();
2702 free_allocated_data();
2703 exit(0);
2704 }
2705 return 0;
2706 }
2707
2708
2709 static const char *lock_tables=
2710 "LOCK TABLES time_zone WRITE,\n"
2711 " time_zone_leap_second WRITE,\n"
2712 " time_zone_name WRITE,\n"
2713 " time_zone_transition WRITE,\n"
2714 " time_zone_transition_type WRITE;\n";
2715 static const char *trunc_tables_const=
2716 "TRUNCATE TABLE time_zone;\n"
2717 "TRUNCATE TABLE time_zone_name;\n"
2718 "TRUNCATE TABLE time_zone_transition;\n"
2719 "TRUNCATE TABLE time_zone_transition_type;\n";
2720
2721 int
main(int argc,char ** argv)2722 main(int argc, char **argv)
2723 {
2724 const char *trunc_tables;
2725 MY_INIT(argv[0]);
2726
2727 load_defaults_or_exit("my", load_default_groups, &argc, &argv);
2728 default_argv= argv;
2729
2730 if ((handle_options(&argc, &argv, my_long_options, get_one_option)))
2731 exit(1);
2732
2733 if ((argc != 1 && argc != 2) || (opt_leap && argc != 1))
2734 {
2735 print_usage();
2736 free_allocated_data();
2737 return 1;
2738 }
2739
2740 if (!(argc == 1 && !opt_leap))
2741 trunc_tables= "SELECT 'skip truncate tables';\n"; // No-op - needed for ELSE clause
2742 else
2743 trunc_tables= trunc_tables_const;
2744
2745 if (opt_skip_write_binlog)
2746 /* If skip_write_binlog is set and wsrep is compiled in we disable
2747 sql_log_bin and wsrep_on to avoid Galera replicating below
2748 TRUNCATE TABLE clauses. This will allow user to set different
2749 time zones to nodes in Galera cluster. */
2750 printf("set @prep1=if((select count(*) from information_schema.global_variables where variable_name='wsrep_on' and variable_value='ON'), 'SET SESSION SQL_LOG_BIN=?, WSREP_ON=OFF;', 'do ?');\n"
2751 "prepare set_wsrep_write_binlog from @prep1;\n"
2752 "set @toggle=0; execute set_wsrep_write_binlog using @toggle;\n"
2753 "%s%s", trunc_tables, lock_tables);
2754 else
2755 // Alter time zone tables to InnoDB if wsrep_on is enabled
2756 // to allow changes to them to replicate with Galera
2757 printf("\\d |\n"
2758 "IF (select count(*) from information_schema.global_variables where\n"
2759 "variable_name='wsrep_on' and variable_value='ON') = 1 THEN\n"
2760 "ALTER TABLE time_zone ENGINE=InnoDB;\n"
2761 "ALTER TABLE time_zone_name ENGINE=InnoDB;\n"
2762 "ALTER TABLE time_zone_transition ENGINE=InnoDB;\n"
2763 "ALTER TABLE time_zone_transition_type ENGINE=InnoDB;\n"
2764 "%s"
2765 "START TRANSACTION;\n"
2766 "ELSE\n%s"
2767 "END IF|\n"
2768 "\\d ;\n",
2769 trunc_tables, trunc_tables);
2770 // Ideally we'd like to put lock_tables in the ELSE branch however
2771 // "ERROR 1314 (0A000) at line 2: LOCK is not allowed in stored procedures"
2772
2773 if (argc == 1 && !opt_leap)
2774 {
2775 /* Argument is timezonedir */
2776
2777 root_name_end= strmake_buf(fullname, argv[0]);
2778
2779 if (scan_tz_dir(root_name_end, 0, opt_verbose))
2780 {
2781 printf("ROLLBACK;\n");
2782 fflush(stdout);
2783 fprintf(stderr,
2784 "There were fatal errors during processing "
2785 "of zoneinfo directory '%s'\n", fullname);
2786 return 1;
2787 }
2788
2789 printf("UNLOCK TABLES;\n"
2790 "COMMIT;\n");
2791 printf("ALTER TABLE time_zone_transition "
2792 "ORDER BY Time_zone_id, Transition_time;\n");
2793 printf("ALTER TABLE time_zone_transition_type "
2794 "ORDER BY Time_zone_id, Transition_type_id;\n");
2795 }
2796 else
2797 {
2798 /*
2799 First argument is timezonefile.
2800 The second is timezonename if opt_leap is not given
2801 */
2802 init_alloc_root(&tz_storage, "timezone_storage", 32768, 0, MYF(0));
2803
2804 if (tz_load(argv[0], &tz_info, &tz_storage))
2805 {
2806 fflush(stdout);
2807 fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[0]);
2808 return 1;
2809 }
2810 if (opt_leap)
2811 print_tz_leaps_as_sql(&tz_info);
2812 else
2813 print_tz_as_sql(argv[1], &tz_info);
2814 printf("UNLOCK TABLES;\n"
2815 "COMMIT;\n");
2816 free_root(&tz_storage, MYF(0));
2817 }
2818
2819 if(!opt_skip_write_binlog)
2820 // Fall back to Aria
2821 printf("\\d |\n"
2822 "IF (select count(*) from information_schema.global_variables where\n"
2823 "variable_name='wsrep_on' and variable_value='ON') = 1 THEN\n"
2824 "ALTER TABLE time_zone ENGINE=Aria;\n"
2825 "ALTER TABLE time_zone_name ENGINE=Aria;\n"
2826 "ALTER TABLE time_zone_transition ENGINE=Aria, ORDER BY Time_zone_id, Transition_time;\n"
2827 "ALTER TABLE time_zone_transition_type ENGINE=Aria, ORDER BY Time_zone_id, Transition_type_id;\n"
2828 "END IF|\n"
2829 "\\d ;\n");
2830
2831 free_allocated_data();
2832 my_end(0);
2833 return 0;
2834 }
2835
2836 #endif /* defined(TZINFO2SQL) */
2837
2838
2839 #ifdef TESTTIME
2840
2841 /*
2842 Some simple brute-force test which allowed to catch a pair of bugs.
2843 Also can provide interesting facts about system's time zone support
2844 implementation.
2845 */
2846
2847 #ifndef CHAR_BIT
2848 #define CHAR_BIT 8
2849 #endif
2850
2851 #ifndef TYPE_BIT
2852 #define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
2853 #endif
2854
2855 #ifndef TYPE_SIGNED
2856 #define TYPE_SIGNED(type) (((type) -1) < 0)
2857 #endif
2858
2859 my_bool
is_equal_TIME_tm(const TIME * time_arg,const struct tm * tm_arg)2860 is_equal_TIME_tm(const TIME* time_arg, const struct tm * tm_arg)
2861 {
2862 return (time_arg->year == (uint)tm_arg->tm_year+TM_YEAR_BASE) &&
2863 (time_arg->month == (uint)tm_arg->tm_mon+1) &&
2864 (time_arg->day == (uint)tm_arg->tm_mday) &&
2865 (time_arg->hour == (uint)tm_arg->tm_hour) &&
2866 (time_arg->minute == (uint)tm_arg->tm_min) &&
2867 (time_arg->second == (uint)tm_arg->tm_sec) &&
2868 time_arg->second_part == 0;
2869 }
2870
2871
2872 int
main(int argc,char ** argv)2873 main(int argc, char **argv)
2874 {
2875 my_bool localtime_negative;
2876 TIME_ZONE_INFO tz_info;
2877 struct tm tmp;
2878 MYSQL_TIME time_tmp;
2879 time_t t, t1, t2;
2880 char fullname[FN_REFLEN+1];
2881 char *str_end;
2882 MEM_ROOT tz_storage;
2883
2884 MY_INIT(argv[0]);
2885
2886 init_alloc_root(&tz_storage, "timezone_storage", 32768, MYF(0));
2887
2888 /* let us set some well known timezone */
2889 setenv("TZ", "MET", 1);
2890 tzset();
2891
2892 /* Some initial time zone related system info */
2893 printf("time_t: %s %u bit\n", TYPE_SIGNED(time_t) ? "signed" : "unsigned",
2894 (uint)TYPE_BIT(time_t));
2895 if (TYPE_SIGNED(time_t))
2896 {
2897 t= -100;
2898 localtime_negative= MY_TEST(localtime_r(&t, &tmp) != 0);
2899 printf("localtime_r %s negative params \
2900 (time_t=%d is %d-%d-%d %d:%d:%d)\n",
2901 (localtime_negative ? "supports" : "doesn't support"), (int)t,
2902 TM_YEAR_BASE + tmp.tm_year, tmp.tm_mon + 1, tmp.tm_mday,
2903 tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
2904
2905 printf("mktime %s negative results (%d)\n",
2906 (t == mktime(&tmp) ? "doesn't support" : "supports"),
2907 (int)mktime(&tmp));
2908 }
2909
2910 tmp.tm_year= 103; tmp.tm_mon= 2; tmp.tm_mday= 30;
2911 tmp.tm_hour= 2; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= -1;
2912 t= mktime(&tmp);
2913 printf("mktime returns %s for spring time gap (%d)\n",
2914 (t != (time_t)-1 ? "something" : "error"), (int)t);
2915
2916 tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
2917 tmp.tm_hour= 0; tmp.tm_min= 0; tmp.tm_sec= 0; tmp.tm_isdst= 0;
2918 t= mktime(&tmp);
2919 printf("mktime returns %s for non existing date (%d)\n",
2920 (t != (time_t)-1 ? "something" : "error"), (int)t);
2921
2922 tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
2923 tmp.tm_hour= 25; tmp.tm_min=0; tmp.tm_sec=0; tmp.tm_isdst=1;
2924 t= mktime(&tmp);
2925 printf("mktime %s unnormalized input (%d)\n",
2926 (t != (time_t)-1 ? "handles" : "doesn't handle"), (int)t);
2927
2928 tmp.tm_year= 103; tmp.tm_mon= 9; tmp.tm_mday= 26;
2929 tmp.tm_hour= 0; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= 1;
2930 mktime(&tmp);
2931 tmp.tm_hour= 2; tmp.tm_isdst= -1;
2932 t= mktime(&tmp);
2933 tmp.tm_hour= 4; tmp.tm_isdst= 0;
2934 mktime(&tmp);
2935 tmp.tm_hour= 2; tmp.tm_isdst= -1;
2936 t1= mktime(&tmp);
2937 printf("mktime is %s (%d %d)\n",
2938 (t == t1 ? "determenistic" : "is non-determenistic"),
2939 (int)t, (int)t1);
2940
2941 /* Let us load time zone description */
2942 str_end= strmake_buf(fullname, TZDIR);
2943 strmake(str_end, "/MET", FN_REFLEN - (str_end - fullname));
2944
2945 if (tz_load(fullname, &tz_info, &tz_storage))
2946 {
2947 printf("Unable to load time zone info from '%s'\n", fullname);
2948 free_root(&tz_storage, MYF(0));
2949 return 1;
2950 }
2951
2952 printf("Testing our implementation\n");
2953
2954 if (TYPE_SIGNED(time_t) && localtime_negative)
2955 {
2956 for (t= -40000; t < 20000; t++)
2957 {
2958 localtime_r(&t, &tmp);
2959 gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
2960 if (!is_equal_TIME_tm(&time_tmp, &tmp))
2961 {
2962 printf("Problem with negative time_t = %d\n", (int)t);
2963 free_root(&tz_storage, MYF(0));
2964 return 1;
2965 }
2966 }
2967 printf("gmt_sec_to_TIME = localtime for time_t in [-40000,20000) range\n");
2968 }
2969
2970 for (t= 1000000000; t < 1100000000; t+= 13)
2971 {
2972 localtime_r(&t,&tmp);
2973 gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
2974
2975 if (!is_equal_TIME_tm(&time_tmp, &tmp))
2976 {
2977 printf("Problem with time_t = %d\n", (int)t);
2978 free_root(&tz_storage, MYF(0));
2979 return 1;
2980 }
2981 }
2982 printf("gmt_sec_to_TIME = localtime for time_t in [1000000000,1100000000) range\n");
2983
2984 my_init_time();
2985
2986 /*
2987 Be careful here! my_system_gmt_sec doesn't fully handle unnormalized
2988 dates.
2989 */
2990 for (time_tmp.year= 1980; time_tmp.year < 2010; time_tmp.year++)
2991 {
2992 for (time_tmp.month= 1; time_tmp.month < 13; time_tmp.month++)
2993 {
2994 for (time_tmp.day= 1;
2995 time_tmp.day < mon_lengths[isleap(time_tmp.year)][time_tmp.month-1];
2996 time_tmp.day++)
2997 {
2998 for (time_tmp.hour= 0; time_tmp.hour < 24; time_tmp.hour++)
2999 {
3000 for (time_tmp.minute= 0; time_tmp.minute < 60; time_tmp.minute+= 5)
3001 {
3002 for (time_tmp.second=0; time_tmp.second<60; time_tmp.second+=25)
3003 {
3004 long not_used;
3005 uint not_used_2;
3006 t= (time_t)my_system_gmt_sec(&time_tmp, ¬_used, ¬_used_2);
3007 t1= (time_t)TIME_to_gmt_sec(&time_tmp, &tz_info, ¬_used_2);
3008 if (t != t1)
3009 {
3010 /*
3011 We need special handling during autumn since my_system_gmt_sec
3012 prefers greater time_t values (in MET) for ambiguity.
3013 And BTW that is a bug which should be fixed !!!
3014 */
3015 tmp.tm_year= time_tmp.year - TM_YEAR_BASE;
3016 tmp.tm_mon= time_tmp.month - 1;
3017 tmp.tm_mday= time_tmp.day;
3018 tmp.tm_hour= time_tmp.hour;
3019 tmp.tm_min= time_tmp.minute;
3020 tmp.tm_sec= time_tmp.second;
3021 tmp.tm_isdst= 1;
3022
3023 t2= mktime(&tmp);
3024
3025 if (t1 == t2)
3026 continue;
3027
3028 printf("Problem: %u/%u/%u %u:%u:%u with times t=%d, t1=%d\n",
3029 time_tmp.year, time_tmp.month, time_tmp.day,
3030 time_tmp.hour, time_tmp.minute, time_tmp.second,
3031 (int)t,(int)t1);
3032
3033 free_root(&tz_storage, MYF(0));
3034 return 1;
3035 }
3036 }
3037 }
3038 }
3039 }
3040 }
3041 }
3042
3043 printf("TIME_to_gmt_sec = my_system_gmt_sec for test range\n");
3044
3045 free_root(&tz_storage, MYF(0));
3046 return 0;
3047 }
3048
3049 #endif /* defined(TESTTIME) */
3050