~drizzle-trunk/drizzle/development

1 by brian
clean slate
1
/* Copyright (C) 2004-2006 MySQL AB
2
3
 This program is free software; you can redistribute it and/or modify
4
 it under the terms of the GNU General Public License as published by
5
 the Free Software Foundation; version 2 of the License.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 GNU General Public License for more details.
11
12
 You should have received a copy of the GNU General Public License
13
 along with this program; if not, write to the Free Software
14
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
15
16
#include <my_time.h>
17
#include <m_string.h>
18
#include <m_ctype.h>
19
/* Windows version of localtime_r() is declared in my_ptrhead.h */
20
#include <my_pthread.h>
21
22
ulonglong log_10_int[20]=
23
{
24
  1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL,
25
  ULL(100000000), ULL(1000000000), ULL(10000000000), ULL(100000000000),
26
  ULL(1000000000000), ULL(10000000000000), ULL(100000000000000),
27
  ULL(1000000000000000), ULL(10000000000000000), ULL(100000000000000000),
28
  ULL(1000000000000000000), ULL(10000000000000000000)
29
};
30
31
32
/* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */
33
34
static uchar internal_format_positions[]=
35
{0, 1, 2, 3, 4, 5, 6, (uchar) 255};
36
37
static char time_separator=':';
38
39
static ulong const days_at_timestart=719528;	/* daynr at 1970.01.01 */
40
uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
41
42
/*
43
  Offset of system time zone from UTC in seconds used to speed up 
44
  work of my_system_gmt_sec() function.
45
*/
46
static long my_time_zone=0;
47
48
49
/* Calc days in one year. works with 0 <= year <= 99 */
50
51
uint calc_days_in_year(uint year)
52
{
53
  return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
54
          366 : 365);
55
}
56
57
/**
58
  @brief Check datetime value for validity according to flags.
59
60
  @param[in]  ltime          Date to check.
61
  @param[in]  not_zero_date  ltime is not the zero date
62
  @param[in]  flags          flags to check
63
                             (see str_to_datetime() flags in my_time.h)
64
  @param[out] was_cut        set to 2 if value was invalid according to flags.
65
                             (Feb 29 in non-leap etc.)  This remains unchanged
66
                             if value is not invalid.
67
68
  @details Here we assume that year and month is ok!
69
    If month is 0 we allow any date. (This only happens if we allow zero
70
    date parts in str_to_datetime())
71
    Disallow dates with zero year and non-zero month and/or day.
72
73
  @return
74
    0  OK
75
    1  error
76
*/
77
78
my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
79
                   ulong flags, int *was_cut)
80
{
81
  if (not_zero_date)
82
  {
83
    if ((((flags & TIME_NO_ZERO_IN_DATE) || !(flags & TIME_FUZZY_DATE)) &&
84
         (ltime->month == 0 || ltime->day == 0)) ||
85
        (!(flags & TIME_INVALID_DATES) &&
86
         ltime->month && ltime->day > days_in_month[ltime->month-1] &&
87
         (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 ||
88
          ltime->day != 29)))
89
    {
90
      *was_cut= 2;
91
      return TRUE;
92
    }
93
  }
94
  else if (flags & TIME_NO_ZERO_DATE)
95
  {
96
    /*
97
      We don't set *was_cut here to signal that the problem was a zero date
98
      and not an invalid date
99
    */
100
    return TRUE;
101
  }
102
  return FALSE;
103
}
104
105
106
/*
107
  Convert a timestamp string to a MYSQL_TIME value.
108
109
  SYNOPSIS
110
    str_to_datetime()
111
    str                 String to parse
112
    length              Length of string
113
    l_time              Date is stored here
114
    flags               Bitmap of following items
115
                        TIME_FUZZY_DATE    Set if we should allow partial dates
116
                        TIME_DATETIME_ONLY Set if we only allow full datetimes.
117
                        TIME_NO_ZERO_IN_DATE	Don't allow partial dates
118
                        TIME_NO_ZERO_DATE	Don't allow 0000-00-00 date
119
                        TIME_INVALID_DATES	Allow 2000-02-31
120
    was_cut             0	Value OK
121
			1       If value was cut during conversion
122
			2	check_date(date,flags) considers date invalid
123
124
  DESCRIPTION
125
    At least the following formats are recogniced (based on number of digits)
126
    YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
127
    YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS
128
    YYYYMMDDTHHMMSS  where T is a the character T (ISO8601)
129
    Also dates where all parts are zero are allowed
130
131
    The second part may have an optional .###### fraction part.
132
133
  NOTES
134
   This function should work with a format position vector as long as the
135
   following things holds:
136
   - All date are kept together and all time parts are kept together
137
   - Date and time parts must be separated by blank
138
   - Second fractions must come after second part and be separated
139
     by a '.'.  (The second fractions are optional)
140
   - AM/PM must come after second fractions (or after seconds if no fractions)
141
   - Year must always been specified.
142
   - If time is before date, then we will use datetime format only if
143
     the argument consist of two parts, separated by space.
144
     Otherwise we will assume the argument is a date.
145
   - The hour part must be specified in hour-minute-second order.
146
147
  RETURN VALUES
148
    MYSQL_TIMESTAMP_NONE        String wasn't a timestamp, like
149
                                [DD [HH:[MM:[SS]]]].fraction.
150
                                l_time is not changed.
151
    MYSQL_TIMESTAMP_DATE        DATE string (YY MM and DD parts ok)
152
    MYSQL_TIMESTAMP_DATETIME    Full timestamp
153
    MYSQL_TIMESTAMP_ERROR       Timestamp with wrong values.
154
                                All elements in l_time is set to 0
155
*/
156
157
#define MAX_DATE_PARTS 8
158
159
enum enum_mysql_timestamp_type
160
str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
161
                uint flags, int *was_cut)
