in sql_utils/public/functions/date_time_util.cc [1318:1376]
static absl::Status TruncateDateImpl(int32_t date, DateTimestampPart part,
bool enforce_range, int32_t* output) {
if (!IsValidDate(date)) {
return MakeEvalError() << "Invalid date value: " << date;
}
absl::CivilDay civil_day = EpochDaysToCivilDay(date);
switch (part) {
case YEAR:
*output = CivilDayToEpochDays(absl::CivilDay(civil_day.year(), 1, 1));
break;
case ISOYEAR: {
*output = CivilDayToEpochDays(
date_time_util_internal::GetFirstDayOfIsoYear(civil_day));
break;
}
case MONTH: {
*output = CivilDayToEpochDays(
absl::CivilDay(civil_day.year(), civil_day.month(), 1));
break;
}
case QUARTER: {
int m = civil_day.month();
m = (m - 1) / 3 * 3 + 1;
*output = CivilDayToEpochDays(absl::CivilDay(civil_day.year(), m, 1));
break;
}
case WEEK:
case ISOWEEK:
case WEEK_MONDAY:
case WEEK_TUESDAY:
case WEEK_WEDNESDAY:
case WEEK_THURSDAY:
case WEEK_FRIDAY:
case WEEK_SATURDAY: {
SQL_ASSIGN_OR_RETURN(const absl::Weekday first_day_of_week,
GetFirstWeekDayOfWeek(part));
*output =
CivilDayToEpochDays(PrevWeekdayOrToday(civil_day, first_day_of_week));
break;
}
case DAY:
*output = date; // nothing to truncate.
break;
default:
return MakeEvalError() << "Unsupported DateTimestampPart "
<< DateTimestampPart_Name(part);
}
// Truncating to WEEK and WEEK(<WEEKDAY>) can result in a date that is out
// of bounds (i.e., before 0001-01-01), so we check the truncated date
// result here. The other date parts do not have the potential to underflow,
// but we validate the result anyway as a sanity check.
if (enforce_range && !IsValidDate(*output)) {
return MakeEvalError() << "Truncating date " << DateErrorString(date)
<< " to " << DateTimestampPartToSQL(part)
<< " resulted in an out of range date value: "
<< *output;
}
return absl::OkStatus();
}