static absl::Status TimestampTruncImpl()

in sql_utils/public/functions/date_time_util.cc [1588:1713]


static absl::Status TimestampTruncImpl(int64_t timestamp, TimestampScale scale,
                                       NewOrLegacyTimestampType timestamp_type,
                                       absl::TimeZone timezone,
                                       DateTimestampPart part,
                                       int64_t* output) {
  if (!IsValidTimestamp(timestamp, scale)) {
    return MakeEvalError() << "Invalid timestamp value: " << timestamp;
  }
  switch (scale) {
    case kSeconds:
      SQL_RET_CHECK_EQ(LEGACY_TIMESTAMP_TYPE, timestamp_type);
      if (part == SECOND) {
        *output = timestamp;  // nothing to truncate;
        return absl::OkStatus();
      }
      if (part == MILLISECOND || part == MICROSECOND || part == NANOSECOND) {
        return MakeEvalError()
               << "Cannot truncate a TIMESTAMP_SECONDS value to "
               << DateTimestampPart_Name(part);
      }
      break;
    case kMilliseconds:
      SQL_RET_CHECK_EQ(LEGACY_TIMESTAMP_TYPE, timestamp_type);
      if (part == SECOND) {
        // Truncating subsecond of a timestamp before epoch
        // (1970-01-01 00:00:00) to another subsecond needs special
        // treatment.  This is because the subsecond is represented as 10's
        // complement (negative) for timestamp before epoch.
        // From example:
        //   1969-12-31 23:59:59.123456  -->
        //        -876544 (TIMESTAMP_MICRO)
        //   1969-12-31 23:59:59.123  -->
        //        -877 (TIMESTAMP_MILLIS)
        // The truncation needs to compensate by -1 after the division if the
        // truncated part is not zero.
        *output = timestamp / powers_of_ten[3];
        if (timestamp < 0 && timestamp % powers_of_ten[3] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[3];
        return absl::OkStatus();
      }
      if (part == MILLISECOND) {
        *output = timestamp;  // nothing to truncate;
        return absl::OkStatus();
      }
      if (part == MICROSECOND || part == NANOSECOND) {
        return MakeEvalError() << "Cannot truncate a TIMESTAMP_MILLIS value to "
                               << DateTimestampPart_Name(part);
      }
      break;
    case kMicroseconds:
      // This could be either the new TIMESTAMP type or the legacy
      // TIMESTAMP_MICROS type.
      if (part == SECOND) {
        *output = timestamp / powers_of_ten[6];
        if (timestamp < 0 && timestamp % powers_of_ten[6] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[6];
        return absl::OkStatus();
      }
      if (part == MILLISECOND) {
        *output = timestamp / powers_of_ten[3];
        if (timestamp < 0 && timestamp % powers_of_ten[3] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[3];
        return absl::OkStatus();
      }
      if (part == MICROSECOND) {
        *output = timestamp;  // nothing to truncate;
        return absl::OkStatus();
      }
      if (part == NANOSECOND) {
        return MakeEvalError()
               << "Cannot truncate a "
               << (timestamp_type == LEGACY_TIMESTAMP_TYPE ? "TIMESTAMP_MICROS"
                                                           : "TIMESTAMP")
               << " value to " << DateTimestampPart_Name(part);
      }
      break;
    case kNanoseconds:
      SQL_RET_CHECK_EQ(LEGACY_TIMESTAMP_TYPE, timestamp_type);
      if (part == SECOND) {
        *output = timestamp / powers_of_ten[9];
        if (timestamp < 0 && timestamp % powers_of_ten[9] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[9];
        return absl::OkStatus();
      }
      if (part == MILLISECOND) {
        *output = timestamp / powers_of_ten[6];
        if (timestamp < 0 && timestamp % powers_of_ten[6] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[6];
        return absl::OkStatus();
      }
      if (part == MICROSECOND) {
        *output = timestamp / powers_of_ten[3];
        if (timestamp < 0 && timestamp % powers_of_ten[3] != 0) {
          *output -= 1;
        }
        *output *= powers_of_ten[3];
        return absl::OkStatus();
      }
      if (part == NANOSECOND) {
        *output = timestamp;  // nothing to truncate;
        return absl::OkStatus();
      }
      break;
  }
  const absl::Time base_time = MakeTime(timestamp, scale);
  absl::Time output_base_time;
  SQL_RETURN_IF_ERROR(TimestampTruncAtLeastMinute(base_time, scale, timezone, part,
                                              &output_base_time));
  // In this case we know we have a valid timestamp input to the function, so
  // the truncated timestamp must be valid as well.
  SQL_RET_CHECK(FromTime(output_base_time, scale, output))
      << "base_time: " << base_time
      << "\noutput_base_time: " << output_base_time << ", scale: " << scale
      << ", output: " << *output;
  return absl::OkStatus();
}