|
|
@ -271,6 +271,128 @@ int check_time_range(MYSQL_TIME *ltime, uint dec, int *warning) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Remove trailing spaces and garbage */ |
|
|
|
static my_bool get_suffix(const char *str, size_t length, size_t *new_length) |
|
|
|
{ |
|
|
|
/* |
|
|
|
QQ: perhaps 'T' should be considered as a date/time delimiter only |
|
|
|
if it's followed by a digit. Learn ISO 8601 details. |
|
|
|
*/ |
|
|
|
my_bool garbage= FALSE; |
|
|
|
for ( ; length > 0 ; length--) |
|
|
|
{ |
|
|
|
char ch= str[length - 1]; |
|
|
|
if (my_isdigit(&my_charset_latin1, ch) || |
|
|
|
my_ispunct(&my_charset_latin1, ch)) |
|
|
|
break; |
|
|
|
if (my_isspace(&my_charset_latin1, ch)) |
|
|
|
continue; |
|
|
|
if (ch == 'T') |
|
|
|
{ |
|
|
|
/* 'T' has a meaning only after a digit. Otherwise it's a garbage */ |
|
|
|
if (length >= 2 && my_isdigit(&my_charset_latin1, str[length - 2])) |
|
|
|
break; |
|
|
|
} |
|
|
|
garbage= TRUE; |
|
|
|
} |
|
|
|
*new_length= length; |
|
|
|
return garbage; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static size_t get_prefix(const char *str, size_t length, const char **endptr) |
|
|
|
{ |
|
|
|
const char *str0= str, *end= str + length; |
|
|
|
for (; str < end && my_isspace(&my_charset_latin1, *str) ; str++) |
|
|
|
{ } |
|
|
|
*endptr= str; |
|
|
|
return str - str0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static size_t get_sign(my_bool *neg, const char *str, size_t length, |
|
|
|
const char **endptr) |
|
|
|
{ |
|
|
|
const char *str0= str; |
|
|
|
if (length) |
|
|
|
{ |
|
|
|
if ((*neg= (*str == '-')) || (*str == '+')) |
|
|
|
str++; |
|
|
|
} |
|
|
|
else |
|
|
|
*neg= FALSE; |
|
|
|
*endptr= str; |
|
|
|
return str - str0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static my_bool find_body(my_bool *neg, const char *str, size_t length, |
|
|
|
MYSQL_TIME *to, int *warn, |
|
|
|
const char **new_str, size_t *new_length) |
|
|
|
{ |
|
|
|
size_t sign_length; |
|
|
|
*warn= 0; |
|
|
|
length-= get_prefix(str, length, &str); |
|
|
|
sign_length= get_sign(neg, str, length, &str); |
|
|
|
length-= sign_length; |
|
|
|
/* There can be a space after a sign again: '- 10:20:30' or '- 1 10:20:30' */ |
|
|
|
length-= get_prefix(str, length, &str); |
|
|
|
if (get_suffix(str, length, &length)) |
|
|
|
*warn|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
*new_str= str; |
|
|
|
*new_length= length; |
|
|
|
if (!length || !my_isdigit(&my_charset_latin1, *str)) |
|
|
|
{ |
|
|
|
*warn|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
set_zero_time(to, MYSQL_TIMESTAMP_ERROR); |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static my_bool |
|
|
|
is_datetime_body_candidate(const char *str, size_t length) |
|
|
|
{ |
|
|
|
static uint min_date_length= 5; /* '1-1-1' -> '0001-01-01' */ |
|
|
|
uint pos; |
|
|
|
if (length >= 12) |
|
|
|
return TRUE; |
|
|
|
/* |
|
|
|
The shortest possible DATE is '1-1-1', which is 5 characters. |
|
|
|
To make a full datetime it should be at least followed by a space or a 'T'. |
|
|
|
*/ |
|
|
|
if (length < min_date_length + 1/* DATE/TIME separator */) |
|
|
|
return FALSE; |
|
|
|
for (pos= min_date_length; pos < length; pos++) |
|
|
|
{ |
|
|
|
if (str[pos] == 'T') /* Date/time separator */ |
|
|
|
return TRUE; |
|
|
|
if (str[pos] == ' ') |
|
|
|
{ |
|
|
|
/* |
|
|
|
We found a space. If can be a DATE/TIME separator: |
|
|
|
TIME('1-1-1 1:1:1.0) -> '0001-01-01 01:01:01.0' |
|
|
|
|
|
|
|
But it can be also a DAY/TIME separator: |
|
|
|
TIME('1 11') -> 35:00:00 = 1 day 11 hours |
|
|
|
TIME('1 111') -> 135:00:00 = 1 day 111 hours |
|
|
|
TIME('11 11') -> 275:00:00 = 11 days 11 hours |
|
|
|
TIME('111 11') -> 838:59:59 = 111 days 11 hours with overflow |
|
|
|
TIME('1111 11') -> 838:59:59 = 1111 days 11 hours with overflow |
|
|
|
*/ |
|
|
|
for (pos= 0 ; pos < min_date_length; pos++) |
|
|
|
{ |
|
|
|
if (my_ispunct(&my_charset_latin1, str[pos])) /* Can be a DATE */ |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
} |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static my_bool |
|
|
|
str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length, |
|
|
|
MYSQL_TIME *l_time, ulong max_hour, |
|
|
@ -282,7 +404,7 @@ str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length, |
|
|
|
Convert a timestamp string to a MYSQL_TIME value. |
|
|
|
|
|
|
|
SYNOPSIS |
|
|
|
str_to_datetime() |
|
|
|
str_to_datetime_or_date_body() |
|
|
|
str String to parse |
|
|
|
length Length of string |
|
|
|
l_time Date is stored here |
|
|
@ -323,34 +445,16 @@ str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length, |
|
|
|
|
|
|
|
#define MAX_DATE_PARTS 8 |
|
|
|
|
|
|
|
my_bool |
|
|
|
str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
ulonglong flags, MYSQL_TIME_STATUS *status) |
|
|
|
static my_bool |
|
|
|
str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
ulonglong flags, MYSQL_TIME_STATUS *status) |
|
|
|
{ |
|
|
|
const char *end=str+length, *pos; |
|
|
|
uint number_of_fields= 0, digits, year_length, not_zero_date; |
|
|
|
DBUG_ENTER("str_to_datetime"); |
|
|
|
DBUG_ENTER("str_to_datetime_or_date_body"); |
|
|
|
DBUG_ASSERT(C_FLAGS_OK(flags)); |
|
|
|
bzero(l_time, sizeof(*l_time)); |
|
|
|
|
|
|
|
if (flags & C_TIME_TIME_ONLY) |
|
|
|
{ |
|
|
|
my_bool ret= str_to_time(str, length, l_time, flags, status); |
|
|
|
DBUG_RETURN(ret); |
|
|
|
} |
|
|
|
|
|
|
|
my_time_status_init(status); |
|
|
|
|
|
|
|
/* Skip space at start */ |
|
|
|
for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++) |
|
|
|
; |
|
|
|
if (str == end || ! my_isdigit(&my_charset_latin1, *str)) |
|
|
|
{ |
|
|
|
status->warnings= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
l_time->time_type= MYSQL_TIMESTAMP_NONE; |
|
|
|
DBUG_RETURN(1); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
Calculate number of digits in first part. |
|
|
|
If length= 8 or >= 14 then year is of format YYYY. |
|
|
@ -442,43 +546,22 @@ str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
l_time->time_type= (number_of_fields <= 3 ? |
|
|
|
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME); |
|
|
|
|
|
|
|
for (; str != end ; str++) |
|
|
|
{ |
|
|
|
if (!my_isspace(&my_charset_latin1,*str)) |
|
|
|
{ |
|
|
|
status->warnings= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if (str != end) |
|
|
|
status->warnings= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
|
|
|
|
DBUG_RETURN(FALSE); |
|
|
|
|
|
|
|
err: |
|
|
|
bzero((char*) l_time, sizeof(*l_time)); |
|
|
|
l_time->time_type= MYSQL_TIMESTAMP_ERROR; |
|
|
|
set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR); |
|
|
|
DBUG_RETURN(TRUE); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static size_t get_prefix_and_sign(my_bool *neg, const char *str, size_t length) |
|
|
|
{ |
|
|
|
const char *str0= str, *end= str + length; |
|
|
|
for (; str < end && my_isspace(&my_charset_latin1, *str) ; str++) |
|
|
|
{ } |
|
|
|
if (str < end && *str == '-') |
|
|
|
{ |
|
|
|
*neg= TRUE; |
|
|
|
str++; |
|
|
|
} |
|
|
|
return str - str0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
Convert a time string to a MYSQL_TIME struct. |
|
|
|
|
|
|
|
SYNOPSIS |
|
|
|
str_to_time() |
|
|
|
str_to_datetime_or_date_or_time_body() |
|
|
|
str A string in full TIMESTAMP format or |
|
|
|
[-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, |
|
|
|
[M]MSS or [S]S |
|
|
@ -503,44 +586,31 @@ static size_t get_prefix_and_sign(my_bool *neg, const char *str, size_t length) |
|
|
|
TRUE on error |
|
|
|
*/ |
|
|
|
|
|
|
|
my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
ulonglong fuzzydate, MYSQL_TIME_STATUS *status) |
|
|
|
my_bool str_to_datetime_or_date_or_time_body(const char *str, size_t length, |
|
|
|
MYSQL_TIME *l_time, |
|
|
|
ulonglong fuzzydate, |
|
|
|
MYSQL_TIME_STATUS *status) |
|
|
|
{ |
|
|
|
my_bool neg= 0; |
|
|
|
size_t tmp_length; |
|
|
|
const char *endptr; |
|
|
|
DBUG_ASSERT(C_FLAGS_OK(fuzzydate)); |
|
|
|
|
|
|
|
my_time_status_init(status); |
|
|
|
|
|
|
|
if ((tmp_length= get_prefix_and_sign(&neg, str, length))) |
|
|
|
{ |
|
|
|
str+= tmp_length; |
|
|
|
length-= tmp_length; |
|
|
|
} |
|
|
|
if (!length) |
|
|
|
{ |
|
|
|
status->warnings|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
goto err; |
|
|
|
} |
|
|
|
|
|
|
|
/* Check first if this is a full TIMESTAMP */ |
|
|
|
if (length >= 12) |
|
|
|
if (is_datetime_body_candidate(str, length)) |
|
|
|
{ /* Probably full timestamp */ |
|
|
|
(void) str_to_datetime(str, length, l_time, |
|
|
|
(fuzzydate & ~C_TIME_TIME_ONLY) | C_TIME_DATETIME_ONLY, |
|
|
|
status); |
|
|
|
(void) str_to_datetime_or_date_body(str, length, l_time, |
|
|
|
(fuzzydate & ~C_TIME_TIME_ONLY) | |
|
|
|
C_TIME_DATETIME_ONLY, |
|
|
|
status); |
|
|
|
if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR) |
|
|
|
return l_time->time_type == MYSQL_TIMESTAMP_ERROR; |
|
|
|
my_time_status_init(status); |
|
|
|
} |
|
|
|
|
|
|
|
if (!str_to_DDhhmmssff_internal(neg, str, length, l_time, TIME_MAX_HOUR, |
|
|
|
if (!str_to_DDhhmmssff_internal(FALSE, str, length, l_time, TIME_MAX_HOUR, |
|
|
|
status, &endptr)) |
|
|
|
return FALSE; |
|
|
|
err: |
|
|
|
bzero((char*) l_time, sizeof(*l_time)); |
|
|
|
l_time->time_type= MYSQL_TIMESTAMP_ERROR; |
|
|
|
|
|
|
|
set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR); |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
|
|
|
@ -548,31 +618,22 @@ err: |
|
|
|
my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime, |
|
|
|
ulong max_hour, MYSQL_TIME_STATUS *status) |
|
|
|
{ |
|
|
|
my_bool neg= 0; |
|
|
|
size_t tmp_length; |
|
|
|
my_bool neg; |
|
|
|
const char *endptr; |
|
|
|
my_time_status_init(status); |
|
|
|
int warn; |
|
|
|
|
|
|
|
/* Remove trailing spaces */ |
|
|
|
for ( ; length > 0 && my_isspace(&my_charset_latin1, str[length - 1]) ; ) |
|
|
|
length--; |
|
|
|
|
|
|
|
if ((tmp_length= get_prefix_and_sign(&neg, str, length))) |
|
|
|
{ |
|
|
|
str+= tmp_length; |
|
|
|
length-= tmp_length; |
|
|
|
} |
|
|
|
if (!length) |
|
|
|
my_time_status_init(status); |
|
|
|
if (find_body(&neg, str, length, ltime, &warn, &str, &length)) |
|
|
|
{ |
|
|
|
status->warnings|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
set_zero_time(ltime, MYSQL_TIMESTAMP_ERROR); |
|
|
|
status->warnings= warn; |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
|
|
|
|
/* Reject anything that might be parsed as a full TIMESTAMP */ |
|
|
|
if (length >= 12) /* The same condition with str_to_time() */ |
|
|
|
if (is_datetime_body_candidate(str, length)) |
|
|
|
{ |
|
|
|
(void) str_to_datetime(str, length, ltime, C_TIME_DATETIME_ONLY, status); |
|
|
|
(void) str_to_datetime_or_date_body(str, length, ltime, |
|
|
|
C_TIME_DATETIME_ONLY, status); |
|
|
|
if (ltime->time_type > MYSQL_TIMESTAMP_ERROR) |
|
|
|
{ |
|
|
|
status->warnings|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
@ -591,6 +652,57 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime, |
|
|
|
status, &endptr) || |
|
|
|
(endptr < str + length && endptr[0] == '-')) |
|
|
|
return TRUE; |
|
|
|
status->warnings|= warn; |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
ulonglong fuzzydate, MYSQL_TIME_STATUS *status) |
|
|
|
{ |
|
|
|
my_bool neg; |
|
|
|
int warn; |
|
|
|
DBUG_ASSERT(C_FLAGS_OK(fuzzydate)); |
|
|
|
my_time_status_init(status); |
|
|
|
if (find_body(&neg, str, length, l_time, &warn, &str, &length)) |
|
|
|
{ |
|
|
|
status->warnings= warn; |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
/* |
|
|
|
QQ: Perhaps we should modify xxx_body() to return endptr. |
|
|
|
If endptr points to '-', return an error. |
|
|
|
*/ |
|
|
|
if (str_to_datetime_or_date_or_time_body(str, length, l_time, |
|
|
|
fuzzydate, status)) |
|
|
|
return TRUE; |
|
|
|
status->warnings|= warn; |
|
|
|
l_time->neg= neg; |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
my_bool |
|
|
|
str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, |
|
|
|
ulonglong flags, MYSQL_TIME_STATUS *status) |
|
|
|
{ |
|
|
|
my_bool neg, rc; |
|
|
|
int warn; |
|
|
|
DBUG_ASSERT(C_FLAGS_OK(flags)); |
|
|
|
my_time_status_init(status); |
|
|
|
if (find_body(&neg, str, length, l_time, &warn, &str, &length)) |
|
|
|
{ |
|
|
|
status->warnings= warn; |
|
|
|
return TRUE; |
|
|
|
} |
|
|
|
rc= (flags & C_TIME_TIME_ONLY) ? |
|
|
|
str_to_datetime_or_date_or_time_body(str, length, l_time, flags, status) : |
|
|
|
str_to_datetime_or_date_body(str, length, l_time, flags, status); |
|
|
|
status->warnings|= warn; |
|
|
|
if (rc) |
|
|
|
return rc; |
|
|
|
if ((l_time->neg= neg) && l_time->time_type != MYSQL_TIMESTAMP_TIME) |
|
|
|
return TRUE; |
|
|
|
return FALSE; |
|
|
|
} |
|
|
|
|
|
|
@ -749,16 +861,7 @@ fractional: |
|
|
|
|
|
|
|
/* Check if there is garbage at end of the MYSQL_TIME specification */ |
|
|
|
if (str != end) |
|
|
|
{ |
|
|
|
do |
|
|
|
{ |
|
|
|
if (!my_isspace(&my_charset_latin1,*str)) |
|
|
|
{ |
|
|
|
status->warnings|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
break; |
|
|
|
} |
|
|
|
} while (++str != end); |
|
|
|
} |
|
|
|
status->warnings|= MYSQL_TIME_WARN_TRUNCATED; |
|
|
|
return FALSE; |
|
|
|
|
|
|
|
err: |
|
|
|