1use chrono::{Datelike, IsoWeek, NaiveDate, TimeDelta, TimeZone, Utc, Weekday};
2use serde::de::{Deserialize, Deserializer, Unexpected, Visitor};
3use std::cmp::Ordering;
4use std::fmt::{self, Debug, Display};
5use std::hash::{Hash, Hasher};
6use std::ops::{Add, AddAssign, Sub, SubAssign};
7
8#[derive(Copy, Clone)]
11pub struct Date<Tz> {
12 naive: NaiveDate,
13 tz: Tz,
14}
15
16impl Date<Utc> {
17 #[inline]
26 #[must_use]
27 pub const fn from_ymd(year: i32, month: u32, day: u32) -> Self {
28 let Some(naive) = NaiveDate::from_ymd_opt(year, month, day) else {
29 panic!("invalid or out-of-range date");
30 };
31 Date { naive, tz: Utc }
32 }
33}
34
35impl<Tz> Date<Tz>
36where
37 Tz: TimeZone,
38{
39 #[inline]
40 pub const fn naive_utc(&self) -> NaiveDate {
41 self.naive
42 }
43}
44
45impl<Tz> Datelike for Date<Tz>
46where
47 Tz: TimeZone,
48{
49 #[inline]
50 fn year(&self) -> i32 {
51 self.naive.year()
52 }
53
54 #[inline]
55 fn month(&self) -> u32 {
56 self.naive.month()
57 }
58
59 #[inline]
60 fn month0(&self) -> u32 {
61 self.naive.month0()
62 }
63
64 #[inline]
65 fn day(&self) -> u32 {
66 self.naive.day()
67 }
68
69 #[inline]
70 fn day0(&self) -> u32 {
71 self.naive.day0()
72 }
73
74 #[inline]
75 fn ordinal(&self) -> u32 {
76 self.naive.ordinal()
77 }
78
79 #[inline]
80 fn ordinal0(&self) -> u32 {
81 self.naive.ordinal0()
82 }
83
84 #[inline]
85 fn weekday(&self) -> Weekday {
86 self.naive.weekday()
87 }
88
89 #[inline]
90 fn iso_week(&self) -> IsoWeek {
91 self.naive.iso_week()
92 }
93
94 #[inline]
95 fn with_year(&self, year: i32) -> Option<Self> {
96 Some(Date {
97 naive: self.naive.with_year(year)?,
98 tz: self.tz.clone(),
99 })
100 }
101
102 #[inline]
103 fn with_month(&self, month: u32) -> Option<Self> {
104 Some(Date {
105 naive: self.naive.with_month(month)?,
106 tz: self.tz.clone(),
107 })
108 }
109
110 #[inline]
111 fn with_month0(&self, month0: u32) -> Option<Self> {
112 Some(Date {
113 naive: self.naive.with_month0(month0)?,
114 tz: self.tz.clone(),
115 })
116 }
117
118 #[inline]
119 fn with_day(&self, day: u32) -> Option<Self> {
120 Some(Date {
121 naive: self.naive.with_day(day)?,
122 tz: self.tz.clone(),
123 })
124 }
125
126 #[inline]
127 fn with_day0(&self, day0: u32) -> Option<Self> {
128 Some(Date {
129 naive: self.naive.with_day0(day0)?,
130 tz: self.tz.clone(),
131 })
132 }
133
134 #[inline]
135 fn with_ordinal(&self, ordinal: u32) -> Option<Self> {
136 Some(Date {
137 naive: self.naive.with_ordinal(ordinal)?,
138 tz: self.tz.clone(),
139 })
140 }
141
142 #[inline]
143 fn with_ordinal0(&self, ordinal0: u32) -> Option<Self> {
144 Some(Date {
145 naive: self.naive.with_ordinal0(ordinal0)?,
146 tz: self.tz.clone(),
147 })
148 }
149}
150
151impl Display for Date<Utc> {
152 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
153 Display::fmt(&self.naive, formatter)
154 }
155}
156
157impl Debug for Date<Utc> {
158 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
159 Debug::fmt(&self.naive, formatter)
160 }
161}
162
163impl<Tz, Tz2> PartialEq<Date<Tz2>> for Date<Tz>
164where
165 Tz: TimeZone,
166 Tz2: TimeZone,
167{
168 #[inline]
169 fn eq(&self, other: &Date<Tz2>) -> bool {
170 self.naive == other.naive
171 }
172}
173
174impl<Tz> Eq for Date<Tz> where Tz: TimeZone {}
175
176impl<Tz, Tz2> PartialOrd<Date<Tz2>> for Date<Tz>
177where
178 Tz: TimeZone,
179 Tz2: TimeZone,
180{
181 #[inline]
182 fn partial_cmp(&self, other: &Date<Tz2>) -> Option<Ordering> {
183 self.naive.partial_cmp(&other.naive)
184 }
185}
186
187impl<Tz> Ord for Date<Tz>
188where
189 Tz: TimeZone,
190{
191 #[inline]
192 fn cmp(&self, other: &Date<Tz>) -> Ordering {
193 self.naive.cmp(&other.naive)
194 }
195}
196
197impl<Tz> Hash for Date<Tz>
198where
199 Tz: TimeZone,
200{
201 #[inline]
202 fn hash<H: Hasher>(&self, state: &mut H) {
203 self.naive.hash(state);
204 }
205}
206
207impl<Tz> Add<TimeDelta> for Date<Tz>
208where
209 Tz: TimeZone,
210{
211 type Output = Date<Tz>;
212
213 #[inline]
214 fn add(self, delta: TimeDelta) -> Date<Tz> {
215 let date = self
216 .naive
217 .checked_add_signed(delta)
218 .expect("`Date + TimeDelta` overflowed");
219 Date {
220 naive: date,
221 tz: self.tz,
222 }
223 }
224}
225
226impl<Tz> AddAssign<TimeDelta> for Date<Tz>
227where
228 Tz: TimeZone,
229{
230 #[inline]
231 fn add_assign(&mut self, delta: TimeDelta) {
232 self.naive = self
233 .naive
234 .checked_add_signed(delta)
235 .expect("`Date + TimeDelta` overflowed");
236 }
237}
238
239impl<Tz> Sub<TimeDelta> for Date<Tz>
240where
241 Tz: TimeZone,
242{
243 type Output = Date<Tz>;
244
245 #[inline]
246 fn sub(self, delta: TimeDelta) -> Date<Tz> {
247 let date = self
248 .naive
249 .checked_sub_signed(delta)
250 .expect("`Date - TimeDelta` overflowed");
251 Date {
252 naive: date,
253 tz: self.tz,
254 }
255 }
256}
257
258impl<Tz> SubAssign<TimeDelta> for Date<Tz>
259where
260 Tz: TimeZone,
261{
262 #[inline]
263 fn sub_assign(&mut self, delta: TimeDelta) {
264 self.naive = self
265 .naive
266 .checked_sub_signed(delta)
267 .expect("`Date - TimeDelta` overflowed");
268 }
269}
270
271impl<Tz> Sub<Date<Tz>> for Date<Tz>
272where
273 Tz: TimeZone,
274{
275 type Output = TimeDelta;
276
277 #[inline]
278 fn sub(self, rhs: Date<Tz>) -> TimeDelta {
279 self.naive.signed_duration_since(rhs.naive)
280 }
281}
282
283impl From<NaiveDate> for Date<Utc> {
284 fn from(date: NaiveDate) -> Self {
285 Date {
286 naive: date,
287 tz: Utc,
288 }
289 }
290}
291
292impl From<Date<Utc>> for NaiveDate {
293 fn from(date: Date<Utc>) -> Self {
294 date.naive
295 }
296}
297
298struct CratesioDateVisitor;
299
300impl<'de> Visitor<'de> for CratesioDateVisitor {
301 type Value = Date<Utc>;
302
303 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
304 formatter.write_str("date in format 'YYYY-MM-DD'")
305 }
306
307 fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
308 where
309 E: serde::de::Error,
310 {
311 'err: {
312 if string.len() != 10 {
313 break 'err;
314 }
315 let year: u16 = match string[0..4].parse() {
316 Ok(year) => year,
317 Err(_) => break 'err,
318 };
319 if string[4..5] != *"-" {
320 break 'err;
321 }
322 let month: u8 = match string[5..7].parse() {
323 Ok(month) => month,
324 Err(_) => break 'err,
325 };
326 if string[7..8] != *"-" {
327 break 'err;
328 }
329 let day: u8 = match string[8..10].parse() {
330 Ok(day) => day,
331 Err(_) => break 'err,
332 };
333 let Some(naive_date) = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32)
334 else {
335 break 'err;
336 };
337 return Ok(Date::from(naive_date));
338 }
339 Err(serde::de::Error::invalid_value(
340 Unexpected::Str(string),
341 &self,
342 ))
343 }
344}
345
346impl<'de> Deserialize<'de> for Date<Utc> {
347 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
348 where
349 D: Deserializer<'de>,
350 {
351 deserializer.deserialize_str(CratesioDateVisitor)
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::Date;
358 use serde::de::value::Error;
359 use serde::de::{Deserialize, IntoDeserializer};
360
361 #[test]
362 fn test_de() {
363 let csv = "2020-01-01";
364 let deserializer = IntoDeserializer::<Error>::into_deserializer;
365 assert_eq!(
366 Date::deserialize(deserializer(csv)).unwrap(),
367 Date::from_ymd(2020, 1, 1),
368 );
369 }
370}