db_dump/
date.rs

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/// Represents the range of local timestamps between midnight and the adjacent
9/// midnight in the given timezone.
10#[derive(Copy, Clone)]
11pub struct Date<Tz> {
12    naive: NaiveDate,
13    tz: Tz,
14}
15
16impl Date<Utc> {
17    /// Makes a new `Date` from the [calendar date] (year, month and day).
18    ///
19    /// [calendar date]: NaiveDate#calendar-date
20    ///
21    /// # Panics
22    ///
23    /// Panics if the specified calendar day does not exist, on invalid values
24    /// for `month` or `day`, or if `year` is out of range for `Date`.
25    #[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}