liquid_core/model/scalar/
date.rs1use std::{convert::TryInto, fmt, ops};
2
3#[derive(
5 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
6)]
7#[serde(transparent)]
8pub struct Date {
9 #[serde(with = "friendly_date")]
10 pub(crate) inner: DateImpl,
11}
12
13type DateImpl = time::Date;
14
15impl Date {
16 pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
20 Self {
21 inner: DateImpl::from_calendar_date(
22 year,
23 month.try_into().expect("the month is out of range"),
24 day,
25 )
26 .expect("one or more components were invalid"),
27 }
28 }
29
30 #[allow(clippy::should_implement_trait)]
32 pub fn from_str(other: &str) -> Option<Self> {
33 parse_date(other).map(|d| Self { inner: d })
34 }
35}
36
37impl Date {
38 #[inline]
40 pub fn year(&self) -> i32 {
41 self.inner.year()
42 }
43 #[inline]
45 pub fn month(&self) -> u8 {
46 self.inner.month() as u8
47 }
48 #[inline]
52 pub fn day(&self) -> u8 {
53 self.inner.day()
54 }
55 #[inline]
59 pub fn ordinal(&self) -> u16 {
60 self.inner.ordinal()
61 }
62 #[inline]
66 pub fn iso_week(&self) -> u8 {
67 self.inner.iso_week()
68 }
69}
70
71impl fmt::Display for Date {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(
74 f,
75 "{}",
76 self.inner.format(DATE_FORMAT).map_err(|_e| fmt::Error)?
77 )
78 }
79}
80
81impl ops::Deref for Date {
82 type Target = DateImpl;
83 fn deref(&self) -> &Self::Target {
84 &self.inner
85 }
86}
87
88impl ops::DerefMut for Date {
89 fn deref_mut(&mut self) -> &mut Self::Target {
90 &mut self.inner
91 }
92}
93
94const DATE_FORMAT: &[time::format_description::FormatItem<'_>] =
95 time::macros::format_description!("[year]-[month]-[day]");
96
97mod friendly_date {
98 use super::*;
99 use serde::{self, Deserialize, Deserializer, Serializer};
100
101 pub(crate) fn serialize<S>(date: &DateImpl, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 let s = date
106 .format(DATE_FORMAT)
107 .map_err(serde::ser::Error::custom)?;
108 serializer.serialize_str(&s)
109 }
110
111 pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateImpl, D::Error>
112 where
113 D: Deserializer<'de>,
114 {
115 let s = String::deserialize(deserializer)?;
116 DateImpl::parse(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
117 }
118}
119
120fn parse_date(s: &str) -> Option<DateImpl> {
121 const USER_FORMATS: &[&[time::format_description::FormatItem<'_>]] = &[
122 time::macros::format_description!("[day] [month repr:long] [year]"),
123 time::macros::format_description!("[day] [month repr:short] [year]"),
124 DATE_FORMAT,
125 ];
126
127 match s {
128 "today" => Some(time::OffsetDateTime::now_utc().date()),
129 _ => USER_FORMATS
130 .iter()
131 .filter_map(|f| DateImpl::parse(s, f).ok())
132 .next(),
133 }
134}
135
136#[cfg(test)]
137mod test {
138 use super::*;
139 #[test]
140 fn parse_date_time_empty_is_bad() {
141 let input = "";
142 let actual = parse_date(input);
143 assert!(actual.is_none());
144 }
145
146 #[test]
147 fn parse_date_time_bad() {
148 let input = "aaaaa";
149 let actual = parse_date(input);
150 assert!(actual.is_none());
151 }
152
153 #[test]
154 fn parse_date_today() {
155 let input = "today";
156 let actual = parse_date(input);
157 assert!(actual.is_some());
158 }
159
160 #[test]
161 fn parse_long_month() {
162 let input = "01 March 2022";
163 let actual = parse_date(input);
164 assert_eq!(
165 DateImpl::from_calendar_date(2022, time::Month::March, 1).unwrap(),
166 actual.unwrap()
167 );
168 }
169
170 #[test]
171 fn parse_short_month() {
172 let input = "01 Mar 2022";
173 let actual = parse_date(input);
174 assert_eq!(
175 DateImpl::from_calendar_date(2022, time::Month::March, 1).unwrap(),
176 actual.unwrap()
177 );
178 }
179
180 #[test]
181 fn parse_iso() {
182 let input = "2022-03-02";
183 let actual = parse_date(input);
184 assert_eq!(
185 DateImpl::from_calendar_date(2022, time::Month::March, 2).unwrap(),
186 actual.unwrap()
187 );
188 }
189}