162
{
163
  uint field_length, year_length, digits, i, number_of_fields;
164
  uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS];
165
  uint add_hours= 0, start_loop;
166
  ulong not_zero_date, allow_space;
167
  my_bool is_internal_format;
168
  const char *pos, *last_field_pos;
169
  const char *end=str+length;
170
  const uchar *format_position;
171
  my_bool found_delimitier= 0, found_space= 0;
172
  uint frac_pos, frac_len;
173
  DBUG_ENTER("str_to_datetime");
174
  DBUG_PRINT("ENTER",("str: %.*s",length,str));
175
176
  *was_cut= 0;
177
178
  /* Skip space at start */
179
  for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
180
    ;
181
  if (str == end || ! my_isdigit(&my_charset_latin1, *str))
182
  {
183
    *was_cut= 1;
184
    DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
185
  }
186
187
  is_internal_format= 0;
188
  /* This has to be changed if want to activate different timestamp formats */
189
  format_position= internal_format_positions;
190
191
  /*
192
    Calculate number of digits in first part.
193
    If length= 8 or >= 14 then year is of format YYYY.
194
    (YYYY-MM-DD,  YYYYMMDD, YYYYYMMDDHHMMSS)
195
  */
196
  for (pos=str;
197
       pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T');
198
       pos++)
199
    ;
200
201
  digits= (uint) (pos-str);
202
  start_loop= 0;                                /* Start of scan loop */
203
  date_len[format_position[0]]= 0;              /* Length of year field */
204
  if (pos == end || *pos == '.')
205
  {
206
    /* Found date in internal format (only numbers like YYYYMMDD) */
207
    year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
208
    field_length= year_length;
209
    is_internal_format= 1;
210
    format_position= internal_format_positions;
211
  }
212
  else
213
  {
214
    if (format_position[0] >= 3)                /* If year is after HHMMDD */
215
    {
216
      /*
217
        If year is not in first part then we have to determinate if we got
218
        a date field or a datetime field.
219
        We do this by checking if there is two numbers separated by
220
        space in the input.
221
      */
222
      while (pos < end && !my_isspace(&my_charset_latin1, *pos))
223
        pos++;
224
      while (pos < end && !my_isdigit(&my_charset_latin1, *pos))
225
        pos++;
226
      if (pos == end)
227
      {
228
        if (flags & TIME_DATETIME_ONLY)
229
        {
230
          *was_cut= 1;
231
          DBUG_RETURN(MYSQL_TIMESTAMP_NONE);   /* Can't be a full datetime */
232
        }
233
        /* Date field.  Set hour, minutes and seconds to 0 */
234
        date[0]= date[1]= date[2]= date[3]= date[4]= 0;
235
        start_loop= 5;                         /* Start with first date part */
236
      }
237
    }
238
239
    field_length= format_position[0] == 0 ? 4 : 2;
240
  }
241
242
  /*
243
    Only allow space in the first "part" of the datetime field and:
244
    - after days, part seconds
245
    - before and after AM/PM (handled by code later)
246
247
    2003-03-03 20:00:20 AM
248
    20:00:20.000000 AM 03-03-2000
249
  */
250
  i= max((uint) format_position[0], (uint) format_position[1]);
251
  set_if_bigger(i, (uint) format_position[2]);
252
  allow_space= ((1 << i) | (1 << format_position[6]));
253
  allow_space&= (1 | 2 | 4 | 8);
254
255
  not_zero_date= 0;
256
  for (i = start_loop;
257
       i < MAX_DATE_PARTS-1 && str != end &&
258
         my_isdigit(&my_charset_latin1,*str);
259
       i++)
260
  {
261
    const char *start= str;
262
    ulong tmp_value= (uint) (uchar) (*str++ - '0');
263
    while (str != end && my_isdigit(&my_charset_latin1,str[0]) &&
264
           (!is_internal_format || --field_length))
265
    {
266
      tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0');
267
      str++;
268
    }
269
    date_len[i]= (uint) (str - start);
270
    if (tmp_value > 999999)                     /* Impossible date part */
271
    {
272
      *was_cut= 1;
273
      DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
274
    }
275
    date[i]=tmp_value;
276
    not_zero_date|= tmp_value;
277
278
    /* Length of next field */
279
    field_length= format_position[i+1] == 0 ? 4 : 2;
280
281
    if ((last_field_pos= str) == end)
282
    {
283
      i++;                                      /* Register last found part */
284
      break;
285
    }
286
    /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
287
    if (i == format_position[2] && *str == 'T')
288
    {
289
      str++;                                    /* ISO8601:  CCYYMMDDThhmmss */
290
      continue;
291
    }
292
    if (i == format_position[5])                /* Seconds */
293
    {
294
      if (*str == '.')                          /* Followed by part seconds */
295
      {
296
        str++;
297
        field_length= 6;                        /* 6 digits */
298
      }
299
      continue;
300
    }
301
    while (str != end &&
302
           (my_ispunct(&my_charset_latin1,*str) ||
303
            my_isspace(&my_charset_latin1,*str)))
304
    {
305
      if (my_isspace(&my_charset_latin1,*str))
306
      {
307
        if (!(allow_space & (1 << i)))
308
        {
309
          *was_cut= 1;
310
          DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
311
        }
312
        found_space= 1;
313
      }
314
      str++;
315
      found_delimitier= 1;                      /* Should be a 'normal' date */
316
    }
317
    /* Check if next position is AM/PM */
318
    if (i == format_position[6])                /* Seconds, time for AM/PM */
319
    {
320
      i++;                                      /* Skip AM/PM part */
321
      if (format_position[7] != 255)            /* If using AM/PM */
322
      {
323
        if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
324
        {
325
          if (str[0] == 'p' || str[0] == 'P')
326
            add_hours= 12;
327
          else if (str[0] != 'a' || str[0] != 'A')
328
            continue;                           /* Not AM/PM */
329
          str+= 2;                              /* Skip AM/PM */
330
          /* Skip space after AM/PM */
331
          while (str != end && my_isspace(&my_charset_latin1,*str))
332
            str++;
333
        }
334
      }
335
    }
336
    last_field_pos= str;
337
  }
