zino_core/datetime/
mod.rs

1//! ISO 8601 combined date and time with local time zone.
2
3use crate::{AvroValue, JsonValue};
4use chrono::{
5    Datelike, Days, Local, Months, NaiveDate, NaiveDateTime, NaiveTime, SecondsFormat, TimeZone,
6    Timelike, Utc, Weekday, format::ParseError,
7};
8use serde::{Deserialize, Serialize, Serializer};
9use std::{
10    fmt,
11    ops::{Add, AddAssign, Sub, SubAssign},
12    str::FromStr,
13    time::Duration,
14};
15use uuid::{NoContext, Timestamp};
16
17mod date;
18mod duration;
19mod time;
20
21pub use date::Date;
22pub use duration::{ParseDurationError, parse_duration};
23pub use time::Time;
24
25/// Alias for [`chrono::DateTime<Local>`](chrono::DateTime).
26type LocalDateTime = chrono::DateTime<Local>;
27
28/// A wrapper type for [`chrono::DateTime<Local>`](chrono::DateTime).
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
30pub struct DateTime(LocalDateTime);
31
32impl DateTime {
33    /// Returns a new instance which corresponds to the current date and time.
34    #[inline]
35    pub fn now() -> Self {
36        Self(Local::now())
37    }
38
39    /// Returns the number of non-leap seconds since the midnight UTC on January 1, 1970.
40    #[inline]
41    pub fn current_timestamp() -> i64 {
42        Utc::now().timestamp()
43    }
44
45    /// Returns the number of non-leap milliseconds since the midnight UTC on January 1, 1970.
46    #[inline]
47    pub fn current_timestamp_millis() -> i64 {
48        Utc::now().timestamp_millis()
49    }
50
51    /// Returns the number of non-leap microseconds since the midnight UTC on January 1, 1970.
52    #[inline]
53    pub fn current_timestamp_micros() -> i64 {
54        Utc::now().timestamp_micros()
55    }
56
57    /// Returns the number of non-leap nanoseconds since the midnight UTC on January 1, 1970.
58    #[inline]
59    pub fn current_timestamp_nanos() -> i64 {
60        Utc::now().timestamp_nanos_opt().unwrap_or_default()
61    }
62
63    /// Returns a new instance corresponding to a UTC date and time,
64    /// from the number of non-leap seconds since the midnight UTC on January 1, 1970.
65    #[inline]
66    pub fn from_timestamp(secs: i64) -> Self {
67        let dt = chrono::DateTime::from_timestamp(secs, 0).unwrap_or_default();
68        Self(dt.with_timezone(&Local))
69    }
70
71    /// Returns a new instance corresponding to a UTC date and time,
72    /// from the number of non-leap milliseconds since the midnight UTC on January 1, 1970.
73    #[inline]
74    pub fn from_timestamp_millis(millis: i64) -> Self {
75        let dt = chrono::DateTime::from_timestamp_millis(millis).unwrap_or_default();
76        Self(dt.with_timezone(&Local))
77    }
78
79    /// Returns a new instance corresponding to a UTC date and time,
80    /// from the number of non-leap microseconds since the midnight UTC on January 1, 1970.
81    #[inline]
82    pub fn from_timestamp_micros(micros: i64) -> Self {
83        let dt = chrono::DateTime::from_timestamp_micros(micros).unwrap_or_default();
84        Self(dt.with_timezone(&Local))
85    }
86
87    /// Returns a new instance corresponding to a UTC date and time,
88    /// from the number of non-leap nanoseconds since the midnight UTC on January 1, 1970.
89    #[inline]
90    pub fn from_timestamp_nanos(nanos: i64) -> Self {
91        let dt = chrono::DateTime::from_timestamp_nanos(nanos);
92        Self(dt.with_timezone(&Local))
93    }
94
95    /// Returns a new instance corresponding to a UTC date and time from a UUID timestamp.
96    #[inline]
97    pub fn from_uuid_timestamp(ts: Timestamp) -> Self {
98        let (secs, nanos) = ts.to_unix();
99        let nanos = secs * 1_000_000_000 + u64::from(nanos);
100        Self::from_timestamp_nanos(nanos.try_into().unwrap_or_default())
101    }
102
103    /// Returns the number of non-leap seconds since the midnight UTC on January 1, 1970.
104    #[inline]
105    pub fn timestamp(&self) -> i64 {
106        self.0.timestamp()
107    }
108
109    /// Returns the number of non-leap milliseconds since the midnight UTC on January 1, 1970.
110    #[inline]
111    pub fn timestamp_millis(&self) -> i64 {
112        self.0.timestamp_millis()
113    }
114
115    /// Returns the number of non-leap microseconds since the midnight UTC on January 1, 1970.
116    #[inline]
117    pub fn timestamp_micros(&self) -> i64 {
118        self.0.timestamp_micros()
119    }
120
121    /// Returns the number of non-leap nanoseconds since the midnight UTC on January 1, 1970.
122    #[inline]
123    pub fn timestamp_nanos(&self) -> i64 {
124        self.0.timestamp_nanos_opt().unwrap_or_default()
125    }
126
127    /// Returns a timestamp that can be encoded into a UUID.
128    pub fn uuid_timestamp(&self) -> Timestamp {
129        let secs = self.timestamp().try_into().unwrap_or_default();
130        let nanos = self.0.nanosecond();
131        Timestamp::from_unix(NoContext, secs, nanos)
132    }
133
134    /// Returns the difference in seconds between `self` and
135    /// the same date-time as evaluated in the UTC time zone.
136    #[inline]
137    pub fn timezone_offset(&self) -> i32 {
138        self.0.offset().local_minus_utc()
139    }
140
141    /// Parses an RFC 2822 date and time.
142    #[inline]
143    pub fn parse_utc_str(s: &str) -> Result<Self, ParseError> {
144        let datetime = chrono::DateTime::parse_from_rfc2822(s)?;
145        Ok(Self(datetime.with_timezone(&Local)))
146    }
147
148    /// Parses an RFC 3339 and ISO 8601 date and time.
149    #[inline]
150    pub fn parse_iso_str(s: &str) -> Result<Self, ParseError> {
151        let datetime = chrono::DateTime::parse_from_rfc3339(s)?;
152        Ok(Self(datetime.with_timezone(&Local)))
153    }
154
155    /// Parses a string with the specified format string.
156    /// See [`format::strftime`](chrono::format::strftime) for the supported escape sequences.
157    #[inline]
158    pub fn parse_from_str(s: &str, fmt: &str) -> Result<Self, ParseError> {
159        let datetime = chrono::DateTime::parse_from_str(s, fmt)?;
160        Ok(Self(datetime.with_timezone(&Local)))
161    }
162
163    /// Returns a UTC timestamp string.
164    #[inline]
165    pub fn to_utc_timestamp(&self) -> String {
166        let datetime = self.0.with_timezone(&Utc);
167        format!("{}", datetime.format("%Y-%m-%d %H:%M:%S%.6f"))
168    }
169
170    /// Returns an RFC 2822 date and time string.
171    #[inline]
172    pub fn to_utc_string(&self) -> String {
173        let datetime = self.0.with_timezone(&Utc);
174        format!("{} GMT", datetime.to_rfc2822().trim_end_matches(" +0000"))
175    }
176
177    /// Return an RFC 3339 and ISO 8601 date and time string with subseconds
178    /// formatted as [`SecondsFormat::Millis`].
179    #[inline]
180    pub fn to_iso_string(&self) -> String {
181        let datetime = self.0.with_timezone(&Utc);
182        datetime.to_rfc3339_opts(SecondsFormat::Millis, true)
183    }
184
185    /// Returns a date-time string with the `Local` time zone.
186    #[inline]
187    pub fn to_local_string(&self) -> String {
188        format!("{}", self.0.format("%Y-%m-%d %H:%M:%S %:z"))
189    }
190
191    /// Formats the combined date and time with the specified format string.
192    /// See [`format::strftime`](chrono::format::strftime) for the supported escape sequences.
193    #[inline]
194    pub fn format(&self, fmt: &str) -> String {
195        format!("{}", self.0.format(fmt))
196    }
197
198    /// Returns a date-only string in the format `%Y-%m-%d`.
199    #[inline]
200    pub fn format_date(&self) -> String {
201        format!("{}", self.0.format("%Y-%m-%d"))
202    }
203
204    /// Returns a time-only string in the format `%H:%M:%S`.
205    #[inline]
206    pub fn format_time(&self) -> String {
207        format!("{}", self.0.format("%H:%M:%S"))
208    }
209
210    /// Returns a date-time string in the format `%Y-%m-%d %H:%M:%S` with the `Local` time zone.
211    pub fn format_local(&self) -> String {
212        format!("{}", self.0.format("%Y-%m-%d %H:%M:%S"))
213    }
214
215    /// Returns a date-time string in the format `%Y-%m-%d %H:%M:%S` with the `Utc` time zone.
216    #[inline]
217    pub fn format_utc(&self) -> String {
218        let datetime = self.0.with_timezone(&Utc);
219        format!("{}", datetime.format("%Y-%m-%d %H:%M:%S"))
220    }
221
222    /// Returns the amount of time elapsed from another datetime to this one,
223    /// or zero duration if that datetime is later than this one.
224    #[inline]
225    pub fn duration_since(&self, earlier: DateTime) -> Duration {
226        (self.0 - earlier.0).to_std().unwrap_or_default()
227    }
228
229    /// Returns the duration of time between `self` and `other`.
230    #[inline]
231    pub fn span_between(&self, other: DateTime) -> Duration {
232        let duration = if self > &other {
233            self.0 - other.0
234        } else {
235            other.0 - self.0
236        };
237        duration.to_std().unwrap_or_default()
238    }
239
240    /// Returns the duration of time between `self` and `DateTime::now()`.
241    #[inline]
242    pub fn span_between_now(&self) -> Duration {
243        self.span_between(Self::now())
244    }
245
246    /// Returns the duration of time from `self` to `DateTime::now()`.
247    #[inline]
248    pub fn span_before_now(&self) -> Option<Duration> {
249        let current = Self::now();
250        if self <= &current {
251            (current.0 - self.0).to_std().ok()
252        } else {
253            None
254        }
255    }
256
257    /// Returns the duration of time from `DateTime::now()` to `self`.
258    #[inline]
259    pub fn span_after_now(&self) -> Option<Duration> {
260        let current = Self::now();
261        if self >= &current {
262            (self.0 - current.0).to_std().ok()
263        } else {
264            None
265        }
266    }
267
268    /// Retrieves the date component.
269    #[inline]
270    pub fn date(&self) -> Date {
271        self.0.date_naive().into()
272    }
273
274    /// Retrieves the time component.
275    #[inline]
276    pub fn time(&self) -> Time {
277        self.0.time().into()
278    }
279
280    /// Returns the year number in the calendar date.
281    #[inline]
282    pub fn year(&self) -> i32 {
283        self.0.year()
284    }
285
286    /// Returns the quarter number starting from 1.
287    ///
288    /// The return value ranges from 1 to 4.
289    #[inline]
290    pub fn quarter(&self) -> u32 {
291        self.0.month().div_ceil(3)
292    }
293
294    /// Returns the month number starting from 1.
295    ///
296    /// The return value ranges from 1 to 12.
297    #[inline]
298    pub fn month(&self) -> u32 {
299        self.0.month()
300    }
301
302    /// Returns the day of month starting from 1.
303    ///
304    /// The return value ranges from 1 to 31. (The last day of month differs by months.)
305    #[inline]
306    pub fn day(&self) -> u32 {
307        self.0.day()
308    }
309
310    /// Returns the hour number from 0 to 23.
311    #[inline]
312    pub fn hour(&self) -> u32 {
313        self.0.hour()
314    }
315
316    /// Returns the minute number from 0 to 59.
317    #[inline]
318    pub fn minute(&self) -> u32 {
319        self.0.minute()
320    }
321
322    /// Returns the second number from 0 to 59.
323    #[inline]
324    pub fn second(&self) -> u32 {
325        self.0.second()
326    }
327
328    /// Returns the millisecond number from 0 to 999.
329    #[inline]
330    pub fn millisecond(&self) -> u32 {
331        self.0.timestamp_subsec_millis() % 1000
332    }
333
334    /// Returns the microsecond number from 0 to 999.
335    #[inline]
336    pub fn microsecond(&self) -> u32 {
337        self.0.timestamp_subsec_micros() % 1000
338    }
339
340    /// Returns the nanosecond number from 0 to 999.
341    #[inline]
342    pub fn nanosecond(&self) -> u32 {
343        self.0.timestamp_subsec_nanos() % 1_000_000
344    }
345
346    /// Returns the ISO week number starting from 1.
347    ///
348    /// The return value ranges from 1 to 53. (The last week of year differs by years.)
349    #[inline]
350    pub fn week(&self) -> u32 {
351        self.0.iso_week().week()
352    }
353
354    /// Returns the day of year starting from 1.
355    ///
356    /// The return value ranges from 1 to 366. (The last day of year differs by years.)
357    #[inline]
358    pub fn day_of_year(&self) -> u32 {
359        self.0.ordinal()
360    }
361
362    /// Returns the day of week starting from 0 (Sunday) to 6 (Saturday).
363    #[inline]
364    pub fn day_of_week(&self) -> u8 {
365        self.iso_day_of_week() % 7
366    }
367
368    /// Returns the ISO day of week starting from 1 (Monday) to 7 (Sunday).
369    #[inline]
370    pub fn iso_day_of_week(&self) -> u8 {
371        (self.0.weekday() as u8) + 1
372    }
373
374    /// Returns `true` if the current year is a leap year.
375    #[inline]
376    pub fn is_leap_year(&self) -> bool {
377        self.0.date_naive().leap_year()
378    }
379
380    /// Returns `true` if the current day is weekend.
381    #[inline]
382    pub fn is_weekend(&self) -> bool {
383        matches!(self.0.weekday(), Weekday::Sat | Weekday::Sun)
384    }
385
386    /// Returns the number of days in the current year.
387    #[inline]
388    pub fn days_in_current_year(&self) -> u32 {
389        if self.is_leap_year() { 366 } else { 365 }
390    }
391
392    /// Returns the number of days in the current month.
393    pub fn days_in_current_month(&self) -> u32 {
394        let month = self.month();
395        match month {
396            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
397            4 | 6 | 9 | 11 => 30,
398            2 => {
399                if self.is_leap_year() {
400                    29
401                } else {
402                    28
403                }
404            }
405            _ => panic!("invalid month: {month}"),
406        }
407    }
408
409    /// Returns the start of the current year.
410    pub fn start_of_current_year(&self) -> Self {
411        let year = self.year();
412        let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default();
413        let dt = NaiveDateTime::new(date, NaiveTime::default());
414        let offset = Local.offset_from_utc_datetime(&dt);
415        Self(LocalDateTime::from_naive_utc_and_offset(
416            dt - offset,
417            offset,
418        ))
419    }
420
421    /// Returns the end of the current year.
422    pub fn end_of_current_year(&self) -> Self {
423        let year = self.year();
424        let dt = NaiveDate::from_ymd_opt(year, 12, 31)
425            .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
426            .unwrap_or_default();
427        let offset = Local.offset_from_utc_datetime(&dt);
428        Self(LocalDateTime::from_naive_utc_and_offset(
429            dt - offset,
430            offset,
431        ))
432    }
433
434    /// Returns the start of the current quarter.
435    pub fn start_of_current_quarter(&self) -> Self {
436        let year = self.year();
437        let month = 3 * self.quarter() - 2;
438        let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
439        let dt = NaiveDateTime::new(date, NaiveTime::default());
440        let offset = Local.offset_from_utc_datetime(&dt);
441        Self(LocalDateTime::from_naive_utc_and_offset(
442            dt - offset,
443            offset,
444        ))
445    }
446
447    /// Returns the end of the current quarter.
448    pub fn end_of_current_quarter(&self) -> Self {
449        let year = self.year();
450        let month = 3 * self.quarter();
451        let day = Date::days_in_month(year, month);
452        let dt = NaiveDate::from_ymd_opt(year, month, day)
453            .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
454            .unwrap_or_default();
455        let offset = Local.offset_from_utc_datetime(&dt);
456        Self(LocalDateTime::from_naive_utc_and_offset(
457            dt - offset,
458            offset,
459        ))
460    }
461
462    /// Returns the start of the current month.
463    pub fn start_of_current_month(&self) -> Self {
464        let year = self.year();
465        let month = self.month();
466        let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
467        let dt = NaiveDateTime::new(date, NaiveTime::default());
468        let offset = Local.offset_from_utc_datetime(&dt);
469        Self(LocalDateTime::from_naive_utc_and_offset(
470            dt - offset,
471            offset,
472        ))
473    }
474
475    /// Returns the end of the current month.
476    pub fn end_of_current_month(&self) -> Self {
477        let year = self.year();
478        let month = self.month();
479        let day = self.days_in_current_month();
480        let dt = NaiveDate::from_ymd_opt(year, month, day)
481            .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
482            .unwrap_or_default();
483        let offset = Local.offset_from_utc_datetime(&dt);
484        Self(LocalDateTime::from_naive_utc_and_offset(
485            dt - offset,
486            offset,
487        ))
488    }
489
490    /// Returns the start of the current day.
491    pub fn start_of_current_day(&self) -> Self {
492        let date = self.0.date_naive();
493        let dt = NaiveDateTime::new(date, NaiveTime::default());
494        let offset = Local.offset_from_utc_datetime(&dt);
495        Self(LocalDateTime::from_naive_utc_and_offset(
496            dt - offset,
497            offset,
498        ))
499    }
500
501    /// Returns the end of the current day.
502    pub fn end_of_current_day(&self) -> Self {
503        let date = self.0.date_naive();
504        let dt = date
505            .and_hms_milli_opt(23, 59, 59, 1_000)
506            .unwrap_or_default();
507        let offset = Local.offset_from_utc_datetime(&dt);
508        Self(LocalDateTime::from_naive_utc_and_offset(
509            dt - offset,
510            offset,
511        ))
512    }
513
514    /// Returns the start of the year.
515    pub fn start_of_year(year: i32) -> Self {
516        let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default();
517        let dt = NaiveDateTime::new(date, NaiveTime::default());
518        let offset = Local.offset_from_utc_datetime(&dt);
519        Self(LocalDateTime::from_naive_utc_and_offset(
520            dt - offset,
521            offset,
522        ))
523    }
524
525    /// Returns the end of the year.
526    pub fn end_of_year(year: i32) -> Self {
527        let dt = NaiveDate::from_ymd_opt(year + 1, 1, 1)
528            .and_then(|date| date.pred_opt())
529            .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
530            .unwrap_or_default();
531        let offset = Local.offset_from_utc_datetime(&dt);
532        Self(LocalDateTime::from_naive_utc_and_offset(
533            dt - offset,
534            offset,
535        ))
536    }
537
538    /// Returns the start of the month.
539    pub fn start_of_month(year: i32, month: u32) -> Self {
540        let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
541        let dt = NaiveDateTime::new(date, NaiveTime::default());
542        let offset = Local.offset_from_utc_datetime(&dt);
543        Self(LocalDateTime::from_naive_utc_and_offset(
544            dt - offset,
545            offset,
546        ))
547    }
548
549    /// Returns the end of the month.
550    pub fn end_of_month(year: i32, month: u32) -> Self {
551        let dt = NaiveDate::from_ymd_opt(year, month + 1, 1)
552            .and_then(|date| date.pred_opt())
553            .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
554            .unwrap_or_default();
555        let offset = Local.offset_from_utc_datetime(&dt);
556        Self(LocalDateTime::from_naive_utc_and_offset(
557            dt - offset,
558            offset,
559        ))
560    }
561
562    /// Returns the start of the day.
563    pub fn start_of_day(year: i32, month: u32, day: u32) -> Self {
564        let date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default();
565        let dt = NaiveDateTime::new(date, NaiveTime::default());
566        let offset = Local.offset_from_utc_datetime(&dt);
567        Self(LocalDateTime::from_naive_utc_and_offset(
568            dt - offset,
569            offset,
570        ))
571    }
572
573    /// Returns the end of the month.
574    pub fn end_of_day(year: i32, month: u32, day: u32) -> Self {
575        let date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default();
576        let dt = date
577            .and_hms_milli_opt(23, 59, 59, 1_000)
578            .unwrap_or_default();
579        let offset = Local.offset_from_utc_datetime(&dt);
580        Self(LocalDateTime::from_naive_utc_and_offset(
581            dt - offset,
582            offset,
583        ))
584    }
585
586    /// Adds a duration in months to the date part of the `DateTime`.
587    /// Returns `None` if the resulting date would be out of range.
588    #[inline]
589    pub fn checked_add_months(self, months: u32) -> Option<Self> {
590        self.0.checked_add_months(Months::new(months)).map(Self)
591    }
592
593    /// Subtracts a duration in months from the date part of the `DateTime`.
594    /// Returns `None` if the resulting date would be out of range.
595    #[inline]
596    pub fn checked_sub_months(self, months: u32) -> Option<Self> {
597        self.0.checked_sub_months(Months::new(months)).map(Self)
598    }
599
600    /// Adds a duration in days to the date part of the `DateTime`.
601    /// Returns `None` if the resulting date would be out of range.
602    #[inline]
603    pub fn checked_add_days(self, days: u32) -> Option<Self> {
604        self.0
605            .checked_add_days(Days::new(u64::from(days)))
606            .map(Self)
607    }
608
609    /// Subtracts a duration in days from the date part of the `DateTime`.
610    /// Returns `None` if the resulting date would be out of range.
611    #[inline]
612    pub fn checked_sub_days(self, days: u32) -> Option<Self> {
613        self.0
614            .checked_sub_days(Days::new(u64::from(days)))
615            .map(Self)
616    }
617}
618
619impl Default for DateTime {
620    /// Returns an instance which corresponds to **the current date and time**.
621    #[inline]
622    fn default() -> Self {
623        Self::now()
624    }
625}
626
627impl fmt::Display for DateTime {
628    #[inline]
629    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
630        self.0.format("%Y-%m-%d %H:%M:%S%.6f %z").fmt(f)
631    }
632}
633
634impl Serialize for DateTime {
635    #[inline]
636    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
637        serializer.serialize_str(&self.to_utc_timestamp())
638    }
639}
640
641impl From<Date> for DateTime {
642    fn from(d: Date) -> Self {
643        let dt = NaiveDateTime::new(d.into(), NaiveTime::default());
644        let offset = Local.offset_from_utc_datetime(&dt);
645        Self(LocalDateTime::from_naive_utc_and_offset(
646            dt - offset,
647            offset,
648        ))
649    }
650}
651
652impl From<LocalDateTime> for DateTime {
653    #[inline]
654    fn from(dt: LocalDateTime) -> Self {
655        Self(dt)
656    }
657}
658
659impl From<DateTime> for LocalDateTime {
660    #[inline]
661    fn from(dt: DateTime) -> Self {
662        dt.0
663    }
664}
665
666impl From<DateTime> for AvroValue {
667    #[inline]
668    fn from(dt: DateTime) -> Self {
669        AvroValue::String(dt.to_string())
670    }
671}
672
673impl From<DateTime> for JsonValue {
674    #[inline]
675    fn from(dt: DateTime) -> Self {
676        JsonValue::String(dt.to_string())
677    }
678}
679
680#[cfg(feature = "i18n")]
681impl<'a> From<DateTime> for fluent::FluentValue<'a> {
682    #[inline]
683    fn from(dt: DateTime) -> Self {
684        fluent::FluentValue::String(dt.to_string().into())
685    }
686}
687
688impl FromStr for DateTime {
689    type Err = ParseError;
690
691    fn from_str(s: &str) -> Result<Self, Self::Err> {
692        let length = s.len();
693        if length == 10 {
694            let date = s.parse::<NaiveDate>()?;
695            let dt = NaiveDateTime::new(date, NaiveTime::default());
696            let offset = Local.offset_from_utc_datetime(&dt);
697            Ok(LocalDateTime::from_naive_utc_and_offset(dt, offset).into())
698        } else if length == 19 {
699            let dt = s.parse::<NaiveDateTime>()?;
700            let offset = Local.offset_from_utc_datetime(&dt);
701            Ok(LocalDateTime::from_naive_utc_and_offset(dt, offset).into())
702        } else if s.contains('+') || s.contains(" -") {
703            LocalDateTime::from_str(s).map(Self)
704        } else if s.ends_with('Z') {
705            let dt = s.parse::<chrono::DateTime<Utc>>()?;
706            Ok(dt.with_timezone(&Local).into())
707        } else {
708            let dt = [s, "Z"].concat().parse::<chrono::DateTime<Utc>>()?;
709            Ok(dt.with_timezone(&Local).into())
710        }
711    }
712}
713
714impl Add<Duration> for DateTime {
715    type Output = Self;
716
717    #[inline]
718    fn add(self, rhs: Duration) -> Self {
719        let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
720        let datetime = self
721            .0
722            .checked_add_signed(duration)
723            .expect("`DateTime + Duration` overflowed");
724        Self(datetime)
725    }
726}
727
728impl AddAssign<Duration> for DateTime {
729    #[inline]
730    fn add_assign(&mut self, rhs: Duration) {
731        *self = *self + rhs;
732    }
733}
734
735impl Sub<Duration> for DateTime {
736    type Output = Self;
737
738    #[inline]
739    fn sub(self, rhs: Duration) -> Self {
740        let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
741        let datetime = self
742            .0
743            .checked_sub_signed(duration)
744            .expect("`DateTime - Duration` overflowed");
745        Self(datetime)
746    }
747}
748
749impl SubAssign<Duration> for DateTime {
750    #[inline]
751    fn sub_assign(&mut self, rhs: Duration) {
752        *self = *self - rhs;
753    }
754}
755
756#[cfg(feature = "sqlx")]
757impl<DB> sqlx::Type<DB> for DateTime
758where
759    DB: sqlx::Database,
760    LocalDateTime: sqlx::Type<DB>,
761{
762    #[inline]
763    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
764        <LocalDateTime as sqlx::Type<DB>>::type_info()
765    }
766}
767
768#[cfg(feature = "sqlx")]
769impl<'r, DB> sqlx::Decode<'r, DB> for DateTime
770where
771    DB: sqlx::Database,
772    LocalDateTime: sqlx::Decode<'r, DB>,
773{
774    #[inline]
775    fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, crate::BoxError> {
776        <LocalDateTime as sqlx::Decode<'r, DB>>::decode(value).map(|dt| dt.into())
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::{Date, DateTime};
783
784    #[test]
785    fn it_parses_datetime() {
786        assert!("2023-12-31".parse::<DateTime>().is_ok());
787        assert!("2023-12-31T18:00:00".parse::<DateTime>().is_ok());
788        assert!("2023-07-13T02:16:33.449Z".parse::<DateTime>().is_ok());
789        assert!(
790            "2023-06-10 05:17:23.713071 +0800"
791                .parse::<DateTime>()
792                .is_ok()
793        );
794
795        let datetime = "2023-11-30 16:24:30.654321 +0800"
796            .parse::<DateTime>()
797            .unwrap();
798        let start_day = datetime.start_of_current_day();
799        let end_day = datetime.end_of_current_day();
800        assert_eq!("2023-11-30", start_day.format_date());
801        assert_eq!("00:00:00", start_day.format_time());
802        assert_eq!("2023-11-30", end_day.format_date());
803        assert_eq!("23:59:60", end_day.format_time());
804
805        let date = "2023-11-30".parse::<Date>().unwrap();
806        let datetime = DateTime::from(date);
807        assert!(datetime.day_of_week() == 4);
808        assert_eq!("2023-11-30", datetime.format_date());
809        assert_eq!("00:00:00", datetime.format_time());
810    }
811}