tztime.cc 81.6 KB
Newer Older
1 2 3 4
/* Copyright (C) 2004 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
5
   the Free Software Foundation; version 2 of the License.
6 7 8 9 10 11 12 13 14 15 16

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/*
serg@serg.mylan's avatar
serg@serg.mylan committed
17 18
   Most of the following code and structures were derived from
   public domain code from ftp://elsie.nci.nih.gov/pub
19 20 21
   (We will refer to this code as to elsie-code further.)
*/

22 23 24 25
/*
  We should not include mysql_priv.h in mysql_tzinfo_to_sql utility since
  it creates unsolved link dependencies on some platforms.
*/
kent@mysql.com's avatar
kent@mysql.com committed
26 27 28 29 30

#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation				// gcc: Class implementation
#endif

31
#include <my_global.h>
32
#if !defined(TZINFO2SQL) && !defined(TESTTIME)
33
#include "mysql_priv.h"
34
#else
35
#include <my_time.h>
36 37 38 39
#include "tztime.h"
#include <my_sys.h>
#endif

40 41 42 43 44 45 46 47 48
#include "tzfile.h"
#include <m_string.h>
#include <my_dir.h>

/*
  Now we don't use abbreviations in server but we will do this in future.
*/
#if defined(TZINFO2SQL) || defined(TESTTIME)
#define ABBR_ARE_USED
49 50 51
#else
#if !defined(DBUG_OFF)
/* Let use abbreviations for debug purposes */
52 53
#undef ABBR_ARE_USED
#define ABBR_ARE_USED
54 55
#endif /* !defined(DBUG_OFF) */
#endif /* defined(TZINFO2SQL) || defined(TESTTIME) */
56 57 58 59 60

/* Structure describing local time type (e.g. Moscow summer time (MSD)) */
typedef struct ttinfo
{
  long tt_gmtoff; // Offset from UTC in seconds
61
  uint tt_isdst;   // Is daylight saving time or not. Used to set tm_isdst
62
#ifdef ABBR_ARE_USED
63
  uint tt_abbrind; // Index of start of abbreviation for this time type.
64
#endif
serg@serg.mylan's avatar
serg@serg.mylan committed
65
  /*
66 67
    We don't use tt_ttisstd and tt_ttisgmt members of original elsie-code
    struct since we don't support POSIX-style TZ descriptions in variables.
68 69 70 71
  */
} TRAN_TYPE_INFO;

/* Structure describing leap-second corrections. */
serg@serg.mylan's avatar
serg@serg.mylan committed
72 73
typedef struct lsinfo
{
74 75 76 77 78
  my_time_t ls_trans; // Transition time
  long      ls_corr;  // Correction to apply
} LS_INFO;

/*
serg@serg.mylan's avatar
serg@serg.mylan committed
79
  Structure with information describing ranges of my_time_t shifted to local
80
  time (my_time_t + offset). Used for local MYSQL_TIME -> my_time_t conversion.
81 82
  See comments for TIME_to_gmt_sec() for more info.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
83
typedef struct revtinfo
84 85
{
  long rt_offset; // Offset of local time from UTC in seconds
86
  uint rt_type;    // Type of period 0 - Normal period. 1 - Spring time-gap
87 88 89 90 91 92 93 94 95 96
} REVT_INFO;

#ifdef TZNAME_MAX
#define MY_TZNAME_MAX	TZNAME_MAX
#endif
#ifndef TZNAME_MAX
#define MY_TZNAME_MAX	255
#endif

/*
serg@serg.mylan's avatar
serg@serg.mylan committed
97
  Structure which fully describes time zone which is
98 99
  described in our db or in zoneinfo files.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
100
typedef struct st_time_zone_info
101
{
102 103 104 105 106
  uint leapcnt;  // Number of leap-second corrections
  uint timecnt;  // Number of transitions between time types
  uint typecnt;  // Number of local time types
  uint charcnt;  // Number of characters used for abbreviations
  uint revcnt;   // Number of transition descr. for TIME->my_time_t conversion
107 108
  /* The following are dynamical arrays are allocated in MEM_ROOT */
  my_time_t *ats;       // Times of transitions between time types
monty@mysql.com's avatar
monty@mysql.com committed
109
  uchar	*types; // Local time types for transitions
110 111 112 113 114
  TRAN_TYPE_INFO *ttis; // Local time types descriptions
#ifdef ABBR_ARE_USED
  /* Storage for local time types abbreviations. They are stored as ASCIIZ */
  char *chars;
#endif
serg@serg.mylan's avatar
serg@serg.mylan committed
115 116
  /*
    Leap seconds corrections descriptions, this array is shared by
117 118 119
    all time zones who use leap seconds.
  */
  LS_INFO *lsis;
serg@serg.mylan's avatar
serg@serg.mylan committed
120 121
  /*
    Starting points and descriptions of shifted my_time_t (my_time_t + offset)
122 123 124 125 126 127 128 129 130 131
    ranges on which shifted my_time_t -> my_time_t mapping is linear or undefined.
    Used for tm -> my_time_t conversion.
  */
  my_time_t *revts;
  REVT_INFO *revtis;
  /*
    Time type which is used for times smaller than first transition or if
    there are no transitions at all.
  */
  TRAN_TYPE_INFO *fallback_tti;
serg@serg.mylan's avatar
serg@serg.mylan committed
132

133 134 135 136 137 138 139 140 141 142
} TIME_ZONE_INFO;


static my_bool prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage);


#if defined(TZINFO2SQL) || defined(TESTTIME)