338
  if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
339
  {
340
    *was_cut= 1;
341
    DBUG_RETURN(MYSQL_TIMESTAMP_NONE);          /* Can't be a datetime */
342
  }
343
344
  str= last_field_pos;
345
346
  number_of_fields= i - start_loop;
347
  while (i < MAX_DATE_PARTS)
348
  {
349
    date_len[i]= 0;
350
    date[i++]= 0;
351
  }
352
353
  if (!is_internal_format)
354
  {
355
    year_length= date_len[(uint) format_position[0]];
356
    if (!year_length)                           /* Year must be specified */
357
    {
358
      *was_cut= 1;
359
      DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
360
    }
361
362
    l_time->year=               date[(uint) format_position[0]];
363
    l_time->month=              date[(uint) format_position[1]];
364
    l_time->day=                date[(uint) format_position[2]];
365
    l_time->hour=               date[(uint) format_position[3]];
366
    l_time->minute=             date[(uint) format_position[4]];
367
    l_time->second=             date[(uint) format_position[5]];
368
369
    frac_pos= (uint) format_position[6];
370
    frac_len= date_len[frac_pos];
371
    if (frac_len < 6)
372
      date[frac_pos]*= (uint) log_10_int[6 - frac_len];
373
    l_time->second_part= date[frac_pos];
374
375
    if (format_position[7] != (uchar) 255)
376
    {
377
      if (l_time->hour > 12)
378
      {
379
        *was_cut= 1;
380
        goto err;
381
      }
382
      l_time->hour= l_time->hour%12 + add_hours;
383
    }
384
  }
385
  else
386
  {
387
    l_time->year=       date[0];
388
    l_time->month=      date[1];
389
    l_time->day=        date[2];
390
    l_time->hour=       date[3];
391
    l_time->minute=     date[4];
392
    l_time->second=     date[5];
393
    if (date_len[6] < 6)
394
      date[6]*= (uint) log_10_int[6 - date_len[6]];
395
    l_time->second_part=date[6];
396
  }
397
  l_time->neg= 0;
398
399
  if (year_length == 2 && not_zero_date)
400
    l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
401
402
  if (number_of_fields < 3 ||
403
      l_time->year > 9999 || l_time->month > 12 ||
404
      l_time->day > 31 || l_time->hour > 23 ||
405
      l_time->minute > 59 || l_time->second > 59)
406
  {
407
    /* Only give warning for a zero date if there is some garbage after */
408
    if (!not_zero_date)                         /* If zero date */
409
    {
410
      for (; str != end ; str++)
411
      {
412
        if (!my_isspace(&my_charset_latin1, *str))
413
        {
414
          not_zero_date= 1;                     /* Give warning */
415
          break;
416
        }
417
      }
418
    }
419
    *was_cut= test(not_zero_date);
420
    goto err;
421
  }
422
423
  if (check_date(l_time, not_zero_date != 0, flags, was_cut))
424
    goto err;
425
426
  l_time->time_type= (number_of_fields <= 3 ?
427
                      MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
428
429
  for (; str != end ; str++)
430
  {
431
    if (!my_isspace(&my_charset_latin1,*str))
432
    {
433
      *was_cut= 1;
434
      break;
435
    }
436
  }
437
438
  DBUG_RETURN(l_time->time_type=
439
              (number_of_fields <= 3 ? MYSQL_TIMESTAMP_DATE :
440
                                       MYSQL_TIMESTAMP_DATETIME));
441
442
err:
443
  bzero((char*) l_time, sizeof(*l_time));
444
  DBUG_RETURN(MYSQL_TIMESTAMP_ERROR);
445
}
446
447
448
/*
449
 Convert a time string to a MYSQL_TIME struct.
450
451
  SYNOPSIS
452
   str_to_time()
453
   str                  A string in full TIMESTAMP format or
454
                        [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
455
                        [M]MSS or [S]S
456
                        There may be an optional [.second_part] after seconds
457
   length               Length of str
458
   l_time               Store result here
459
   warning              Set MYSQL_TIME_WARN_TRUNCATED flag if the input string
460
                        was cut during conversion, and/or
461
                        MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is
462
                        out of range.
463
464
   NOTES
465
     Because of the extra days argument, this function can only
466
     work with times where the time arguments are in the above order.
467
468
   RETURN
469
     0  ok
470
     1  error
471
*/
472
473
my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
474
                    int *warning)
