List:Internals« Previous MessageNext Message »
From:Jim Winstead Date:June 3 2005 1:26am
Subject:bk commit into 4.1 tree (jimw:1.2307) BUG#8588
View as plain text  
Below is the list of changes that have just been committed into a local
4.1 repository of jimw. When jimw does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet
  1.2307 05/06/02 18:26:33 jimw@stripped +3 -0
  Fix handling of 'T' and 'Z' in strings interpreted as dates,
  which is more in line with how 4.0 handled them. (Bug #7308,
  Bug #8588)

  sql-common/my_time.c
    1.13 05/06/02 18:26:30 jimw@stripped +38 -164
    Remove a lot of code that is actually unused, and in the process
    fix str_to_datetime() to handle the 'T' and end 'Z' in
    ISO-8601-like dates.

  mysql-test/t/type_datetime.test
    1.16 05/06/02 18:26:30 jimw@stripped +20 -0
    Add new tests

  mysql-test/r/type_datetime.result
    1.24 05/06/02 18:26:30 jimw@stripped +19 -0
    Update results

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	jimw
# Host:	rama.(none)
# Root:	/home/jimw/my/mysql-4.1-7308

--- 1.12/sql-common/my_time.c	2004-12-30 12:44:13 -08:00
+++ 1.13/sql-common/my_time.c	2005-06-02 18:26:30 -07:00
@@ -32,14 +32,14 @@
 
 /* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */
 
-static uchar internal_format_positions[]=
-{0, 1, 2, 3, 4, 5, 6, (uchar) 255};
-
 static char time_separator=':';
 
 static ulong const days_at_timestart=719528;	/* daynr at 1970.01.01 */
 uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
 
+/* Max value for parts of a datetime,   YYYY  MM  DD  HH  MM  SS FFFFFF */
+static uint max_datetime_part_value[]= {9999, 12, 31, 23, 59, 59, 999999};
+
 /*
   Offset of system time zone from UTC in seconds used to speed up 
   work of my_system_gmt_sec() function.
@@ -100,21 +100,16 @@
 str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
                 uint flags, int *was_cut)
 {
-  uint field_length, year_length, digits, i, number_of_fields;
+  uint field_length, digits, i, number_of_fields;
   uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS];
-  uint add_hours= 0, start_loop;
-  ulong not_zero_date, allow_space;
-  bool is_internal_format;
+  ulong not_zero_date;
   const char *pos, *last_field_pos;
   const char *end=str+length;
-  const uchar *format_position;
-  bool found_delimitier= 0, found_space= 0;
-  uint frac_pos, frac_len;
+  bool found_delimiter= 0, found_space= 0;
   DBUG_ENTER("str_to_datetime");
   DBUG_PRINT("ENTER",("str: %.*s",length,str));
 
   LINT_INIT(field_length);
-  LINT_INIT(year_length);
   LINT_INIT(last_field_pos);
 
   *was_cut= 0;
@@ -128,72 +123,20 @@
     DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
   }
 
-  is_internal_format= 0;
-  /* This has to be changed if want to activate different timestamp formats */
-  format_position= internal_format_positions;
-
   /*
-    Calculate number of digits in first part.
-    If length= 8 or >= 14 then year is of format YYYY.
-    (YYYY-MM-DD,  YYYYMMDD, YYYYYMMDDHHMMSS)
+    Find the number of digits in the first part, so we can guess how many
+    digits we have for the year.
   */
   for (pos=str; pos != end && my_isdigit(&my_charset_latin1,*pos) ; pos++)
     ;
-
   digits= (uint) (pos-str);
-  start_loop= 0;                                /* Start of scan loop */
-  date_len[format_position[0]]= 0;              /* Length of year field */
-  if (pos == end)
-  {
-    /* Found date in internal format (only numbers like YYYYMMDD) */
-    year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
-    field_length=year_length-1;
-    is_internal_format= 1;
-    format_position= internal_format_positions;
-  }
-  else
-  {
-    if (format_position[0] >= 3)                /* If year is after HHMMDD */
-    {
-      /*
-        If year is not in first part then we have to determinate if we got
-        a date field or a datetime field.
-        We do this by checking if there is two numbers separated by
-        space in the input.
-      */
-      while (pos < end && !my_isspace(&my_charset_latin1, *pos))
-        pos++;
-      while (pos < end && !my_isdigit(&my_charset_latin1, *pos))
-        pos++;
-      if (pos == end)
-      {
-        if (flags & TIME_DATETIME_ONLY)
-        {
-          *was_cut= 1;
-          DBUG_RETURN(MYSQL_TIMESTAMP_NONE);   /* Can't be a full datetime */
-        }
-        /* Date field.  Set hour, minutes and seconds to 0 */
-        date[0]= date[1]= date[2]= date[3]= date[4]= 0;
-        start_loop= 5;                         /* Start with first date part */
-      }
-    }
-  }
 
-  /*
-    Only allow space in the first "part" of the datetime field and:
-    - after days, part seconds
-    - before and after AM/PM (handled by code later)
-
-    2003-03-03 20:00:20 AM
-    20:00:20.000000 AM 03-03-2000
-  */
-  i= max((uint) format_position[0], (uint) format_position[1]);
-  set_if_bigger(i, (uint) format_position[2]);
-  allow_space= ((1 << i) | (1 << format_position[6]));
-  allow_space&= (1 | 2 | 4 | 8);
+  /* Guess length of first part of date, which is the year. */
+  date_len[0]= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
+  field_length=date_len[0]-1;
 
   not_zero_date= 0;
-  for (i = start_loop;
+  for (i = 0;
        i < MAX_DATE_PARTS-1 && str != end &&
          my_isdigit(&my_charset_latin1,*str);
        i++)
@@ -201,22 +144,22 @@
     const char *start= str;
     ulong tmp_value= (uint) (uchar) (*str++ - '0');
     while (str != end && my_isdigit(&my_charset_latin1,str[0]) &&
-           (!is_internal_format || field_length--))
+           field_length--)
     {
       tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0');
       str++;
     }
     date_len[i]= (uint) (str - start);
-    if (tmp_value > 999999)                     /* Impossible date part */
+    if (tmp_value > max_datetime_part_value[i])
     {
       *was_cut= 1;
       DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
     }
-    date[i]=tmp_value;
+    date[i]= tmp_value;
     not_zero_date|= tmp_value;
 
     /* Length-1 of next field */
-    field_length= format_position[i+1] == 0 ? 3 : 1;
+    field_length= 1;
 
     if ((last_field_pos= str) == end)
     {
@@ -224,30 +167,31 @@
       break;
     }
     /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
-    if (i == format_position[2] && *str == 'T')
-    {
+    if (i == 2 && *str == 'T')
       str++;                                    /* ISO8601:  CCYYMMDDThhmmss */
-      continue;
-    }
-    if (i == format_position[5])                /* Seconds */
+    if (i == 5)                /* Seconds */
     {
       if (*str == '.')                          /* Followed by part seconds */
       {
         str++;
         field_length= 5;                        /* 5 digits after first (=6) */
+        continue;
       }
-      continue;
 
       /* No part seconds */
       date[++i]= 0;
     }
+    /* Allow a 'Z' after seconds to allow trailing 'Z' as in ISO dates */
+    if ((i == 5 || i == 6) && *str == 'Z')
+      str++;
     while (str != end &&
            (my_ispunct(&my_charset_latin1,*str) ||
             my_isspace(&my_charset_latin1,*str)))
     {
       if (my_isspace(&my_charset_latin1,*str))
       {
-        if (!(allow_space & (1 << i)))
+        /* We only really allow spaces after the day or seconds */
+        if (i != 2 && i != 5 && i != 6)
         {
           *was_cut= 1;
           DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
@@ -255,30 +199,11 @@
         found_space= 1;
       }
       str++;
-      found_delimitier= 1;                      /* Should be a 'normal' date */
-    }
-    /* Check if next position is AM/PM */
-    if (i == format_position[6])                /* Seconds, time for AM/PM */
-    {
-      i++;                                      /* Skip AM/PM part */
-      if (format_position[7] != 255)            /* If using AM/PM */
-      {
-        if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
-        {
-          if (str[0] == 'p' || str[0] == 'P')
-            add_hours= 12;
-          else if (str[0] != 'a' || str[0] != 'A')
-            continue;                           /* Not AM/PM */
-          str+= 2;                              /* Skip AM/PM */
-          /* Skip space after AM/PM */
-          while (str != end && my_isspace(&my_charset_latin1,*str))
-            str++;
-        }
-      }
+      found_delimiter= 1;                      /* Should be a 'normal' date */
     }
     last_field_pos= str;
   }
-  if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
+  if (found_delimiter && !found_space && (flags & TIME_DATETIME_ONLY))
   {
     *was_cut= 1;
     DBUG_RETURN(MYSQL_TIMESTAMP_NONE);          /* Can't be a datetime */
@@ -286,60 +211,25 @@
 
   str= last_field_pos;
 
-  number_of_fields= i - start_loop;
+  number_of_fields= i;
   while (i < MAX_DATE_PARTS)
   {
     date_len[i]= 0;
     date[i++]= 0;
   }
 
-  if (!is_internal_format)
-  {
-    year_length= date_len[(uint) format_position[0]];
-    if (!year_length)                           /* Year must be specified */
-    {
-      *was_cut= 1;
-      DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
-    }
-
-    l_time->year=               date[(uint) format_position[0]];
-    l_time->month=              date[(uint) format_position[1]];
-    l_time->day=                date[(uint) format_position[2]];
-    l_time->hour=               date[(uint) format_position[3]];
-    l_time->minute=             date[(uint) format_position[4]];
-    l_time->second=             date[(uint) format_position[5]];
-
-    frac_pos= (uint) format_position[6];
-    frac_len= date_len[frac_pos];
-    if (frac_len < 6)
-      date[frac_pos]*= (uint) log_10_int[6 - frac_len];
-    l_time->second_part= date[frac_pos];
-
-    if (format_position[7] != (uchar) 255)
-    {
-      if (l_time->hour > 12)
-      {
-        *was_cut= 1;
-        goto err;
-      }
-      l_time->hour= l_time->hour%12 + add_hours;
-    }
-  }
-  else
-  {
-    l_time->year=       date[0];
-    l_time->month=      date[1];
-    l_time->day=        date[2];
-    l_time->hour=       date[3];
-    l_time->minute=     date[4];
-    l_time->second=     date[5];
-    if (date_len[6] < 6)
-      date[6]*= (uint) log_10_int[6 - date_len[6]];
-    l_time->second_part=date[6];
-  }
+  l_time->year=       date[0];
+  l_time->month=      date[1];
+  l_time->day=        date[2];
+  l_time->hour=       date[3];
+  l_time->minute=     date[4];
+  l_time->second=     date[5];
+  if (date_len[6] < 6)
+    date[6]*= (uint) log_10_int[6 - date_len[6]];
+  l_time->second_part=date[6];
   l_time->neg= 0;
 
-  if (year_length == 2 && not_zero_date)
+  if (date_len[0] == 2 && not_zero_date)
     l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
 
   if (number_of_fields < 3 ||
@@ -368,6 +258,7 @@
   l_time->time_type= (number_of_fields <= 3 ?
                       MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
 
+  /* If there was anything left other than trailing spaces, complain. */
   for (; str != end ; str++)
   {
     if (!my_isspace(&my_charset_latin1,*str))
@@ -522,23 +413,6 @@
   }
   else
     date[4]=0;
-
-  if (internal_format_positions[7] != 255)
-  {
-    /* Read a possible AM/PM */
-    while (str != end && my_isspace(&my_charset_latin1, *str))
-      str++;
-    if (str+2 <= end && (str[1] == 'M' || str[1] == 'm'))
-    {
-      if (str[0] == 'p' || str[0] == 'P')
-      {
-        str+= 2;
-        date[1]= date[1]%12 + 12;
-      }
-      else if (str[0] == 'a' || str[0] == 'A')
-        str+=2;
-    }
-  }
 
   /* Some simple checks */
   if (date[2] >= 60 || date[3] >= 60)

--- 1.23/mysql-test/r/type_datetime.result	2004-12-30 10:37:29 -08:00
+++ 1.24/mysql-test/r/type_datetime.result	2005-06-02 18:26:30 -07:00
@@ -153,3 +153,22 @@
 0000-00-00 00:00:00
 0000-00-00 00:00:00
 drop table t1;
+create table t1 (dt datetime);
+insert into t1 values ("20010101T010101");
+insert into t1 values ("2001-01-01T01:01:01");
+insert into t1 values ("2001-1-1T1:01:01");
+select * from t1;
+dt
+2001-01-01 01:01:01
+2001-01-01 01:01:01
+2001-01-01 01:01:01
+truncate table t1;
+insert into t1 values ("20010101T010101Z");
+insert into t1 values ("2001-01-01T01:01:01Z");
+insert into t1 values ("2001-1-1T1:01:01Z");
+select * from t1;
+dt
+2001-01-01 01:01:01
+2001-01-01 01:01:01
+2001-01-01 01:01:01
+drop table t1;

--- 1.15/mysql-test/t/type_datetime.test	2004-12-30 10:37:29 -08:00
+++ 1.16/mysql-test/t/type_datetime.test	2005-06-02 18:26:30 -07:00
@@ -101,3 +101,23 @@
 insert into t1 values ("00-00-00"), ("00-00-00 00:00:00");
 select * from t1;
 drop table t1;
+
+#
+# Bug #7308: ISO-8601 date format not handled correctly
+#
+create table t1 (dt datetime);
+insert into t1 values ("20010101T010101");
+insert into t1 values ("2001-01-01T01:01:01");
+insert into t1 values ("2001-1-1T1:01:01");
+select * from t1;
+# t1 also used by next test.
+
+#
+# Bug #8588: Ignore 'Z' at end of datetime, also an ISO-8601 feature
+#
+truncate table t1;
+insert into t1 values ("20010101T010101Z");
+insert into t1 values ("2001-01-01T01:01:01Z");
+insert into t1 values ("2001-1-1T1:01:01Z");
+select * from t1;
+drop table t1;
Thread
bk commit into 4.1 tree (jimw:1.2307) BUG#8588Jim Winstead3 Jun