static absl::Status ParseStringToTimestampParts()

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);
}