summaryrefslogtreecommitdiff
path: root/sql/time.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/time.cc')
-rw-r--r--sql/time.cc885
1 files changed, 610 insertions, 275 deletions
diff --git a/sql/time.cc b/sql/time.cc
index f2e41afa560..4f2a2a23910 100644
--- a/sql/time.cc
+++ b/sql/time.cc
@@ -23,14 +23,9 @@
static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */
uchar *days_in_month= (uchar*) "\037\034\037\036\037\036\037\037\036\037\036\037";
-
/* Init some variabels needed when using my_local_time */
/* Currently only my_time_zone is inited */
-bool parse_datetime_formats(datetime_format_types format_type,
- const char *format_str, uint format_length,
- byte *dt_pos);
-
static long my_time_zone=0;
void init_time(void)
@@ -282,6 +277,7 @@ ulong convert_period_to_month(ulong period)
return a*12+b-1;
}
+
ulong convert_month_to_period(ulong month)
{
ulong year;
@@ -295,6 +291,13 @@ ulong convert_month_to_period(ulong month)
}
+/* 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=':';
+
/*
Convert a timestamp string to a TIME value.
@@ -303,7 +306,9 @@ ulong convert_month_to_period(ulong month)
str String to parse
length Length of string
l_time Date is stored here
- fuzzy_date 1 if we should allow dates where one part is zero
+ flags Bitmap of following items
+ TIME_FUZZY_DATE Set if we should allow partial dates
+ TIME_DATETIME_ONLY Set if we only allow full datetimes.
DESCRIPTION
At least the following formats are recogniced (based on number of digits)
@@ -312,161 +317,248 @@ ulong convert_month_to_period(ulong month)
YYYYMMDDTHHMMSS where T is a the character T (ISO8601)
Also dates where all parts are zero are allowed
+ The second part may have an optional .###### fraction part.
+
+ NOTES
+ This function should work with a format position vector as long as the
+ following things holds:
+ - All date are kept together and all time parts are kept together
+ - Date and time parts must be separated by blank
+ - Second fractions must come after second part and be separated
+ by a '.'. (The second fractions are optional)
+ - AM/PM must come after second fractions (or after seconds if no fractions)
+ - Year must always been specified.
+ - If time is before date, then we will use datetime format only if
+ the argument consist of two parts, separated by space.
+ Otherwise we will assume the argument is a date.
+ - The hour part must be specified in hour-minute-second order.
+
RETURN VALUES
TIMESTAMP_NONE String wasn't a timestamp, like
[DD [HH:[MM:[SS]]]].fraction
TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
- TIMESTAMP_FULL Full timestamp
+ TIMESTAMP_DATETIME Full timestamp
+ TIMESTAMP_DATETIME_ERROR Timestamp with wrong values
*/
+#define MAX_DATE_PARTS 8
+
timestamp_type
-str_to_TIME(const char *str, uint length, TIME *l_time,bool fuzzy_date,THD *thd)
+str_to_TIME(const char *str, uint length, TIME *l_time, uint flags)
{
- uint field_length= 0, year_length= 0, digits, i, number_of_fields;
- uint date[7], date_len[7];
- uint not_zero_date;
- bool is_internal_format= 0;
- const char *pos;
+ uint field_length, year_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;
+ const char *pos, *last_field_pos;
const char *end=str+length;
- bool found_delimitier= 0;
+ const uchar *format_position;
+ bool found_delimitier= 0, found_space= 0;
+ DATE_TIME_FORMAT *format;
DBUG_ENTER("str_to_TIME");
- DBUG_PRINT("enter",("str: %.*s",length,str));
+ DBUG_PRINT("ENTER",("str: %.*s",length,str));
- // Skip garbage
- for (; str != end && !my_isdigit(&my_charset_latin1, *str) ; str++) ;
- if (str == end)
+ LINT_INIT(field_length);
+ LINT_INIT(year_length);
+ LINT_INIT(last_field_pos);
+
+ // Skip space at start
+ for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
+ ;
+ if (str == end || ! my_isdigit(&my_charset_latin1, *str))
DBUG_RETURN(TIMESTAMP_NONE);
+
+ is_internal_format= 0;
+ /* This has to be changed if want to activate different timestamp formats */
+ format_position= internal_format_positions;
+
/*
- Calculate first number of digits.
+ Calculate number of digits in first part.
If length= 8 or >= 14 then year is of format YYYY.
(YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
*/
- for (pos=str; pos != end && my_isdigit(&my_charset_latin1,*pos) ; pos++) ;
- /* Check for internal format */
- digits= (uint) (pos-str);
+ for (pos=str; pos != end && my_isdigit(&my_charset_latin1,*pos) ; pos++)
+ ;
- if (pos == end || digits>=12)
+ digits= (uint) (pos-str);
+ start_loop= 0; // Start of scan loop
+ date_len[format_position[0]]= 0; // Length of year field
+ if (pos == end)
{
- is_internal_format= 1;
+ /* Found date in internal format (only numbers like YYYYMMDD) */
year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
field_length=year_length-1;
- date_len[0]= year_length;
+ 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)
+ return 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]= 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);
+
not_zero_date= 0;
- for (i=0 ; i < 6 && str != end && my_isdigit(&my_charset_latin1,*str) ; i++)
+ for (i = start_loop;
+ i < MAX_DATE_PARTS-1 && str != end &&
+ my_isdigit(&my_charset_latin1,*str);
+ i++)
{
- if (!is_internal_format)
- date_len[i]= 1;
- uint tmp_value=(uint) (uchar) (*str++ - '0');
- while (str != end && my_isdigit(&my_charset_latin1,str[0])
- && (is_internal_format && field_length-- || !is_internal_format) )
+ 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--))
{
- tmp_value=tmp_value*10 + (uint) (uchar) (*str - '0');
+ tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0');
str++;
- if (!is_internal_format)
- date_len[i]+= 1;
}
- if (i == 2 && *str == '.')
+ date_len[i]+= (uint) (str - start);
+ if (tmp_value > 999999) // Impossible date part
DBUG_RETURN(TIMESTAMP_NONE);
date[i]=tmp_value;
not_zero_date|= tmp_value;
- if (i == 2 && str != end && *str == 'T')
+
+ /* Length-1 of next field */
+ field_length= format_position[i+1] == 0 ? 3 : 1;
+
+ if ((last_field_pos= str) == end)
+ {
+ i++; // Register last found part
+ break;
+ }
+ /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
+ if (i == format_position[2] && *str == 'T')
+ {
str++; // ISO8601: CCYYMMDDThhmmss
- else if ( i != 5 ) // Skip inter-field delimiters
+ continue;
+ }
+ if (i == format_position[5]) // Seconds
{
- while (str != end &&
- (my_ispunct(&my_charset_latin1,*str) ||
- my_isspace(&my_charset_latin1,*str)))
+ if (*str == '.') // Followed by part seconds
{
- // Only allow space between days and hours
- if (my_isspace(&my_charset_latin1,*str) && i != 2)
- DBUG_RETURN(TIMESTAMP_NONE);
str++;
- found_delimitier=1; // Should be a 'normal' date
+ field_length= 5; // 5 digits after first (=6)
}
+ continue;
+
+ /* No part seconds */
+ date[++i]= 0;
}
- if (is_internal_format)
- field_length=1; // Rest fields can only be 2
- }
- /* Handle second fractions */
- if (i == 6 && (uint) (end-str) >= 2 && *str == '.' &&
- my_isdigit(&my_charset_latin1,str[1]))
- {
- str++;
- uint tmp_value=(uint) (uchar) (*str - '0');
- field_length=5;
- while (str++ != end && my_isdigit(&my_charset_latin1,str[0]) &&
- field_length--)
- tmp_value=tmp_value*10 + (uint) (uchar) (*str - '0');
- date[6]=tmp_value;
- not_zero_date|= tmp_value;
+ 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)))
+ DBUG_RETURN(TIMESTAMP_NONE);
+ 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[1] == 'p' || str[1] == 'P')
+ add_hours= 12;
+ else if (str[1] != 'a' || str[1] != '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++;
+ }
+ }
+ }
+ last_field_pos= str;
}
- else
- date[6]=0;
-
- while (str != end && (my_ispunct(&my_charset_latin1,*str) ||
- my_isspace(&my_charset_latin1,*str)))
- str++;
+ if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
+ DBUG_RETURN(TIMESTAMP_NONE); // Can't be a datetime
- uint add_hours= 0;
- if (!my_strnncoll(&my_charset_latin1,
- (const uchar *)str, 2,
- (const uchar *)"PM", 2))
- add_hours= 12;
+ str= last_field_pos;
- number_of_fields=i;
- while (i < 6)
+ number_of_fields= i - start_loop;
+ while (i < MAX_DATE_PARTS)
date[i++]=0;
if (!is_internal_format)
{
- byte *frm_pos;
+ year_length= date_len[(uint) format_position[0]];
+ if (!year_length) // Year must be specified
+ DBUG_RETURN(TIMESTAMP_NONE);
- if (number_of_fields <= 3)
- {
- frm_pos= t_datetime_frm(thd, DATE_FORMAT_TYPE).datetime_format.dt_pos;
- l_time->hour= 0;
- l_time->minute= 0;
- l_time->second= 0;
- }
- else
+ 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]];
+ l_time->second_part= date[(uint) format_position[6]];
+ if (format_position[7] != (uchar) 255)
{
- frm_pos= t_datetime_frm(thd, DATETIME_FORMAT_TYPE).datetime_format.dt_pos;
- l_time->hour= date[(int) frm_pos[3]];
- l_time->minute=date[(int) frm_pos[4]];
- l_time->second=date[(int) frm_pos[5]];
- if (frm_pos[6] == 1)
- {
- if (l_time->hour > 12)
- DBUG_RETURN(WRONG_TIMESTAMP_FULL);
- l_time->hour= l_time->hour%12 + add_hours;
- }
+ if (l_time->hour > 12)
+ DBUG_RETURN(TIMESTAMP_DATETIME_ERROR);
+ l_time->hour= l_time->hour%12 + add_hours;
}
-
- l_time->year= date[(int) frm_pos[0]];
- l_time->month= date[(int) frm_pos[1]];
- l_time->day= date[(int) frm_pos[2]];
- year_length= date_len[(int) frm_pos[0]];
}
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];
- }
- 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];
+ l_time->second_part=date[6];
+ }
l_time->neg= 0;
- if (year_length == 2 && i >=2 && (l_time->month || l_time->day))
- l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
+ if (year_length == 2 && i >= format_position[1] && i >=format_position[2] &&
+ (l_time->month || l_time->day))
+ l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
if (number_of_fields < 3 || l_time->month > 12 ||
l_time->day > 31 || l_time->hour > 23 ||
l_time->minute > 59 || l_time->second > 59 ||
- (!fuzzy_date && (l_time->month == 0 || l_time->day == 0)))
+ (!(flags & TIME_FUZZY_DATE) && (l_time->month == 0 || l_time->day == 0)))
{
/* Only give warning for a zero date if there is some garbage after */
if (!not_zero_date) // If zero date
@@ -481,46 +573,46 @@ str_to_TIME(const char *str, uint length, TIME *l_time,bool fuzzy_date,THD *thd)
}
}
if (not_zero_date)
- thd->cuted_fields++;
- DBUG_RETURN(WRONG_TIMESTAMP_FULL);
+ current_thd->cuted_fields++;
+ DBUG_RETURN(TIMESTAMP_DATETIME_ERROR);
}
- if (str != end && thd->count_cuted_fields)
+ if (str != end && current_thd->count_cuted_fields)
{
for (; str != end ; str++)
{
if (!my_isspace(&my_charset_latin1,*str))
{
- thd->cuted_fields++;
+ current_thd->cuted_fields++;
break;
}
}
}
DBUG_RETURN(l_time->time_type=
- (number_of_fields <= 3 ? TIMESTAMP_DATE : TIMESTAMP_FULL));
+ (number_of_fields <= 3 ? TIMESTAMP_DATE : TIMESTAMP_DATETIME));
}
-time_t str_to_timestamp(const char *str,uint length, THD *thd)
+time_t str_to_timestamp(const char *str,uint length)
{
TIME l_time;
long not_used;
- if (str_to_TIME(str,length,&l_time,0,thd) <= WRONG_TIMESTAMP_FULL)
+ if (str_to_TIME(str,length,&l_time,0) <= TIMESTAMP_DATETIME_ERROR)
return(0);
if (l_time.year >= TIMESTAMP_MAX_YEAR || l_time.year < 1900+YY_PART_YEAR)
{
- thd->cuted_fields++;
+ current_thd->cuted_fields++;
return(0);
}
return(my_gmt_sec(&l_time, &not_used));
}
-longlong str_to_datetime(const char *str,uint length,bool fuzzy_date, THD *thd)
+longlong str_to_datetime(const char *str,uint length, uint fuzzy_date)
{
TIME l_time;
- if (str_to_TIME(str,length,&l_time,fuzzy_date,thd) <= WRONG_TIMESTAMP_FULL)
+ if (str_to_TIME(str,length,&l_time,fuzzy_date) <= TIMESTAMP_DATETIME_ERROR)
return(0);
return (longlong) (l_time.year*LL(10000000000) +
l_time.month*LL(100000000)+
@@ -542,22 +634,24 @@ longlong str_to_datetime(const char *str,uint length,bool fuzzy_date, THD *thd)
length Length of str
l_time Store result here
+ NOTES
+ Because of the extra days argument, this function can only
+ work with times where the time arguments are in the above order.
+
RETURN
0 ok
1 error
*/
-bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
+bool str_to_time(const char *str,uint length,TIME *l_time)
{
long date[5],value;
- const char *end=str+length;
+ const char *end=str+length, *end_of_days;
bool found_days,found_hours;
uint state;
- byte *frm_pos= t_datetime_frm(thd, TIME_FORMAT_TYPE).datetime_format.dt_pos;
l_time->neg=0;
- for (; str != end &&
- !my_isdigit(&my_charset_latin1,*str) && *str != '-' ; str++)
+ for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
length--;
if (str != end && *str == '-')
{
@@ -571,37 +665,33 @@ bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
/* Check first if this is a full TIMESTAMP */
if (length >= 12)
{ // Probably full timestamp
- enum timestamp_type tres= str_to_TIME(str,length,l_time,1,thd);
- if (tres == TIMESTAMP_FULL)
- return 0;
- else if (tres == WRONG_TIMESTAMP_FULL)
- return 1;
+ enum timestamp_type res= str_to_TIME(str,length,l_time,
+ (TIME_FUZZY_DATE |
+ TIME_DATETIME_ONLY));
+ if ((int) res >= (int) TIMESTAMP_DATETIME_ERROR)
+ return res == TIMESTAMP_DATETIME_ERROR;
}
/* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
value=value*10L + (long) (*str - '0');
- /* Move to last space */
- if (str != end && *str == ' ')
- {
- while (++str != end && str[0] == ' ')
- {}
- str--;
- }
+ /* Skipp all space after 'days' */
+ end_of_days= str;
+ for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++)
+ ;
LINT_INIT(state);
found_days=found_hours=0;
- if ((uint) (end-str) > 1 && (*str == ' ' &&
- my_isdigit(&my_charset_latin1,str[1])))
- { // days !
- date[0]=value;
- state=1; // Assume next is hours
- found_days=1;
- str++; // Skip space;
- }
- else if ((end-str) > 1 && *str == frm_pos[7] &&
- my_isdigit(&my_charset_latin1,str[1]))
+ if ((uint) (end-str) > 1 && str != end_of_days &&
+ my_isdigit(&my_charset_latin1, *str))
+ { // Found days part
+ date[0]= value;
+ state= 1; // Assume next is hours
+ found_days= 1;
+ }
+ else if ((end-str) > 1 && *str == time_separator &&
+ my_isdigit(&my_charset_latin1, str[1]))
{
date[0]=0; // Assume we found hours
date[1]=value;
@@ -626,10 +716,10 @@ bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++)
value=value*10L + (long) (*str - '0');
date[state++]=value;
- if (state == 4 || (end-str) < 2 || *str != frm_pos[7] ||
+ if (state == 4 || (end-str) < 2 || *str != time_separator ||
!my_isdigit(&my_charset_latin1,str[1]))
break;
- str++; // Skip ':'
+ str++; // Skip time_separator (':')
}
if (state != 4)
@@ -644,7 +734,8 @@ bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
else
bzero((char*) (date+state), sizeof(long)*(4-state));
}
- fractional:
+
+fractional:
/* Get fractional second part */
if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1]))
{
@@ -659,18 +750,21 @@ bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
else
date[4]=0;
- while (str != end && !my_isalpha(&my_charset_latin1,*str))
- str++;
-
- if ( (end-str)>= 2 &&
- !my_strnncoll(&my_charset_latin1,
- (const uchar *)str, 2,
- (const uchar *)"PM", 2) &&
- frm_pos[6] == 1)
+ if (internal_format_positions[7] != 255)
{
- uint days_i= date[1]/24;
- uint hours_i= date[1]%24;
- date[1]= hours_i%12 + 12 + 24*days_i;
+ /* 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[1] == 'p' || str[1] == 'P')
+ {
+ str+= 2;
+ date[1]= date[1]%12 + 12;
+ }
+ else if (str[1] == 'a' || str[1] == 'A')
+ str+=2;
+ }
}
/* Some simple checks */
@@ -680,11 +774,11 @@ bool str_to_time(const char *str,uint length,TIME *l_time, THD *thd)
return 1;
}
l_time->month=0;
- l_time->day=date[0];
- l_time->hour=date[frm_pos[3] + 1];
- l_time->minute=date[frm_pos[4] + 1];
- l_time->second=date[frm_pos[5] + 1];
- l_time->second_part=date[4];
+ l_time->day= date[0];
+ l_time->hour= date[1];
+ l_time->minute= date[2];
+ l_time->second= date[3];
+ l_time->second_part= date[4];
l_time->time_type= TIMESTAMP_TIME;
/* Check if there is garbage at end of the TIME specification */
@@ -730,163 +824,404 @@ void calc_time_from_sec(TIME *to, long seconds, long microseconds)
}
-DATETIME_FORMAT *make_format(DATETIME_FORMAT *datetime_format,
- datetime_format_types format_type,
- const char *format_str,
- uint format_length, bool is_alloc)
-{
- if (format_length &&
- !parse_datetime_formats(format_type, format_str,
- format_length,
- datetime_format->dt_pos))
- {
- if (is_alloc)
- {
- if (!(datetime_format->format= my_strdup_with_length(format_str,
- format_length,
- MYF(0))))
- return 0;
- }
- else
- datetime_format->format= (char *) format_str;
- datetime_format->format_length= format_length;
- return datetime_format;
- }
- return 0;
-}
+/*
+ Parse a format string specification
+ SYNOPSIS
+ parse_date_time_format()
+ format_type Format of string (time, date or datetime)
+ format_str String to parse
+ format_length Length of string
+ date_time_format Format to fill in
+
+ NOTES
+ Fills in date_time_format->positions for all date time parts.
+
+ positions marks the position for a datetime element in the format string.
+ The position array elements are in the following order:
+ YYYY-DD-MM HH-MM-DD.FFFFFF AM
+ 0 1 2 3 4 5 6 7
+
+ If positions[0]= 5, it means that year will be the forth element to
+ read from the parsed date string.
+
+ RETURN
+ 0 ok
+ 1 error
+*/
-bool parse_datetime_formats(datetime_format_types format_type,
- const char *format_str, uint format_length,
- byte *dt_pos)
+bool parse_date_time_format(timestamp_type format_type,
+ const char *format, uint format_length,
+ DATE_TIME_FORMAT *date_time_format)
{
- uint pos= 0;
- dt_pos[0]= dt_pos[1]= dt_pos[2]= dt_pos[3]=
- dt_pos[4]= dt_pos[5]= dt_pos[6]= dt_pos[7]= -1;
+ uint offset= 0, separators= 0;
+ const char *ptr= format, *format_str;
+ const char *end= ptr+format_length;
+ uchar *dt_pos= date_time_format->positions;
+ /* need_p is set if we are using AM/PM format */
+ bool need_p= 0, allow_separator= 0;
+ ulong part_map= 0, separator_map= 0;
+ const char *parts[16];
+
+ date_time_format->time_separator= 0;
+ date_time_format->flag= 0; // For future
- const char *ptr=format_str;
- const char *end=ptr+format_length;
- bool need_p= 0;
+ /*
+ Fill position with 'dummy' arguments to found out if a format tag is
+ used twice (This limit's the format to 255 characters, but this is ok)
+ */
+ dt_pos[0]= dt_pos[1]= dt_pos[2]= dt_pos[3]=
+ dt_pos[4]= dt_pos[5]= dt_pos[6]= dt_pos[7]= 255;
for (; ptr != end; ptr++)
{
if (*ptr == '%' && ptr+1 != end)
{
+ uint position;
+ LINT_INIT(position);
switch (*++ptr) {
- case 'y':
+ case 'y': // Year
case 'Y':
- if (dt_pos[0] > -1)
- return 1;
- dt_pos[0]= pos;
+ position= 0;
break;
- case 'c':
+ case 'c': // Month
case 'm':
- if (dt_pos[1] > -1)
- return 1;
- dt_pos[1]= pos;
+ position= 1;
break;
case 'd':
case 'e':
- if (dt_pos[2] > -1)
- return 1;
- dt_pos[2]= pos;
+ position= 2;
break;
- case 'H':
- case 'k':
case 'h':
case 'I':
case 'l':
- if (dt_pos[3] > -1)
- return 1;
- dt_pos[3]= pos;
- need_p= (*ptr == 'h' || *ptr == 'l' || *ptr == 'I');
+ need_p= 1; // Need AM/PM
+ /* Fall through */
+ case 'k':
+ case 'H':
+ position= 3;
break;
case 'i':
- if (dt_pos[4] > -1)
- return 1;
- dt_pos[4]= pos;
+ position= 4;
break;
case 's':
case 'S':
- if (dt_pos[5] > -1)
- return 1;
- dt_pos[5]= pos;
+ position= 5;
+ break;
+ case 'f':
+ position= 6;
+ if (dt_pos[5] != offset-1 || ptr[-2] != '.')
+ return 1; // Wrong usage of %f
break;
- case 'p':
- if (dt_pos[6] > -1)
- return 1;
- /* %p should be last in format string */
- if (format_type == DATE_FORMAT_TYPE ||
- (pos != 6 && format_type == DATETIME_FORMAT_TYPE) ||
- (pos != 3 && format_type == TIME_FORMAT_TYPE))
- return 1;
- dt_pos[6]= 1;
+ case 'p': // AM/PM
+ if (offset == 0) // Can't be first
+ return 0;
+ position= 7;
break;
default:
- return 1;
+ return 1; // Unknown controll char
}
- if (dt_pos[6] == -1)
- pos++;
+ if (dt_pos[position] != 255) // Don't allow same tag twice
+ return 1;
+ parts[position]= ptr-1;
+
+ /*
+ If switching from time to date, ensure that all time parts
+ are used
+ */
+ if (part_map && position <= 2 && !(part_map & (1 | 2 | 4)))
+ offset=5;
+ part_map|= (ulong) 1 << position;
+ dt_pos[position]= offset++;
+ allow_separator= 1;
+ }
+ else
+ {
+ /*
+ Don't allow any characters in format as this could easily confuse
+ the date reader
+ */
+ if (!allow_separator)
+ return 1; // No separator here
+ allow_separator= 0; // Don't allow two separators
+ separators++;
+ /* Store in separator_map which parts are punct characters */
+ if (my_ispunct(&my_charset_latin1, *ptr))
+ separator_map|= (ulong) 1 << (offset-1);
+ else if (!my_isspace(&my_charset_latin1, *ptr))
+ return 1;
}
}
- if (pos > 5 && format_type == DATETIME_FORMAT_TYPE &&
- (dt_pos[0] + dt_pos[1] + dt_pos[2] +
- dt_pos[3] + dt_pos[4] + dt_pos[5] != 15) ||
- pos > 2 && format_type == DATE_FORMAT_TYPE &&
- (dt_pos[0] + dt_pos[1] + dt_pos[2] != 3) ||
- pos > 2 && format_type == TIME_FORMAT_TYPE &&
- (dt_pos[3] + dt_pos[4] + dt_pos[5] != 3) ||
- (need_p && dt_pos[6] != 1))
- return 1;
+ /* If no %f, specify it after seconds. Move %p up, if necessary */
+ if ((part_map & 32) && !(part_map & 64))
+ {
+ dt_pos[6]= dt_pos[5] +1;
+ parts[6]= parts[5]; // For later test in (need_p)
+ if (dt_pos[6] == dt_pos[7]) // Move %p one step up if used
+ dt_pos[7]++;
+ }
/*
- Check for valid separators between date/time parst
+ Check that we have not used a non legal format specifier and that all
+ format specifiers have been used
+
+ The last test is to ensure that %p is used if and only if
+ it's needed.
*/
- uint tmp_len= format_length;
- if (dt_pos[6] == 1)
+ if ((format_type == TIMESTAMP_DATETIME &&
+ !test_all_bits(part_map, (1 | 2 | 4 | 8 | 16 | 32))) ||
+ (format_type == TIMESTAMP_DATE && part_map != (1 | 2 | 4)) ||
+ (format_type == TIMESTAMP_TIME &&
+ !test_all_bits(part_map, 8 | 16 | 32)) ||
+ !allow_separator || // %option should be last
+ (need_p && dt_pos[6] +1 != dt_pos[7]) ||
+ (need_p ^ (dt_pos[7] != 255)))
+ return 1;
+
+ if (dt_pos[6] != 255) // If fractional seconds
{
- end= end - 2;
- if (my_ispunct(&my_charset_latin1, *end) || my_isspace(&my_charset_latin1, *end))
- end--;
- tmp_len= end - format_str;
+ /* remove fractional seconds from later tests */
+ uint pos= dt_pos[6] -1;
+ /* Remove separator before %f from sep map */
+ separator_map= ((separator_map & ((ulong) (1 << pos)-1)) |
+ ((separator_map & ~((ulong) (1 << pos)-1)) >> 1));
+ if (part_map & 64)
+ {
+ separators--; // There is always a separator
+ need_p= 1; // force use of separators
+ }
}
+
+ /*
+ Remove possible separator before %p from sep_map
+ (This can either be at position 3, 4, 6 or 7) h.m.d.%f %p
+ */
+ if (dt_pos[7] != 255)
+ {
+ if (need_p && parts[7] != parts[6]+2)
+ separators--;
+ }
+ /*
+ Calculate if %p is in first or last part of the datetime field
+
+ At this point we have either %H-%i-%s %p 'year parts' or
+ 'year parts' &H-%i-%s %p" as %f was removed above
+ */
+ offset= dt_pos[6] <= 3 ? 3 : 6;
+ /* Remove separator before %p from sep map */
+ separator_map= ((separator_map & ((ulong) (1 << offset)-1)) |
+ ((separator_map & ~((ulong) (1 << offset)-1)) >> 1));
+
+ format_str= 0;
switch (format_type) {
- case DATE_FORMAT_TYPE:
- case TIME_FORMAT_TYPE:
- if ((tmp_len == 6 &&
- !my_strnncoll(&my_charset_bin,
- (const uchar *) format_str, 6,
- (const uchar *) datetime_formats
- [format_type][INTERNAL_FORMAT], 6)) ||
- tmp_len == 8 &&
- my_ispunct(&my_charset_latin1, *(format_str+2)) &&
- my_ispunct(&my_charset_latin1, *(format_str+5)))
+ case TIMESTAMP_DATE:
+ format_str= known_date_time_formats[INTERNAL_FORMAT].date_format;
+ /* fall through */
+ case TIMESTAMP_TIME:
+ if (!format_str)
+ format_str=known_date_time_formats[INTERNAL_FORMAT].time_format;
+
+ /*
+ If there is no separators, allow the internal format as we can read
+ this. If separators are used, they must be between each part
+ */
+ if (format_length == 6 && !need_p &&
+ !my_strnncoll(&my_charset_bin,
+ (const uchar *) format, 6,
+ (const uchar *) format_str, 6))
+ return 0;
+ if (separator_map == (1 | 2))
{
- if (format_type == TIME_FORMAT_TYPE && tmp_len == 8)
+ if (format_type == TIMESTAMP_TIME)
{
- if (*(format_str+2) != *(format_str+5))
- return 1;
- dt_pos[7]= *(format_str+2);
+ if (*(format+2) != *(format+5))
+ break; // Error
+ /* Store the character used for time formats */
+ date_time_format->time_separator= *(format+2);
}
return 0;
}
break;
- case DATETIME_FORMAT_TYPE:
- if ((tmp_len == 12 &&
+ case TIMESTAMP_DATETIME:
+ /*
+ If there is no separators, allow the internal format as we can read
+ this. If separators are used, they must be between each part.
+ Between DATE and TIME we also allow space as separator
+ */
+ if ((format_length == 12 && !need_p &&
!my_strnncoll(&my_charset_bin,
- (const uchar *) format_str, 12,
- (const uchar *) datetime_formats
- [DATETIME_FORMAT_TYPE][INTERNAL_FORMAT], 12)) ||
- tmp_len == 17 &&
- my_ispunct(&my_charset_latin1, *(format_str+2)) &&
- my_ispunct(&my_charset_latin1, *(format_str+5)) &&
- my_ispunct(&my_charset_latin1, *(format_str+11)) &&
- my_ispunct(&my_charset_latin1, *(format_str+14)) &&
- (my_ispunct(&my_charset_latin1, *(format_str+8)) ||
- my_isspace(&my_charset_latin1, *(format_str+8))))
+ (const uchar *) format, 12,
+ (const uchar*) known_date_time_formats[INTERNAL_FORMAT].datetime_format,
+ 12)) ||
+ (separators == 5 && separator_map == (1 | 2 | 8 | 16)))
return 0;
break;
- }
- return 1;
+ default:
+ DBUG_ASSERT(1);
+ break;
+ }
+ return 1; // Error
+}
+
+
+/*
+ Create a DATE_TIME_FORMAT object from a format string specification
+
+ SYNOPSIS
+ date_time_format_make()
+ format_type Format to parse (time, date or datetime)
+ format_str String to parse
+ format_length Length of string
+
+ NOTES
+ The returned object should be freed with my_free()
+
+ RETURN
+ NULL ponter: Error
+ new object
+*/
+
+DATE_TIME_FORMAT
+*date_time_format_make(timestamp_type format_type,
+ const char *format_str, uint format_length)
+{
+ DATE_TIME_FORMAT tmp;
+
+ if (format_length && format_length < 255 &&
+ !parse_date_time_format(format_type, format_str,
+ format_length, &tmp))
+ {
+ tmp.format.str= (char*) format_str;
+ tmp.format.length= format_length;
+ return date_time_format_copy((THD *)0, &tmp);
+ }
+ return 0;
+}
+
+
+/*
+ Create a copy of a DATE_TIME_FORMAT object
+
+ SYNOPSIS
+ date_and_time_format_copy()
+ thd Set if variable should be allocated in thread mem
+ format format to copy
+
+ NOTES
+ The returned object should be freed with my_free()
+
+ RETURN
+ NULL ponter: Error
+ new object
+*/
+
+DATE_TIME_FORMAT *date_time_format_copy(THD *thd, DATE_TIME_FORMAT *format)
+{
+ DATE_TIME_FORMAT *new_format;
+ ulong length= sizeof(*format) + format->format.length + 1;
+
+ if (thd)
+ new_format= (DATE_TIME_FORMAT *) thd->alloc(length);
+ else
+ new_format= (DATE_TIME_FORMAT *) my_malloc(length, MYF(MY_WME));
+ if (new_format)
+ {
+ /* Put format string after current pos */
+ new_format->format.str= (char*) (new_format+1);
+ memcpy((char*) new_format->positions, (char*) format->positions,
+ sizeof(format->positions));
+ new_format->time_separator= format->time_separator;
+ /* We make the string null terminated for easy printf in SHOW VARIABLES */
+ memcpy((char*) new_format->format.str, format->format.str,
+ format->format.length);
+ new_format->format.str[format->format.length]= 0;
+ new_format->format.length= format->format.length;
+ }
+ return new_format;
+}
+
+
+KNOWN_DATE_TIME_FORMAT known_date_time_formats[6]=
+{
+ {"USA", "%m.%d.%Y", "%Y-%m-%d %H.%i.%s", "%h:%i:%s %p" },
+ {"JIS", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
+ {"ISO", "%Y-%m-%d", "%Y-%m-%d %H:%i:%s", "%H:%i:%s" },
+ {"EUR", "%d.%m.%Y", "%Y-%m-%d %H.%i.%s", "%H.%i.%s" },
+ {"INTERNAL", "%Y%m%d", "%Y%m%d%H%i%s", "%H%i%s" },
+ { 0, 0, 0, 0 }
+};
+
+
+/*
+ Return format string according format name.
+ If name is unknown, result is NULL
+*/
+
+const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format,
+ timestamp_type type)
+{
+ switch (type) {
+ case TIMESTAMP_DATE:
+ return format->date_format;
+ case TIMESTAMP_DATETIME:
+ return format->datetime_format;
+ case TIMESTAMP_TIME:
+ return format->time_format;
+ default:
+ DBUG_ASSERT(0); // Impossible
+ return 0;
+ }
+}
+
+/****************************************************************************
+ Functions to create default time/date/datetime strings
+
+ NOTE:
+ For the moment the DATE_TIME_FORMAT argument is ignored becasue
+ MySQL doesn't support comparing of date/time/datetime strings that
+ are not in arbutary order as dates are compared as strings in some
+ context)
+****************************************************************************/
+
+void make_time(DATE_TIME_FORMAT *format, TIME *l_time, String *str)
+{
+ long length= my_sprintf((char*) str->ptr(),
+ ((char*) str->ptr(),
+ "%s%02d:%02d:%02d",
+ (l_time->neg ? "-" : ""),
+ l_time->hour,
+ l_time->minute,
+ l_time->second));
+ str->length(length);
+ str->set_charset(&my_charset_bin);
+}
+
+
+void make_date(DATE_TIME_FORMAT *format, TIME *l_time, String *str)
+{
+ long length= my_sprintf((char*) str->ptr(),
+ ((char*) str->ptr(),
+ "%04d-%02d-%02d",
+ l_time->year,
+ l_time->month,
+ l_time->day));
+ str->length(length);
+ str->set_charset(&my_charset_bin);
+}
+
+
+void make_datetime(DATE_TIME_FORMAT *format, TIME *l_time, String *str)
+{
+ long length= my_sprintf((char*) str->ptr(),
+ ((char*) str->ptr(),
+ "%04d-%02d-%02d %02d:%02d:%02d",
+ l_time->year,
+ l_time->month,
+ l_time->day,
+ l_time->hour,
+ l_time->minute,
+ l_time->second));
+ str->length(length);
+ str->set_charset(&my_charset_bin);
}