pub mod arithmetic;
pub mod boundary;
mod compare;
pub mod parse;
use std::fmt;
use rust_decimal::Decimal;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Date {
year: i16,
month: Option<i8>,
day: Option<i8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Seconds {
whole: i8,
fraction: Option<(i32, u8)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Clock {
hour: i8,
minute: Option<i8>,
second: Option<Seconds>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Offset {
Utc,
Minutes(i16),
}
impl Offset {
const fn minutes(self) -> i16 {
match self {
Self::Utc => 0,
Self::Minutes(minutes) => minutes,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DateTime {
date: Date,
clock: Option<Clock>,
offset: Option<Offset>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Time {
clock: Clock,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precision {
Year,
Month,
Day,
Hour,
Minute,
Second,
}
impl Date {
#[must_use]
pub const fn precision(self) -> Precision {
match (self.month, self.day) {
(None, _) => Precision::Year,
(Some(_), None) => Precision::Month,
(Some(_), Some(_)) => Precision::Day,
}
}
#[must_use]
pub const fn to_date_time(self) -> DateTime {
DateTime {
date: self,
clock: None,
offset: None,
}
}
}
impl Clock {
const fn precision(&self) -> Precision {
match (self.minute, self.second) {
(None, _) => Precision::Hour,
(Some(_), None) => Precision::Minute,
(Some(_), Some(_)) => Precision::Second,
}
}
fn seconds(&self) -> Option<Decimal> {
self.second.map(|second| {
let whole = Decimal::from(second.whole);
match second.fraction {
Some((nanos, _)) => whole + Decimal::new(i64::from(nanos), 9),
None => whole,
}
})
}
}
impl DateTime {
#[must_use]
pub const fn precision(&self) -> Precision {
match &self.clock {
Some(clock) => clock.precision(),
None => self.date.precision(),
}
}
#[must_use]
pub const fn date(&self) -> Date {
self.date
}
#[must_use]
pub const fn has_offset(&self) -> bool {
self.offset.is_some()
}
}
impl Time {
#[must_use]
pub const fn precision(&self) -> Precision {
self.clock.precision()
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}", self.year)?;
if let Some(month) = self.month {
write!(f, "-{month:02}")?;
}
if let Some(day) = self.day {
write!(f, "-{day:02}")?;
}
Ok(())
}
}
impl fmt::Display for Clock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02}", self.hour)?;
if let Some(minute) = self.minute {
write!(f, ":{minute:02}")?;
}
if let Some(second) = self.second {
write!(f, ":{:02}", second.whole)?;
if let Some((nanos, digits)) = second.fraction {
let all = format!("{nanos:09}");
let shown = all.get(..usize::from(digits.clamp(1, 9))).unwrap_or(&all);
write!(f, ".{shown}")?;
}
}
Ok(())
}
}
impl fmt::Display for Offset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Utc => f.write_str("Z"),
Self::Minutes(minutes) => {
let sign = if *minutes < 0 { '-' } else { '+' };
let magnitude = minutes.unsigned_abs();
write!(
f,
"{sign}{:02}:{:02}",
magnitude.div_euclid(60),
magnitude.rem_euclid(60)
)
}
}
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.date)?;
if let Some(clock) = &self.clock {
write!(f, "T{clock}")?;
if let Some(offset) = &self.offset {
write!(f, "{offset}")?;
}
}
Ok(())
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.clock)
}
}