use jiff::{Span, civil::DateTime};
const SPAN_MULTIPLIER: i64 = 10;
#[inline]
pub(super) fn advance_by_until(start: DateTime, span: Span, upper_bound: DateTime) -> DateTime {
assert!(start <= upper_bound);
let (advanced, span_multiplier) = advance_by_until_fast(start, span, upper_bound);
advance_by_until_slow(advanced, span, span_multiplier, upper_bound)
}
#[inline]
fn advance_by_until_fast(
mut current: DateTime,
span: Span,
upper_bound: DateTime,
) -> (DateTime, i64) {
let mut span_multiplier = SPAN_MULTIPLIER;
while let Ok(span_multiple) = span.checked_mul(span_multiplier) {
let Ok(next) = current.checked_add(span_multiple) else {
return (current, span_multiplier);
};
if next > upper_bound {
return (current, span_multiplier);
}
current = next;
let Some(multiplier) = span_multiplier.checked_mul(SPAN_MULTIPLIER) else {
return (current, span_multiplier);
};
span_multiplier = multiplier;
}
(current, span_multiplier / SPAN_MULTIPLIER)
}
#[inline]
fn advance_by_until_slow(
mut current: DateTime,
span: Span,
mut span_multiplier: i64,
upper_bound: DateTime,
) -> DateTime {
while span_multiplier > 0 {
let span_multiple = span_multiplier * span;
while let Ok(next) = current.checked_add(span_multiple) {
if next > upper_bound {
break;
}
current = next;
}
span_multiplier /= SPAN_MULTIPLIER;
}
current
}
pub(super) fn closest_to(instant: DateTime, left: DateTime, right: DateTime) -> DateTime {
if left.duration_until(instant).abs() < right.duration_until(instant).abs() {
left
} else {
right
}
}
#[inline]
pub(super) fn pick_best<F: FnOnce(DateTime, DateTime) -> DateTime>(
left: Option<DateTime>,
right: Option<DateTime>,
f: F,
) -> Option<DateTime> {
match (left, right) {
(Some(left), Some(right)) => Some(f(left, right)),
(left, right) => left.or(right),
}
}
#[cfg(test)]
mod tests {
use super::*;
use jiff::{ToSpan, civil::date};
use pretty_assertions::assert_eq;
#[test]
fn test_advance_until() {
assert_eq!(
advance_by_until(
date(2025, 1, 1).at(0, 0, 0, 0),
1.minute(),
date(2025, 1, 1).at(0, 0, 0, 0)
),
date(2025, 1, 1).at(0, 0, 0, 0)
);
assert_eq!(
advance_by_until(
date(2025, 1, 1).at(0, 0, 0, 0),
1.minute(),
date(2025, 1, 10).at(1, 1, 30, 0)
),
date(2025, 1, 10).at(1, 1, 0, 0)
);
assert_eq!(
advance_by_until(
date(2024, 1, 1).at(2, 2, 2, 0),
1.year(),
date(2027, 1, 10).at(1, 1, 30, 0)
),
date(2027, 1, 1).at(2, 2, 2, 0)
);
assert_eq!(
advance_by_until(
date(2024, 1, 1).at(2, 2, 2, 0),
1.month(),
date(2024, 10, 10).at(1, 1, 30, 0)
),
date(2024, 10, 1).at(2, 2, 2, 0)
);
assert_eq!(
advance_by_until(
date(2025, 1, 1).at(2, 2, 2, 0),
1.day(),
date(2027, 1, 3).at(2, 2, 2, 0)
),
date(2027, 1, 3).at(2, 2, 2, 0)
);
}
#[test]
fn advance_until_worst_case() {
assert_eq!(
advance_by_until(DateTime::MIN, 1.nanosecond(), DateTime::MAX),
date(9999, 12, 31).at(23, 59, 59, 999_999_999)
);
}
}