zino_core/datetime/
date.rs

1use crate::{AvroValue, JsonValue, error::Error};
2use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday, format::ParseError};
3use serde::{Deserialize, Serialize, Serializer};
4use std::{
5    fmt,
6    ops::{Add, AddAssign, Sub, SubAssign},
7    str::FromStr,
8    time::Duration,
9};
10
11/// A wrapper type for [`chrono::NaiveDate`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
13pub struct Date(NaiveDate);
14
15impl Date {
16    /// Attempts to create a new instance.
17    #[inline]
18    pub fn try_new(year: i32, month: u32, day: u32) -> Result<Self, Error> {
19        NaiveDate::from_ymd_opt(year, month, day)
20            .map(Self)
21            .ok_or_else(|| {
22                let message = format!(
23                    "fail to create a date from year: `{year}`, month: `{month}`, day: `{day}`"
24                );
25                Error::new(message)
26            })
27    }
28
29    /// Returns a new instance which corresponds to the current date.
30    #[inline]
31    pub fn today() -> Self {
32        Self(Local::now().date_naive())
33    }
34
35    /// Returns a new instance which corresponds to the tomorrow date.
36    #[inline]
37    pub fn tomorrow() -> Self {
38        let date = Local::now()
39            .date_naive()
40            .succ_opt()
41            .unwrap_or(NaiveDate::MAX);
42        Self(date)
43    }
44
45    /// Returns a new instance which corresponds to the yesterday date.
46    #[inline]
47    pub fn yesterday() -> Self {
48        let date = Local::now()
49            .date_naive()
50            .pred_opt()
51            .unwrap_or(NaiveDate::MIN);
52        Self(date)
53    }
54
55    /// Returns a new instance which corresponds to 1st of January 1970.
56    #[inline]
57    pub fn epoch() -> Self {
58        Self(NaiveDate::default())
59    }
60
61    /// Counts the days from the 1st of January 1970.
62    #[inline]
63    pub fn num_days_from_epoch(&self) -> i32 {
64        let unix_epoch = NaiveDate::default();
65        self.0
66            .signed_duration_since(unix_epoch)
67            .num_days()
68            .try_into()
69            .unwrap_or_default()
70    }
71
72    /// Counts the days from the `other` date.
73    #[inline]
74    pub fn num_days_from(&self, other: Date) -> i32 {
75        self.0
76            .signed_duration_since(other.0)
77            .num_days()
78            .try_into()
79            .unwrap_or_default()
80    }
81
82    /// Formats the date with the specified format string.
83    /// See [`format::strftime`](chrono::format::strftime) for the supported escape sequences.
84    #[inline]
85    pub fn format(&self, fmt: &str) -> String {
86        format!("{}", self.0.format(fmt))
87    }
88
89    /// Returns the amount of time elapsed from another date to this one,
90    /// or zero duration if that date is later than this one.
91    #[inline]
92    pub fn duration_since(&self, earlier: Date) -> Duration {
93        (self.0 - earlier.0).to_std().unwrap_or_default()
94    }
95
96    /// Returns the duration of time between `self` and `other`.
97    #[inline]
98    pub fn span_between(&self, other: Date) -> Duration {
99        let duration = if self > &other {
100            self.0 - other.0
101        } else {
102            other.0 - self.0
103        };
104        duration.to_std().unwrap_or_default()
105    }
106
107    /// Returns the year number in the calendar date.
108    #[inline]
109    pub fn year(&self) -> i32 {
110        self.0.year()
111    }
112
113    /// Returns the quarter number starting from 1.
114    ///
115    /// The return value ranges from 1 to 4.
116    #[inline]
117    pub fn quarter(&self) -> u32 {
118        self.0.month().div_ceil(3)
119    }
120
121    /// Returns the month number starting from 1.
122    ///
123    /// The return value ranges from 1 to 12.
124    #[inline]
125    pub fn month(&self) -> u32 {
126        self.0.month()
127    }
128
129    /// Returns the day of month starting from 1.
130    ///
131    /// The return value ranges from 1 to 31. (The last day of month differs by months.)
132    #[inline]
133    pub fn day(&self) -> u32 {
134        self.0.day()
135    }
136
137    /// Returns the ISO week number starting from 1.
138    ///
139    /// The return value ranges from 1 to 53. (The last week of year differs by years.)
140    #[inline]
141    pub fn week(&self) -> u32 {
142        self.0.iso_week().week()
143    }
144
145    /// Returns the day of year starting from 1.
146    ///
147    /// The return value ranges from 1 to 366. (The last day of year differs by years.)
148    #[inline]
149    pub fn day_of_year(&self) -> u32 {
150        self.0.ordinal()
151    }
152
153    /// Returns the day of week starting from 0 (Sunday) to 6 (Saturday).
154    #[inline]
155    pub fn day_of_week(&self) -> u8 {
156        self.iso_day_of_week() % 7
157    }
158
159    /// Returns the ISO day of week starting from 1 (Monday) to 7 (Sunday).
160    #[inline]
161    pub fn iso_day_of_week(&self) -> u8 {
162        (self.0.weekday() as u8) + 1
163    }
164
165    /// Returns `true` if the current year is a leap year.
166    #[inline]
167    pub fn is_leap_year(&self) -> bool {
168        self.0.leap_year()
169    }
170
171    /// Returns `true` if the current day is weekend.
172    #[inline]
173    pub fn is_weekend(&self) -> bool {
174        matches!(self.0.weekday(), Weekday::Sat | Weekday::Sun)
175    }
176
177    /// Returns the number of days in the current year.
178    #[inline]
179    pub fn days_in_current_year(&self) -> u32 {
180        if self.is_leap_year() { 366 } else { 365 }
181    }
182
183    /// Returns the number of days in the current month.
184    pub fn days_in_current_month(&self) -> u32 {
185        let month = self.month();
186        match month {
187            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
188            4 | 6 | 9 | 11 => 30,
189            2 => {
190                if self.is_leap_year() {
191                    29
192                } else {
193                    28
194                }
195            }
196            _ => panic!("invalid month: {month}"),
197        }
198    }
199
200    /// Returns the start of the current year.
201    pub fn start_of_current_year(&self) -> Self {
202        let year = self.year();
203        Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default())
204    }
205
206    /// Returns the end of the current year.
207    pub fn end_of_current_year(&self) -> Self {
208        let year = self.year();
209        Self(NaiveDate::from_ymd_opt(year, 12, 31).unwrap_or_default())
210    }
211
212    /// Returns the start of the next year.
213    pub fn start_of_next_year(&self) -> Self {
214        let year = self.year() + 1;
215        Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default())
216    }
217
218    /// Returns the start of the current quarter.
219    pub fn start_of_current_quarter(&self) -> Self {
220        let year = self.year();
221        let month = 3 * self.quarter() - 2;
222        Self(NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default())
223    }
224
225    /// Returns the end of the current quarter.
226    pub fn end_of_current_quarter(&self) -> Self {
227        let year = self.year();
228        let month = 3 * self.quarter();
229        let day = Self::days_in_month(year, month);
230        Self(NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default())
231    }
232
233    /// Returns the start of the current month.
234    pub fn start_of_current_month(&self) -> Self {
235        let year = self.year();
236        let month = self.month();
237        Self(NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default())
238    }
239
240    /// Returns the end of the current month.
241    pub fn end_of_current_month(&self) -> Self {
242        let year = self.year();
243        let month = self.month();
244        let day = self.days_in_current_month();
245        Self(NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default())
246    }
247
248    /// Returns the start of the next month.
249    pub fn start_of_next_month(&self) -> Self {
250        let year = self.year();
251        let month = self.month();
252        let date_opt = if month == 12 {
253            NaiveDate::from_ymd_opt(year + 1, 1, 1)
254        } else {
255            NaiveDate::from_ymd_opt(year, month, 1)
256        };
257        Self(date_opt.unwrap_or_default())
258    }
259
260    /// Adds a duration in months to the date.
261    /// Returns `None` if the resulting date would be out of range.
262    #[inline]
263    pub fn checked_add_months(self, months: u32) -> Option<Self> {
264        self.0.checked_add_months(Months::new(months)).map(Self)
265    }
266
267    /// Subtracts a duration in months from the date.
268    /// Returns `None` if the resulting date would be out of range.
269    #[inline]
270    pub fn checked_sub_months(self, months: u32) -> Option<Self> {
271        self.0.checked_sub_months(Months::new(months)).map(Self)
272    }
273
274    /// Adds a duration in days to the date.
275    /// Returns `None` if the resulting date would be out of range.
276    #[inline]
277    pub fn checked_add_days(self, days: u32) -> Option<Self> {
278        self.0
279            .checked_add_days(Days::new(u64::from(days)))
280            .map(Self)
281    }
282
283    /// Subtracts a duration in days from the date.
284    /// Returns `None` if the resulting date would be out of range.
285    #[inline]
286    pub fn checked_sub_days(self, days: u32) -> Option<Self> {
287        self.0
288            .checked_sub_days(Days::new(u64::from(days)))
289            .map(Self)
290    }
291
292    /// Returns the number of days in the year.
293    #[inline]
294    pub fn days_in_year(year: i32) -> u32 {
295        if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
296            366
297        } else {
298            365
299        }
300    }
301
302    /// Returns the number of days in the month.
303    #[inline]
304    pub fn days_in_month(year: i32, month: u32) -> u32 {
305        match month {
306            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
307            4 | 6 | 9 | 11 => 30,
308            2 => {
309                if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
310                    29
311                } else {
312                    28
313                }
314            }
315            _ => panic!("invalid month: {month}"),
316        }
317    }
318}
319
320impl Default for Date {
321    /// Returns an instance which corresponds to **the current date**.
322    #[inline]
323    fn default() -> Self {
324        Self::today()
325    }
326}
327
328impl fmt::Display for Date {
329    #[inline]
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        self.0.format("%Y-%m-%d").fmt(f)
332    }
333}
334
335impl Serialize for Date {
336    #[inline]
337    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
338        serializer.serialize_str(&self.to_string())
339    }
340}
341
342impl From<NaiveDate> for Date {
343    #[inline]
344    fn from(d: NaiveDate) -> Self {
345        Self(d)
346    }
347}
348
349impl From<Date> for NaiveDate {
350    #[inline]
351    fn from(d: Date) -> Self {
352        d.0
353    }
354}
355
356impl From<Date> for AvroValue {
357    #[inline]
358    fn from(d: Date) -> Self {
359        AvroValue::Date(d.num_days_from_epoch())
360    }
361}
362
363impl From<Date> for JsonValue {
364    #[inline]
365    fn from(d: Date) -> Self {
366        JsonValue::String(d.to_string())
367    }
368}
369
370#[cfg(feature = "i18n")]
371impl<'a> From<Date> for fluent::FluentValue<'a> {
372    #[inline]
373    fn from(d: Date) -> Self {
374        fluent::FluentValue::String(d.to_string().into())
375    }
376}
377
378impl FromStr for Date {
379    type Err = ParseError;
380
381    #[inline]
382    fn from_str(s: &str) -> Result<Self, Self::Err> {
383        s.parse::<NaiveDate>().map(Self)
384    }
385}
386
387impl Add<Duration> for Date {
388    type Output = Self;
389
390    #[inline]
391    fn add(self, rhs: Duration) -> Self {
392        let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
393        let date = self
394            .0
395            .checked_add_signed(duration)
396            .expect("`Date + Duration` overflowed");
397        Self(date)
398    }
399}
400
401impl AddAssign<Duration> for Date {
402    #[inline]
403    fn add_assign(&mut self, rhs: Duration) {
404        *self = *self + rhs;
405    }
406}
407
408impl Sub<Duration> for Date {
409    type Output = Self;
410
411    #[inline]
412    fn sub(self, rhs: Duration) -> Self {
413        let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
414        let date = self
415            .0
416            .checked_sub_signed(duration)
417            .expect("`Date - Duration` overflowed");
418        Self(date)
419    }
420}
421
422impl SubAssign<Duration> for Date {
423    #[inline]
424    fn sub_assign(&mut self, rhs: Duration) {
425        *self = *self - rhs;
426    }
427}
428
429#[cfg(feature = "sqlx")]
430impl<DB> sqlx::Type<DB> for Date
431where
432    DB: sqlx::Database,
433    NaiveDate: sqlx::Type<DB>,
434{
435    #[inline]
436    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
437        <NaiveDate as sqlx::Type<DB>>::type_info()
438    }
439}
440
441#[cfg(feature = "sqlx")]
442impl<'r, DB> sqlx::Decode<'r, DB> for Date
443where
444    DB: sqlx::Database,
445    NaiveDate: sqlx::Decode<'r, DB>,
446{
447    #[inline]
448    fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, crate::BoxError> {
449        <NaiveDate as sqlx::Decode<'r, DB>>::decode(value).map(|dt| dt.into())
450    }
451}