#![cfg_attr(
not(test),
allow(
unused,
reason = "TODO(#5448): difference resolution is not yet used in non-test code"
)
)]
use crate::format::DateTimeInputUnchecked;
use crate::provider::names::DayPeriodNames;
use icu_calendar::types::YearInfo;
use icu_time::Hour;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Difference {
None,
Second,
Minute,
Hour,
DayPeriodB,
DayPeriodA,
Day,
Month,
Year,
Era,
Incomparable,
}
impl Difference {
pub(crate) fn is_time_diff(self) -> bool {
matches!(
self,
Self::Minute | Self::Hour | Self::DayPeriodB | Self::DayPeriodA
)
}
pub(crate) fn is_date_diff(self) -> bool {
matches!(self, Self::Day | Self::Month | Self::Year | Self::Era)
}
}
pub(crate) fn resolve_difference(
input1: &DateTimeInputUnchecked,
input2: &DateTimeInputUnchecked,
dayperiod_names: Option<&DayPeriodNames<'_>>,
) -> Difference {
if !input1.has_same_zone(input2) {
return Difference::Incomparable;
}
match (input1.year, input2.year) {
(Some(YearInfo::Era(e1)), Some(YearInfo::Era(e2))) => {
if e1.era != e2.era {
return Difference::Era;
}
if e1.year != e2.year {
return Difference::Year;
}
}
(Some(YearInfo::Cyclic(c1)), Some(YearInfo::Cyclic(c2))) => {
if c1.related_iso != c2.related_iso {
return Difference::Year;
}
}
(None, None) => {}
_ => {
return Difference::Era;
}
}
let month1 = input1.month.map(|m| m.to_input());
let month2 = input2.month.map(|m| m.to_input());
if month1 != month2 {
return Difference::Month;
}
if input1.day_of_month != input2.day_of_month {
return Difference::Day;
}
match (input1.hour, input2.hour) {
(Some(h1), Some(h2)) => {
if h1 != h2 {
let ampm1 = h1.number() < 12;
let ampm2 = h2.number() < 12;
if ampm1 != ampm2 {
return Difference::DayPeriodA;
}
if dayperiod_names.is_some_and(|dp| has_flexible_day_period_difference(dp, h1, h2))
{
return Difference::DayPeriodB;
}
return Difference::Hour;
}
}
(None, None) => {}
_ => {
return Difference::Hour;
}
}
if input1.minute != input2.minute {
return Difference::Minute;
}
if input1.second != input2.second || input1.subsecond != input2.subsecond {
return Difference::Second;
}
Difference::None
}
fn has_flexible_day_period_difference(
dayperiod_names: &DayPeriodNames<'_>,
hour1: Hour,
hour2: Hour,
) -> bool {
if let Some(rules) = dayperiod_names.day_period_rules() {
return rules.name_offset(hour1) != rules.name_offset(hour2);
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use icu_calendar::Date;
use icu_time::Time;
fn create_input(y: i32, m: u8, d: u8, hr: u8, min: u8, sec: u8) -> DateTimeInputUnchecked {
let mut input = DateTimeInputUnchecked::default();
let date = Date::try_new_iso(y, m, d).unwrap();
input.set_date_fields_unchecked(date);
let time = Time::try_new(hr, min, sec, 0).unwrap();
input.set_time_fields(time);
input
}
#[test]
fn test_identical() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 15, 10, 30, 0);
assert_eq!(resolve_difference(&input1, &input2, None), Difference::None);
}
#[test]
fn test_year_diff() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2025, 1, 15, 10, 30, 0);
assert_eq!(resolve_difference(&input1, &input2, None), Difference::Year);
}
#[test]
fn test_month_diff() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 2, 15, 10, 30, 0);
assert_eq!(
resolve_difference(&input1, &input2, None),
Difference::Month
);
}
#[test]
fn test_day_diff() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 16, 10, 30, 0);
assert_eq!(resolve_difference(&input1, &input2, None), Difference::Day);
}
#[test]
fn test_hour_diff_same_ampm() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 15, 11, 30, 0);
assert_eq!(resolve_difference(&input1, &input2, None), Difference::Hour);
}
#[test]
fn test_hour_diff_different_ampm() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 15, 22, 30, 0); assert_eq!(
resolve_difference(&input1, &input2, None),
Difference::DayPeriodA
);
}
#[test]
fn test_minute_diff() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 15, 10, 31, 0);
assert_eq!(
resolve_difference(&input1, &input2, None),
Difference::Minute
);
}
#[test]
fn test_second_diff() {
let input1 = create_input(2024, 1, 15, 10, 30, 0);
let input2 = create_input(2024, 1, 15, 10, 30, 1);
assert_eq!(
resolve_difference(&input1, &input2, None),
Difference::Second
);
}
#[test]
fn test_timezone_diff() {
let mut input1 = create_input(2024, 1, 15, 10, 30, 0);
let mut input2 = create_input(2024, 1, 15, 10, 30, 0);
input1.zone_offset = Some(icu_time::zone::UtcOffset::zero());
input2.zone_offset = Some(icu_time::zone::UtcOffset::try_from_seconds(3600).unwrap());
assert_eq!(
resolve_difference(&input1, &input2, None),
Difference::Incomparable
);
}
}