///|
// CPython timedelta accumulates each float's integer contribution exactly and
// carries fractional microseconds separately, in us/seconds/minutes/hours/days
// order. Round the combined fraction using the integer sum's parity.
fn RelativeDelta::wall_duration(
self : RelativeDelta,
effective_days : Double,
) -> TimeDelta raise DateError {
relative_finite(effective_days)
if effective_days < -3652060.0 || effective_days > 3652060.0 {
raise DateError::InvalidDate(
message="relative date arithmetic overflows years 1..9999",
)
}
let mut whole = 0L
let mut leftover = 0.0
for
(value, factor) in [
(self.microseconds, 1L),
(self.seconds, 1000000L),
(self.minutes, 60000000L),
(self.hours, 3600000000L),
(effective_days, 86400000000L),
] {
let integer = value.trunc()
whole += integer.to_int64() * factor
let fraction = (value - integer) * factor.to_double()
let integer = fraction.trunc()
whole += integer.to_int64()
leftover += fraction - integer
}
let floor = leftover.floor().to_int64()
let fraction = leftover - floor.to_double()
let rounded = if fraction < 0.5 {
floor
} else if fraction > 0.5 {
floor + 1L
} else if (whole + floor) % 2L == 0L {
floor
} else {
floor + 1L
}
normalized_timedelta(0L, whole + rounded)
}
///|
/// Apply calendar replacements/relative fields, then weekday movement. Months
/// clamp to month end. Relative time uses exact wall-clock microseconds and the
/// retained rule zone; folds reset to the earlier occurrence and gaps use the
/// dateutil post-transition offset. Neither rc timezone nor epoch is read.
pub fn DateTime::add_relative_delta(
self : DateTime,
delta : RelativeDelta,
) -> DateTime raise DateError {
let year = match delta.year {
Some(value) if value != 0 => value
_ => self.year
}
let mut year = relative_add_years(Int64::from_int(year), delta.years)
let month = match delta.month {
Some(value) if value != 0 => value
_ => self.month
}
let mut month = Int64::from_int(month)
if delta.months != 0 {
month += Int64::from_int(delta.months)
if month > 12L {
year = relative_add_years(year, 1L)
month -= 12L
} else if month < 1L {
year = relative_add_years(year, -1L)
month += 12L
}
}
if year < 1L || year > 9999L || month < 1L || month > 12L {
raise DateError::InvalidDate(
message="relative date year or month is outside the calendar",
)
}
let year = year.to_int()
let month = month.to_int()
let day = match delta.day {
Some(value) if value != 0 => value
_ => self.day
}
let base = datetime(
year,
month,
Int32::min(day, days_in_month(year, month)),
hour=delta.hour.unwrap_or(self.hour),
minute=delta.minute.unwrap_or(self.minute),
second=delta.second.unwrap_or(self.second),
microsecond=delta.microsecond.unwrap_or(self.microsecond),
offset_seconds=self.offset_seconds,
)
let days = if delta.leapdays != 0.0 && month > 2 && is_leap_year(year) {
delta.days + delta.leapdays
} else {
delta.days
}
let duration = delta.wall_duration(days)
let mut result = base.add_timedelta(duration)
if delta.weekday is Some(weekday) {
let nth = weekday.nth.unwrap_or(1)
let nth = if nth == 0 { 1L } else { Int64::from_int(nth) }
let target = Int64::from_int(weekday.weekday)
let current = Int64::from_int(result.weekday())
let shift = if nth > 0L {
(nth - 1L) * 7L + ((7L - current + target) % 7L + 7L) % 7L
} else {
-((-nth - 1L) * 7L + ((current - target) % 7L + 7L) % 7L)
}
if shift < -3652060L || shift > 3652060L {
raise DateError::InvalidDate(
message="relative weekday movement overflows the calendar",
)
}
result = result.add_timedelta(timedelta(days=shift.to_int()))
}
match self.timezone {
Some(zone) => zone.resolve_arithmetic_local(result)
None => { ..result, abbreviation: self.abbreviation, }
}
}