in sql_utils/public/functions/date_time_util.cc [498:576]
static absl::Status ParseStringToTimestampParts(
absl::string_view str, TimestampScale scale, int* year, int* month,
int* day, int* hour, int* minute, int* second, int* subsecond,
absl::TimeZone* timezone, bool* string_includes_timezone) {
int idx = 0;
// Minimum required length is 8 for a valid timestamp.
if (!CheckRemainingLength(str, idx, 8 /* remaining_length */) ||
!ParseDigits(str, 4, 5, &idx, year) || !ParseCharacter(str, '-', &idx) ||
!ParseDigits(str, 1, 2, &idx, month) || !ParseCharacter(str, '-', &idx) ||
!ParseDigits(str, 1, 2, &idx, day)) {
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
if (idx >= static_cast<int64_t>(str.length()))
return absl::OkStatus(); // Done consuming <str>.
// The rest are all optional: time, subseconds, time zone
// Initial space, 'T', or 't' to kick off the rest.
if ((!ParseCharacter(str, ' ', &idx) && !ParseCharacter(str, 'T', &idx) &&
!ParseCharacter(str, 't', &idx)) ||
!CheckRemainingLength(str, idx, 2 /* remaining_length */)) {
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
// Time is '[H]H:[M]M:[S]S'.
if (absl::ascii_isdigit(str[idx])) {
if (!ParsePrefixToTimeParts(str, scale, &idx, hour, minute, second,
subsecond)) {
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
if (idx >= static_cast<int64_t>(str.length()))
return absl::OkStatus(); // Done consuming <str>.
} else if (str[idx] != '+' && str[idx] != '-') {
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
if (absl::ClippedSubstr(str, idx).empty()) {
*string_includes_timezone = false;
return absl::OkStatus();
}
*string_includes_timezone = true;
// Optional UTC prefix for timezone (preceded by exactly one space)
if (absl::StartsWith(str.substr(idx), " UTC")) {
idx += 4;
// If there is no offset after "UTC", it was simply UTC timezone.
if (absl::ClippedSubstr(str, idx).empty()) {
*timezone = absl::UTCTimeZone();
return absl::OkStatus();
}
}
switch (str[idx]) {
case '+':
case '-':
// Canonical time zone form.
return MakeTimeZone(absl::ClippedSubstr(str, idx), timezone);
case 'Z':
case 'z':
if (idx + 1 != str.size()) {
// Trailing content after 'Z'.
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
// UTC.
*timezone = absl::UTCTimeZone();
return absl::OkStatus();
default:
break;
}
// For a time zone name, it must be a space followed by the name. Do not
// allow a space followed by the canonical form (as indicated by a leading
// '+/-').
if (str[idx] != ' ' || static_cast<int64_t>(str.size()) < idx + 2 ||
str[idx + 1] == '+' || str[idx + 1] == '-') {
return MakeEvalError() << "Invalid timestamp: '" << str << "'";
}
++idx;
return MakeTimeZone(absl::ClippedSubstr(str, idx), timezone);
}