475
{
476
  ulong date[5];
477
  ulonglong value;
478
  const char *end=str+length, *end_of_days;
479
  my_bool found_days,found_hours;
480
  uint state;
481
482
  l_time->neg=0;
483
  *warning= 0;
484
  for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
485
    length--;
486
  if (str != end && *str == '-')
487
  {
488
    l_time->neg=1;
489
    str++;
490
    length--;
491
  }
492
  if (str == end)
493
    return 1;
494
495
  /* Check first if this is a full TIMESTAMP */
496
  if (length >= 12)
497
  {                                             /* Probably full timestamp */
498
    int was_cut;
499
    enum enum_mysql_timestamp_type
500
      res= str_to_datetime(str, length, l_time,
501
                           (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut);
502
    if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR)
503
    {
504
      if (was_cut)
505
        *warning|= MYSQL_TIME_WARN_TRUNCATED;
506
      return res == MYSQL_TIMESTAMP_ERROR;
507
    }
508
  }
509
510
  /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
511
  for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
512
    value=value*10L + (long) (*str - '0');
513
514
  /* Skip all space after 'days' */
515
  end_of_days= str;
516
  for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++)
517
    ;
518
519
  found_days=found_hours=0;
520
  if ((uint) (end-str) > 1 && str != end_of_days &&
521
      my_isdigit(&my_charset_latin1, *str))
522
  {                                             /* Found days part */
523
    date[0]= (ulong) value;
524
    state= 1;                                   /* Assume next is hours */
525
    found_days= 1;
526
  }
527
  else if ((end-str) > 1 &&  *str == time_separator &&
528
           my_isdigit(&my_charset_latin1, str[1]))
529
  {
530
    date[0]= 0;                                 /* Assume we found hours */
531
    date[1]= (ulong) value;
532
    state=2;
533
    found_hours=1;
534
    str++;                                      /* skip ':' */
535
  }
536
  else
537
  {
538
    /* String given as one number; assume HHMMSS format */
539
    date[0]= 0;
540
    date[1]= (ulong) (value/10000);
541
    date[2]= (ulong) (value/100 % 100);
542
    date[3]= (ulong) (value % 100);
543
    state=4;
544
    goto fractional;
545
  }
546
547
  /* Read hours, minutes and seconds */
548
  for (;;)
549
  {
550
    for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
551
      value=value*10L + (long) (*str - '0');
552
    date[state++]= (ulong) value;
553
    if (state == 4 || (end-str) < 2 || *str != time_separator ||
554
        !my_isdigit(&my_charset_latin1,str[1]))
555
      break;
556
    str++;                                      /* Skip time_separator (':') */
557
  }
558
559
  if (state != 4)
560
  {                                             /* Not HH:MM:SS */
561
    /* Fix the date to assume that seconds was given */
562
    if (!found_hours && !found_days)
563
    {
564
      bmove_upp((uchar*) (date+4), (uchar*) (date+state),
565
                sizeof(long)*(state-1));
566
      bzero((uchar*) date, sizeof(long)*(4-state));
567
    }
568
    else
569
      bzero((uchar*) (date+state), sizeof(long)*(4-state));
570
  }
571
572
fractional:
573
  /* Get fractional second part */
574
  if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1]))
575
  {
576
    int field_length= 5;
577
    str++; value=(uint) (uchar) (*str - '0');
578
    while (++str != end && my_isdigit(&my_charset_latin1, *str))
579
    {
580
      if (field_length-- > 0)
581
        value= value*10 + (uint) (uchar) (*str - '0');
582
    }
583
    if (field_length > 0)
584
      value*= (long) log_10_int[field_length];
585
    else if (field_length < 0)
586
      *warning|= MYSQL_TIME_WARN_TRUNCATED;
587
    date[4]= (ulong) value;
588
  }
589
  else
590
    date[4]=0;
591
    
592
  /* Check for exponent part: E<gigit> | E<sign><digit> */
593
  /* (may occur as result of %g formatting of time value) */
594
  if ((end - str) > 1 &&
595
      (*str == 'e' || *str == 'E') &&
596
      (my_isdigit(&my_charset_latin1, str[1]) ||
597
       ((str[1] == '-' || str[1] == '+') &&
598
        (end - str) > 2 &&
599
        my_isdigit(&my_charset_latin1, str[2]))))
600
    return 1;
601
602
  if (internal_format_positions[7] != 255)
603
  {
604
    /* Read a possible AM/PM */
605
    while (str != end && my_isspace(&my_charset_latin1, *str))
606
      str++;
607
    if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
608
    {
609
      if (str[0] == 'p' || str[0] == 'P')
610
      {
611
        str+= 2;
612
        date[1]= date[1]%12 + 12;
613
      }
614
      else if (str[0] == 'a' || str[0] == 'A')
615
        str+=2;
616
    }
617
  }
618
619
  /* Integer overflow checks */
620
  if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
621
      date[2] > UINT_MAX || date[3] > UINT_MAX ||
622
      date[4] > UINT_MAX)
623
    return 1;
624
  
625
  l_time->year=         0;                      /* For protocol::store_time */
626
  l_time->month=        0;
627
  l_time->day=          date[0];
628
  l_time->hour=         date[1];
629
  l_time->minute=       date[2];
630
  l_time->second=       date[3];
631
  l_time->second_part=  date[4];
632
  l_time->time_type= MYSQL_TIMESTAMP_TIME;
633
634
  /* Check if the value is valid and fits into MYSQL_TIME range */
