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