use super::{Clock, Date, DateTime, Precision, Seconds, Time};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Boundary {
Low,
High,
}
impl Boundary {
fn pick<T>(self, low: T, high: T) -> T {
match self {
Self::Low => low,
Self::High => high,
}
}
}
const MILLIS: (i32, i32) = (0, 999_000_000);
fn days_in(year: i16, month: i8) -> Option<i8> {
jiff::civil::Date::new(year, month, 1)
.ok()
.map(jiff::civil::Date::days_in_month)
}
impl Date {
#[must_use]
pub const fn digits(self) -> u8 {
match self.precision() {
Precision::Year => 4,
Precision::Month => 6,
_ => 8,
}
}
#[must_use]
pub fn boundary(self, end: Boundary, digits: u8) -> Option<Self> {
let month = || self.month.unwrap_or(end.pick(1, 12));
Some(match digits {
4 => Self {
year: self.year,
month: None,
day: None,
},
6 => Self {
year: self.year,
month: Some(month()),
day: None,
},
8 => {
let month = month();
let day = match self.day {
Some(day) => day,
None => end.pick(Some(1), days_in(self.year, month))?,
};
Self {
year: self.year,
month: Some(month),
day: Some(day),
}
}
_ => return None,
})
}
}
impl Clock {
const fn digits(&self) -> u8 {
match (self.minute, self.second) {
(None, _) => 2,
(Some(_), None) => 4,
(Some(_), Some(Seconds { fraction: None, .. })) => 6,
(
Some(_),
Some(Seconds {
fraction: Some(_), ..
}),
) => 9,
}
}
fn boundary(clock: Option<&Self>, end: Boundary, digits: u8) -> Option<Self> {
let hour = clock.map_or(end.pick(0, 23), |clock| clock.hour);
let minute = clock
.and_then(|clock| clock.minute)
.unwrap_or(end.pick(0, 59));
let second = clock.and_then(|clock| clock.second);
let whole = second.map_or(end.pick(0, 59), |second| second.whole);
let fraction = second
.and_then(|second| second.fraction)
.unwrap_or((end.pick(MILLIS.0, MILLIS.1), 3));
Some(match digits {
2 => Self {
hour,
minute: None,
second: None,
},
4 => Self {
hour,
minute: Some(minute),
second: None,
},
6 => Self {
hour,
minute: Some(minute),
second: Some(Seconds {
whole,
fraction: None,
}),
},
9 => Self {
hour,
minute: Some(minute),
second: Some(Seconds {
whole,
fraction: Some(fraction),
}),
},
_ => return None,
})
}
}
impl DateTime {
#[must_use]
pub const fn digits(&self) -> u8 {
match &self.clock {
None => self.date.digits(),
Some(clock) => match clock.digits() {
9 => 17,
digits => 8 + digits,
},
}
}
#[must_use]
pub fn boundary(&self, end: Boundary, digits: u8) -> Option<Self> {
if digits <= 8 {
return Some(Self {
date: self.date.boundary(end, digits)?,
clock: None,
offset: None,
});
}
let clock_digits = match digits {
17 => 9,
10 | 12 | 14 => digits - 8,
_ => return None,
};
Some(Self {
date: self.date.boundary(end, 8)?,
clock: Some(Clock::boundary(self.clock.as_ref(), end, clock_digits)?),
offset: self.offset,
})
}
}
impl Time {
#[must_use]
pub const fn digits(&self) -> u8 {
self.clock.digits()
}
#[must_use]
pub fn boundary(&self, end: Boundary, digits: u8) -> Option<Self> {
Some(Self {
clock: Clock::boundary(Some(&self.clock), end, digits)?,
})
}
}
#[cfg(test)]
mod tests {
use super::Boundary;
use crate::temporal::{Date, DateTime, Time};
#[test]
fn a_year_widens_to_its_months_and_days() {
let year = Date::parse("2014").expect("a date");
assert_eq!(
year.boundary(Boundary::Low, 6).map(|d| d.to_string()),
Some(String::from("2014-01"))
);
assert_eq!(
year.boundary(Boundary::High, 8).map(|d| d.to_string()),
Some(String::from("2014-12-31"))
);
assert_eq!(year.boundary(Boundary::High, 5), None);
}
#[test]
fn february_ends_on_its_last_day() {
let month = Date::parse("2024-02").expect("a date");
assert_eq!(
month.boundary(Boundary::High, 8).map(|d| d.to_string()),
Some(String::from("2024-02-29"))
);
}
#[test]
fn an_hour_widens_to_the_millisecond() {
let hour = DateTime::parse("2014-01-01T08").expect("a datetime");
assert_eq!(hour.digits(), 10);
assert_eq!(
hour.boundary(Boundary::Low, 17).map(|d| d.to_string()),
Some(String::from("2014-01-01T08:00:00.000"))
);
assert_eq!(
hour.boundary(Boundary::High, 17).map(|d| d.to_string()),
Some(String::from("2014-01-01T08:59:59.999"))
);
assert_eq!(
hour.boundary(Boundary::Low, 8).map(|d| d.to_string()),
Some(String::from("2014-01-01"))
);
}
#[test]
fn a_time_widens_and_counts_its_digits() {
let minute = Time::parse("10:30").expect("a time");
assert_eq!(minute.digits(), 4);
assert_eq!(
minute.boundary(Boundary::High, 9).map(|t| t.to_string()),
Some(String::from("10:30:59.999"))
);
}
}