1use serde::{Deserialize, Deserializer, Serialize};
8use std::cmp::Ordering;
9use thiserror::Error;
10use time::OffsetDateTime;
11
12pub const MIN_YEAR: i64 = -50000;
14
15pub const MAX_YEAR: i64 = 10000;
17
18#[derive(Error, Debug, Clone)]
20pub enum DateError {
21 #[error("Day `{0}` is not allowed")]
23 InvalidDay(i64),
24
25 #[error("Month `{0}` is not allowed")]
27 InvalidMonth(i64),
28
29 #[error("Month `{0}` is not allowed")]
31 InvalidYear(i64),
32
33 #[error("e.g. can't set day without setting month")]
36 InvalidFields,
37}
38
39#[derive(Serialize, PartialEq, Eq, Clone, Copy, Debug, Hash)]
45pub struct Date {
46 day: Option<Day>,
47 month: Option<Month>,
48 year: Year,
49}
50
51#[rustfmt::skip]
53#[derive(derive_more::Display, Serialize, Eq, PartialEq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
54#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
55#[cfg_attr(feature = "sqlx", sqlx(transparent))]
56pub struct Day(u8);
57
58#[rustfmt::skip]
60#[derive(derive_more::Display, Serialize, Eq, PartialEq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
61#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
62#[cfg_attr(feature = "sqlx", sqlx(transparent))]
63pub struct Month(u8);
64
65#[rustfmt::skip]
70#[derive(derive_more::Display, Serialize, Eq, PartialEq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
71#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
72#[cfg_attr(feature = "sqlx", sqlx(transparent))]
73pub struct Year(i32);
74
75impl Day {
76 pub fn value(&self) -> u8 {
77 self.0
78 }
79
80 pub fn current() -> Self {
81 Date::today().day().unwrap()
82 }
83}
84
85impl Month {
86 pub fn value(&self) -> u8 {
87 self.0
88 }
89
90 pub fn current() -> Self {
91 Date::today().month().unwrap()
92 }
93}
94
95impl Year {
96 pub fn value(&self) -> i32 {
97 self.0
98 }
99
100 pub fn min() -> Self {
101 Year(MIN_YEAR as i32)
102 }
103
104 pub fn max() -> Self {
105 Year(MAX_YEAR as i32)
106 }
107
108 pub fn current() -> Self {
109 Date::today().year()
110 }
111}
112
113impl TryFrom<i64> for Day {
114 type Error = DateError;
115 fn try_from(value: i64) -> Result<Self, Self::Error> {
116 if (1..=31).contains(&value) {
117 Ok(Day(value as u8))
118 } else {
119 Err(DateError::InvalidDay(value))
120 }
121 }
122}
123
124impl TryFrom<i64> for Month {
125 type Error = DateError;
126 fn try_from(value: i64) -> Result<Self, Self::Error> {
127 if (1..=12).contains(&value) {
128 Ok(Month(value as u8))
129 } else {
130 Err(DateError::InvalidMonth(value))
131 }
132 }
133}
134
135impl TryFrom<i64> for Year {
136 type Error = DateError;
137 fn try_from(value: i64) -> Result<Self, Self::Error> {
138 if (MIN_YEAR..=MAX_YEAR).contains(&value) {
139 Ok(Year(value as i32))
140 } else {
141 Err(DateError::InvalidYear(value))
142 }
143 }
144}
145
146impl From<time::Month> for Month {
147 fn from(month: time::Month) -> Self {
148 Self(month.into())
149 }
150}
151
152impl From<Month> for time::Month {
153 fn from(month: Month) -> Self {
154 Self::try_from(month.value()).unwrap()
155 }
156}
157
158impl<'de> Deserialize<'de> for Day {
160 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161 where
162 D: Deserializer<'de>,
163 {
164 let value = i64::deserialize(deserializer)?;
165 Day::try_from(value).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
166 }
167}
168
169impl<'de> Deserialize<'de> for Month {
171 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
172 where
173 D: Deserializer<'de>,
174 {
175 let value = i64::deserialize(deserializer)?;
176 Month::try_from(value).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
177 }
178}
179
180impl<'de> Deserialize<'de> for Year {
182 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 let value = i64::deserialize(deserializer)?;
187 Year::try_from(value).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
188 }
189}
190
191impl Date {
192 pub fn today() -> Self {
194 let today = OffsetDateTime::now_utc();
195 let month: Month = today.month().into();
196 Self::from(
197 Some(today.day().into()),
198 Some(month.value().into()),
199 today.year().into(),
200 )
201 .unwrap()
202 }
203
204 pub fn from(day: Option<i64>, month: Option<i64>, year: i64) -> Result<Date, DateError> {
206 let mut date = Date {
207 day: None,
208 month: None,
209 year: Year(0),
210 };
211 date.set_year(year)?;
212 date.set_month(month)?;
213 date.set_day(day)?;
214 Ok(date)
215 }
216
217 pub fn as_long_date_format(&self) -> String {
219 let day = match self.day() {
221 Some(day) => format!("{day}"),
222 None => String::new(),
223 };
224
225 let month = match self.month() {
227 Some(month) => match month.value() {
228 1 => "Jan",
229 2 => "Feb",
230 3 => "Mar",
231 4 => "Apr",
232 5 => "May",
233 6 => "Jun",
234 7 => "Jul",
235 8 => "Aug",
236 9 => "Sep",
237 10 => "Oct",
238 11 => "Nov",
239 12 => "Dec",
240 _ => panic!("Month value must be 1 <= x <= 12"),
241 },
242 None => "",
243 };
244
245 let year = self.year();
247
248 format!("{day} {month} {year}").trim().to_string()
249 }
250
251 pub fn as_short_date_format(&self) -> String {
253 let day = match self.day() {
255 Some(day) => format!("{day}"),
256 None => String::from("-"),
257 };
258
259 let month = match self.month() {
261 Some(month) => format!("{month}"),
262 None => String::from("-"),
263 };
264
265 let year = format!("{}", self.year());
267
268 format!("{day} / {month} / {year}")
270 }
271
272 pub fn set_day(&mut self, day: Option<i64>) -> Result<(), DateError> {
275 match day {
276 None => self.day = None,
277 Some(day) => {
278 let mut new_date = *self;
280 new_date.day = Some(Day::try_from(day)?);
281 new_date.is_valid()?;
282 *self = new_date;
283 }
284 }
285 Ok(())
286 }
287
288 pub fn set_month(&mut self, month: Option<i64>) -> Result<(), DateError> {
292 match month {
293 None => self.month = None,
294 Some(month) => {
295 let mut new_date = *self;
297 new_date.month = Some(Month::try_from(month)?);
298 new_date.is_valid()?;
299 *self = new_date;
300 }
301 }
302 Ok(())
303 }
304
305 pub fn set_year(&mut self, year: i64) -> Result<(), DateError> {
308 let mut new_date = *self;
310 new_date.year = Year::try_from(year)?;
311 new_date.is_valid()?;
312 *self = new_date;
313
314 Ok(())
315 }
316
317 pub fn day(&self) -> Option<Day> {
319 self.day
320 }
321
322 pub fn month(&self) -> Option<Month> {
324 self.month
325 }
326
327 pub fn year(&self) -> Year {
329 self.year
330 }
331
332 fn is_valid(&self) -> Result<(), DateError> {
334 match (self.day, self.month, self.year) {
335 (None, None, _) => Ok(()),
337 (None, Some(_), _) => Ok(()),
338 (Some(_), Some(_), _) => Ok(()),
339
340 _ => Err(DateError::InvalidFields),
342 }
343 }
344}
345
346impl PartialOrd for Date {
347 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
348 match self.year.cmp(&other.year) {
349 Ordering::Less => return Some(Ordering::Less),
350 Ordering::Greater => return Some(Ordering::Greater),
351 Ordering::Equal => (),
352 };
353 if let (Some(this_month), Some(other_month)) = (self.month, other.month) {
354 match this_month.cmp(&other_month) {
355 Ordering::Less => return Some(Ordering::Less),
356 Ordering::Greater => return Some(Ordering::Greater),
357 Ordering::Equal => (),
358 };
359 } else {
360 return None;
361 }
362 if let (Some(this_day), Some(other_day)) = (self.day, other.day) {
363 match this_day.cmp(&other_day) {
364 Ordering::Less => Some(Ordering::Less),
365 Ordering::Greater => Some(Ordering::Greater),
366 Ordering::Equal => Some(Ordering::Equal),
367 }
368 } else {
369 None
370 }
371 }
372}
373
374impl Ord for Date {
376 fn cmp(&self, other: &Self) -> Ordering {
377 let this_month = self.month().map(|m| m.value()).unwrap_or(1);
378 let other_month = other.month().map(|m| m.value()).unwrap_or(1);
379
380 let this_day = self.day().map(|d| d.value()).unwrap_or(1);
381 let other_day = other.day().map(|d| d.value()).unwrap_or(1);
382
383 (self.year, this_month, this_day).cmp(&(other.year, other_month, other_day))
384 }
385}
386
387#[derive(Deserialize)]
388struct RawDate {
389 day: Option<i64>,
390 month: Option<i64>,
391 year: i64,
392}
393
394impl<'de> Deserialize<'de> for Date {
395 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
396 where
397 D: Deserializer<'de>,
398 {
399 let raw_date = RawDate::deserialize(deserializer)?;
401 let date = Date::from(raw_date.day, raw_date.month, raw_date.year);
402 match date {
403 Ok(date) => Ok(date),
404 Err(error) => Err(serde::de::Error::custom(error)),
405 }
406 }
407}
408
409#[cfg(test)]
410mod test {
411 use super::Date;
412
413 #[test]
414 fn from() {
415 assert!(Date::from(Some(1), None, 234).is_err());
417 assert!(Date::from(None, None, 999_999).is_err());
418 assert!(Date::from(None, None, -999_999).is_err());
419 assert!(Date::from(Some(0), Some(0), 1234).is_err());
420 assert!(Date::from(Some(32), Some(13), 1234).is_err());
421
422 assert!(Date::from(Some(1), Some(1), 1).is_ok());
424 }
425
426 #[test]
427 fn cmp() {
428 let date_1 = Date::from(None, None, 234).unwrap();
430 let date_2 = Date::from(None, None, 4321).unwrap();
431 assert!(date_2 > date_1);
432 assert!(date_1 < date_2);
433 assert!(date_1 == date_1);
434 assert!(date_1 != date_2);
435
436 let date_1 = Date::from(Some(1), Some(1), 234).unwrap();
438 let date_2 = Date::from(Some(2), Some(1), 234).unwrap();
439 assert!(date_2 > date_1);
440 }
441
442 #[test]
443 fn today() {
444 let today = Date::today();
445 println!("today = {}", today.as_long_date_format());
446 println!("today = {}", today.as_short_date_format());
447 }
448}