summaryrefslogtreecommitdiff
path: root/sql-common/my_time.c
diff options
context:
space:
mode:
Diffstat (limited to 'sql-common/my_time.c')
-rw-r--r--sql-common/my_time.c647
1 files changed, 315 insertions, 332 deletions
diff --git a/sql-common/my_time.c b/sql-common/my_time.c
index 7efd0539206..01b4d1fc2c9 100644
--- a/sql-common/my_time.c
+++ b/sql-common/my_time.c
@@ -1,5 +1,6 @@
/*
Copyright (c) 2004, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2013, Monty Program Ab.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -24,10 +25,10 @@
ulonglong log_10_int[20]=
{
1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL,
- ULL(100000000), ULL(1000000000), ULL(10000000000), ULL(100000000000),
- ULL(1000000000000), ULL(10000000000000), ULL(100000000000000),
- ULL(1000000000000000), ULL(10000000000000000), ULL(100000000000000000),
- ULL(1000000000000000000), ULL(10000000000000000000)
+ 100000000ULL, 1000000000ULL, 10000000000ULL, 100000000000ULL,
+ 1000000000000ULL, 10000000000000ULL, 100000000000000ULL,
+ 1000000000000000ULL, 10000000000000000ULL, 100000000000000000ULL,
+ 1000000000000000000ULL, 10000000000000000000ULL
};
@@ -80,6 +81,8 @@ uint calc_days_in_year(uint year)
my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
ulonglong flags, int *was_cut)
{
+ if (ltime->time_type == MYSQL_TIMESTAMP_TIME)
+ return FALSE;
if (not_zero_date)
{
if (((flags & TIME_NO_ZERO_IN_DATE) &&
@@ -104,6 +107,143 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
return FALSE;
}
+static int get_number(uint *val, uint *number_of_fields, const char **str,
+ const char *end)
+{
+ const char *s = *str;
+
+ if (s >= end)
+ return 0;
+
+ if (!my_isdigit(&my_charset_latin1, *s))
+ return 1;
+ *val= *s++ - '0';
+
+ for (; s < end && my_isdigit(&my_charset_latin1, *s); s++)
+ *val= *val * 10 + *s - '0';
+ *str = s;
+ (*number_of_fields)++;
+ return 0;
+}
+
+static int get_digits(uint *val, uint *number_of_fields, const char **str,
+ const char *end, uint length)
+{
+ return get_number(val, number_of_fields, str, MY_MIN(end, *str + length));
+}
+
+static int get_punct(const char **str, const char *end)
+{
+ if (*str >= end)
+ return 0;
+ if (my_ispunct(&my_charset_latin1, **str))
+ {
+ (*str)++;
+ return 0;
+ }
+ return 1;
+}
+
+static int get_date_time_separator(uint *number_of_fields, ulonglong flags,
+ const char **str, const char *end)
+{
+ const char *s= *str;
+ if (s >= end)
+ return 0;
+
+ if (*s == 'T')
+ {
+ (*str)++;
+ return 0;
+ }
+
+ /*
+ now, this is tricky, for backward compatibility reasons.
+ cast("11:11:11.12.12.12" as datetime) should give 2011-11-11 12:12:12
+ but
+ cast("11:11:11.12.12.12" as time) should give 11:11:11.12
+ that is, a punctuation character can be accepted as a date/time separator
+ only if TIME_DATETIME_ONLY (see str_to_time) is not set.
+ */
+ if (my_ispunct(&my_charset_latin1, *s))
+ {
+ if (flags & TIME_DATETIME_ONLY)
+ {
+ /* see above, returning 1 is not enough, we need hard abort here */
+ *number_of_fields= 0;
+ return 1;
+ }
+
+ (*str)++;
+ return 0;
+ }
+
+ if (!my_isspace(&my_charset_latin1, *s))
+ return 1;
+
+ do
+ {
+ s++;
+ } while (my_isspace(&my_charset_latin1, *s));
+ *str= s;
+ return 0;
+}
+
+static int get_maybe_T(const char **str, const char *end)
+{
+ if (*str < end && **str == 'T')
+ (*str)++;
+ return 0;
+}
+
+static uint skip_digits(const char **str, const char *end)
+{
+ const char *start= *str, *s= *str;
+ while (s < end && my_isdigit(&my_charset_latin1, *s))
+ s++;
+ *str= s;
+ return s - start;
+}
+
+
+/**
+ Check datetime, date, or normalized time (i.e. time without days) range.
+ @param ltime Datetime value.
+ @returns
+ @retval FALSE on success
+ @retval TRUE on error
+*/
+my_bool check_datetime_range(const MYSQL_TIME *ltime)
+{
+ /*
+ In case of MYSQL_TIMESTAMP_TIME hour value can be up to TIME_MAX_HOUR.
+ In case of MYSQL_TIMESTAMP_DATETIME it cannot be bigger than 23.
+ */
+ return
+ ltime->year > 9999 || ltime->month > 12 || ltime->day > 31 ||
+ ltime->minute > 59 || ltime->second > 59 ||
+ ltime->second_part > TIME_MAX_SECOND_PART ||
+ (ltime->hour >
+ (uint) (ltime->time_type == MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR : 23));
+}
+
+
+static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status,
+ uint *number_of_fields,
+ const char **str, const char *end)
+{
+ const char *start= *str;
+ uint tmp= 0; /* For the case '10:10:10.' */
+ if (get_digits(&tmp, number_of_fields, str, end, 6))
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
+ if ((status->precision= (*str - start)) < 6)
+ *val= (ulong) (tmp * log_10_int[6 - (*str - start)]);
+ else
+ *val= tmp;
+ if (skip_digits(str, end))
+ status->warnings|= MYSQL_TIME_NOTE_TRUNCATED;
+}
+
/*
Convert a timestamp string to a MYSQL_TIME value.
@@ -119,9 +259,8 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
TIME_NO_ZERO_IN_DATE Don't allow partial dates
TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
TIME_INVALID_DATES Allow 2000-02-31
- was_cut 0 Value OK
- 1 If value was cut during conversion
- 2 check_date(date,flags) considers date invalid
+ status Conversion status
+
DESCRIPTION
At least the following formats are recogniced (based on number of digits)
@@ -132,21 +271,12 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
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.
+ status->warnings is set to:
+ 0 Value OK
+ MYSQL_TIME_WARN_TRUNCATED If value was cut during conversion
+ MYSQL_TIME_WARN_OUT_OF_RANGE check_date(date,flags) considers date invalid
- RETURN VALUES
+ l_time->time_type is set as follows:
MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like
[DD [HH:[MM:[SS]]]].fraction.
l_time is not changed.
@@ -154,293 +284,126 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date,
MYSQL_TIMESTAMP_DATETIME Full timestamp
MYSQL_TIMESTAMP_ERROR Timestamp with wrong values.
All elements in l_time is set to 0
+ RETURN VALUES
+ 0 - Ok
+ 1 - Error
*/
#define MAX_DATE_PARTS 8
-enum enum_mysql_timestamp_type
+my_bool
str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
- ulonglong flags, int *was_cut)
+ ulonglong flags, MYSQL_TIME_STATUS *status)
{
- uint UNINIT_VAR(field_length), UNINIT_VAR(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;
- my_bool is_internal_format;
- const char *pos, *UNINIT_VAR(last_field_pos);
- const char *end=str+length;
- const uchar *format_position;
- my_bool found_delimitier= 0, found_space= 0;
- uint frac_pos, frac_len;
+ const char *end=str+length, *pos;
+ uint number_of_fields= 0, digits, year_length, not_zero_date;
DBUG_ENTER("str_to_datetime");
- DBUG_PRINT("enter",("str: %.*s",length,str));
+ bzero(l_time, sizeof(*l_time));
if (flags & TIME_TIME_ONLY)
{
- enum enum_mysql_timestamp_type ret;
- ret= str_to_time(str, length, l_time, flags, was_cut);
+ my_bool ret= str_to_time(str, length, l_time, flags, status);
DBUG_RETURN(ret);
}
- *was_cut= 0;
+ 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))
{
- *was_cut= 1;
- DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
+ status->warnings= MYSQL_TIME_WARN_TRUNCATED;
+ l_time->time_type= MYSQL_TIMESTAMP_NONE;
+ DBUG_RETURN(1);
}
- 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)
*/
- for (pos=str;
- pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T');
- pos++)
- ;
+ pos= str;
+ digits= skip_digits(&pos, end);
- digits= (uint) (pos-str);
- start_loop= 0; /* Start of scan loop */
- date_len[format_position[0]]= 0; /* Length of year field */
- if (pos == end || *pos == '.')
+ if (pos < end && *pos == 'T') /* YYYYYMMDDHHMMSSThhmmss is supported too */
{
- /* Found date in internal format (only numbers like YYYYMMDD) */
- year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
- field_length= year_length;
- is_internal_format= 1;
- format_position= internal_format_positions;
+ pos++;
+ digits+= skip_digits(&pos, end);
}
- else
+ if (pos < end && *pos == '.' && digits >= 12) /* YYYYYMMDDHHMMSShhmmss.uuuuuu is supported too */
{
- 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 */
- }
- }
-
- field_length= format_position[0] == 0 ? 4 : 2;
+ pos++;
+ skip_digits(&pos, end); // ignore the return value
}
- /*
- 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 = start_loop;
- i < MAX_DATE_PARTS-1 && str != end &&
- my_isdigit(&my_charset_latin1,*str);
- i++)
+ if (pos == end)
{
- const char *start= str;
- ulong tmp_value= (uint) (uchar) (*str++ - '0');
-
/*
- Internal format means no delimiters; every field has a fixed
- width. Otherwise, we scan until we find a delimiter and discard
- leading zeroes -- except for the microsecond part, where leading
- zeroes are significant, and where we never process more than six
- digits.
+ Found date in internal format
+ (only numbers like [YY]YYMMDD[T][hhmmss[.uuuuuu]])
*/
- my_bool scan_until_delim= !is_internal_format &&
- ((i != format_position[6]));
-
- while (str != end && my_isdigit(&my_charset_latin1,str[0]) &&
- (scan_until_delim || --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 */
- {
- *was_cut= 1;
- DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
- }
- date[i]=tmp_value;
- not_zero_date|= tmp_value;
-
- /* Length of next field */
- field_length= format_position[i+1] == 0 ? 4 : 2;
-
- 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 */
- continue;
- }
- if (i == format_position[5]) /* Seconds */
- {
- if (*str == '.') /* Followed by part seconds */
- {
- str++;
- field_length= 6; /* 6 digits */
- }
- continue;
- }
- 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)))
- {
- *was_cut= 1;
- DBUG_RETURN(MYSQL_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[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++;
- }
- }
- }
- last_field_pos= str;
+ year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
+ if (get_digits(&l_time->year, &number_of_fields, &str, end, year_length)
+ || get_digits(&l_time->month, &number_of_fields, &str, end, 2)
+ || get_digits(&l_time->day, &number_of_fields, &str, end, 2)
+ || get_maybe_T(&str, end)
+ || get_digits(&l_time->hour, &number_of_fields, &str, end, 2)
+ || get_digits(&l_time->minute, &number_of_fields, &str, end, 2)
+ || get_digits(&l_time->second, &number_of_fields, &str, end, 2))
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
}
- if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY))
+ else
{
- *was_cut= 1;
- DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a datetime */
+ const char *start= str;
+ if (get_number(&l_time->year, &number_of_fields, &str, end))
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
+ year_length= str - start;
+
+ if (!status->warnings &&
+ (get_punct(&str, end)
+ || get_number(&l_time->month, &number_of_fields, &str, end)
+ || get_punct(&str, end)
+ || get_number(&l_time->day, &number_of_fields, &str, end)
+ || get_date_time_separator(&number_of_fields, flags, &str, end)
+ || get_number(&l_time->hour, &number_of_fields, &str, end)
+ || get_punct(&str, end)
+ || get_number(&l_time->minute, &number_of_fields, &str, end)
+ || get_punct(&str, end)
+ || get_number(&l_time->second, &number_of_fields, &str, end)))
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
}
- str= last_field_pos;
-
- number_of_fields= i - start_loop;
- while (i < MAX_DATE_PARTS)
+ /* we're ok if date part is correct. even if the rest is truncated */
+ if (number_of_fields < 3)
{
- date_len[i]= 0;
- date[i++]= 0;
+ l_time->time_type= MYSQL_TIMESTAMP_NONE;
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
+ DBUG_RETURN(TRUE);
}
- if (!is_internal_format)
+ if (!status->warnings && str < end && *str == '.')
{
- 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];
+ str++;
+ get_microseconds(&l_time->second_part, status,
+ &number_of_fields, &str, end);
}
- l_time->neg= 0;
+
+ not_zero_date = l_time->year || l_time->month || l_time->day ||
+ l_time->hour || l_time->minute || l_time->second ||
+ l_time->second_part;
if (year_length == 2 && not_zero_date)
l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
- if (number_of_fields < 3 ||
- l_time->year > 9999 || l_time->month > 12 ||
- l_time->day > 31 || l_time->hour > 23 ||
- l_time->minute > 59 || l_time->second > 59)
+ if (l_time->year > 9999 || l_time->month > 12 || l_time->day > 31 ||
+ l_time->hour > 23 || l_time->minute > 59 || l_time->second > 59)
{
- /* Only give warning for a zero date if there is some garbage after */
- if (!not_zero_date) /* If zero date */
- {
- for (; str != end ; str++)
- {
- if (!my_isspace(&my_charset_latin1, *str))
- {
- not_zero_date= 1; /* Give warning */
- break;
- }
- }
- }
- *was_cut= test(not_zero_date);
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
goto err;
}
- if (check_date(l_time, not_zero_date != 0, flags, was_cut))
+ if (check_date(l_time, not_zero_date, flags, &status->warnings))
goto err;
l_time->time_type= (number_of_fields <= 3 ?
@@ -450,16 +413,17 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
{
if (!my_isspace(&my_charset_latin1,*str))
{
- *was_cut= 1;
+ status->warnings= MYSQL_TIME_WARN_TRUNCATED;
break;
}
}
- DBUG_RETURN(l_time->time_type);
+ DBUG_RETURN(FALSE);
err:
bzero((char*) l_time, sizeof(*l_time));
- DBUG_RETURN(MYSQL_TIMESTAMP_ERROR);
+ l_time->time_type= MYSQL_TIMESTAMP_ERROR;
+ DBUG_RETURN(TRUE);
}
@@ -474,59 +438,60 @@ err:
There may be an optional [.second_part] after seconds
length Length of str
l_time Store result here
- warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string
- was cut during conversion, and/or
- MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is
- out of range.
+ status Conversion status
+
NOTES
+
Because of the extra days argument, this function can only
work with times where the time arguments are in the above order.
+ status->warnings is set as follows:
+ MYSQL_TIME_WARN_TRUNCATED if the input string was cut during conversion,
+ and/or
+ MYSQL_TIME_WARN_OUT_OF_RANGE flag is set if the value is out of range.
+
RETURN
- MYSQL_TIMESTAMP_TIME
- MYSQL_TIMESTAMP_ERROR
+ FALSE on success
+ TRUE on error
*/
-enum enum_mysql_timestamp_type
-str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
- ulonglong fuzzydate, int *warning)
+my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
+ ulonglong fuzzydate, MYSQL_TIME_STATUS *status)
{
ulong date[5];
ulonglong value;
const char *end=str+length, *end_of_days;
- my_bool found_days,found_hours;
- uint state;
+ my_bool found_days,found_hours, neg= 0;
+ uint UNINIT_VAR(state);
- l_time->neg=0;
- *warning= 0;
+ my_time_status_init(status);
for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++)
length--;
if (str != end && *str == '-')
{
- l_time->neg=1;
+ neg=1;
str++;
length--;
}
if (str == end)
- return MYSQL_TIMESTAMP_ERROR;
+ {
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
+ goto err;
+ }
/* Check first if this is a full TIMESTAMP */
if (length >= 12)
{ /* Probably full timestamp */
- int was_cut;
- enum enum_mysql_timestamp_type
- res= str_to_datetime(str, length, l_time,
+ (void) str_to_datetime(str, length, l_time,
(fuzzydate & ~TIME_TIME_ONLY) | TIME_DATETIME_ONLY,
- &was_cut);
- if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR)
- {
- if (was_cut)
- *warning|= MYSQL_TIME_WARN_TRUNCATED;
- return res;
- }
+ status);
+ if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR)
+ return l_time->time_type == MYSQL_TIMESTAMP_ERROR;
+ my_time_status_init(status);
}
+ l_time->neg= neg;
/* 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');
@@ -536,7 +501,6 @@ str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
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 != end_of_days &&
my_isdigit(&my_charset_latin1, *str))
@@ -592,24 +556,15 @@ str_to_time(const char *str, uint length, MYSQL_TIME *l_time,
fractional:
/* Get fractional second part */
- if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1]))
+ if (!status->warnings && str < end && *str == '.')
{
- int field_length= 5;
- str++; value=(uint) (uchar) (*str - '0');
- while (++str != end && my_isdigit(&my_charset_latin1, *str))
- {
- if (field_length-- > 0)
- value= value*10 + (uint) (uchar) (*str - '0');
- }
- if (field_length > 0)
- value*= (long) log_10_int[field_length];
- else if (field_length < 0)
- *warning|= MYSQL_TIME_WARN_TRUNCATED;
- date[4]= (ulong) value;
+ uint number_of_fields= 0;
+ str++;
+ get_microseconds(&date[4], status, &number_of_fields, &str, end);
}
else
- date[4]=0;
-
+ date[4]= 0;
+
/* Check for exponent part: E<gigit> | E<sign><digit> */
/* (may occur as result of %g formatting of time value) */
if ((end - str) > 1 &&
@@ -618,7 +573,10 @@ fractional:
((str[1] == '-' || str[1] == '+') &&
(end - str) > 2 &&
my_isdigit(&my_charset_latin1, str[2]))))
- return MYSQL_TIMESTAMP_ERROR;
+ {
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
+ goto err;
+ }
if (internal_format_positions[7] != 255)
{
@@ -641,8 +599,11 @@ fractional:
if (date[0] > UINT_MAX || date[1] > UINT_MAX ||
date[2] > UINT_MAX || date[3] > UINT_MAX ||
date[4] > UINT_MAX)
- return MYSQL_TIMESTAMP_ERROR;
-
+ {
+ status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE;
+ goto err;
+ }
+
l_time->year= 0; /* For protocol::store_time */
l_time->month= 0;
l_time->day= 0;
@@ -653,9 +614,9 @@ fractional:
l_time->time_type= MYSQL_TIMESTAMP_TIME;
/* Check if the value is valid and fits into MYSQL_TIME range */
- if (check_time_range(l_time, 6, warning))
- return MYSQL_TIMESTAMP_ERROR;
-
+ if (check_time_range(l_time, 6, &status->warnings))
+ return TRUE;
+
/* Check if there is garbage at end of the MYSQL_TIME specification */
if (str != end)
{
@@ -663,12 +624,17 @@ fractional:
{
if (!my_isspace(&my_charset_latin1,*str))
{
- *warning|= MYSQL_TIME_WARN_TRUNCATED;
+ status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
break;
}
} while (++str != end);
}
- return MYSQL_TIMESTAMP_TIME;
+ return FALSE;
+
+err:
+ bzero((char*) l_time, sizeof(*l_time));
+ l_time->time_type= MYSQL_TIMESTAMP_ERROR;
+ return TRUE;
}
@@ -698,7 +664,10 @@ int check_time_range(struct st_mysql_time *my_time, uint dec, int *warning)
999000, 999900, 999990, 999999};
if (my_time->minute >= 60 || my_time->second >= 60)
+ {
+ *warning|= MYSQL_TIME_WARN_TRUNCATED;
return 1;
+ }
hour= my_time->hour + (24*my_time->day);
@@ -950,7 +919,8 @@ my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, uint *error_code)
#endif
tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) -
- (long) days_at_timestart)*86400L + (long) t->hour*3600L +
+ (long) days_at_timestart) * SECONDS_IN_24H +
+ (long) t->hour*3600L +
(long) (t->minute*60 + t->second)) + (time_t) my_time_zone -
3600);
@@ -1009,7 +979,7 @@ my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, uint *error_code)
/* shift back, if we were dealing with boundary dates */
- tmp+= shift*86400L;
+ tmp+= shift * SECONDS_IN_24H;
/*
This is possible for dates, which slightly exceed boundaries.
@@ -1189,6 +1159,27 @@ int my_TIME_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
}
+/**
+ Print a timestamp with an optional fractional part: XXXXX[.YYYYY]
+
+ @param tm The timestamp value to print.
+ @param OUT to The string pointer to print at.
+ @param dec Precision, in the range 0..6.
+ @return The length of the result string.
+*/
+int my_timeval_to_str(const struct timeval *tm, char *to, uint dec)
+{
+ char *pos= longlong10_to_str((longlong) tm->tv_sec, to, 10);
+ if (dec)
+ {
+ *pos++= '.';
+ pos= fmt_number((uint) sec_part_shift(tm->tv_usec, dec), pos, dec);
+ }
+ *pos= '\0';
+ return (int) (pos - to);
+}
+
+
/*
Convert datetime value specified as number to broken-down TIME
representation and form value of DATETIME type as side-effect.
@@ -1254,19 +1245,19 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res,
time_res->time_type=MYSQL_TIMESTAMP_DATETIME;
- if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
+ if (nr <= (YY_PART_YEAR-1)*10000000000LL+1231235959LL)
{
- nr= nr+LL(20000000000000); /* YYMMDDHHMMSS, 2000-2069 */
+ nr= nr+20000000000000LL; /* YYMMDDHHMMSS, 2000-2069 */
goto ok;
}
- if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
+ if (nr < YY_PART_YEAR*10000000000LL+ 101000000LL)
goto err;
- if (nr <= LL(991231235959))
- nr= nr+LL(19000000000000); /* YYMMDDHHMMSS, 1970-1999 */
+ if (nr <= 991231235959LL)
+ nr= nr+19000000000000LL; /* YYMMDDHHMMSS, 1970-1999 */
ok:
- part1=(long) (nr/LL(1000000));
- part2=(long) (nr - (longlong) part1*LL(1000000));
+ part1=(long) (nr/1000000LL);
+ part2=(long) (nr - (longlong) part1*1000000LL);
time_res->year= (int) (part1/10000L); part1%=10000L;
time_res->month= (int) part1 / 100;
time_res->day= (int) part1 % 100;
@@ -1290,7 +1281,7 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res,
/* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
if (nr || !(flags & TIME_NO_ZERO_DATE))
*was_cut= 1;
- return LL(-1);
+ return -1;
err:
{
@@ -1300,7 +1291,7 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res,
time_res->time_type= save; /* Restore range */
*was_cut= 1; /* Found invalid date */
}
- return LL(-1);
+ return -1;
}
/*
@@ -1323,16 +1314,8 @@ int number_to_time(my_bool neg, ulonglong nr, ulong sec_part,
MYSQL_TIME *ltime, int *was_cut)
{
if (nr > 9999999 && nr < 99991231235959ULL && neg == 0)
- {
- if (number_to_datetime(nr, sec_part, ltime,
- TIME_INVALID_DATES, was_cut) < 0)
- return -1;
-
- ltime->year= ltime->month= ltime->day= 0;
- ltime->time_type= MYSQL_TIMESTAMP_TIME;
- *was_cut= MYSQL_TIME_NOTE_TRUNCATED;
- return 0;
- }
+ return number_to_datetime(nr, sec_part, ltime,
+ TIME_INVALID_DATES, was_cut) < 0 ? -1 : 0;
*was_cut= 0;
ltime->year= ltime->month= ltime->day= 0;
@@ -1365,7 +1348,7 @@ ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)
{
return ((ulonglong) (my_time->year * 10000UL +
my_time->month * 100UL +
- my_time->day) * ULL(1000000) +
+ my_time->day) * 1000000ULL +
(ulonglong) (my_time->hour * 10000UL +
my_time->minute * 100UL +
my_time->second));
@@ -1426,7 +1409,7 @@ ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time)
return TIME_to_ulonglong_time(my_time);
case MYSQL_TIMESTAMP_NONE:
case MYSQL_TIMESTAMP_ERROR:
- return ULL(0);
+ return 0;
default:
DBUG_ASSERT(0);
}
@@ -1444,7 +1427,7 @@ double TIME_to_double(const MYSQL_TIME *my_time)
return my_time->neg ? -d : d;
}
-longlong pack_time(MYSQL_TIME *my_time)
+longlong pack_time(const MYSQL_TIME *my_time)
{
return ((((((my_time->year * 13ULL +
my_time->month) * 32ULL +