635
  if (check_time_range(l_time, warning))
636
    return 1;
637
  
638
  /* Check if there is garbage at end of the MYSQL_TIME specification */
639
  if (str != end)
640
  {
641
    do
642
    {
643
      if (!my_isspace(&my_charset_latin1,*str))
644
      {
645
        *warning|= MYSQL_TIME_WARN_TRUNCATED;
646
        break;
647
      }
648
    } while (++str != end);
649
  }
650
  return 0;
651
}
652
653
654
/*
655
  Check 'time' value to lie in the MYSQL_TIME range
656
657
  SYNOPSIS:
658
    check_time_range()
659
    time     pointer to MYSQL_TIME value
660
    warning  set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
661
662
  DESCRIPTION
663
  If the time value lies outside of the range [-838:59:59, 838:59:59],
664
  set it to the closest endpoint of the range and set
665
  MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable.
666
667
  RETURN
668
    0        time value is valid, but was possibly truncated
669
    1        time value is invalid
670
*/
671
672
int check_time_range(struct st_mysql_time *my_time, int *warning) 
673
{
674
  longlong hour;
675
676
  if (my_time->minute >= 60 || my_time->second >= 60)
677
    return 1;
678
679
  hour= my_time->hour + (24*my_time->day);
680
  if (hour <= TIME_MAX_HOUR &&
681
      (hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE ||
682
       my_time->second != TIME_MAX_SECOND || !my_time->second_part))
683
    return 0;
684
685
  my_time->day= 0;
686
  my_time->hour= TIME_MAX_HOUR;
687
  my_time->minute= TIME_MAX_MINUTE;
688
  my_time->second= TIME_MAX_SECOND;
689
  my_time->second_part= 0;
690
  *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE;
691
  return 0;
692
}
693
694
695
/*
696
  Prepare offset of system time zone from UTC for my_system_gmt_sec() func.
697
698
  SYNOPSIS
699
    init_time()
700
*/
701
void init_time(void)
702
{
703
  time_t seconds;
704
  struct tm *l_time,tm_tmp;
705
  MYSQL_TIME my_time;
706
  my_bool not_used;
707
708
  seconds= (time_t) time((time_t*) 0);
709
  localtime_r(&seconds,&tm_tmp);
710
  l_time= &tm_tmp;
711
  my_time_zone=		3600;		/* Comp. for -3600 in my_gmt_sec */
712
  my_time.year=		(uint) l_time->tm_year+1900;
713
  my_time.month=	(uint) l_time->tm_mon+1;
714
  my_time.day=		(uint) l_time->tm_mday;
715
  my_time.hour=		(uint) l_time->tm_hour;
716
  my_time.minute=	(uint) l_time->tm_min;
717
  my_time.second=	(uint) l_time->tm_sec;
718
  my_system_gmt_sec(&my_time, &my_time_zone, &not_used); /* Init my_time_zone */
719
}
720
721
722
/*
723
  Handle 2 digit year conversions
724
725
  SYNOPSIS
726
  year_2000_handling()
727
  year     2 digit year
728
729
  RETURN
730
    Year between 1970-2069
731
*/
732
733
uint year_2000_handling(uint year)
734
{
735
  if ((year=year+1900) < 1900+YY_PART_YEAR)
736
    year+=100;
737
  return year;
738
}
739
740
741
/*
742
  Calculate nr of day since year 0 in new date-system (from 1615)
743
744
  SYNOPSIS
745
    calc_daynr()
746
    year		 Year (exact 4 digit year, no year conversions)
747
    month		 Month
748
    day			 Day
749
750
  NOTES: 0000-00-00 is a valid date, and will return 0
751
752
  RETURN
753
    Days since 0000-00-00
754
*/
755
756
long calc_daynr(uint year,uint month,uint day)
757
{
758
  long delsum;
759
  int temp;
760
  DBUG_ENTER("calc_daynr");
761
762
  if (year == 0 && month == 0 && day == 0)
763
    DBUG_RETURN(0);				/* Skip errors */
764
  delsum= (long) (365L * year+ 31*(month-1) +day);
765
  if (month <= 2)
766
      year--;
767
  else
768
    delsum-= (long) (month*4+23)/10;
769
  temp=(int) ((year/100+1)*3)/4;
770
  DBUG_PRINT("exit",("year: %d  month: %d  day: %d -> daynr: %ld",
771
		     year+(month <= 2),month,day,delsum+year/4-temp));
772
  DBUG_RETURN(delsum+(int) year/4-temp);
773
} /* calc_daynr */
774
775
776
/*
777
  Convert time in MYSQL_TIME representation in system time zone to its
778
  my_time_t form (number of seconds in UTC since begginning of Unix Epoch).
779
780
  SYNOPSIS
781
    my_system_gmt_sec()
782
      t               - time value to be converted
783
      my_timezone     - pointer to long where offset of system time zone
784
                        from UTC will be stored for caching
785
      in_dst_time_gap - set to true if time falls into spring time-gap
786
787
  NOTES
788
    The idea is to cache the time zone offset from UTC (including daylight 
789
    saving time) for the next call to make things faster. But currently we 
790
    just calculate this offset during startup (by calling init_time() 
791
    function) and use it all the time.
792
    Time value provided should be legal time value (e.g. '2003-01-01 25:00:00'
793
    is not allowed).
794
795
  RETURN VALUE
796
    Time in UTC seconds since Unix Epoch representation.
797
*/
798
my_time_t
799
my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone,
800
                  my_bool *in_dst_time_gap)
