in mysql_sys/my_time.cc [373:722]
bool str_to_datetime(const char *str, std::size_t length, MYSQL_TIME *l_time,
my_time_flags_t flags, MYSQL_TIME_STATUS *status) {
uint field_length = 0;
uint year_length = 0;
uint digits;
uint i;
uint number_of_fields;
uint date[MAX_DATE_PARTS];
uint date_len[MAX_DATE_PARTS];
uint add_hours = 0;
uint start_loop;
ulong not_zero_date;
ulong allow_space;
bool is_internal_format = false;
const char *pos;
const char *last_field_pos = nullptr;
const char *end = str + length;
const uchar *format_position;
bool found_delimiter = false;
bool found_space = false;
bool found_displacement = false;
uint frac_pos;
uint frac_len;
int displacement = 0;
assert(status->warnings == 0 && status->fractional_digits == 0 &&
status->nanoseconds == 0);
/* Skip space at start */
for (; str != end && isspace_char(*str); str++)
;
if (str == end || !isdigit_char(*str)) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
is_internal_format = false;
/* 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 && (isdigit_char(*pos) || *pos == 'T'); pos++)
;
digits = static_cast<uint>(pos - str);
start_loop = 0; /* Start of scan loop */
date_len[format_position[0]] = 0; /* Length of year field */
if (pos == end || *pos == '.') {
/* 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 = true;
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 && !isspace_char(*pos)) pos++;
while (pos < end && !isdigit_char(*pos)) pos++;
if (pos == end) {
if (flags & TIME_DATETIME_ONLY) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true; /* Can't be a full datetime */
}
/* Date field. Set hour, minutes and seconds to 0 */
date[0] = 0;
date[1] = 0;
date[2] = 0;
date[3] = 0;
date[4] = 0;
start_loop = 5; /* Start with first date part */
}
}
field_length = format_position[0] == 0 ? 4 : 2;
}
/*
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 = *std::max_element(format_position, format_position + 3);
allow_space = ((1 << i) | (1 << format_position[6]));
allow_space &= (1 | 2 | 4 | 8 | 64);
not_zero_date = 0;
for (i = start_loop;
i < MAX_DATE_PARTS - 1 && str != end && isdigit_char(*str); i++) {
const char *start = str;
ulong tmp_value = static_cast<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.
*/
bool scan_until_delim = !is_internal_format && (i != format_position[6]);
while (str != end && isdigit_char(str[0]) &&
(scan_until_delim || --field_length)) {
tmp_value =
tmp_value * 10 + static_cast<ulong>(static_cast<uchar>(*str - '0'));
str++;
if (tmp_value > 999999) /* Impossible date part */
{
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
}
date_len[i] = static_cast<uint>(str - start);
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++;
/*
Shift last_field_pos, so '2001-01-01 00:00:00.'
is treated as a valid value
*/
last_field_pos = str;
field_length = 6; /* 6 digits */
} else if (isdigit_char(str[0])) {
/*
We do not see a decimal point which would have indicated a
fractional second part in further read. So we skip the further
processing of digits.
*/
i++;
break;
} else if (str[0] == '+' || str[0] == '-') {
if (!time_zone_displacement_to_seconds(str, end - str, &displacement)) {
found_displacement = true;
str += end - str;
last_field_pos = str;
} else {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
}
continue;
}
if (i == format_position[6] && (str[0] == '+' || str[0] == '-')) {
if (!time_zone_displacement_to_seconds(str, end - str, &displacement)) {
found_displacement = true;
str += end - str;
last_field_pos = str;
} else {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
}
while (str != end && (ispunct_char(*str) || isspace_char(*str))) {
if (isspace_char(*str)) {
if (!(allow_space & (1 << i))) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
found_space = true;
}
str++;
found_delimiter = true; /* 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 && isspace_char(*str)) str++;
}
}
}
last_field_pos = str;
}
if (found_delimiter && !found_space && (flags & TIME_DATETIME_ONLY)) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true; /* Can't be a datetime */
}
str = last_field_pos;
number_of_fields = i - start_loop;
while (i < MAX_DATE_PARTS) {
date_len[i] = 0;
date[i++] = 0;
}
if (!is_internal_format) {
year_length = date_len[static_cast<uint>(format_position[0])];
if (!year_length) /* Year must be specified */
{
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
}
l_time->year = date[static_cast<uint>(format_position[0])];
l_time->month = date[static_cast<uint>(format_position[1])];
l_time->day = date[static_cast<uint>(format_position[2])];
l_time->hour = date[static_cast<uint>(format_position[3])];
l_time->minute = date[static_cast<uint>(format_position[4])];
l_time->second = date[static_cast<uint>(format_position[5])];
l_time->time_zone_displacement = displacement;
frac_pos = static_cast<uint>(format_position[6]);
frac_len = date_len[frac_pos];
status->fractional_digits = frac_len;
if (frac_len < 6)
date[frac_pos] *=
static_cast<uint>(log_10_int[DATETIME_MAX_DECIMALS - frac_len]);
l_time->second_part = date[frac_pos];
if (format_position[7] != static_cast<uchar>(255)) {
if (l_time->hour > 12) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
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] *=
static_cast<uint>(log_10_int[DATETIME_MAX_DECIMALS - date_len[6]]);
l_time->second_part = date[6];
l_time->time_zone_displacement = displacement;
status->fractional_digits = date_len[6];
}
l_time->neg = false;
if (year_length == 2 && not_zero_date)
l_time->year += (l_time->year < YY_PART_YEAR ? 2000 : 1900);
/*
Set time_type before check_datetime_range(),
as the latter relies on initialized time_type value.
*/
l_time->time_type =
(number_of_fields <= 3 ? MYSQL_TIMESTAMP_DATE
: (found_displacement ? MYSQL_TIMESTAMP_DATETIME_TZ
: MYSQL_TIMESTAMP_DATETIME));
if (number_of_fields < 3 || check_datetime_range(*l_time)) {
/* 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 (!isspace_char(*str)) {
not_zero_date = 1; /* Give warning */
break;
}
}
}
status->warnings |=
not_zero_date ? MYSQL_TIME_WARN_TRUNCATED : MYSQL_TIME_WARN_ZERO_DATE;
goto err;
}
if (check_date(*l_time, not_zero_date != 0, flags, &status->warnings))
goto err;
/* Scan all digits left after microseconds */
if (status->fractional_digits == 6 && str != end) {
if (isdigit_char(*str)) {
/*
We don't need the exact nanoseconds value.
Knowing the first digit is enough for rounding.
*/
status->nanoseconds = 100 * (*str++ - '0');
for (; str != end && isdigit_char(*str); str++) {
}
}
}
if (str != end && (str[0] == '+' || str[0] == '-')) {
if (time_zone_displacement_to_seconds(str, end - str, &displacement)) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type = MYSQL_TIMESTAMP_NONE;
return true;
} else {
l_time->time_type = MYSQL_TIMESTAMP_DATETIME_TZ;
l_time->time_zone_displacement = displacement;
return false;
}
}
for (; str != end; str++) {
if (!isspace_char(*str)) {
status->warnings = MYSQL_TIME_WARN_TRUNCATED;
break;
}
}
return false;
err:
set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR);
return true;
}