///|
fn auto_date_difference(left : DateTime, right : DateTime) -> Int64 {
match (left.timezone, right.timezone) {
(Some(first), Some(last)) if physical_equal(first, last) =>
locator_microseconds(left) - locator_microseconds(right)
_ => rruleset_instant(left) - rruleset_instant(right)
}
}
///|
fn auto_shift_months(
value : DateTime,
months : Int32,
) -> DateTime raise DateError {
let shifted = match locator_shift_months(value, months) {
Some(value) => value
None =>
raise DateError::InvalidDate(
message="calendar difference exceeds supported years",
)
}
match value.timezone {
Some(zone) => zone.resolve_arithmetic_local(shifted)
None => shifted
}
}
///|
// dates.get_locator combines relativedelta calendar components with timedelta
// days and seconds. Retain its signed month-end behavior and Python's floor
// seconds representation before normalizing the residual into hours/minutes.
fn automatic_distances(
first : DateTime,
last : DateTime,
) -> Array[Double] raise DateError {
let difference = auto_date_difference(last, first)
let reverse = difference < 0L
let mut months = (last.year - first.year) * 12 + last.month - first.month
let mut shifted = auto_shift_months(first, months)
while (if reverse {
auto_date_difference(last, shifted) > 0L
} else {
auto_date_difference(last, shifted) < 0L
}) {
months += if reverse { 1 } else { -1 }
shifted = auto_shift_months(first, months)
}
let residual = auto_date_difference(last, shifted)
let seconds = locator_floor_div64(residual, 1_000_000L)
let sign = if reverse { -1 } else { 1 }
let absolute = if reverse { -difference } else { difference }
let days = absolute / 86_400_000_000L
let total_seconds = timedelta(microseconds=absolute).total_seconds()
let hours = days.to_double() * 24.0 +
(seconds / 3600L % 24L).to_double() * Double::from_int(sign)
let minutes = hours * 60.0 +
(seconds / 60L % 60L).to_double() * Double::from_int(sign)
[
Double::from_int(months / 12 * sign),
Double::from_int(months * sign),
days.to_double(),
hours,
minutes,
total_seconds.floor(),
(total_seconds * 1.0e6).floor(),
]
}