801
{
802
  uint loop;
803
  time_t tmp= 0;
804
  int shift= 0;
805
  MYSQL_TIME tmp_time;
806
  MYSQL_TIME *t= &tmp_time;
807
  struct tm *l_time,tm_tmp;
808
  long diff, current_timezone;
809
810
  /*
811
    Use temp variable to avoid trashing input data, which could happen in
812
    case of shift required for boundary dates processing.
813
  */
814
  memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME));
815
816
  if (!validate_timestamp_range(t))
817
    return 0;
818
819
  /*
820
    Calculate the gmt time based on current time and timezone
821
    The -1 on the end is to ensure that if have a date that exists twice
822
    (like 2002-10-27 02:00:0 MET), we will find the initial date.
823
824
    By doing -3600 we will have to call localtime_r() several times, but
825
    I couldn't come up with a better way to get a repeatable result :(
826
827
    We can't use mktime() as it's buggy on many platforms and not thread safe.
828
829
    Note: this code assumes that our time_t estimation is not too far away
830
    from real value (we assume that localtime_r(tmp) will return something
831
    within 24 hrs from t) which is probably true for all current time zones.
832
833
    Note2: For the dates, which have time_t representation close to
834
    MAX_INT32 (efficient time_t limit for supported platforms), we should
835
    do a small trick to avoid overflow. That is, convert the date, which is
836
    two days earlier, and then add these days to the final value.
837
838
    The same trick is done for the values close to 0 in time_t
839
    representation for platfroms with unsigned time_t (QNX).
840
841
    To be more verbose, here is a sample (extracted from the code below):
842
    (calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L
843
    would return -2147480896 because of the long type overflow. In result
844
    we would get 1901 year in localtime_r(), which is an obvious error.
845
846
    Alike problem raises with the dates close to Epoch. E.g.
847
    (calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L
848
    will give -3600.
849
850
    On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600)
851
    wil give us a date around 2106 year. Which is no good.
852
853
    Theoreticaly, there could be problems with the latter conversion:
854
    there are at least two timezones, which had time switches near 1 Jan
855
    of 1970 (because of political reasons). These are America/Hermosillo and
856
    America/Mazatlan time zones. They changed their offset on
857
    1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones
858
    the code below will give incorrect results for dates close to
859
    1970-01-01, in the case OS takes into account these historical switches.
860
    Luckily, it seems that we support only one platform with unsigned
861
    time_t. It's QNX. And QNX does not support historical timezone data at all.
862
    E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply
863
    historical information for localtime_r() etc. That is, the problem is not
864
    relevant to QNX.
865
866
    We are safe with shifts close to MAX_INT32, as there are no known
867
    time switches on Jan 2038 yet :)
868
  */
869
  if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4))
870
  {
871
    /*
872
      Below we will pass (uint) (t->day - shift) to calc_daynr.
873
      As we don't want to get an overflow here, we will shift
874
      only safe dates. That's why we have (t->day > 4) above.
875
    */
876
    t->day-= 2;
877
    shift= 2;
878
  }
879
#ifdef TIME_T_UNSIGNED
880
  else
881
  {
882
    /*
883
      We can get 0 in time_t representaion only on 1969, 31 of Dec or on
884
      1970, 1 of Jan. For both dates we use shift, which is added
885
      to t->day in order to step out a bit from the border.
886
      This is required for platforms, where time_t is unsigned.
887
      As far as I know, among the platforms we support it's only QNX.
888
      Note: the order of below if-statements is significant.
889
    */
890
891
    if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1)
892
        && (t->day <= 10))
893
    {
894
      t->day+= 2;
895
      shift= -2;
896
    }
897
898
    if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12)
899
        && (t->day == 31))
900
    {
901
      t->year++;
902
      t->month= 1;
903
      t->day= 2;
904
      shift= -2;
905
    }
906
  }
907
#endif
908
909
  tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) -
910
                   (long) days_at_timestart)*86400L + (long) t->hour*3600L +
911
                  (long) (t->minute*60 + t->second)) + (time_t) my_time_zone -
912
                 3600);
913
914
  current_timezone= my_time_zone;
915
  localtime_r(&tmp,&tm_tmp);
916
  l_time=&tm_tmp;
917
  for (loop=0;
918
       loop < 2 &&
919
	 (t->hour != (uint) l_time->tm_hour ||
920
	  t->minute != (uint) l_time->tm_min ||
921
          t->second != (uint) l_time->tm_sec);
922
       loop++)
923
  {					/* One check should be enough ? */
924
    /* Get difference in days */
925
    int days= t->day - l_time->tm_mday;
926
    if (days < -1)
927
      days= 1;					/* Month has wrapped */
928
    else if (days > 1)
929
      days= -1;
930
    diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) +
931
          (long) (60*((int) t->minute - (int) l_time->tm_min)) +
932
          (long) ((int) t->second - (int) l_time->tm_sec));
933
    current_timezone+= diff+3600;		/* Compensate for -3600 above */
934
    tmp+= (time_t) diff;
935
    localtime_r(&tmp,&tm_tmp);
936
    l_time=&tm_tmp;
937
  }
938
  /*
939
    Fix that if we are in the non existing daylight saving time hour
940
    we move the start of the next real hour.
941
942
    This code doesn't handle such exotical thing as time-gaps whose length
943
    is more than one hour or non-integer (latter can theoretically happen
944
    if one of seconds will be removed due leap correction, or because of
945
    general time correction like it happened for Africa/Monrovia time zone
946
    in year 1972).
947
  */
