citeworks_cff/
date.rs

1use std::{
2	fmt::{Debug, Display},
3	str::FromStr,
4};
5
6use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
7
8/// A date.
9///
10/// In CFF this is a string in `YYYY-MM-DD` format.
11#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
12pub struct Date {
13	/// Year, in the Gregorian calendar
14	pub year: i64,
15
16	/// Month, starting from 1
17	pub month: u8,
18
19	/// Day of the month, starting from 1
20	pub day: u8,
21}
22
23impl Display for Date {
24	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25		let Self { year, month, day } = self;
26		write!(f, "{year:04}-{month:02}-{day:02}")
27	}
28}
29
30impl FromStr for Date {
31	type Err = String;
32
33	fn from_str(s: &str) -> Result<Self, Self::Err> {
34		let err = || -> String { format!("expected YYYY-MM-DD, got: {s:?}") };
35
36		let [year, month, day]: [&str; 3] = s
37			.splitn(3, '-')
38			.collect::<Vec<_>>()
39			.try_into()
40			.map_err(|_| err())?;
41
42		if year.len() != 4 || month.len() != 2 || day.len() != 2 {
43			Err(err())
44		} else {
45			let date = Self {
46				year: year.parse().map_err(|_| err())?,
47				month: month.parse().map_err(|_| err())?,
48				day: day.parse().map_err(|_| err())?,
49			};
50
51			if date.month == 0 || date.month > 12 {
52				Err(format!(
53					"month should be in range 1-12, got: {}",
54					date.month
55				))
56			} else if date.day == 0 || date.day > 31 {
57				Err(format!("day should be in range 1-31, got: {}", date.day))
58			} else {
59				Ok(date)
60			}
61		}
62	}
63}
64
65impl Serialize for Date {
66	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67	where
68		S: Serializer,
69	{
70		let s = self.to_string();
71		s.serialize(serializer)
72	}
73}
74
75impl<'de> Deserialize<'de> for Date {
76	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77	where
78		D: Deserializer<'de>,
79	{
80		let s = String::deserialize(deserializer)?;
81		Date::from_str(&s).map_err(D::Error::custom)
82	}
83}