use core::fmt;
use std::str;
#[derive(Debug, thiserror::Error)]
#[error("invalid date `{given}`: {err}")]
pub struct InvalidDate {
given: String,
err: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Date(pub time::Date);
impl From<time::Date> for Date {
fn from(value: time::Date) -> Self {
Self(value)
}
}
impl From<Date> for time::Date {
fn from(d: Date) -> Self {
d.0
}
}
static DATE_FORMAT: &[time::format_description::BorrowedFormatItem<'_>] =
time::macros::format_description!("[year]-[month]-[day]");
impl str::FromStr for Date {
type Err = InvalidDate;
fn from_str(s: &str) -> Result<Self, Self::Err> {
time::Date::parse(s, DATE_FORMAT)
.map_err(|err| InvalidDate {
given: s.into(),
err: err.to_string(),
})
.map(Date)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = self.0.format(DATE_FORMAT).expect("valid format");
f.write_str(&s)
}
}
mod serde_impls {
use super::*;
use serde::de;
struct Visitor;
impl<'de> de::Deserialize<'de> for Date {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_str(Visitor)
}
}
impl<'de> de::Visitor<'de> for Visitor {
type Value = Date;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a date on the form [year]-[month]-[day]")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
v.parse::<Date>().map_err(E::custom)
}
}
impl serde::Serialize for Date {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = self.to_string();
ser.serialize_str(&s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_date() {
assert_eq!(
"2025-01-01".parse::<Date>().expect("parsing"),
Date(time::macros::date!(2025 - 01 - 01)),
);
assert_eq!(
"1900-12-31".parse::<Date>().expect("parsing"),
Date(time::macros::date!(1900 - 12 - 31)),
);
}
#[test]
fn fmt_date() {
assert_eq!(
Date(time::macros::date!(2025 - 01 - 01)).to_string(),
"2025-01-01",
);
assert_eq!(
Date(time::macros::date!(1900 - 12 - 31)).to_string(),
"1900-12-31"
);
}
}