948
  if (loop == 2 && t->hour != (uint) l_time->tm_hour)
949
  {
950
    int days= t->day - l_time->tm_mday;
951
    if (days < -1)
952
      days=1;					/* Month has wrapped */
953
    else if (days > 1)
954
      days= -1;
955
    diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+
956
	  (long) (60*((int) t->minute - (int) l_time->tm_min)) +
957
          (long) ((int) t->second - (int) l_time->tm_sec));
958
    if (diff == 3600)
959
      tmp+=3600 - t->minute*60 - t->second;	/* Move to next hour */
960
    else if (diff == -3600)
961
      tmp-=t->minute*60 + t->second;		/* Move to previous hour */
962
963
    *in_dst_time_gap= 1;
964
  }
965
  *my_timezone= current_timezone;
966
967
968
  /* shift back, if we were dealing with boundary dates */
969
  tmp+= shift*86400L;
970
971
  /*
972
    This is possible for dates, which slightly exceed boundaries.
973
    Conversion will pass ok for them, but we don't allow them.
974
    First check will pass for platforms with signed time_t.
975
    instruction above (tmp+= shift*86400L) could exceed
976
    MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen.
977
    So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms
978
    with unsigned time_t tmp+= shift*86400L might result in a number,
979
    larger then TIMESTAMP_MAX_VALUE, so another check will work.
980
  */
981
  if ((tmp < TIMESTAMP_MIN_VALUE) || (tmp > TIMESTAMP_MAX_VALUE))
982
    tmp= 0;
983
984
  return (my_time_t) tmp;
985
} /* my_system_gmt_sec */
986
987
988
/* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */
989
990
void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
991
{
992
  bzero((void*) tm, sizeof(*tm));
993
  tm->time_type= time_type;
994
}
995
996
997
/*
998
  Functions to convert time/date/datetime value to a string,
999
  using default format.
1000
  This functions don't check that given MYSQL_TIME structure members are
1001
  in valid range. If they are not, return value won't reflect any
1002
  valid date either. Additionally, make_time doesn't take into
1003
  account time->day member: it's assumed that days have been converted
1004
  to hours already.
1005
1006
  RETURN
1007
    number of characters written to 'to'
1008
*/
1009
1010
int my_time_to_str(const MYSQL_TIME *l_time, char *to)
1011
{
1012
  uint extra_hours= 0;
1013
  return my_sprintf(to, (to, "%s%02u:%02u:%02u",
1014
                         (l_time->neg ? "-" : ""),
1015
                         extra_hours+ l_time->hour,
1016
                         l_time->minute,
1017
                         l_time->second));
1018
}
1019
1020
int my_date_to_str(const MYSQL_TIME *l_time, char *to)
1021
{
1022
  return my_sprintf(to, (to, "%04u-%02u-%02u",
1023
                         l_time->year,
1024
                         l_time->month,
1025
                         l_time->day));
1026
}
1027
1028
int my_datetime_to_str(const MYSQL_TIME *l_time, char *to)
1029
{
1030
  return my_sprintf(to, (to, "%04u-%02u-%02u %02u:%02u:%02u",
1031
                         l_time->year,
1032
                         l_time->month,
1033
                         l_time->day,
1034
                         l_time->hour,
1035
                         l_time->minute,
1036
                         l_time->second));
1037
}
1038
1039
1040
/*
1041
  Convert struct DATE/TIME/DATETIME value to string using built-in
1042
  MySQL time conversion formats.
1043
1044
  SYNOPSIS
1045
    my_TIME_to_string()
1046
1047
  NOTE
1048
    The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved.
1049
*/
1050
1051
int my_TIME_to_str(const MYSQL_TIME *l_time, char *to)
1052
{
1053
  switch (l_time->time_type) {
1054
  case MYSQL_TIMESTAMP_DATETIME:
1055
    return my_datetime_to_str(l_time, to);
1056
  case MYSQL_TIMESTAMP_DATE:
1057
    return my_date_to_str(l_time, to);
1058
  case MYSQL_TIMESTAMP_TIME:
1059
    return my_time_to_str(l_time, to);
1060
  case MYSQL_TIMESTAMP_NONE:
1061
  case MYSQL_TIMESTAMP_ERROR:
1062
    to[0]='\0';
1063
    return 0;
1064
  default:
1065
    DBUG_ASSERT(0);
1066
    return 0;
1067
  }
1068
}
1069
1070
1071
/*
1072
  Convert datetime value specified as number to broken-down TIME
1073
  representation and form value of DATETIME type as side-effect.
1074
1075
  SYNOPSIS
1076
    number_to_datetime()
1077
      nr         - datetime value as number
1078
      time_res   - pointer for structure for broken-down representation
1079
      flags      - flags to use in validating date, as in str_to_datetime()
1080
      was_cut    0      Value ok
1081
                 1      If value was cut during conversion
1082
                 2      check_date(date,flags) considers date invalid
1083
1084
  DESCRIPTION
1085
    Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
1086
    YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in
1087
    YYYYMMDDHHMMSS format as side-effect.
1088
1089
    This function also checks if datetime value fits in DATETIME range.
1090
1091
  RETURN VALUE
1092
    -1              Timestamp with wrong values
1093
    anything else   DATETIME as integer in YYYYMMDDHHMMSS format
1094
    Datetime value in YYYYMMDDHHMMSS format.
1095
*/
1096
1097
longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
1098
                            uint flags, int *was_cut)
