in mysql_sys/my_time.cc [2290:2414]
bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type,
Interval interval, int *warnings) {
ltime->neg = false;
long long sign = (interval.neg ? -1 : 1);
switch (int_type) {
case INTERVAL_SECOND:
case INTERVAL_SECOND_MICROSECOND:
case INTERVAL_MICROSECOND:
case INTERVAL_MINUTE:
case INTERVAL_HOUR:
case INTERVAL_MINUTE_MICROSECOND:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_HOUR_MICROSECOND:
case INTERVAL_HOUR_SECOND:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_DAY_MICROSECOND:
case INTERVAL_DAY_SECOND:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_HOUR: {
longlong sec, days, daynr, microseconds, extra_sec;
ltime->time_type = MYSQL_TIMESTAMP_DATETIME; // Return full date
microseconds = ltime->second_part + sign * interval.second_part;
extra_sec = microseconds / 1000000L;
microseconds = microseconds % 1000000L;
if (interval.day > MAX_DAY_NUMBER) goto invalid_date;
if (interval.hour > MAX_DAY_NUMBER * 24ULL) goto invalid_date;
if (interval.minute > MAX_DAY_NUMBER * 24ULL * 60ULL) goto invalid_date;
if (interval.second > MAX_DAY_NUMBER * 24ULL * 60ULL * 60ULL)
goto invalid_date;
sec =
((ltime->day - 1) * 3600LL * 24LL + ltime->hour * 3600LL +
ltime->minute * 60LL + ltime->second +
sign * static_cast<longlong>(
interval.day * 3600ULL * 24ULL + interval.hour * 3600ULL +
interval.minute * 60ULL + interval.second)) +
extra_sec;
if (microseconds < 0) {
microseconds += 1000000LL;
sec--;
}
days = sec / (3600 * 24LL);
sec -= days * 3600 * 24LL;
if (sec < 0) {
days--;
sec += 3600 * 24LL;
}
ltime->second_part = static_cast<uint>(microseconds);
ltime->second = static_cast<uint>(sec % 60);
ltime->minute = static_cast<uint>(sec / 60 % 60);
ltime->hour = static_cast<uint>(sec / 3600);
daynr = calc_daynr(ltime->year, ltime->month, 1) + days;
/* Day number from year 0 to 9999-12-31 */
if (daynr < 0 || daynr > MAX_DAY_NUMBER) goto invalid_date;
get_date_from_daynr(daynr, <ime->year, <ime->month, <ime->day);
break;
}
case INTERVAL_DAY:
case INTERVAL_WEEK: {
unsigned long period;
period = calc_daynr(ltime->year, ltime->month, ltime->day);
if (interval.neg) {
if (period < interval.day) // Before 0.
goto invalid_date;
period -= interval.day;
} else {
if (period + interval.day < period) // Overflow.
goto invalid_date;
if (period + interval.day > MAX_DAY_NUMBER) // After 9999-12-31.
goto invalid_date;
period += interval.day;
}
get_date_from_daynr(period, <ime->year, <ime->month, <ime->day);
} break;
case INTERVAL_YEAR:
if (interval.year > 10000UL) goto invalid_date;
ltime->year += sign * static_cast<long>(interval.year);
if (static_cast<ulong>(ltime->year) >= 10000L) goto invalid_date;
if (ltime->month == 2 && ltime->day == 29 &&
calc_days_in_year(ltime->year) != 366)
ltime->day = 28; // Was leap-year
break;
case INTERVAL_YEAR_MONTH:
case INTERVAL_QUARTER:
case INTERVAL_MONTH: {
unsigned long long period;
// Simple guards against arithmetic overflow when calculating period.
if (interval.month >= UINT_MAX / 2) goto invalid_date;
if (interval.year >= UINT_MAX / 12) goto invalid_date;
period = (ltime->year * 12ULL +
sign * static_cast<unsigned long long>(interval.year) * 12ULL +
ltime->month - 1ULL +
sign * static_cast<unsigned long long>(interval.month));
if (period >= 120000LL) goto invalid_date;
ltime->year = period / 12;
ltime->month = (period % 12L) + 1;
/* Adjust day if the new month doesn't have enough days */
if (ltime->day > days_in_month[ltime->month - 1]) {
ltime->day = days_in_month[ltime->month - 1];
if (ltime->month == 2 && calc_days_in_year(ltime->year) == 366)
ltime->day++; // Leap-year
}
} break;
default:
fprintf(stderr, "Unexpected interval type: %u\n",
static_cast<unsigned int>(int_type));
assert(false);
goto null_date;
}
return false; // Ok
invalid_date:
if (warnings) {
*warnings |= MYSQL_TIME_WARN_DATETIME_OVERFLOW;
}
null_date:
return true;
}