/*
  Load time zone description from zoneinfo (TZinfo) file.
serg@serg.mylan's avatar
serg@serg.mylan committed
143

144 145 146
  SYNOPSIS
    tz_load()
      name - path to zoneinfo file
serg@serg.mylan's avatar
serg@serg.mylan committed
147
      sp   - TIME_ZONE_INFO structure to fill
148 149 150 151 152 153 154 155

  RETURN VALUES
    0 - Ok
    1 - Error
*/
static my_bool
tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage)
{
156
  uchar *p;
157 158
  int read_from_file;
  uint i;
159
  FILE *file;
serg@serg.mylan's avatar
serg@serg.mylan committed
160

161 162 163 164 165 166
  if (!(file= my_fopen(name, O_RDONLY|O_BINARY, MYF(MY_WME))))
    return 1;
  {
    union
    {
      struct tzhead tzhead;
167 168
      uchar buf[sizeof(struct tzhead) + sizeof(my_time_t) * TZ_MAX_TIMES +
                TZ_MAX_TIMES + sizeof(TRAN_TYPE_INFO) * TZ_MAX_TYPES +
169 170 171 172 173
#ifdef ABBR_ARE_USED
               max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) +
#endif
               sizeof(LS_INFO) * TZ_MAX_LEAPS];
    } u;
174 175
    uint ttisstdcnt;
    uint ttisgmtcnt;
176
    char *tzinfo_buf;
serg@serg.mylan's avatar
serg@serg.mylan committed
177

178
    read_from_file= my_fread(file, u.buf, sizeof(u.buf), MYF(MY_WME));
179 180 181 182

    if (my_fclose(file, MYF(MY_WME)) != 0)
      return 1;

183
    if (read_from_file < (int)sizeof(struct tzhead))
184
      return 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
185

186 187 188 189 190 191
    ttisstdcnt= int4net(u.tzhead.tzh_ttisgmtcnt);
    ttisgmtcnt= int4net(u.tzhead.tzh_ttisstdcnt);
    sp->leapcnt= int4net(u.tzhead.tzh_leapcnt);
    sp->timecnt= int4net(u.tzhead.tzh_timecnt);
    sp->typecnt= int4net(u.tzhead.tzh_typecnt);
    sp->charcnt= int4net(u.tzhead.tzh_charcnt);
192
    p= u.tzhead.tzh_charcnt + sizeof(u.tzhead.tzh_charcnt);
193 194 195 196
    if (sp->leapcnt > TZ_MAX_LEAPS ||
        sp->typecnt == 0 || sp->typecnt > TZ_MAX_TYPES ||
        sp->timecnt > TZ_MAX_TIMES ||
        sp->charcnt > TZ_MAX_CHARS ||
197 198 199
        (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
        (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
      return 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
200
    if ((uint)(read_from_file - (p - u.buf)) <
201
        sp->timecnt * 4 +                       /* ats */
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        sp->timecnt +                           /* types */
        sp->typecnt * (4 + 2) +                 /* ttinfos */
        sp->charcnt +                           /* chars */
        sp->leapcnt * (4 + 4) +                 /* lsinfos */
        ttisstdcnt +                            /* ttisstds */
        ttisgmtcnt)                             /* ttisgmts */
      return 1;

    if (!(tzinfo_buf= (char *)alloc_root(storage,
                                         ALIGN_SIZE(sp->timecnt *
                                                    sizeof(my_time_t)) +
                                         ALIGN_SIZE(sp->timecnt) +
                                         ALIGN_SIZE(sp->typecnt *
                                                    sizeof(TRAN_TYPE_INFO)) +
#ifdef ABBR_ARE_USED
                                         ALIGN_SIZE(sp->charcnt) +
#endif
                                         sp->leapcnt * sizeof(LS_INFO))))
      return 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
221

222 223
    sp->ats= (my_time_t *)tzinfo_buf;
    tzinfo_buf+= ALIGN_SIZE(sp->timecnt * sizeof(my_time_t));
monty@mysql.com's avatar
monty@mysql.com committed
224
    sp->types= (uchar *)tzinfo_buf;
225 226 227 228 229 230 231 232
    tzinfo_buf+= ALIGN_SIZE(sp->timecnt);
    sp->ttis= (TRAN_TYPE_INFO *)tzinfo_buf;
    tzinfo_buf+= ALIGN_SIZE(sp->typecnt * sizeof(TRAN_TYPE_INFO));
#ifdef ABBR_ARE_USED
    sp->chars= tzinfo_buf;
    tzinfo_buf+= ALIGN_SIZE(sp->charcnt);
#endif
    sp->lsis= (LS_INFO *)tzinfo_buf;
serg@serg.mylan's avatar
serg@serg.mylan committed
233

234 235
    for (i= 0; i < sp->timecnt; i++, p+= 4)
      sp->ats[i]= int4net(p);
serg@serg.mylan's avatar
serg@serg.mylan committed
236

237 238
    for (i= 0; i < sp->timecnt; i++)
    {
monty@mysql.com's avatar
monty@mysql.com committed
239
      sp->types[i]= (uchar) *p++;
240 241 242 243 244 245
      if (sp->types[i] >= sp->typecnt)
        return 1;
    }
    for (i= 0; i < sp->typecnt; i++)
    {
      TRAN_TYPE_INFO * ttisp;
serg@serg.mylan's avatar
serg@serg.mylan committed
246

247 248 249
      ttisp= &sp->ttis[i];
      ttisp->tt_gmtoff= int4net(p);
      p+= 4;
monty@mysql.com's avatar
monty@mysql.com committed
250
      ttisp->tt_isdst= (uchar) *p++;
251 252
      if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
        return 1;
monty@mysql.com's avatar
monty@mysql.com committed
253
      ttisp->tt_abbrind= (uchar) *p++;
254
      if (ttisp->tt_abbrind > sp->charcnt)
255 256 257 258 259 260 261 262
        return 1;
    }
    for (i= 0; i < sp->charcnt; i++)
      sp->chars[i]= *p++;
    sp->chars[i]= '\0';	/* ensure '\0' at end */
    for (i= 0; i < sp->leapcnt; i++)
    {
      LS_INFO *lsisp;
serg@serg.mylan's avatar
serg@serg.mylan committed
263

264 265 266 267 268 269
      lsisp= &sp->lsis[i];
      lsisp->ls_trans= int4net(p);
      p+= 4;
      lsisp->ls_corr= int4net(p);
      p+= 4;
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
270
    /*
271
      Since we don't support POSIX style TZ definitions in variables we
serg@serg.mylan's avatar
serg@serg.mylan committed
272
      don't read further like glibc or elsie code.
273 274
    */
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
275

276 277 278 279 280 281
  return prepare_tz_info(sp, storage);
}
#endif /* defined(TZINFO2SQL) || defined(TESTTIME) */


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
282
  Finish preparation of time zone description for use in TIME_to_gmt_sec()
283
  and gmt_sec_to_TIME() functions.
serg@serg.mylan's avatar
serg@serg.mylan committed
284

285 286 287 288
  SYNOPSIS
    prepare_tz_info()
      sp - pointer to time zone description
      storage - pointer to MEM_ROOT where arrays for map allocated
serg@serg.mylan's avatar
serg@serg.mylan committed
289

290
  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
291 292 293 294
    First task of this function is to find fallback time type which will
    be used if there are no transitions or we have moment in time before
    any transitions.
    Second task is to build "shifted my_time_t" -> my_time_t map used in
295
    MYSQL_TIME -> my_time_t conversion.
serg@serg.mylan's avatar
serg@serg.mylan committed
296
    Note: See description of TIME_to_gmt_sec() function first.
297
    In order to perform MYSQL_TIME -> my_time_t conversion we need to build table
serg@serg.mylan's avatar
serg@serg.mylan committed
298 299 300
    which defines "shifted by tz offset and leap seconds my_time_t" ->
    my_time_t function wich is almost the same (except ranges of ambiguity)
    as reverse function to piecewise linear function used for my_time_t ->
301 302
    "shifted my_time_t" conversion and which is also specified as table in
    zoneinfo file or in our db (It is specified as start of time type ranges
serg@serg.mylan's avatar
serg@serg.mylan committed
303
    and time type offsets). So basic idea is very simple - let us iterate
304 305
    through my_time_t space from one point of discontinuity of my_time_t ->
    "shifted my_time_t" function to another and build our approximation of
serg@serg.mylan's avatar
serg@serg.mylan committed
306
    reverse function. (Actually we iterate through ranges on which
307
    my_time_t -> "shifted my_time_t" is linear function).
serg@serg.mylan's avatar
serg@serg.mylan committed
308

309 310
  RETURN VALUES
    0	Ok
serg@serg.mylan's avatar
serg@serg.mylan committed
311
    1	Error
312
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
313
static my_bool
314 315 316 317 318 319
prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage)
{
  my_time_t cur_t= MY_TIME_T_MIN;
  my_time_t cur_l, end_t, end_l;
  my_time_t cur_max_seen_l= MY_TIME_T_MIN;
  long cur_offset, cur_corr, cur_off_and_corr;
320 321
  uint next_trans_idx, next_leap_idx;
  uint i;
serg@serg.mylan's avatar
serg@serg.mylan committed
322
  /*
323 324 325 326 327 328 329 330 331
    Temporary arrays where we will store tables. Needed because
    we don't know table sizes ahead. (Well we can estimate their
    upper bound but this will take extra space.)
  */
  my_time_t revts[TZ_MAX_REV_RANGES];
  REVT_INFO revtis[TZ_MAX_REV_RANGES];

  LINT_INIT(end_l);

serg@serg.mylan's avatar
serg@serg.mylan committed
332 333 334 335
  /*
    Let us setup fallback time type which will be used if we have not any
    transitions or if we have moment of time before first transition.
    We will find first non-DST local time type and use it (or use first
336 337 338 339 340 341 342
    local time type if all of them are DST types).
  */
  for (i= 0; i < sp->typecnt && sp->ttis[i].tt_isdst; i++)
    /* no-op */ ;
  if (i == sp->typecnt)
    i= 0;
  sp->fallback_tti= &(sp->ttis[i]);
serg@serg.mylan's avatar
serg@serg.mylan committed
343 344 345 346


  /*
    Let us build shifted my_time_t -> my_time_t map.
347 348
  */
  sp->revcnt= 0;
serg@serg.mylan's avatar
serg@serg.mylan committed
349

350 351 352
  /* Let us find initial offset */
  if (sp->timecnt == 0 || cur_t < sp->ats[0])
  {
serg@serg.mylan's avatar
serg@serg.mylan committed
353
    /*
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
      If we have not any transitions or t is before first transition we are using
      already found fallback time type which index is already in i.
    */
    next_trans_idx= 0;
  }
  else
  {
    /* cur_t == sp->ats[0] so we found transition */
    i= sp->types[0];
    next_trans_idx= 1;
  }

  cur_offset= sp->ttis[i].tt_gmtoff;


  /* let us find leap correction... unprobable, but... */
serg@serg.mylan's avatar
serg@serg.mylan committed
370
  for (next_leap_idx= 0; next_leap_idx < sp->leapcnt &&
371 372 373 374 375 376 377 378 379 380 381 382 383
         cur_t >= sp->lsis[next_leap_idx].ls_trans;
         ++next_leap_idx)
    continue;

  if (next_leap_idx > 0)
    cur_corr= sp->lsis[next_leap_idx - 1].ls_corr;
  else
    cur_corr= 0;

  /* Iterate trough t space */
  while (sp->revcnt < TZ_MAX_REV_RANGES - 1)
  {
    cur_off_and_corr= cur_offset - cur_corr;
serg@serg.mylan's avatar
serg@serg.mylan committed
384 385

    /*
386 387 388
      We assuming that cur_t could be only overflowed downwards,
      we also assume that end_t won't be overflowed in this case.
    */
serg@serg.mylan's avatar
serg@serg.mylan committed
389
    if (cur_off_and_corr < 0 &&
390 391
        cur_t < MY_TIME_T_MIN - cur_off_and_corr)
      cur_t= MY_TIME_T_MIN - cur_off_and_corr;
serg@serg.mylan's avatar
serg@serg.mylan committed
392

393
    cur_l= cur_t + cur_off_and_corr;
serg@serg.mylan's avatar
serg@serg.mylan committed
394 395

    /*
396 397 398 399 400
      Let us choose end_t as point before next time type change or leap
      second correction.
    */
    end_t= min((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1:
                                                MY_TIME_T_MAX,
serg@serg.mylan's avatar
serg@serg.mylan committed
401
               (next_leap_idx < sp->leapcnt) ?
402
                 sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX);
serg@serg.mylan's avatar
serg@serg.mylan committed
403
    /*
404 405 406 407 408 409
      again assuming that end_t can be overlowed only in positive side
      we also assume that end_t won't be overflowed in this case.
    */
    if (cur_off_and_corr > 0 &&
        end_t > MY_TIME_T_MAX - cur_off_and_corr)
      end_t= MY_TIME_T_MAX - cur_off_and_corr;
serg@serg.mylan's avatar
serg@serg.mylan committed
410

411
    end_l= end_t + cur_off_and_corr;
serg@serg.mylan's avatar
serg@serg.mylan committed
412

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437

    if (end_l > cur_max_seen_l)
    {
      /* We want special handling in the case of first range */
      if (cur_max_seen_l == MY_TIME_T_MIN)
      {
        revts[sp->revcnt]= cur_l;
        revtis[sp->revcnt].rt_offset= cur_off_and_corr;
        revtis[sp->revcnt].rt_type= 0;
        sp->revcnt++;
        cur_max_seen_l= end_l;
      }
      else
      {
        if (cur_l > cur_max_seen_l + 1)
        {
          /* We have a spring time-gap and we are not at the first range */
          revts[sp->revcnt]= cur_max_seen_l + 1;
          revtis[sp->revcnt].rt_offset= revtis[sp->revcnt-1].rt_offset;
          revtis[sp->revcnt].rt_type= 1;
          sp->revcnt++;
          if (sp->revcnt == TZ_MAX_TIMES + TZ_MAX_LEAPS + 1)
            break; /* That was too much */
          cur_max_seen_l= cur_l - 1;
        }
serg@serg.mylan's avatar
serg@serg.mylan committed
438

439 440 441 442 443 444 445 446 447 448
        /* Assume here end_l > cur_max_seen_l (because end_l>=cur_l) */

        revts[sp->revcnt]= cur_max_seen_l + 1;
        revtis[sp->revcnt].rt_offset= cur_off_and_corr;
        revtis[sp->revcnt].rt_type= 0;
        sp->revcnt++;
        cur_max_seen_l= end_l;
      }
    }

serg@serg.mylan's avatar
serg@serg.mylan committed
449
    if (end_t == MY_TIME_T_MAX ||
450 451
        ((cur_off_and_corr > 0) &&
        (end_t >= MY_TIME_T_MAX - cur_off_and_corr)))
452 453
      /* end of t space */
      break;
serg@serg.mylan's avatar
serg@serg.mylan committed
454

455 456
    cur_t= end_t + 1;

serg@serg.mylan's avatar
serg@serg.mylan committed
457
    /*
458
      Let us find new offset and correction. Because of our choice of end_t
serg@serg.mylan's avatar
serg@serg.mylan committed
459
      cur_t can only be point where new time type starts or/and leap
460 461 462
      correction is performed.
    */
    if (sp->timecnt != 0 && cur_t >= sp->ats[0]) /* else reuse old offset */
serg@serg.mylan's avatar
serg@serg.mylan committed
463
      if (next_trans_idx < sp->timecnt &&
464 465 466 467 468 469
          cur_t == sp->ats[next_trans_idx])
      {
        /* We are at offset point */
        cur_offset= sp->ttis[sp->types[next_trans_idx]].tt_gmtoff;
        ++next_trans_idx;
      }
serg@serg.mylan's avatar
serg@serg.mylan committed
470

471 472 473 474 475 476 477 478
    if (next_leap_idx < sp->leapcnt &&
        cur_t == sp->lsis[next_leap_idx].ls_trans)
    {
      /* we are at leap point */
      cur_corr= sp->lsis[next_leap_idx].ls_corr;
      ++next_leap_idx;
    }
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
  /* check if we have had enough space */
  if (sp->revcnt == TZ_MAX_REV_RANGES - 1)
    return 1;

  /* set maximum end_l as finisher */
  revts[sp->revcnt]= end_l;

  /* Allocate arrays of proper size in sp and copy result there */
  if (!(sp->revts= (my_time_t *)alloc_root(storage,
                                  sizeof(my_time_t) * (sp->revcnt + 1))) ||
      !(sp->revtis= (REVT_INFO *)alloc_root(storage,
                                  sizeof(REVT_INFO) * sp->revcnt)))
    return 1;

  memcpy(sp->revts, revts, sizeof(my_time_t) * (sp->revcnt + 1));
  memcpy(sp->revtis, revtis, sizeof(REVT_INFO) * sp->revcnt);
serg@serg.mylan's avatar
serg@serg.mylan committed
496

497 498 499 500
  return 0;
}


501 502
#if !defined(TZINFO2SQL)

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
static const uint mon_lengths[2][MONS_PER_YEAR]=
{
  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
  { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

static const uint mon_starts[2][MONS_PER_YEAR]=
{
  { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
  { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
};

static const uint year_lengths[2]=
{
  DAYS_PER_NYEAR, DAYS_PER_LYEAR
};

#define LEAPS_THRU_END_OF(y)  ((y) / 4 - (y) / 100 + (y) / 400)


serg@serg.mylan's avatar
serg@serg.mylan committed
523 524
/*
  Converts time from my_time_t representation (seconds in UTC since Epoch)
525
  to broken down representation using given local time zone offset.
serg@serg.mylan's avatar
serg@serg.mylan committed
526

527 528 529 530 531
  SYNOPSIS
    sec_to_TIME()
      tmp    - pointer to structure for broken down representation
      t      - my_time_t value to be converted
      offset - local time zone offset
serg@serg.mylan's avatar
serg@serg.mylan committed
532

533
  DESCRIPTION
534
    Convert my_time_t with offset to MYSQL_TIME struct. Differs from timesub
serg@serg.mylan's avatar
serg@serg.mylan committed
535 536
    (from elsie code) because doesn't contain any leap correction and
    TM_GMTOFF and is_dst setting and contains some MySQL specific
537 538 539 540
    initialization. Funny but with removing of these we almost have
    glibc's offtime function.
*/
static void
541
sec_to_TIME(MYSQL_TIME * tmp, my_time_t t, long offset)
542 543 544 545 546 547 548
{
  long days;
  long rem;
  int y;
  int yleap;
  const uint *ip;

549 550
  days= (long) (t / SECS_PER_DAY);
  rem=  (long) (t % SECS_PER_DAY);
serg@serg.mylan's avatar
serg@serg.mylan committed
551 552 553

  /*
    We do this as separate step after dividing t, because this
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
    allows us handle times near my_time_t bounds without overflows.
  */
  rem+= offset;
  while (rem < 0)
  {
    rem+= SECS_PER_DAY;
    days--;
  }
  while (rem >= SECS_PER_DAY)
  {
    rem -= SECS_PER_DAY;
    days++;
  }
  tmp->hour= (uint)(rem / SECS_PER_HOUR);
  rem= rem % SECS_PER_HOUR;
  tmp->minute= (uint)(rem / SECS_PER_MIN);
  /*
    A positive leap second requires a special
    representation.  This uses "... ??:59:60" et seq.
  */
  tmp->second= (uint)(rem % SECS_PER_MIN);
serg@serg.mylan's avatar
serg@serg.mylan committed
575

576 577 578 579
  y= EPOCH_YEAR;
  while (days < 0 || days >= (long)year_lengths[yleap= isleap(y)])
  {
    int	newy;
serg@serg.mylan's avatar
serg@serg.mylan committed
580

581 582 583 584 585 586 587 588 589
    newy= y + days / DAYS_PER_NYEAR;
    if (days < 0)
      newy--;
    days-= (newy - y) * DAYS_PER_NYEAR +
           LEAPS_THRU_END_OF(newy - 1) -
           LEAPS_THRU_END_OF(y - 1);
    y= newy;
  }
  tmp->year= y;
serg@serg.mylan's avatar
serg@serg.mylan committed
590

591 592 593 594 595 596
  ip= mon_lengths[yleap];
  for (tmp->month= 0; days >= (long) ip[tmp->month]; tmp->month++)
    days= days - (long) ip[tmp->month];
  tmp->month++;
  tmp->day= (uint)(days + 1);

597
  /* filling MySQL specific MYSQL_TIME members */
598
  tmp->neg= 0; tmp->second_part= 0;
599
  tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
600 601 602 603 604
}


/*
  Find time range wich contains given my_time_t value
serg@serg.mylan's avatar
serg@serg.mylan committed
605

606 607
  SYNOPSIS
    find_time_range()
serg@serg.mylan's avatar
serg@serg.mylan committed
608
      t                - my_time_t value for which we looking for range
609 610
      range_boundaries - sorted array of range starts.
      higher_bound     - number of ranges
serg@serg.mylan's avatar
serg@serg.mylan committed
611

612
  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
613
    Performs binary search for range which contains given my_time_t value.
614 615 616
    It has sense if number of ranges is greater than zero and my_time_t value
    is greater or equal than beginning of first range. It also assumes that
    t belongs to some range specified or end of last is MY_TIME_T_MAX.
serg@serg.mylan's avatar
serg@serg.mylan committed
617

618 619
    With this localtime_r on real data may takes less time than with linear
    search (I've seen 30% speed up).
serg@serg.mylan's avatar
serg@serg.mylan committed
620

621 622 623
  RETURN VALUE
    Index of range to which t belongs
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
624
static uint
625 626 627 628
find_time_range(my_time_t t, const my_time_t *range_boundaries,
                uint higher_bound)
{
  uint i, lower_bound= 0;
serg@serg.mylan's avatar
serg@serg.mylan committed
629 630

  /*
631 632 633
    Function will work without this assertion but result would be meaningless.
  */
  DBUG_ASSERT(higher_bound > 0 && t >= range_boundaries[0]);
serg@serg.mylan's avatar
serg@serg.mylan committed
634

635 636
  /*
    Do binary search for minimal interval which contain t. We preserve:
serg@serg.mylan's avatar
serg@serg.mylan committed
637 638
    range_boundaries[lower_bound] <= t < range_boundaries[higher_bound]
    invariant and decrease this higher_bound - lower_bound gap twice
639 640
    times on each step.
  */
serg@serg.mylan's avatar
serg@serg.mylan committed
641

642 643 644 645 646 647 648 649 650 651 652 653
  while (higher_bound - lower_bound > 1)
  {
    i= (lower_bound + higher_bound) >> 1;
    if (range_boundaries[i] <= t)
      lower_bound= i;
    else
      higher_bound= i;
  }
  return lower_bound;
}

/*
serg@serg.mylan's avatar
serg@serg.mylan committed
654 655
  Find local time transition for given my_time_t.

656 657 658 659
  SYNOPSIS
    find_transition_type()
      t   - my_time_t value to be converted
      sp  - pointer to struct with time zone description
serg@serg.mylan's avatar
serg@serg.mylan committed
660

661 662 663 664 665
  RETURN VALUE
    Pointer to structure in time zone description describing
    local time type for given my_time_t.
*/
static
serg@serg.mylan's avatar
serg@serg.mylan committed
666
const TRAN_TYPE_INFO *
667 668 669 670
find_transition_type(my_time_t t, const TIME_ZONE_INFO *sp)
{
  if (unlikely(sp->timecnt == 0 || t < sp->ats[0]))
  {
serg@serg.mylan's avatar
serg@serg.mylan committed
671
    /*
672 673 674 675 676
      If we have not any transitions or t is before first transition let
      us use fallback time type.
    */
    return sp->fallback_tti;
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
677

678 679
  /*
    Do binary search for minimal interval between transitions which
serg@serg.mylan's avatar
serg@serg.mylan committed
680
    contain t. With this localtime_r on real data may takes less
681 682 683 684 685 686 687 688
    time than with linear search (I've seen 30% speed up).
  */
  return &(sp->ttis[sp->types[find_time_range(t, sp->ats, sp->timecnt)]]);
}


/*
  Converts time in my_time_t representation (seconds in UTC since Epoch) to
689
  broken down MYSQL_TIME representation in local time zone.
serg@serg.mylan's avatar
serg@serg.mylan committed
690

691 692 693 694 695 696 697
  SYNOPSIS
    gmt_sec_to_TIME()
      tmp          - pointer to structure for broken down represenatation
      sec_in_utc   - my_time_t value to be converted
      sp           - pointer to struct with time zone description

  TODO
serg@serg.mylan's avatar
serg@serg.mylan committed
698
    We can improve this function by creating joined array of transitions and
699
    leap corrections. This will require adding extra field to TRAN_TYPE_INFO
serg@serg.mylan's avatar
serg@serg.mylan committed
700 701
    for storing number of "extra" seconds to minute occured due to correction
    (60th and 61st second, look how we calculate them as "hit" in this
702
    function).
serg@serg.mylan's avatar
serg@serg.mylan committed
703
    Under realistic assumptions about frequency of transitions the same array
704
    can be used fot MYSQL_TIME -> my_time_t conversion. For this we need to
serg@serg.mylan's avatar
serg@serg.mylan committed
705
    implement tweaked binary search which will take into account that some
706
    MYSQL_TIME has two matching my_time_t ranges and some of them have none.
707 708
*/
static void
709
gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t sec_in_utc, const TIME_ZONE_INFO *sp)
710 711 712 713 714 715 716
{
  const TRAN_TYPE_INFO *ttisp;
  const LS_INFO *lp;
  long  corr= 0;
  int   hit= 0;
  int   i;

serg@serg.mylan's avatar
serg@serg.mylan committed
717
  /*
718
    Find proper transition (and its local time type) for our sec_in_utc value.
serg@serg.mylan's avatar
serg@serg.mylan committed
719
    Funny but again by separating this step in function we receive code
720 721 722 723 724
    which very close to glibc's code. No wonder since they obviously use
    the same base and all steps are sensible.
  */
  ttisp= find_transition_type(sec_in_utc, sp);

serg@serg.mylan's avatar
serg@serg.mylan committed
725
  /*
726 727
    Let us find leap correction for our sec_in_utc value and number of extra
    secs to add to this minute.
serg@serg.mylan's avatar
serg@serg.mylan committed
728 729
    This loop is rarely used because most users will use time zones without
    leap seconds, and even in case when we have such time zone there won't
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
    be many iterations (we have about 22 corrections at this moment (2004)).
  */
  for ( i= sp->leapcnt; i-- > 0; )
  {
    lp= &sp->lsis[i];
    if (sec_in_utc >= lp->ls_trans)
    {
      if (sec_in_utc == lp->ls_trans)
      {
        hit= ((i == 0 && lp->ls_corr > 0) ||
              lp->ls_corr > sp->lsis[i - 1].ls_corr);
        if (hit)
        {
          while (i > 0 &&
                 sp->lsis[i].ls_trans == sp->lsis[i - 1].ls_trans + 1 &&
                 sp->lsis[i].ls_corr == sp->lsis[i - 1].ls_corr + 1)
          {
            hit++;
            i--;
          }
        }
      }
      corr= lp->ls_corr;
      break;
    }
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
756

757 758 759 760 761 762 763 764 765
  sec_to_TIME(tmp, sec_in_utc, ttisp->tt_gmtoff - corr);

  tmp->second+= hit;
}


/*
  Converts local time in broken down representation to local
  time zone analog of my_time_t represenation.
serg@serg.mylan's avatar
serg@serg.mylan committed
766

767 768 769
  SYNOPSIS
    sec_since_epoch()
      year, mon, mday, hour, min, sec - broken down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
770

771 772 773
  DESCRIPTION
    Converts time in broken down representation to my_time_t representation
    ignoring time zone. Note that we cannot convert back some valid _local_
serg@serg.mylan's avatar
serg@serg.mylan committed
774
    times near ends of my_time_t range because of my_time_t  overflow. But we
775
    ignore this fact now since MySQL will never pass such argument.
serg@serg.mylan's avatar
serg@serg.mylan committed
776

777 778 779
  RETURN VALUE
    Seconds since epoch time representation.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
780
static my_time_t
781 782
sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec)
{
783 784
  /* Guard against my_time_t overflow(on system with 32 bit my_time_t) */
  DBUG_ASSERT(!(year == TIMESTAMP_MAX_YEAR && mon == 1 && mday > 17));
785
#ifndef WE_WANT_TO_HANDLE_UNORMALIZED_DATES
serg@serg.mylan's avatar
serg@serg.mylan committed
786
  /*
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
    It turns out that only whenever month is normalized or unnormalized
    plays role.
  */
  DBUG_ASSERT(mon > 0 && mon < 13);
  long days= year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
             LEAPS_THRU_END_OF(year - 1) -
             LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
  days+= mon_starts[isleap(year)][mon - 1];
#else
  long norm_month= (mon - 1) % MONS_PER_YEAR;
  long a_year= year + (mon - 1)/MONS_PER_YEAR - (int)(norm_month < 0);
  long days= a_year * DAYS_PER_NYEAR - EPOCH_YEAR * DAYS_PER_NYEAR +
             LEAPS_THRU_END_OF(a_year - 1) -
             LEAPS_THRU_END_OF(EPOCH_YEAR - 1);
  days+= mon_starts[isleap(a_year)]
                    [norm_month + (norm_month < 0 ? MONS_PER_YEAR : 0)];
#endif
  days+= mday - 1;

serg@serg.mylan's avatar
serg@serg.mylan committed
806
  return ((days * HOURS_PER_DAY + hour) * MINS_PER_HOUR + min) *
807 808 809 810
         SECS_PER_MIN + sec;
}

/*
811
  Converts local time in broken down MYSQL_TIME representation to my_time_t
812
  representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
813

814 815 816 817
  SYNOPSIS
    TIME_to_gmt_sec()
      t               - pointer to structure for broken down represenatation
      sp              - pointer to struct with time zone description
serg@serg.mylan's avatar
serg@serg.mylan committed
818
      in_dst_time_gap - pointer to bool which is set to true if datetime
819 820
                        value passed doesn't really exist (i.e. falls into
                        spring time-gap) and is not touched otherwise.
serg@serg.mylan's avatar
serg@serg.mylan committed
821

822
  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
823
    This is mktime analog for MySQL. It is essentially different
824
    from mktime (or hypotetical my_mktime) because:
serg@serg.mylan's avatar
serg@serg.mylan committed
825
    - It has no idea about tm_isdst member so if it
826
      has two answers it will give the smaller one
serg@serg.mylan's avatar
serg@serg.mylan committed
827
    - If we are in spring time gap then it will return
828
      beginning of the gap
serg@serg.mylan's avatar
serg@serg.mylan committed
829 830
    - It can give wrong results near the ends of my_time_t due to
      overflows, but we are safe since in MySQL we will never
831 832
      call this function for such dates (its restriction for year
      between 1970 and 2038 gives us several days of reserve).
serg@serg.mylan's avatar
serg@serg.mylan committed
833
    - By default it doesn't support un-normalized input. But if
834
      sec_since_epoch() function supports un-normalized dates
serg@serg.mylan's avatar
serg@serg.mylan committed
835
      then this function should handle un-normalized input right,
836
      altough it won't normalize structure TIME.
serg@serg.mylan's avatar
serg@serg.mylan committed
837 838 839 840 841 842

    Traditional approach to problem of conversion from broken down
    representation to time_t is iterative. Both elsie's and glibc
    implementation try to guess what time_t value should correspond to
    this broken-down value. They perform localtime_r function on their
    guessed value and then calculate the difference and try to improve
843
    their guess. Elsie's code guesses time_t value in bit by bit manner,
serg@serg.mylan's avatar
serg@serg.mylan committed
844
    Glibc's code tries to add difference between broken-down value
845
    corresponding to guess and target broken-down value to current guess.
serg@serg.mylan's avatar
serg@serg.mylan committed
846 847
    It also uses caching of last found correction... So Glibc's approach
    is essentially faster but introduces some undetermenism (in case if
848 849 850
    is_dst member of broken-down representation (tm struct) is not known
    and we have two possible answers).

serg@serg.mylan's avatar
serg@serg.mylan committed
851
    We use completely different approach. It is better since it is both
852
    faster than iterative implementations and fully determenistic. If you
853
    look at my_time_t to MYSQL_TIME conversion then you'll find that it consist
854 855
    of two steps:
    The first is calculating shifted my_time_t value and the second - TIME
serg@serg.mylan's avatar
serg@serg.mylan committed
856
    calculation from shifted my_time_t value (well it is a bit simplified
857 858
    picture). The part in which we are interested in is my_time_t -> shifted
    my_time_t conversion. It is piecewise linear function which is defined
serg@serg.mylan's avatar
serg@serg.mylan committed
859 860 861 862 863 864 865 866
    by combination of transition times as break points and times offset
    as changing function parameter. The possible inverse function for this
    converison would be ambiguos but with MySQL's restrictions we can use
    some function which is the same as inverse function on unambigiuos
    ranges and coincides with one of branches of inverse function in
    other ranges. Thus we just need to build table which will determine
    this shifted my_time_t -> my_time_t conversion similar to existing
    (my_time_t -> shifted my_time_t table). We do this in
867
    prepare_tz_info function.
serg@serg.mylan's avatar
serg@serg.mylan committed
868

869
  TODO
serg@serg.mylan's avatar
serg@serg.mylan committed
870
    If we can even more improve this function. For doing this we will need to
871
    build joined map of transitions and leap corrections for gmt_sec_to_TIME()
serg@serg.mylan's avatar
serg@serg.mylan committed
872
    function (similar to revts/revtis). Under realistic assumptions about
873 874 875 876
    frequency of transitions we can use the same array for TIME_to_gmt_sec().
    We need to implement special version of binary search for this. Such step
    will be beneficial to CPU cache since we will decrease data-set used for
    conversion twice.
serg@serg.mylan's avatar
serg@serg.mylan committed
877

878
  RETURN VALUE
serg@serg.mylan's avatar
serg@serg.mylan committed
879
    Seconds in UTC since Epoch.
880 881 882
    0 in case of error.
*/
static my_time_t
883
TIME_to_gmt_sec(const MYSQL_TIME *t, const TIME_ZONE_INFO *sp,
884
                my_bool *in_dst_time_gap)
885 886 887 888
{
  my_time_t local_t;
  uint saved_seconds;
  uint i;
889
  int shift= 0;
890 891

  DBUG_ENTER("TIME_to_gmt_sec");
serg@serg.mylan's avatar
serg@serg.mylan committed
892

893
  if (!validate_timestamp_range(t))
894
    DBUG_RETURN(0);
895 896


897 898 899 900 901 902
  /* We need this for correct leap seconds handling */
  if (t->second < SECS_PER_MIN)
    saved_seconds= 0;
  else
    saved_seconds= t->second;

serg@serg.mylan's avatar
serg@serg.mylan committed
903
  /*
904 905 906 907 908 909 910 911 912
    NOTE: to convert full my_time_t range we do a shift of the
    boundary dates here to avoid overflow of my_time_t.
    We use alike approach in my_system_gmt_sec().

    However in that function we also have to take into account
    overflow near 0 on some platforms. That's because my_system_gmt_sec
    uses localtime_r(), which doesn't work with negative values correctly
    on platforms with unsigned time_t (QNX). Here we don't use localtime()
    => we negative values of local_t are ok.
913
  */
serg@serg.mylan's avatar
serg@serg.mylan committed
914

915 916 917 918 919 920 921 922 923 924 925 926
  if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
  {
    /*
      We will pass (t->day - shift) to sec_since_epoch(), and
      want this value to be a positive number, so we shift
      only dates > 4.01.2038 (to avoid owerflow).
    */
    shift= 2;
  }


  local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
serg@serg.mylan's avatar
serg@serg.mylan committed
927
                           t->hour, t->minute,
928 929 930 931 932 933 934
                           saved_seconds ? 0 : t->second);

  /* We have at least one range */
  DBUG_ASSERT(sp->revcnt >= 1);

  if (local_t < sp->revts[0] || local_t > sp->revts[sp->revcnt])
  {
serg@serg.mylan's avatar
serg@serg.mylan committed
935
    /*
936 937 938 939 940 941 942 943
      This means that source time can't be represented as my_time_t due to
      limited my_time_t range.
    */
    DBUG_RETURN(0);
  }

  /* binary search for our range */
  i= find_time_range(local_t, sp->revts, sp->revcnt);
serg@serg.mylan's avatar
serg@serg.mylan committed
944

945 946 947 948 949 950 951
  /*
    As there are no offset switches at the end of TIMESTAMP range,
    we could simply check for overflow here (and don't need to bother
    about DST gaps etc)
  */
  if (shift)
  {
952
    if (local_t > (my_time_t) (TIMESTAMP_MAX_VALUE - shift * SECS_PER_DAY +
953
                               sp->revtis[i].rt_offset - saved_seconds))
954 955 956
    {
      DBUG_RETURN(0);                           /* my_time_t overflow */
    }
957
    local_t+= shift * SECS_PER_DAY;
958 959
  }

960 961
  if (sp->revtis[i].rt_type)
  {
serg@serg.mylan's avatar
serg@serg.mylan committed
962
    /*
963 964 965 966 967 968
      Oops! We are in spring time gap.
      May be we should return error here?
      Now we are returning my_time_t value corresponding to the
      beginning of the gap.
    */
    *in_dst_time_gap= 1;
969
    local_t= sp->revts[i] - sp->revtis[i].rt_offset + saved_seconds;
970 971
  }
  else
972 973 974 975 976 977 978
    local_t= local_t - sp->revtis[i].rt_offset + saved_seconds;

  /* check for TIMESTAMP_MAX_VALUE was already done above */
  if (local_t < TIMESTAMP_MIN_VALUE)
    local_t= 0;

  DBUG_RETURN(local_t);
979 980 981 982
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
983
  End of elsie derived code.
984
*/
985
#endif /* !defined(TZINFO2SQL) */
986 987 988 989 990 991 992 993 994 995 996


#if !defined(TESTTIME) && !defined(TZINFO2SQL)

/*
  String with names of SYSTEM time zone.
*/
static const String tz_SYSTEM_name("SYSTEM", 6, &my_charset_latin1);


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
997
  Instance of this class represents local time zone used on this system
998
  (specified by TZ environment variable or via any other system mechanism).
serg@serg.mylan's avatar
serg@serg.mylan committed
999
  It uses system functions (localtime_r, my_system_gmt_sec) for conversion
1000 1001
  and is always available. Because of this it is used by default - if there
  were no explicit time zone specified. On the other hand because of this
serg@serg.mylan's avatar
serg@serg.mylan committed
1002 1003
  conversion methods provided by this class is significantly slower and
  possibly less multi-threaded-friendly than corresponding Time_zone_db
1004 1005
  methods so the latter should be preffered there it is possible.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1006
class Time_zone_system : public Time_zone
1007 1008
{
public:
1009
  Time_zone_system() {}                       /* Remove gcc warning */
1010
  virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1011
                                    my_bool *in_dst_time_gap) const;
1012
  virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1013 1014 1015 1016 1017
  virtual const String * get_name() const;
};


/*
1018
  Converts local time in system time zone in MYSQL_TIME representation
1019
  to its my_time_t representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1020

1021 1022
  SYNOPSIS
    TIME_to_gmt_sec()
1023
      t               - pointer to MYSQL_TIME structure with local time in
1024
                        broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1025
      in_dst_time_gap - pointer to bool which is set to true if datetime
1026 1027 1028 1029 1030
                        value passed doesn't really exist (i.e. falls into
                        spring time-gap) and is not touched otherwise.

  DESCRIPTION
    This method uses system function (localtime_r()) for conversion
1031
    local time in system time zone in MYSQL_TIME structure to its my_time_t
1032
    representation. Unlike the same function for Time_zone_db class
serg@serg.mylan's avatar
serg@serg.mylan committed
1033 1034
    it it won't handle unnormalized input properly. Still it will
    return lowest possible my_time_t in case of ambiguity or if we
1035
    provide time corresponding to the time-gap.
serg@serg.mylan's avatar
serg@serg.mylan committed
1036

1037
    You should call my_init_time() function before using this function.
1038 1039 1040 1041

  RETURN VALUE
    Corresponding my_time_t value or 0 in case of error
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1042
my_time_t
1043
Time_zone_system::TIME_to_gmt_sec(const MYSQL_TIME *t, my_bool *in_dst_time_gap) const
1044 1045 1046 1047 1048 1049 1050 1051 1052
{
  long not_used;
  return my_system_gmt_sec(t, &not_used, in_dst_time_gap);
}


/*
  Converts time from UTC seconds since Epoch (my_time_t) representation
  to system local time zone broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1053

1054 1055
  SYNOPSIS
    gmt_sec_to_TIME()
1056
      tmp - pointer to MYSQL_TIME structure to fill-in
serg@serg.mylan's avatar
serg@serg.mylan committed
1057
      t   - my_time_t value to be converted
1058

serg@serg.mylan's avatar
serg@serg.mylan committed
1059
  NOTE
1060
    We assume that value passed to this function will fit into time_t range
serg@serg.mylan's avatar
serg@serg.mylan committed
1061
    supported by localtime_r. This conversion is putting restriction on
1062
    TIMESTAMP range in MySQL. If we can get rid of SYSTEM time zone at least
serg@serg.mylan's avatar
serg@serg.mylan committed
1063
    for interaction with client then we can extend TIMESTAMP range down to
1064 1065
    the 1902 easily.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1066
void
1067
Time_zone_system::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1068 1069 1070 1071 1072 1073
{
  struct tm tmp_tm;
  time_t tmp_t= (time_t)t;

  localtime_r(&tmp_t, &tmp_tm);
  localtime_to_TIME(tmp, &tmp_tm);
1074
  tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
1075
  adjust_leap_second(tmp);
1076 1077 1078 1079 1080
}


/*
  Get name of time zone
serg@serg.mylan's avatar
serg@serg.mylan committed
1081

1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
  SYNOPSIS
    get_name()

  RETURN VALUE
    Name of time zone as String
*/
const String *
Time_zone_system::get_name() const
{
  return &tz_SYSTEM_name;
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1096 1097
  Instance of this class represents UTC time zone. It uses system gmtime_r
  function for conversions and is always available. It is used only for
1098 1099
  my_time_t -> MYSQL_TIME conversions in various UTC_...  functions, it is not
  intended for MYSQL_TIME -> my_time_t conversions and shouldn't be exposed to user.
1100
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1101
class Time_zone_utc : public Time_zone
1102 1103
{
public:
1104
  Time_zone_utc() {}                          /* Remove gcc warning */
1105
  virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1106
                                    my_bool *in_dst_time_gap) const;
1107
  virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1108 1109 1110 1111 1112
  virtual const String * get_name() const;
};


/*
1113
  Convert UTC time from MYSQL_TIME representation to its my_time_t representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1114

1115 1116
  SYNOPSIS
    TIME_to_gmt_sec()
1117
      t               - pointer to MYSQL_TIME structure with local time
1118
                        in broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1119
      in_dst_time_gap - pointer to bool which is set to true if datetime
1120 1121 1122 1123
                        value passed doesn't really exist (i.e. falls into
                        spring time-gap) and is not touched otherwise.

  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
1124 1125
    Since Time_zone_utc is used only internally for my_time_t -> TIME
    conversions, this function of Time_zone interface is not implemented for
1126 1127 1128 1129 1130
    this class and should not be called.

  RETURN VALUE
    0
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1131
my_time_t
1132
Time_zone_utc::TIME_to_gmt_sec(const MYSQL_TIME *t, my_bool *in_dst_time_gap) const
1133 1134 1135 1136 1137
{
  /* Should be never called */
  DBUG_ASSERT(0);
  return 0;
}
serg@serg.mylan's avatar
serg@serg.mylan committed
1138

1139 1140 1141 1142

/*
  Converts time from UTC seconds since Epoch (my_time_t) representation
  to broken-down representation (also in UTC).
serg@serg.mylan's avatar
serg@serg.mylan committed
1143

1144 1145
  SYNOPSIS
    gmt_sec_to_TIME()
1146
      tmp - pointer to MYSQL_TIME structure to fill-in
serg@serg.mylan's avatar
serg@serg.mylan committed
1147 1148
      t   - my_time_t value to be converted

1149 1150 1151
  NOTE
    See note for apropriate Time_zone_system method.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1152
void
1153
Time_zone_utc::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1154 1155 1156 1157 1158
{
  struct tm tmp_tm;
  time_t tmp_t= (time_t)t;
  gmtime_r(&tmp_t, &tmp_tm);
  localtime_to_TIME(tmp, &tmp_tm);
1159
  tmp->time_type= MYSQL_TIMESTAMP_DATETIME;
1160
  adjust_leap_second(tmp);
1161 1162 1163 1164 1165
}


/*
  Get name of time zone
serg@serg.mylan's avatar
serg@serg.mylan committed
1166

1167 1168 1169 1170
  SYNOPSIS
    get_name()

  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
1171 1172
    Since Time_zone_utc is used only internally by SQL's UTC_* functions it
    is not accessible directly, and hence this function of Time_zone
1173
    interface is not implemented for this class and should not be called.
serg@serg.mylan's avatar
serg@serg.mylan committed
1174

1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
  RETURN VALUE
    0
*/
const String *
Time_zone_utc::get_name() const
{
  /* Should be never called */
  DBUG_ASSERT(0);
  return 0;
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1188 1189
  Instance of this class represents some time zone which is
  described in mysql.time_zone family of tables.
1190
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1191
class Time_zone_db : public Time_zone
1192 1193 1194
{
public:
  Time_zone_db(TIME_ZONE_INFO *tz_info_arg, const String * tz_name_arg);
1195
  virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1196
                                    my_bool *in_dst_time_gap) const;
1197
  virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1198 1199 1200 1201 1202 1203 1204 1205
  virtual const String * get_name() const;
private:
  TIME_ZONE_INFO *tz_info;
  const String *tz_name;
};


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1206
  Initializes object representing time zone described by mysql.time_zone
1207
  tables.
serg@serg.mylan's avatar
serg@serg.mylan committed
1208

1209 1210
  SYNOPSIS
    Time_zone_db()
serg@serg.mylan's avatar
serg@serg.mylan committed
1211 1212
      tz_info_arg - pointer to TIME_ZONE_INFO structure which is filled
                    according to db or other time zone description
1213
                    (for example by my_tz_init()).
serg@serg.mylan's avatar
serg@serg.mylan committed
1214
                    Several Time_zone_db instances can share one
1215 1216 1217
                    TIME_ZONE_INFO structure.
      tz_name_arg - name of time zone.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1218
Time_zone_db::Time_zone_db(TIME_ZONE_INFO *tz_info_arg,
1219 1220 1221 1222 1223 1224 1225
                           const String *tz_name_arg):
  tz_info(tz_info_arg), tz_name(tz_name_arg)
{
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1226
  Converts local time in time zone described from TIME
1227
  representation to its my_time_t representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1228

1229 1230
  SYNOPSIS
    TIME_to_gmt_sec()
1231
      t               - pointer to MYSQL_TIME structure with local time
1232
                        in broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1233
      in_dst_time_gap - pointer to bool which is set to true if datetime
1234 1235 1236 1237
                        value passed doesn't really exist (i.e. falls into
                        spring time-gap) and is not touched otherwise.

  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
1238
    Please see ::TIME_to_gmt_sec for function description and
1239 1240 1241 1242 1243
    parameter restrictions.

  RETURN VALUE
    Corresponding my_time_t value or 0 in case of error
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1244
my_time_t
1245
Time_zone_db::TIME_to_gmt_sec(const MYSQL_TIME *t, my_bool *in_dst_time_gap) const
1246 1247 1248 1249 1250 1251 1252 1253
{
  return ::TIME_to_gmt_sec(t, tz_info, in_dst_time_gap);
}


/*
  Converts time from UTC seconds since Epoch (my_time_t) representation
  to local time zone described in broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1254

1255 1256
  SYNOPSIS
    gmt_sec_to_TIME()
1257
      tmp - pointer to MYSQL_TIME structure to fill-in
serg@serg.mylan's avatar
serg@serg.mylan committed
1258
      t   - my_time_t value to be converted
1259
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1260
void
1261
Time_zone_db::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1262 1263
{
  ::gmt_sec_to_TIME(tmp, t, tz_info);
1264
  adjust_leap_second(tmp);
1265 1266 1267 1268 1269
}


/*
  Get name of time zone
serg@serg.mylan's avatar
serg@serg.mylan committed
1270

1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
  SYNOPSIS
    get_name()

  RETURN VALUE
    Name of time zone as ASCIIZ-string
*/
const String *
Time_zone_db::get_name() const
{
  return tz_name;
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1285
  Instance of this class represents time zone which
1286 1287
  was specified as offset from UTC.
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1288
class Time_zone_offset : public Time_zone
1289 1290 1291
{
public:
  Time_zone_offset(long tz_offset_arg);
1292
  virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t,
1293
                                    my_bool *in_dst_time_gap) const;
1294
  virtual void   gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
1295
  virtual const String * get_name() const;
serg@serg.mylan's avatar
serg@serg.mylan committed
1296
  /*
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309
    This have to be public because we want to be able to access it from
    my_offset_tzs_get_key() function
  */
  long offset;
private:
  /* Extra reserve because of snprintf */
  char name_buff[7+16];
  String name;
};


/*
  Initializes object representing time zone described by its offset from UTC.
serg@serg.mylan's avatar
serg@serg.mylan committed
1310

1311 1312
  SYNOPSIS
    Time_zone_offset()
serg@serg.mylan's avatar
serg@serg.mylan committed
1313
      tz_offset_arg - offset from UTC in seconds.
1314 1315 1316 1317 1318
                      Positive for direction to east.
*/
Time_zone_offset::Time_zone_offset(long tz_offset_arg):
  offset(tz_offset_arg)
{
1319 1320
  uint hours= abs((int)(offset / SECS_PER_HOUR));
  uint minutes= abs((int)(offset % SECS_PER_HOUR / SECS_PER_MIN));
serg@serg.mylan's avatar
serg@serg.mylan committed
1321
  ulong length= my_snprintf(name_buff, sizeof(name_buff), "%s%02d:%02d",
1322 1323 1324 1325 1326 1327 1328
                            (offset>=0) ? "+" : "-", hours, minutes);
  name.set(name_buff, length, &my_charset_latin1);
}


/*
  Converts local time in time zone described as offset from UTC
1329
  from MYSQL_TIME representation to its my_time_t representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1330

1331 1332
  SYNOPSIS
    TIME_to_gmt_sec()
1333
      t               - pointer to MYSQL_TIME structure with local time
1334
                        in broken-down representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1335 1336 1337
      in_dst_time_gap - pointer to bool which should be set to true if
                        datetime  value passed doesn't really exist
                        (i.e. falls into spring time-gap) and is not
1338 1339 1340 1341 1342 1343
                        touched otherwise.
                        It is not really used in this class.

  RETURN VALUE
    Corresponding my_time_t value or 0 in case of error
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1344
my_time_t
1345
Time_zone_offset::TIME_to_gmt_sec(const MYSQL_TIME *t, my_bool *in_dst_time_gap) const
1346
{
1347
  my_time_t local_t;
1348
  int shift= 0;
1349 1350 1351 1352 1353 1354 1355 1356

  /*
    Check timestamp range.we have to do this as calling function relies on
    us to make all validation checks here.
  */
  if (!validate_timestamp_range(t))
    return 0;

1357 1358 1359 1360 1361 1362 1363 1364 1365
  /*
    Do a temporary shift of the boundary dates to avoid
    overflow of my_time_t if the time value is near it's
    maximum range
  */
  if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && t->day > 4)
    shift= 2;

  local_t= sec_since_epoch(t->year, t->month, (t->day - shift),
1366 1367 1368
                           t->hour, t->minute, t->second) -
           offset;

1369 1370 1371 1372 1373 1374
  if (shift)
  {
    /* Add back the shifted time */
    local_t+= shift * SECS_PER_DAY;
  }

1375 1376 1377 1378 1379
  if (local_t >= TIMESTAMP_MIN_VALUE && local_t <= TIMESTAMP_MAX_VALUE)
    return local_t;

  /* range error*/
  return 0;
1380 1381 1382 1383 1384
}


/*
  Converts time from UTC seconds since Epoch (my_time_t) representation
serg@serg.mylan's avatar
serg@serg.mylan committed
1385
  to local time zone described as offset from UTC and in broken-down
1386
  representation.
serg@serg.mylan's avatar
serg@serg.mylan committed
1387

1388 1389
  SYNOPSIS
    gmt_sec_to_TIME()
1390
      tmp - pointer to MYSQL_TIME structure to fill-in
serg@serg.mylan's avatar
serg@serg.mylan committed
1391
      t   - my_time_t value to be converted
1392
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1393
void
1394
Time_zone_offset::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
1395 1396 1397 1398 1399 1400 1401
{
  sec_to_TIME(tmp, t, offset);
}


/*
  Get name of time zone
serg@serg.mylan's avatar
serg@serg.mylan committed
1402

1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417
  SYNOPSIS
    get_name()

  RETURN VALUE
    Name of time zone as pointer to String object
*/
const String *
Time_zone_offset::get_name() const
{
  return &name;
}


static Time_zone_utc tz_UTC;
static Time_zone_system tz_SYSTEM;
1418
static Time_zone_offset tz_OFFSET0(0);
1419

1420
Time_zone *my_tz_OFFSET0= &tz_OFFSET0;
1421 1422 1423 1424 1425 1426 1427
Time_zone *my_tz_UTC= &tz_UTC;
Time_zone *my_tz_SYSTEM= &tz_SYSTEM;

static HASH tz_names;
static HASH offset_tzs;
static MEM_ROOT tz_storage;

serg@serg.mylan's avatar
serg@serg.mylan committed
1428
/*
1429
  These mutex protects offset_tzs and tz_storage.
serg@serg.mylan's avatar
serg@serg.mylan committed
1430 1431
  These protection needed only when we are trying to set
  time zone which is specified as offset, and searching for existing
1432 1433 1434 1435
  time zone in offset_tzs or creating if it didn't existed before in
  tz_storage. So contention is low.
*/
static pthread_mutex_t tz_LOCK;
1436
static bool tz_inited= 0;
1437 1438 1439 1440 1441 1442 1443 1444

/*
  This two static variables are inteded for holding info about leap seconds
  shared by all time zones.
*/
static uint tz_leapcnt= 0;
static LS_INFO *tz_lsis= 0;

1445 1446 1447 1448 1449 1450 1451
/*
  Shows whenever we have found time zone tables during start-up.
  Used for avoiding of putting those tables to global table list
  for queries that use time zone info.
*/
static bool time_zone_tables_exist= 1;

serg@serg.mylan's avatar
serg@serg.mylan committed
1452

1453 1454 1455 1456 1457 1458
/*
  Names of tables (with their lengths) that are needed
  for dynamical loading of time zone descriptions.
*/

static const LEX_STRING tz_tables_names[MY_TZ_TABLES_COUNT]=
1459
{
andrey@example.com's avatar
andrey@example.com committed
1460 1461 1462 1463
  { C_STRING_WITH_LEN("time_zone_name")},
  { C_STRING_WITH_LEN("time_zone")},
  { C_STRING_WITH_LEN("time_zone_transition_type")},
  { C_STRING_WITH_LEN("time_zone_transition")}
1464 1465 1466 1467
};

/* Name of database to which those tables belong. */

andrey@example.com's avatar
andrey@example.com committed
1468
static const LEX_STRING tz_tables_db_name= { C_STRING_WITH_LEN("mysql")};
1469 1470 1471 1472 1473


class Tz_names_entry: public Sql_alloc
{
public:
1474 1475
  String name;
  Time_zone *tz;
1476
};
1477 1478 1479


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
1480 1481
  We are going to call both of these functions from C code so
  they should obey C calling conventions.
1482 1483
*/

1484 1485 1486
extern "C" uchar *
my_tz_names_get_key(Tz_names_entry *entry, size_t *length,
                    my_bool not_used __attribute__((unused)))
1487 1488
{
  *length= entry->name.length();
1489
  return (uchar*) entry->name.ptr();
1490 1491
}

1492 1493 1494 1495
extern "C" uchar *
my_offset_tzs_get_key(Time_zone_offset *entry,
                      size_t *length,
                      my_bool not_used __attribute__((unused)))
1496 1497
{
  *length= sizeof(long);
1498
  return (uchar*) &entry->offset;
1499 1500 1501
}


1502
/*
1503
  Prepare table list with time zone related tables from preallocated array.
1504 1505 1506

  SYNOPSIS
    tz_init_table_list()
1507 1508
      tz_tabs         - pointer to preallocated array of MY_TZ_TABLES_COUNT
                        TABLE_LIST objects
1509 1510 1511

  DESCRIPTION
    This function prepares list of TABLE_LIST objects which can be used
1512
    for opening of time zone tables from preallocated array.
1513 1514
*/

1515
static void
1516
tz_init_table_list(TABLE_LIST *tz_tabs)
1517
{
1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532
  bzero(tz_tabs, sizeof(TABLE_LIST) * MY_TZ_TABLES_COUNT);

  for (int i= 0; i < MY_TZ_TABLES_COUNT; i++)
  {
    tz_tabs[i].alias= tz_tabs[i].table_name= tz_tables_names[i].str;
    tz_tabs[i].table_name_length= tz_tables_names[i].length;
    tz_tabs[i].db= tz_tables_db_name.str;
    tz_tabs[i].db_length= tz_tables_db_name.length;
    tz_tabs[i].lock_type= TL_READ;

    if (i != MY_TZ_TABLES_COUNT - 1)
      tz_tabs[i].next_global= tz_tabs[i].next_local= &tz_tabs[i+1];
    if (i != 0)
      tz_tabs[i].prev_global= &tz_tabs[i-1].next_global;
  }
1533 1534 1535
}


1536 1537 1538 1539 1540 1541 1542 1543
/*
  Initialize time zone support infrastructure.

  SYNOPSIS
    my_tz_init()
      thd            - current thread object
      default_tzname - default time zone or 0 if none.
      bootstrap      - indicates whenever we are in bootstrap mode
serg@serg.mylan's avatar
serg@serg.mylan committed
1544

1545 1546 1547
  DESCRIPTION
    This function will init memory structures needed for time zone support,
    it will register mandatory SYSTEM time zone in them. It will try to open
1548 1549 1550 1551 1552 1553 1554
    mysql.time_zone* tables and load information about default time zone and
    information which further will be shared among all time zones loaded.
    If system tables with time zone descriptions don't exist it won't fail
    (unless default_tzname is time zone from tables). If bootstrap parameter
    is true then this routine assumes that we are in bootstrap mode and won't
    load time zone descriptions unless someone specifies default time zone
    which is supposedly stored in those tables.
1555
    It'll also set default time zone if it is specified.
serg@serg.mylan's avatar
serg@serg.mylan committed
1556

1557 1558
  RETURN VALUES
    0 - ok
serg@serg.mylan's avatar
serg@serg.mylan committed
1559
    1 - Error
1560
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
1561
my_bool
1562 1563 1564
my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap)
{
  THD *thd;
1565 1566
  TABLE_LIST tz_tables[1+MY_TZ_TABLES_COUNT];
  Open_tables_state open_tables_state_backup;
1567
  TABLE *table;
1568
  Tz_names_entry *tmp_tzname;
1569
  my_bool return_val= 1;
1570
  char db[]= "mysql";
1571 1572 1573 1574 1575 1576 1577 1578
  int res;
  DBUG_ENTER("my_tz_init");

  /*
    To be able to run this from boot, we allocate a temporary THD
  */
  if (!(thd= new THD))
    DBUG_RETURN(1);
1579
  thd->thread_stack= (char*) &thd;
1580
  thd->store_globals();
1581
  lex_start(thd);
1582 1583 1584

  /* Init all memory structures that require explicit destruction */
  if (hash_init(&tz_names, &my_charset_latin1, 20,
1585
                0, 0, (hash_get_key) my_tz_names_get_key, 0, 0))
1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598
  {
    sql_print_error("Fatal error: OOM while initializing time zones");
    goto end;
  }
  if (hash_init(&offset_tzs, &my_charset_latin1, 26, 0, 0,
                (hash_get_key)my_offset_tzs_get_key, 0, 0))
  {
    sql_print_error("Fatal error: OOM while initializing time zones");
    hash_free(&tz_names);
    goto end;
  }
  init_alloc_root(&tz_storage, 32 * 1024, 0);
  VOID(pthread_mutex_init(&tz_LOCK, MY_MUTEX_INIT_FAST));
1599
  tz_inited= 1;
1600 1601

  /* Add 'SYSTEM' time zone to tz_names hash */
1602
  if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()))
1603 1604 1605 1606
  {
    sql_print_error("Fatal error: OOM while initializing time zones");
    goto end_with_cleanup;
  }
1607
  tmp_tzname->name.set(STRING_WITH_LEN("SYSTEM"), &my_charset_latin1);
1608
  tmp_tzname->tz= my_tz_SYSTEM;
1609
  if (my_hash_insert(&tz_names, (const uchar *)tmp_tzname))
1610 1611 1612 1613
  {
    sql_print_error("Fatal error: OOM while initializing time zones");
    goto end_with_cleanup;
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1614

1615 1616 1617
  if (bootstrap)
  {
    /* If we are in bootstrap mode we should not load time zone tables */
1618
    return_val= time_zone_tables_exist= 0;
1619 1620
    goto end_with_setting_default_tz;
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1621 1622

  /*
1623 1624 1625 1626 1627
    After this point all memory structures are inited and we even can live
    without time zone description tables. Now try to load information about
    leap seconds shared by all time zones.
  */

1628
  thd->set_db(db, sizeof(db)-1);
1629 1630
  bzero((char*) &tz_tables[0], sizeof(TABLE_LIST));
  tz_tables[0].alias= tz_tables[0].table_name=
1631
    (char*)"time_zone_leap_second";
1632 1633 1634 1635 1636 1637 1638 1639 1640
  tz_tables[0].table_name_length= 21;
  tz_tables[0].db= db;
  tz_tables[0].db_length= sizeof(db)-1;
  tz_tables[0].lock_type= TL_READ;

  tz_init_table_list(tz_tables+1);
  tz_tables[0].next_global= tz_tables[0].next_local= &tz_tables[1];
  tz_tables[1].prev_global= &tz_tables[0].next_global;

1641
  /*
1642 1643
    We need to open only mysql.time_zone_leap_second, but we try to
    open all time zone tables to see if they exist.
1644
  */
1645
  if (open_system_tables_for_read(thd, tz_tables, &open_tables_state_backup))
1646
  {
1647
    sql_print_warning("Can't open and lock time zone table: %s "
1648
                      "trying to live without them", thd->main_da.message());
1649
    /* We will try emulate that everything is ok */
1650
    return_val= time_zone_tables_exist= 0;
1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664
    goto end_with_setting_default_tz;
  }

  /*
    Now we are going to load leap seconds descriptions that are shared
    between all time zones that use them. We are using index for getting
    records in proper order. Since we share the same MEM_ROOT between
    all time zones we just allocate enough memory for it first.
  */
  if (!(tz_lsis= (LS_INFO*) alloc_root(&tz_storage,
                                       sizeof(LS_INFO) * TZ_MAX_LEAPS)))
  {
    sql_print_error("Fatal error: Out of memory while loading "
                    "mysql.time_zone_leap_second table");
1665
    goto end_with_close;
1666
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1667

1668
  table= tz_tables[0].table;
1669 1670 1671 1672 1673
  /*
    It is OK to ignore ha_index_init()/ha_index_end() return values since
    mysql.time_zone* tables are MyISAM and these operations always succeed
    for MyISAM.
  */
1674
  (void)table->file->ha_index_init(0, 1);
1675 1676
  table->use_all_columns();

1677
  tz_leapcnt= 0;
serg@serg.mylan's avatar
serg@serg.mylan committed
1678

1679 1680 1681 1682 1683 1684 1685 1686
  res= table->file->index_first(table->record[0]);

  while (!res)
  {
    if (tz_leapcnt + 1 > TZ_MAX_LEAPS)
    {
      sql_print_error("Fatal error: While loading mysql.time_zone_leap_second"
                      " table: too much leaps");
serg@serg.mylan's avatar
serg@serg.mylan committed
1687
      table->file->ha_index_end();
1688
      goto end_with_close;
1689
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
1690

1691 1692 1693 1694 1695 1696
    tz_lsis[tz_leapcnt].ls_trans= (my_time_t)table->field[0]->val_int();
    tz_lsis[tz_leapcnt].ls_corr= (long)table->field[1]->val_int();

    tz_leapcnt++;

    DBUG_PRINT("info",
1697 1698 1699
               ("time_zone_leap_second table: tz_leapcnt: %u  tt_time: %lu  offset: %ld",
                tz_leapcnt, (ulong) tz_lsis[tz_leapcnt-1].ls_trans,
                tz_lsis[tz_leapcnt-1].ls_corr));
serg@serg.mylan's avatar
serg@serg.mylan committed
1700

1701 1702 1703
    res= table->file->index_next(table->record[0]);
  }

1704
  (void)table->file->ha_index_end();
serg@serg.mylan's avatar
serg@serg.mylan committed
1705

1706 1707 1708 1709
  if (res != HA_ERR_END_OF_FILE)
  {
    sql_print_error("Fatal error: Error while loading "
                    "mysql.time_zone_leap_second table");
1710
    goto end_with_close;
1711 1712 1713 1714 1715
  }

  /*
    Loading of info about leap seconds succeeded
  */
serg@serg.mylan's avatar
serg@serg.mylan committed
1716

1717 1718
  return_val= 0;

serg@serg.mylan's avatar
serg@serg.mylan committed
1719

1720
end_with_setting_default_tz:
1721 1722
  /* If we have default time zone try to load it */
  if (default_tzname)
1723
  {
1724
    String tmp_tzname2(default_tzname, &my_charset_latin1);
1725 1726 1727 1728 1729 1730
    /*
      Time zone tables may be open here, and my_tz_find() may open
      most of them once more, but this is OK for system tables open
      for READ.
    */
    if (!(global_system_variables.time_zone= my_tz_find(thd, &tmp_tzname2)))
1731 1732 1733 1734 1735 1736
    {
      sql_print_error("Fatal error: Illegal or unknown default time zone '%s'",
                      default_tzname);
      return_val= 1;
    }
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1737

1738
end_with_close:
1739 1740 1741 1742 1743
  if (time_zone_tables_exist)
  {
    thd->version--; /* Force close to free memory */
    close_system_tables(thd, &open_tables_state_backup);
  }
1744

1745
end_with_cleanup:
serg@serg.mylan's avatar
serg@serg.mylan committed
1746

1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769
  /* if there were error free time zone describing structs */
  if (return_val)
    my_tz_free();
end:
  delete thd;
  if (org_thd)
    org_thd->store_globals();			/* purecov: inspected */
  else
  {
    /* Remember that we don't have a THD */
    my_pthread_setspecific_ptr(THR_THD,  0);
    my_pthread_setspecific_ptr(THR_MALLOC,  0);
  }
  DBUG_RETURN(return_val);
}


/*
  Free resources used by time zone support infrastructure.

  SYNOPSIS
    my_tz_free()
*/
1770

1771 1772
void my_tz_free()
{
1773 1774 1775 1776 1777 1778 1779 1780
  if (tz_inited)
  {
    tz_inited= 0;
    VOID(pthread_mutex_destroy(&tz_LOCK));
    hash_free(&offset_tzs);
    hash_free(&tz_names);
    free_root(&tz_storage, MYF(0));
  }
1781 1782 1783 1784 1785 1786 1787
}


/*
  Load time zone description from system tables.

  SYNOPSIS
1788 1789 1790 1791
    tz_load_from_open_tables()
      tz_name   - name of time zone that should be loaded.
      tz_tables - list of tables from which time zone description
                  should be loaded
serg@serg.mylan's avatar
serg@serg.mylan committed
1792

1793
  DESCRIPTION
1794 1795 1796 1797 1798
    This function will try to load information about time zone specified
    from the list of the already opened and locked tables (first table in
    tz_tables should be time_zone_name, next time_zone, then
    time_zone_transition_type and time_zone_transition should be last).
    It will also update information in hash used for time zones lookup.
serg@serg.mylan's avatar
serg@serg.mylan committed
1799

1800 1801 1802 1803
  RETURN VALUES
    Returns pointer to newly created Time_zone object or 0 in case of error.

*/
1804

serg@serg.mylan's avatar
serg@serg.mylan committed
1805
static Time_zone*
1806
tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables)
1807 1808 1809
{
  TABLE *table= 0;
  TIME_ZONE_INFO *tz_info;
1810
  Tz_names_entry *tmp_tzname;
1811 1812 1813 1814 1815 1816 1817
  Time_zone *return_val= 0;
  int res;
  uint tzid, ttid;
  my_time_t ttime;
  char buff[MAX_FIELD_WIDTH];
  String abbr(buff, sizeof(buff), &my_charset_latin1);
  char *alloc_buff, *tz_name_buff;
serg@serg.mylan's avatar
serg@serg.mylan committed
1818
  /*
1819 1820 1821 1822
    Temporary arrays that are used for loading of data for filling
    TIME_ZONE_INFO structure
  */
  my_time_t ats[TZ_MAX_TIMES];
monty@mysql.com's avatar
monty@mysql.com committed
1823
  uchar types[TZ_MAX_TIMES];
1824 1825 1826 1827
  TRAN_TYPE_INFO ttis[TZ_MAX_TYPES];
#ifdef ABBR_ARE_USED
  char chars[max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))];
#endif
1828 1829 1830 1831 1832 1833
  /* 
    Used as a temporary tz_info until we decide that we actually want to
    allocate and keep the tz info and tz name in tz_storage.
  */
  TIME_ZONE_INFO tmp_tz_info;
  memset(&tmp_tz_info, 0, sizeof(TIME_ZONE_INFO));
1834

1835
  DBUG_ENTER("tz_load_from_open_tables");
serg@serg.mylan's avatar
serg@serg.mylan committed
1836

1837
  /* Prepare tz_info for loading also let us make copy of time zone name */
1838 1839
  if (!(alloc_buff= (char*) alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) +
                                       tz_name->length() + 1)))
1840
  {
1841
    sql_print_error("Out of memory while loading time zone description");
1842 1843 1844 1845 1846 1847 1848 1849 1850
    return 0;
  }
  tz_info= (TIME_ZONE_INFO *)alloc_buff;
  bzero(tz_info, sizeof(TIME_ZONE_INFO));
  tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO);
  /*
    By writing zero to the end we guarantee that we can call ptr()
    instead of c_ptr() for time zone name.
  */
serg@serg.mylan's avatar
serg@serg.mylan committed
1851
  strmake(tz_name_buff, tz_name->ptr(), tz_name->length());
1852

serg@serg.mylan's avatar
serg@serg.mylan committed
1853 1854
  /*
    Let us find out time zone id by its name (there is only one index
1855 1856
    and it is specifically for this purpose).
  */
1857
  table= tz_tables->table;
1858 1859 1860
  tz_tables= tz_tables->next_local;
  table->field[0]->store(tz_name->ptr(), tz_name->length(),
                         &my_charset_latin1);
1861 1862 1863 1864 1865
  /*
    It is OK to ignore ha_index_init()/ha_index_end() return values since
    mysql.time_zone* tables are MyISAM and these operations always succeed
    for MyISAM.
  */
1866
  (void)table->file->ha_index_init(0, 1);
serg@serg.mylan's avatar
serg@serg.mylan committed
1867

1868 1869
  if (table->file->index_read_map(table->record[0], table->field[0]->ptr,
                                  HA_WHOLE_KEY, HA_READ_KEY_EXACT))
1870
  {
1871
#ifdef EXTRA_DEBUG
1872 1873 1874 1875
    /*
      Most probably user has mistyped time zone name, so no need to bark here
      unless we need it for debugging.
    */
1876 1877
     sql_print_error("Can't find description of time zone '%.*s'", 
                     tz_name->length(), tz_name->ptr());
1878
#endif
1879
    goto end;
1880
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1881

1882
  tzid= (uint)table->field[1]->val_int();
1883

1884
  (void)table->file->ha_index_end();
serg@serg.mylan's avatar
serg@serg.mylan committed
1885 1886

  /*
1887 1888 1889 1890
    Now we need to lookup record in mysql.time_zone table in order to
    understand whenever this timezone uses leap seconds (again we are
    using the only index in this table).
  */
1891
  table= tz_tables->table;
1892
  tz_tables= tz_tables->next_local;
1893
  table->field[0]->store((longlong) tzid, TRUE);
1894
  (void)table->file->ha_index_init(0, 1);
serg@serg.mylan's avatar
serg@serg.mylan committed
1895

1896 1897
  if (table->file->index_read_map(table->record[0], table->field[0]->ptr,
                                  HA_WHOLE_KEY, HA_READ_KEY_EXACT))
1898
  {
monty@mysql.com's avatar
monty@mysql.com committed
1899
    sql_print_error("Can't find description of time zone '%u'", tzid);
1900
    goto end;
1901
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1902

1903 1904 1905
  /* If Uses_leap_seconds == 'Y' */
  if (table->field[1]->val_int() == 1)
  {
1906 1907
    tmp_tz_info.leapcnt= tz_leapcnt;
    tmp_tz_info.lsis= tz_lsis;
1908
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1909

1910
  (void)table->file->ha_index_end();
serg@serg.mylan's avatar
serg@serg.mylan committed
1911 1912 1913 1914 1915

  /*
    Now we will iterate through records for out time zone in
    mysql.time_zone_transition_type table. Because we want records
    only for our time zone guess what are we doing?
1916 1917
    Right - using special index.
  */
1918
  table= tz_tables->table;
1919
  tz_tables= tz_tables->next_local;
1920
  table->field[0]->store((longlong) tzid, TRUE);
1921
  (void)table->file->ha_index_init(0, 1);
serg@serg.mylan's avatar
serg@serg.mylan committed
1922

1923 1924
  res= table->file->index_read_map(table->record[0], table->field[0]->ptr,
                                   (key_part_map)1, HA_READ_KEY_EXACT);
1925 1926
  while (!res)
  {
1927
    ttid= (uint)table->field[1]->val_int();
1928

1929
    if (ttid >= TZ_MAX_TYPES)
1930 1931 1932 1933
    {
      sql_print_error("Error while loading time zone description from "
                      "mysql.time_zone_transition_type table: too big "
                      "transition type id");
1934
      goto end;
1935 1936
    }

1937
    ttis[ttid].tt_gmtoff= (long)table->field[2]->val_int();
1938 1939 1940 1941 1942
    ttis[ttid].tt_isdst= (table->field[3]->val_int() > 0);

#ifdef ABBR_ARE_USED
    // FIXME should we do something with duplicates here ?
    table->field[4]->val_str(&abbr, &abbr);
1943
    if (tmp_tz_info.charcnt + abbr.length() + 1 > sizeof(chars))
1944 1945 1946 1947
    {
      sql_print_error("Error while loading time zone description from "
                      "mysql.time_zone_transition_type table: not enough "
                      "room for abbreviations");
1948
      goto end;
1949
    }
1950 1951 1952 1953 1954
    ttis[ttid].tt_abbrind= tmp_tz_info.charcnt;
    memcpy(chars + tmp_tz_info.charcnt, abbr.ptr(), abbr.length());
    tmp_tz_info.charcnt+= abbr.length();
    chars[tmp_tz_info.charcnt]= 0;
    tmp_tz_info.charcnt++;
serg@serg.mylan's avatar
serg@serg.mylan committed
1955

1956 1957
    DBUG_PRINT("info",
      ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
serg@serg.mylan's avatar
serg@serg.mylan committed
1958
       "abbr='%s' tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff,
1959 1960 1961 1962 1963 1964 1965 1966
       chars + ttis[ttid].tt_abbrind, ttis[ttid].tt_isdst));
#else
    DBUG_PRINT("info",
      ("time_zone_transition_type table: tz_id=%u tt_id=%u tt_gmtoff=%ld "
       "tt_isdst=%u", tzid, ttid, ttis[ttid].tt_gmtoff, ttis[ttid].tt_isdst));
#endif

    /* ttid is increasing because we are reading using index */
1967
    DBUG_ASSERT(ttid >= tmp_tz_info.typecnt);
serg@serg.mylan's avatar
serg@serg.mylan committed
1968

1969
    tmp_tz_info.typecnt= ttid + 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
1970 1971

    res= table->file->index_next_same(table->record[0],
1972
                                      table->field[0]->ptr, 4);
1973
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
1974

1975 1976 1977 1978
  if (res != HA_ERR_END_OF_FILE)
  {
    sql_print_error("Error while loading time zone description from "
                    "mysql.time_zone_transition_type table");
1979
    goto end;
1980 1981
  }

1982
  (void)table->file->ha_index_end();
serg@serg.mylan's avatar
serg@serg.mylan committed
1983

1984 1985

  /*
serg@serg.mylan's avatar
serg@serg.mylan committed
1986 1987
    At last we are doing the same thing for records in
    mysql.time_zone_transition table. Here we additionaly need records
1988 1989
    in ascending order by index scan also satisfies us.
  */
1990
  table= tz_tables->table; 
1991
  table->field[0]->store((longlong) tzid, TRUE);
1992
  (void)table->file->ha_index_init(0, 1);
serg@serg.mylan's avatar
serg@serg.mylan committed
1993

1994 1995
  res= table->file->index_read_map(table->record[0], table->field[0]->ptr,
                                   (key_part_map)1, HA_READ_KEY_EXACT);
1996 1997 1998 1999 2000
  while (!res)
  {
    ttime= (my_time_t)table->field[1]->val_int();
    ttid= (uint)table->field[2]->val_int();

2001
    if (tmp_tz_info.timecnt + 1 > TZ_MAX_TIMES)
2002 2003 2004 2005
    {
      sql_print_error("Error while loading time zone description from "
                      "mysql.time_zone_transition table: "
                      "too much transitions");
2006
      goto end;
2007
    }
2008
    if (ttid + 1 > tmp_tz_info.typecnt)
2009 2010 2011 2012
    {
      sql_print_error("Error while loading time zone description from "
                      "mysql.time_zone_transition table: "
                      "bad transition type id");
2013
      goto end;
2014
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
2015

2016 2017 2018
    ats[tmp_tz_info.timecnt]= ttime;
    types[tmp_tz_info.timecnt]= ttid;
    tmp_tz_info.timecnt++;
2019 2020

    DBUG_PRINT("info",
2021
      ("time_zone_transition table: tz_id: %u  tt_time: %lu  tt_id: %u",
2022
       tzid, (ulong) ttime, ttid));
serg@serg.mylan's avatar
serg@serg.mylan committed
2023 2024

    res= table->file->index_next_same(table->record[0],
2025
                                      table->field[0]->ptr, 4);
2026 2027
  }

serg@serg.mylan's avatar
serg@serg.mylan committed
2028
  /*
2029 2030 2031 2032 2033 2034 2035
    We have to allow HA_ERR_KEY_NOT_FOUND because some time zones
    for example UTC have no transitons.
  */
  if (res != HA_ERR_END_OF_FILE && res != HA_ERR_KEY_NOT_FOUND)
  {
    sql_print_error("Error while loading time zone description from "
                    "mysql.time_zone_transition table");
2036
    goto end;
2037
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2038

2039
  (void)table->file->ha_index_end();
2040
  table= 0;
serg@serg.mylan's avatar
serg@serg.mylan committed
2041

2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069
  /*
    Let us check how correct our time zone description is. We don't check for
    tz->timecnt < 1 since it is ok for GMT.
  */
  if (tmp_tz_info.typecnt < 1)
  {
    sql_print_error("loading time zone without transition types");
    goto end;
  }

  /* Allocate memory for the timezone info and timezone name in tz_storage. */
  if (!(alloc_buff= (char*) alloc_root(&tz_storage, sizeof(TIME_ZONE_INFO) +
                                       tz_name->length() + 1)))
  {
    sql_print_error("Out of memory while loading time zone description");
    return 0;
  }

  /* Move the temporary tz_info into the allocated area */
  tz_info= (TIME_ZONE_INFO *)alloc_buff;
  memcpy(tz_info, &tmp_tz_info, sizeof(TIME_ZONE_INFO));
  tz_name_buff= alloc_buff + sizeof(TIME_ZONE_INFO);
  /*
    By writing zero to the end we guarantee that we can call ptr()
    instead of c_ptr() for time zone name.
  */
  strmake(tz_name_buff, tz_name->ptr(), tz_name->length());

2070 2071 2072
  /*
    Now we will allocate memory and init TIME_ZONE_INFO structure.
  */
2073 2074 2075 2076
  if (!(alloc_buff= (char*) alloc_root(&tz_storage,
                                       ALIGN_SIZE(sizeof(my_time_t) *
                                                  tz_info->timecnt) +
                                       ALIGN_SIZE(tz_info->timecnt) +
2077
#ifdef ABBR_ARE_USED
2078
                                       ALIGN_SIZE(tz_info->charcnt) +
2079
#endif
2080 2081
                                       sizeof(TRAN_TYPE_INFO) *
                                       tz_info->typecnt)))
2082
  {
2083
    sql_print_error("Out of memory while loading time zone description");
2084
    goto end;
2085 2086
  }

2087
  tz_info->ats= (my_time_t *) alloc_buff;
2088 2089
  memcpy(tz_info->ats, ats, tz_info->timecnt * sizeof(my_time_t));
  alloc_buff+= ALIGN_SIZE(sizeof(my_time_t) * tz_info->timecnt);
monty@mysql.com's avatar
monty@mysql.com committed
2090
  tz_info->types= (uchar *)alloc_buff;
2091 2092 2093 2094 2095 2096 2097 2098 2099
  memcpy(tz_info->types, types, tz_info->timecnt);
  alloc_buff+= ALIGN_SIZE(tz_info->timecnt);
#ifdef ABBR_ARE_USED
  tz_info->chars= alloc_buff;
  memcpy(tz_info->chars, chars, tz_info->charcnt);
  alloc_buff+= ALIGN_SIZE(tz_info->charcnt);
#endif
  tz_info->ttis= (TRAN_TYPE_INFO *)alloc_buff;
  memcpy(tz_info->ttis, ttis, tz_info->typecnt * sizeof(TRAN_TYPE_INFO));
serg@serg.mylan's avatar
serg@serg.mylan committed
2100

2101
  /* Build reversed map. */
2102 2103
  if (prepare_tz_info(tz_info, &tz_storage))
  {
2104
    sql_print_error("Unable to build mktime map for time zone");
2105
    goto end;
2106
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2107 2108


2109
  if (!(tmp_tzname= new (&tz_storage) Tz_names_entry()) ||
serg@serg.mylan's avatar
serg@serg.mylan committed
2110
      !(tmp_tzname->tz= new (&tz_storage) Time_zone_db(tz_info,
2111
                                            &(tmp_tzname->name))) ||
serg@serg.mylan's avatar
serg@serg.mylan committed
2112
      (tmp_tzname->name.set(tz_name_buff, tz_name->length(),
2113
                            &my_charset_latin1),
2114
       my_hash_insert(&tz_names, (const uchar *)tmp_tzname)))
2115
  {
2116
    sql_print_error("Out of memory while loading time zone");
2117
    goto end;
2118 2119 2120 2121 2122 2123
  }

  /*
    Loading of time zone succeeded
  */
  return_val= tmp_tzname->tz;
serg@serg.mylan's avatar
serg@serg.mylan committed
2124

2125
end:
2126 2127

  if (table)
2128
    (void)table->file->ha_index_end();
2129 2130 2131 2132 2133 2134 2135 2136 2137 2138

  DBUG_RETURN(return_val);
}


/*
  Parse string that specifies time zone as offset from UTC.

  SYNOPSIS
    str_to_offset()
serg@serg.mylan's avatar
serg@serg.mylan committed
2139
      str    - pointer to string which contains offset
2140 2141 2142 2143
      length - length of string
      offset - out parameter for storing found offset in seconds.

  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
2144 2145
    This function parses string which contains time zone offset
    in form similar to '+10:00' and converts found value to
2146
    seconds from UTC form (east is positive).
serg@serg.mylan's avatar
serg@serg.mylan committed
2147

2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158
  RETURN VALUE
    0 - Ok
    1 - String doesn't contain valid time zone offset
*/
my_bool
str_to_offset(const char *str, uint length, long *offset)
{
  const char *end= str + length;
  my_bool negative;
  ulong number_tmp;
  long offset_tmp;
serg@serg.mylan's avatar
serg@serg.mylan committed
2159

2160 2161
  if (length < 4)
    return 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
2162

2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177
  if (*str == '+')
    negative= 0;
  else if (*str == '-')
    negative= 1;
  else
    return 1;
  str++;

  number_tmp= 0;

  while (str < end && my_isdigit(&my_charset_latin1, *str))
  {
    number_tmp= number_tmp*10 + *str - '0';
    str++;
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2178

2179 2180 2181 2182 2183
  if (str + 1 >= end || *str != ':')
    return 1;
  str++;

  offset_tmp = number_tmp * MINS_PER_HOUR; number_tmp= 0;
serg@serg.mylan's avatar
serg@serg.mylan committed
2184

2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198
  while (str < end && my_isdigit(&my_charset_latin1, *str))
  {
    number_tmp= number_tmp * 10 + *str - '0';
    str++;
  }

  if (str != end)
    return 1;

  offset_tmp= (offset_tmp + number_tmp) * SECS_PER_MIN;

  if (negative)
    offset_tmp= -offset_tmp;

serg@serg.mylan's avatar
serg@serg.mylan committed
2199
  /*
2200 2201 2202
    Check if offset is in range prescribed by standard
    (from -12:59 to 13:00).
  */
serg@serg.mylan's avatar
serg@serg.mylan committed
2203

2204 2205 2206
  if (number_tmp > 59 || offset_tmp < -13 * SECS_PER_HOUR + 1 ||
      offset_tmp > 13 * SECS_PER_HOUR)
    return 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
2207

2208
  *offset= offset_tmp;
serg@serg.mylan's avatar
serg@serg.mylan committed
2209

2210 2211 2212 2213 2214 2215 2216 2217 2218
  return 0;
}


/*
  Get Time_zone object for specified time zone.

  SYNOPSIS
    my_tz_find()
2219
      thd  - pointer to thread THD structure
2220 2221 2222 2223
      name - time zone specification

  DESCRIPTION
    This function checks if name is one of time zones described in db,
serg@serg.mylan's avatar
serg@serg.mylan committed
2224
    predefined SYSTEM time zone or valid time zone specification as
2225 2226 2227 2228
    offset from UTC (In last case it will create proper Time_zone_offset
    object if there were not any.). If name is ok it returns corresponding
    Time_zone object.

serg@serg.mylan's avatar
serg@serg.mylan committed
2229 2230
    Clients of this function are not responsible for releasing resources
    occupied by returned Time_zone object so they can just forget pointers
2231
    to Time_zone object if they are not needed longer.
serg@serg.mylan's avatar
serg@serg.mylan committed
2232

2233 2234 2235 2236 2237 2238
    Other important property of this function: if some Time_zone found once
    it will be for sure found later, so this function can also be used for
    checking if proper Time_zone object exists (and if there will be error
    it will be reported during first call).

    If name pointer is 0 then this function returns 0 (this allows to pass 0
serg@serg.mylan's avatar
serg@serg.mylan committed
2239
    values as parameter without additional external check and this property
2240 2241
    is used by @@time_zone variable handling code).

2242 2243 2244 2245
    It will perform lookup in system tables (mysql.time_zone*),
    opening and locking them, and closing afterwards. It won't perform
    such lookup if no time zone describing tables were found during
    server start up.
serg@serg.mylan's avatar
serg@serg.mylan committed
2246

2247
  RETURN VALUE
serg@serg.mylan's avatar
serg@serg.mylan committed
2248
    Pointer to corresponding Time_zone object. 0 - in case of bad time zone
2249
    specification or other error.
serg@serg.mylan's avatar
serg@serg.mylan committed
2250

2251
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
2252
Time_zone *
2253
my_tz_find(THD *thd, const String *name)
2254
{
2255
  Tz_names_entry *tmp_tzname;
2256 2257 2258
  Time_zone *result_tz= 0;
  long offset;
  DBUG_ENTER("my_tz_find");
serg@serg.mylan's avatar
serg@serg.mylan committed
2259
  DBUG_PRINT("enter", ("time zone name='%s'",
2260
                       name ? ((String *)name)->c_ptr_safe() : "NULL"));
2261

2262
  if (!name || name->is_empty())
2263
    DBUG_RETURN(0);
serg@serg.mylan's avatar
serg@serg.mylan committed
2264

2265
  VOID(pthread_mutex_lock(&tz_LOCK));
serg@serg.mylan's avatar
serg@serg.mylan committed
2266

2267 2268
  if (!str_to_offset(name->ptr(), name->length(), &offset))
  {
serg@serg.mylan's avatar
serg@serg.mylan committed
2269

2270
    if (!(result_tz= (Time_zone_offset *)hash_search(&offset_tzs,
2271
                                                     (const uchar *)&offset,
2272 2273 2274
                                                     sizeof(long))))
    {
      DBUG_PRINT("info", ("Creating new Time_zone_offset object"));
serg@serg.mylan's avatar
serg@serg.mylan committed
2275

2276
      if (!(result_tz= new (&tz_storage) Time_zone_offset(offset)) ||
2277
          my_hash_insert(&offset_tzs, (const uchar *) result_tz))
2278
      {
monty@mysql.com's avatar
monty@mysql.com committed
2279
        result_tz= 0;
2280 2281 2282 2283
        sql_print_error("Fatal error: Out of memory "
                        "while setting new time zone");
      }
    }
monty@mysql.com's avatar
monty@mysql.com committed
2284 2285 2286 2287
  }
  else
  {
    result_tz= 0;
2288
    if ((tmp_tzname= (Tz_names_entry *)hash_search(&tz_names,
2289
                                                   (const uchar *)name->ptr(),
2290 2291
                                                   name->length())))
      result_tz= tmp_tzname->tz;
2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304
    else if (time_zone_tables_exist)
    {
      TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT];
      Open_tables_state open_tables_state_backup;

      tz_init_table_list(tz_tables);
      if (!open_system_tables_for_read(thd, tz_tables,
                                       &open_tables_state_backup))
      {
        result_tz= tz_load_from_open_tables(name, tz_tables);
        close_system_tables(thd, &open_tables_state_backup);
      }
    }
2305
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2306

2307 2308 2309 2310 2311
  VOID(pthread_mutex_unlock(&tz_LOCK));

  DBUG_RETURN(result_tz);
}

2312

2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330
/**
  Convert leap seconds into non-leap

  This function will convert the leap seconds added by the OS to 
  non-leap seconds, e.g. 23:59:59, 23:59:60 -> 23:59:59, 00:00:01 ...
  This check is not checking for years on purpose : although it's not a
  complete check this way it doesn't require looking (and having installed)
  the leap seconds table.

  @param[in,out] broken down time structure as filled in by the OS
*/

void Time_zone::adjust_leap_second(MYSQL_TIME *t)
{
  if (t->second == 60 || t->second == 61)
    t->second= 59;
}

2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342
#endif /* !defined(TESTTIME) && !defined(TZINFO2SQL) */


#ifdef TZINFO2SQL
/*
  This code belongs to mysql_tzinfo_to_sql converter command line utility.
  This utility should be used by db admin for populating mysql.time_zone
  tables.
*/


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
2343
  Print info about time zone described by TIME_ZONE_INFO struct as
2344 2345 2346 2347 2348
  SQL statements populating mysql.time_zone* tables.

  SYNOPSIS
    print_tz_as_sql()
      tz_name - name of time zone
serg@serg.mylan's avatar
serg@serg.mylan committed
2349
      sp      - structure describing time zone
2350 2351 2352 2353 2354 2355 2356
*/
void
print_tz_as_sql(const char* tz_name, const TIME_ZONE_INFO *sp)
{
  uint i;

  /* Here we assume that all time zones have same leap correction tables */
serg@serg.mylan's avatar
serg@serg.mylan committed
2357
  printf("INSERT INTO time_zone (Use_leap_seconds) VALUES ('%s');\n",
2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371
         sp->leapcnt ? "Y" : "N");
  printf("SET @time_zone_id= LAST_INSERT_ID();\n");
  printf("INSERT INTO time_zone_name (Name, Time_zone_id) VALUES \
('%s', @time_zone_id);\n", tz_name);

  if (sp->timecnt)
  {
    printf("INSERT INTO time_zone_transition \
(Time_zone_id, Transition_time, Transition_type_id) VALUES\n");
    for (i= 0; i < sp->timecnt; i++)
      printf("%s(@time_zone_id, %ld, %u)\n", (i == 0 ? " " : ","), sp->ats[i],
             (uint)sp->types[i]);
    printf(";\n");
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2372

2373 2374
  printf("INSERT INTO time_zone_transition_type \
(Time_zone_id, Transition_type_id, Offset, Is_DST, Abbreviation) VALUES\n");
serg@serg.mylan's avatar
serg@serg.mylan committed
2375

2376 2377
  for (i= 0; i < sp->typecnt; i++)
    printf("%s(@time_zone_id, %u, %ld, %d, '%s')\n", (i == 0 ? " " : ","), i,
serg@serg.mylan's avatar
serg@serg.mylan committed
2378
           sp->ttis[i].tt_gmtoff, sp->ttis[i].tt_isdst,
2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389
           sp->chars + sp->ttis[i].tt_abbrind);
  printf(";\n");
}


/*
  Print info about leap seconds in time zone as SQL statements
  populating mysql.time_zone_leap_second table.

  SYNOPSIS
    print_tz_leaps_as_sql()
serg@serg.mylan's avatar
serg@serg.mylan committed
2390
      sp      - structure describing time zone
2391 2392 2393 2394 2395 2396
*/
void
print_tz_leaps_as_sql(const TIME_ZONE_INFO *sp)
{
  uint i;

serg@serg.mylan's avatar
serg@serg.mylan committed
2397 2398
  /*
    We are assuming that there are only one list of leap seconds
2399 2400 2401
    For all timezones.
  */
  printf("TRUNCATE TABLE time_zone_leap_second;\n");
serg@serg.mylan's avatar
serg@serg.mylan committed
2402

2403 2404 2405 2406 2407
  if (sp->leapcnt)
  {
    printf("INSERT INTO time_zone_leap_second \
(Transition_time, Correction) VALUES\n");
    for (i= 0; i < sp->leapcnt; i++)
serg@serg.mylan's avatar
serg@serg.mylan committed
2408
      printf("%s(%ld, %ld)\n", (i == 0 ? " " : ","),
2409 2410 2411 2412 2413 2414 2415 2416 2417
             sp->lsis[i].ls_trans, sp->lsis[i].ls_corr);
    printf(";\n");
  }

  printf("ALTER TABLE time_zone_leap_second ORDER BY Transition_time;\n");
}


/*
serg@serg.mylan's avatar
serg@serg.mylan committed
2418
  Some variables used as temporary or as parameters
2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429
  in recursive scan_tz_dir() code.
*/
TIME_ZONE_INFO tz_info;
MEM_ROOT tz_storage;
char fullname[FN_REFLEN + 1];
char *root_name_end;


/*
  Recursively scan zoneinfo directory and print all found time zone
  descriptions as SQL.
serg@serg.mylan's avatar
serg@serg.mylan committed
2430

2431
  SYNOPSIS
serg@serg.mylan's avatar
serg@serg.mylan committed
2432
    scan_tz_dir()
2433
      name_end - pointer to end of path to directory to be searched.
serg@serg.mylan's avatar
serg@serg.mylan committed
2434

2435
  DESCRIPTION
serg@serg.mylan's avatar
serg@serg.mylan committed
2436
    This auxiliary recursive function also uses several global
2437
    variables as in parameters and for storing temporary values.
serg@serg.mylan's avatar
serg@serg.mylan committed
2438

2439
    fullname      - path to directory that should be scanned.
serg@serg.mylan's avatar
serg@serg.mylan committed
2440
    root_name_end - pointer to place in fullname where part with
2441 2442
                    path to initial directory ends.
    current_tz_id - last used time zone id
serg@serg.mylan's avatar
serg@serg.mylan committed
2443

2444 2445
  RETURN VALUE
    0 - Ok, 1 - Fatal error
serg@serg.mylan's avatar
serg@serg.mylan committed
2446

2447 2448 2449 2450 2451 2452 2453
*/
my_bool
scan_tz_dir(char * name_end)
{
  MY_DIR *cur_dir;
  char *name_end_tmp;
  uint i;
serg@serg.mylan's avatar
serg@serg.mylan committed
2454

2455 2456 2457 2458
  if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT))))
    return 1;

  name_end= strmake(name_end, "/", FN_REFLEN - (name_end - fullname));
serg@serg.mylan's avatar
serg@serg.mylan committed
2459

2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500
  for (i= 0; i < cur_dir->number_off_files; i++)
  {
    if (cur_dir->dir_entry[i].name[0] != '.')
    {
      name_end_tmp= strmake(name_end, cur_dir->dir_entry[i].name,
                            FN_REFLEN - (name_end - fullname));

      if (MY_S_ISDIR(cur_dir->dir_entry[i].mystat->st_mode))
      {
        if (scan_tz_dir(name_end_tmp))
        {
          my_dirend(cur_dir);
          return 1;
        }
      }
      else if (MY_S_ISREG(cur_dir->dir_entry[i].mystat->st_mode))
      {
        init_alloc_root(&tz_storage, 32768, 0);
        if (!tz_load(fullname, &tz_info, &tz_storage))
          print_tz_as_sql(root_name_end + 1, &tz_info);
        else
          fprintf(stderr,
                  "Warning: Unable to load '%s' as time zone. Skipping it.\n",
                  fullname);
        free_root(&tz_storage, MYF(0));
      }
      else
        fprintf(stderr, "Warning: '%s' is not regular file or directory\n",
                fullname);
    }
  }

  my_dirend(cur_dir);

  return 0;
}


int
main(int argc, char **argv)
{
2501
#ifndef __NETWARE__
2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515
  MY_INIT(argv[0]);

  if (argc != 2 && argc != 3)
  {
    fprintf(stderr, "Usage:\n");
    fprintf(stderr, " %s timezonedir\n", argv[0]);
    fprintf(stderr, " %s timezonefile timezonename\n", argv[0]);
    fprintf(stderr, " %s --leap timezonefile\n", argv[0]);
    return 1;
  }

  if (argc == 2)
  {
    root_name_end= strmake(fullname, argv[1], FN_REFLEN);
serg@serg.mylan's avatar
serg@serg.mylan committed
2516

2517 2518 2519 2520
    printf("TRUNCATE TABLE time_zone;\n");
    printf("TRUNCATE TABLE time_zone_name;\n");
    printf("TRUNCATE TABLE time_zone_transition;\n");
    printf("TRUNCATE TABLE time_zone_transition_type;\n");
serg@serg.mylan's avatar
serg@serg.mylan committed
2521

2522 2523 2524 2525 2526 2527
    if (scan_tz_dir(root_name_end))
    {
      fprintf(stderr, "There were fatal errors during processing "
                      "of zoneinfo directory\n");
      return 1;
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
2528

2529 2530 2531 2532 2533 2534 2535 2536
    printf("ALTER TABLE time_zone_transition "
           "ORDER BY Time_zone_id, Transition_time;\n");
    printf("ALTER TABLE time_zone_transition_type "
           "ORDER BY Time_zone_id, Transition_type_id;\n");
  }
  else
  {
    init_alloc_root(&tz_storage, 32768, 0);
serg@serg.mylan's avatar
serg@serg.mylan committed
2537

2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555
    if (strcmp(argv[1], "--leap") == 0)
    {
      if (tz_load(argv[2], &tz_info, &tz_storage))
      {
        fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[2]);
        return 1;
      }
      print_tz_leaps_as_sql(&tz_info);
    }
    else
    {
      if (tz_load(argv[1], &tz_info, &tz_storage))
      {
        fprintf(stderr, "Problems with zoneinfo file '%s'\n", argv[2]);
        return 1;
      }
      print_tz_as_sql(argv[2], &tz_info);
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
2556

2557 2558 2559
    free_root(&tz_storage, MYF(0));
  }

2560 2561 2562 2563
#else
  fprintf(stderr, "This tool has not been ported to NetWare\n");
#endif /* __NETWARE__ */

2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583
  return 0;
}

#endif /* defined(TZINFO2SQL) */


#ifdef TESTTIME

/*
   Some simple brute-force test wich allowed to catch a pair of bugs.
   Also can provide interesting facts about system's time zone support
   implementation.
*/

#ifndef CHAR_BIT
#define CHAR_BIT 8
#endif

#ifndef TYPE_BIT
#define TYPE_BIT(type)	(sizeof (type) * CHAR_BIT)
serg@serg.mylan's avatar
serg@serg.mylan committed
2584
#endif
2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608

#ifndef TYPE_SIGNED
#define TYPE_SIGNED(type) (((type) -1) < 0)
#endif

my_bool
is_equal_TIME_tm(const TIME* time_arg, const struct tm * tm_arg)
{
  return (time_arg->year == (uint)tm_arg->tm_year+TM_YEAR_BASE) &&
         (time_arg->month == (uint)tm_arg->tm_mon+1) &&
         (time_arg->day == (uint)tm_arg->tm_mday) &&
         (time_arg->hour == (uint)tm_arg->tm_hour) &&
         (time_arg->minute == (uint)tm_arg->tm_min) &&
         (time_arg->second == (uint)tm_arg->tm_sec) &&
         time_arg->second_part == 0;
}


int
main(int argc, char **argv)
{
  my_bool localtime_negative;
  TIME_ZONE_INFO tz_info;
  struct tm tmp;
2609
  MYSQL_TIME time_tmp;
2610
  time_t t, t1, t2;
2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621
  char fullname[FN_REFLEN+1];
  char *str_end;
  MEM_ROOT tz_storage;

  MY_INIT(argv[0]);

  init_alloc_root(&tz_storage, 32768, 0);

  /* let us set some well known timezone */
  setenv("TZ", "MET", 1);
  tzset();
serg@serg.mylan's avatar
serg@serg.mylan committed
2622

2623 2624 2625 2626 2627 2628 2629 2630
  /* Some initial time zone related system info */
  printf("time_t: %s %u bit\n", TYPE_SIGNED(time_t) ? "signed" : "unsigned",
                                (uint)TYPE_BIT(time_t));
  if (TYPE_SIGNED(time_t))
  {
    t= -100;
    localtime_negative= test(localtime_r(&t, &tmp) != 0);
    printf("localtime_r %s negative params \
serg@serg.mylan's avatar
serg@serg.mylan committed
2631
           (time_t=%d is %d-%d-%d %d:%d:%d)\n",
2632
           (localtime_negative ? "supports" : "doesn't support"), (int)t,
serg@serg.mylan's avatar
serg@serg.mylan committed
2633
           TM_YEAR_BASE + tmp.tm_year, tmp.tm_mon + 1, tmp.tm_mday,
2634
           tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
serg@serg.mylan's avatar
serg@serg.mylan committed
2635

2636
    printf("mktime %s negative results (%d)\n",
serg@serg.mylan's avatar
serg@serg.mylan committed
2637
           (t == mktime(&tmp) ? "doesn't support" : "supports"),
2638 2639 2640 2641 2642 2643 2644 2645
           (int)mktime(&tmp));
  }

  tmp.tm_year= 103; tmp.tm_mon= 2; tmp.tm_mday= 30;
  tmp.tm_hour= 2; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= -1;
  t= mktime(&tmp);
  printf("mktime returns %s for spring time gap (%d)\n",
         (t != (time_t)-1 ? "something" : "error"), (int)t);
serg@serg.mylan's avatar
serg@serg.mylan committed
2646

2647 2648 2649 2650 2651
  tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
  tmp.tm_hour= 0; tmp.tm_min= 0; tmp.tm_sec= 0; tmp.tm_isdst= 0;
  t= mktime(&tmp);
  printf("mktime returns %s for non existing date (%d)\n",
         (t != (time_t)-1 ? "something" : "error"), (int)t);
serg@serg.mylan's avatar
serg@serg.mylan committed
2652

2653 2654 2655 2656 2657 2658 2659 2660 2661 2662
  tmp.tm_year= 103; tmp.tm_mon= 8; tmp.tm_mday= 1;
  tmp.tm_hour= 25; tmp.tm_min=0; tmp.tm_sec=0; tmp.tm_isdst=1;
  t= mktime(&tmp);
  printf("mktime %s unnormalized input (%d)\n",
         (t != (time_t)-1 ? "handles" : "doesn't handle"), (int)t);

  tmp.tm_year= 103; tmp.tm_mon= 9; tmp.tm_mday= 26;
  tmp.tm_hour= 0; tmp.tm_min= 30; tmp.tm_sec= 0; tmp.tm_isdst= 1;
  mktime(&tmp);
  tmp.tm_hour= 2; tmp.tm_isdst= -1;
2663
  t= mktime(&tmp);
2664 2665 2666
  tmp.tm_hour= 4; tmp.tm_isdst= 0;
  mktime(&tmp);
  tmp.tm_hour= 2; tmp.tm_isdst= -1;
2667
  t1= mktime(&tmp);
2668
  printf("mktime is %s (%d %d)\n",
serg@serg.mylan's avatar
serg@serg.mylan committed
2669
         (t == t1 ? "determenistic" : "is non-determenistic"),
2670 2671 2672 2673 2674
         (int)t, (int)t1);

  /* Let us load time zone description */
  str_end= strmake(fullname, TZDIR, FN_REFLEN);
  strmake(str_end, "/MET", FN_REFLEN - (str_end - fullname));
serg@serg.mylan's avatar
serg@serg.mylan committed
2675

2676 2677 2678 2679 2680 2681 2682 2683
  if (tz_load(fullname, &tz_info, &tz_storage))
  {
    printf("Unable to load time zone info from '%s'\n", fullname);
    free_root(&tz_storage, MYF(0));
    return 1;
  }

  printf("Testing our implementation\n");
serg@serg.mylan's avatar
serg@serg.mylan committed
2684

2685 2686 2687 2688
  if (TYPE_SIGNED(time_t) && localtime_negative)
  {
    for (t= -40000; t < 20000; t++)
    {
2689 2690
      localtime_r(&t, &tmp);
      gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
2691 2692 2693 2694 2695 2696 2697 2698 2699
      if (!is_equal_TIME_tm(&time_tmp, &tmp))
      {
        printf("Problem with negative time_t = %d\n", (int)t);
        free_root(&tz_storage, MYF(0));
        return 1;
      }
    }
    printf("gmt_sec_to_TIME = localtime for time_t in [-40000,20000) range\n");
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2700

2701 2702 2703
  for (t= 1000000000; t < 1100000000; t+= 13)
  {
    localtime_r(&t,&tmp);
2704
    gmt_sec_to_TIME(&time_tmp, (my_time_t)t, &tz_info);
serg@serg.mylan's avatar
serg@serg.mylan committed
2705

2706 2707 2708 2709 2710 2711 2712 2713 2714
    if (!is_equal_TIME_tm(&time_tmp, &tmp))
    {
      printf("Problem with time_t = %d\n", (int)t);
      free_root(&tz_storage, MYF(0));
      return 1;
    }
  }
  printf("gmt_sec_to_TIME = localtime for time_t in [1000000000,1100000000) range\n");

2715
  my_init_time();
serg@serg.mylan's avatar
serg@serg.mylan committed
2716

2717 2718 2719 2720 2721
  /*
    Be careful here! my_system_gmt_sec doesn't fully handle unnormalized
    dates.
  */
  for (time_tmp.year= 1980; time_tmp.year < 2010; time_tmp.year++)
2722
  {
2723
    for (time_tmp.month= 1; time_tmp.month < 13; time_tmp.month++)
2724
    {
serg@serg.mylan's avatar
serg@serg.mylan committed
2725
      for (time_tmp.day= 1;
2726 2727
           time_tmp.day < mon_lengths[isleap(time_tmp.year)][time_tmp.month-1];
           time_tmp.day++)
2728
      {
2729
        for (time_tmp.hour= 0; time_tmp.hour < 24; time_tmp.hour++)
2730
        {
2731
          for (time_tmp.minute= 0; time_tmp.minute < 60; time_tmp.minute+= 5)
2732
          {
2733 2734
            for (time_tmp.second=0; time_tmp.second<60; time_tmp.second+=25)
            {
2735 2736
              long not_used;
              my_bool not_used_2;
2737 2738
              t= (time_t)my_system_gmt_sec(&time_tmp, &not_used, &not_used_2);
              t1= (time_t)TIME_to_gmt_sec(&time_tmp, &tz_info, &not_used_2);
2739 2740
              if (t != t1)
              {
serg@serg.mylan's avatar
serg@serg.mylan committed
2741
                /*
2742 2743 2744
                  We need special handling during autumn since my_system_gmt_sec
                  prefers greater time_t values (in MET) for ambiguity.
                  And BTW that is a bug which should be fixed !!!
serg@serg.mylan's avatar
serg@serg.mylan committed
2745
                */
2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757
                tmp.tm_year= time_tmp.year - TM_YEAR_BASE;
                tmp.tm_mon= time_tmp.month - 1;
                tmp.tm_mday= time_tmp.day;
                tmp.tm_hour= time_tmp.hour;
                tmp.tm_min= time_tmp.minute;
                tmp.tm_sec= time_tmp.second;
                tmp.tm_isdst= 1;

                t2= mktime(&tmp);

                if (t1 == t2)
                  continue;
serg@serg.mylan's avatar
serg@serg.mylan committed
2758

2759 2760 2761 2762
                printf("Problem: %u/%u/%u %u:%u:%u with times t=%d, t1=%d\n",
                       time_tmp.year, time_tmp.month, time_tmp.day,
                       time_tmp.hour, time_tmp.minute, time_tmp.second,
                       (int)t,(int)t1);
serg@serg.mylan's avatar
serg@serg.mylan committed
2763

2764 2765 2766 2767
                free_root(&tz_storage, MYF(0));
                return 1;
              }
            }
2768 2769 2770 2771 2772
          }
        }
      }
    }
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
2773

2774 2775 2776 2777 2778 2779 2780
  printf("TIME_to_gmt_sec = my_system_gmt_sec for test range\n");

  free_root(&tz_storage, MYF(0));
  return 0;
}

#endif /* defined(TESTTIME) */