1099
{
1100
  long part1,part2;
1101
1102
  *was_cut= 0;
1103
  bzero((char*) time_res, sizeof(*time_res));
1104
  time_res->time_type=MYSQL_TIMESTAMP_DATE;
1105
1106
  if (nr == LL(0) || nr >= LL(10000101000000))
1107
  {
1108
    time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1109
    goto ok;
1110
  }
1111
  if (nr < 101)
1112
    goto err;
1113
  if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
1114
  {
1115
    nr= (nr+20000000L)*1000000L;                 /* YYMMDD, year: 2000-2069 */
1116
    goto ok;
1117
  }
1118
  if (nr < (YY_PART_YEAR)*10000L+101L)
1119
    goto err;
1120
  if (nr <= 991231L)
1121
  {
1122
    nr= (nr+19000000L)*1000000L;                 /* YYMMDD, year: 1970-1999 */
1123
    goto ok;
1124
  }
1125
  if (nr < 10000101L)
1126
    goto err;
1127
  if (nr <= 99991231L)
1128
  {
1129
    nr= nr*1000000L;
1130
    goto ok;
1131
  }
1132
  if (nr < 101000000L)
1133
    goto err;
1134
1135
  time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
1136
1137
  if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
1138
  {
1139
    nr= nr+LL(20000000000000);                   /* YYMMDDHHMMSS, 2000-2069 */
1140
    goto ok;
1141
  }
1142
  if (nr <  YY_PART_YEAR*LL(10000000000)+ LL(101000000))
1143
    goto err;
1144
  if (nr <= LL(991231235959))
1145
    nr= nr+LL(19000000000000);		/* YYMMDDHHMMSS, 1970-1999 */
1146
1147
 ok:
1148
  part1=(long) (nr/LL(1000000));
1149
  part2=(long) (nr - (longlong) part1*LL(1000000));
1150
  time_res->year=  (int) (part1/10000L);  part1%=10000L;
1151
  time_res->month= (int) part1 / 100;
1152
  time_res->day=   (int) part1 % 100;
1153
  time_res->hour=  (int) (part2/10000L);  part2%=10000L;
1154
  time_res->minute=(int) part2 / 100;
1155
  time_res->second=(int) part2 % 100;
1156
1157
  if (time_res->year <= 9999 && time_res->month <= 12 &&
1158
      time_res->day <= 31 && time_res->hour <= 23 &&
1159
      time_res->minute <= 59 && time_res->second <= 59 &&
1160
      !check_date(time_res, (nr != 0), flags, was_cut))
1161
    return nr;
1162
1163
  /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
1164
  if (!nr && (flags & TIME_NO_ZERO_DATE))
1165
    return LL(-1);
1166
1167
 err:
1168
  *was_cut= 1;
1169
  return LL(-1);
1170
}
1171
1172
1173
/* Convert time value to integer in YYYYMMDDHHMMSS format */
1174
1175
ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)
1176
{
1177
  return ((ulonglong) (my_time->year * 10000UL +
1178
                       my_time->month * 100UL +
1179
                       my_time->day) * ULL(1000000) +
1180
          (ulonglong) (my_time->hour * 10000UL +
1181
                       my_time->minute * 100UL +
1182
                       my_time->second));
1183
}
1184
1185
1186
/* Convert MYSQL_TIME value to integer in YYYYMMDD format */
1187
1188
ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *my_time)
1189
{
1190
  return (ulonglong) (my_time->year * 10000UL + my_time->month * 100UL +
1191
                      my_time->day);
1192
}
1193
1194
1195
/*
1196
  Convert MYSQL_TIME value to integer in HHMMSS format.
1197
  This function doesn't take into account time->day member:
1198
  it's assumed that days have been converted to hours already.
1199
*/
1200
1201
ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *my_time)
1202
{
1203
  return (ulonglong) (my_time->hour * 10000UL +
1204
                      my_time->minute * 100UL +
1205
                      my_time->second);
1206
}
1207
1208
1209
/*
1210
  Convert struct MYSQL_TIME (date and time split into year/month/day/hour/...
1211
  to a number in format YYYYMMDDHHMMSS (DATETIME),
1212
  YYYYMMDD (DATE)  or HHMMSS (TIME).
1213
1214
  SYNOPSIS
1215
    TIME_to_ulonglong()
1216
1217
  DESCRIPTION
1218
    The function is used when we need to convert value of time item
1219
    to a number if it's used in numeric context, i. e.:
1220
    SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
1221
    SELECT ?+1;
1222
1223
  NOTE
1224
    This function doesn't check that given MYSQL_TIME structure members are
1225
    in valid range. If they are not, return value won't reflect any
1226
    valid date either.
1227
*/
1228
1229
ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time)
1230
{
1231
  switch (my_time->time_type) {
1232
  case MYSQL_TIMESTAMP_DATETIME:
1233
    return TIME_to_ulonglong_datetime(my_time);
1234
  case MYSQL_TIMESTAMP_DATE:
1235
    return TIME_to_ulonglong_date(my_time);
1236
  case MYSQL_TIMESTAMP_TIME:
1237
    return TIME_to_ulonglong_time(my_time);
1238
  case MYSQL_TIMESTAMP_NONE:
1239
  case MYSQL_TIMESTAMP_ERROR:
1240
    return ULL(0);
1241
  default:
1242
    DBUG_ASSERT(0);
1243
  }
1244
  return 0;
1245
}
1246