datetimeparse/
combined.rs

1use core::fmt;
2
3#[cfg(feature = "chrono")]
4use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
5
6use crate::{components::{Day, Error, Hour, Minute, Month, Nanosecond, Second, Timeshift, SimpleYear}, Year};
7
8/// Date without time shift information
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub struct LocalDate<Y = SimpleYear> {
11    pub year: Year<Y>,
12    pub month: Month,
13    pub day: Day,
14}
15
16impl<Y> LocalDate<Y> {
17    pub fn new(year: Year<Y>, month: Month, day: Day) -> Self {
18        Self { year, month, day }
19    }
20}
21
22impl fmt::Display for LocalDate {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{}-{}-{}", self.year, self.month, self.day)
25    }
26}
27
28impl<Y, M, D> TryFrom<(Y, M, D)> for LocalDate
29where
30    Y: TryInto<Year, Error = Error>,
31    M: TryInto<Month, Error = Error>,
32    D: TryInto<Day, Error = Error>,
33{
34    type Error = Error;
35    fn try_from((year, month, day): (Y, M, D)) -> Result<Self, Self::Error> {
36        Ok(Self {
37            year: year.try_into()?,
38            month: month.try_into()?,
39            day: day.try_into()?,
40        })
41    }
42}
43
44#[cfg(feature = "chrono")]
45impl From<LocalDate> for NaiveDate {
46    fn from(val: LocalDate) -> Self {
47        NaiveDate::from_ymd_opt(val.year.into(), val.month.into(), val.day.into())
48            .expect("internal values are already range checked")
49    }
50}
51
52/// Time without time shift information
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
54pub struct LocalTime {
55    pub hour: Hour,
56    pub minute: Minute,
57    pub second: Second,
58}
59
60impl LocalTime {
61    pub fn new(hour: Hour, minute: Minute, second: Second) -> Self {
62        Self {
63            hour,
64            minute,
65            second,
66        }
67    }
68}
69
70impl fmt::Display for LocalTime {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}:{}:{}", self.hour, self.minute, self.second)
73    }
74}
75
76impl<H, M, S> TryFrom<(H, M, S)> for LocalTime
77where
78    H: TryInto<Hour, Error = Error>,
79    M: TryInto<Minute, Error = Error>,
80    S: TryInto<Second, Error = Error>,
81{
82    type Error = Error;
83
84    fn try_from((hour, minute, second): (H, M, S)) -> Result<Self, Self::Error> {
85        Ok(Self {
86            hour: hour.try_into()?,
87            minute: minute.try_into()?,
88            second: second.try_into()?,
89        })
90    }
91}
92
93#[cfg(feature = "chrono")]
94impl From<LocalTime> for NaiveTime {
95    fn from(val: LocalTime) -> Self {
96        NaiveTime::from_hms_opt(val.hour.into(), val.minute.into(), val.second.into())
97            .expect("internal values are already range checked")
98    }
99}
100
101/// Time without time shift information, with nanosecond precision
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
103pub struct PreciseLocalTime {
104    pub hour: Hour,
105    pub minute: Minute,
106    pub second: Second,
107    pub nanosecond: Nanosecond,
108}
109
110impl PreciseLocalTime {
111    pub fn new(hour: Hour, minute: Minute, second: Second, nanosecond: Nanosecond) -> Self {
112        Self {
113            hour,
114            minute,
115            second,
116            nanosecond,
117        }
118    }
119}
120
121impl fmt::Display for PreciseLocalTime {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        let ns_string = format!("{:0>9}", self.nanosecond);
124        let ns = if self.nanosecond == Nanosecond::new(0).unwrap() {
125            "0"
126        } else {
127            ns_string.trim_end_matches('0')
128        };
129        write!(f, "{}:{}:{}.{}", self.hour, self.minute, self.second, ns)
130    }
131}
132
133impl<H, M, S, N> TryFrom<(H, M, S, N)> for PreciseLocalTime
134where
135    H: TryInto<Hour, Error = Error>,
136    M: TryInto<Minute, Error = Error>,
137    S: TryInto<Second, Error = Error>,
138    N: TryInto<Nanosecond, Error = Error>,
139{
140    type Error = Error;
141
142    fn try_from((hour, minute, second, nanosecond): (H, M, S, N)) -> Result<Self, Self::Error> {
143        Ok(Self {
144            hour: hour.try_into()?,
145            minute: minute.try_into()?,
146            second: second.try_into()?,
147            nanosecond: nanosecond.try_into()?,
148        })
149    }
150}
151
152#[cfg(feature = "chrono")]
153impl From<PreciseLocalTime> for NaiveTime {
154    fn from(val: PreciseLocalTime) -> Self {
155        NaiveTime::from_hms_nano_opt(
156            val.hour.into(),
157            val.minute.into(),
158            val.second.into(),
159            val.nanosecond.into(),
160        )
161        .expect("internal values are already range checked")
162    }
163}
164
165/// Date and time without time shift information
166#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
167pub struct LocalDateTime<Y = SimpleYear> {
168    pub year: Year<Y>,
169    pub month: Month,
170    pub day: Day,
171    pub hour: Hour,
172    pub minute: Minute,
173    pub second: Second,
174}
175
176impl<Y> LocalDateTime<Y> {
177    pub fn new(
178        year: Year<Y>,
179        month: Month,
180        day: Day,
181        hour: Hour,
182        minute: Minute,
183        second: Second,
184    ) -> Self {
185        Self {
186            year,
187            month,
188            day,
189            hour,
190            minute,
191            second,
192        }
193    }
194}
195
196impl fmt::Display for LocalDateTime {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        write!(
199            f,
200            "{}-{}-{}T{}:{}:{}",
201            self.year, self.month, self.day, self.hour, self.minute, self.second
202        )
203    }
204}
205
206impl<Y, Mo, D, H, Mi, S> TryFrom<(Y, Mo, D, H, Mi, S)> for LocalDateTime
207where
208    Y: TryInto<Year, Error = Error>,
209    Mo: TryInto<Month, Error = Error>,
210    D: TryInto<Day, Error = Error>,
211    H: TryInto<Hour, Error = Error>,
212    Mi: TryInto<Minute, Error = Error>,
213    S: TryInto<Second, Error = Error>,
214{
215    type Error = Error;
216
217    fn try_from(
218        (year, month, day, hour, minute, second): (Y, Mo, D, H, Mi, S),
219    ) -> Result<Self, Self::Error> {
220        Ok(Self {
221            year: year.try_into()?,
222            month: month.try_into()?,
223            day: day.try_into()?,
224            hour: hour.try_into()?,
225            minute: minute.try_into()?,
226            second: second.try_into()?,
227        })
228    }
229}
230
231#[cfg(feature = "chrono")]
232impl From<LocalDateTime> for NaiveDateTime {
233    fn from(val: LocalDateTime) -> Self {
234        NaiveDateTime::new(
235            NaiveDate::from_ymd_opt(val.year.into(), val.month.into(), val.day.into())
236                .expect("internal values are already range checked"),
237            NaiveTime::from_hms_opt(val.hour.into(), val.minute.into(), val.second.into())
238                .expect("internal values are already range checked"),
239        )
240    }
241}
242
243/// Date and time without time shift information, with nanosecond precision
244#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
245pub struct PreciseLocalDateTime<Y = SimpleYear> {
246    pub year: Year<Y>,
247    pub month: Month,
248    pub day: Day,
249    pub hour: Hour,
250    pub minute: Minute,
251    pub second: Second,
252    pub nanosecond: Nanosecond,
253}
254
255impl<Y> PreciseLocalDateTime<Y> {
256    pub fn new(
257        year: Year<Y>,
258        month: Month,
259        day: Day,
260        hour: Hour,
261        minute: Minute,
262        second: Second,
263        nanosecond: Nanosecond,
264    ) -> Self {
265        Self {
266            year,
267            month,
268            day,
269            hour,
270            minute,
271            second,
272            nanosecond,
273        }
274    }
275}
276
277impl fmt::Display for PreciseLocalDateTime {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        let ns_string = format!("{:0>9}", self.nanosecond);
280        let ns = if self.nanosecond == Nanosecond::new(0).unwrap() {
281            "0"
282        } else {
283            ns_string.trim_end_matches('0')
284        };
285        write!(
286            f,
287            "{}-{}-{}T{}:{}:{}.{}",
288            self.year, self.month, self.day, self.hour, self.minute, self.second, ns
289        )
290    }
291}
292
293impl<Y, Mo, D, H, Mi, S, N> TryFrom<(Y, Mo, D, H, Mi, S, N)> for PreciseLocalDateTime
294where
295    Y: TryInto<Year, Error = Error>,
296    Mo: TryInto<Month, Error = Error>,
297    D: TryInto<Day, Error = Error>,
298    H: TryInto<Hour, Error = Error>,
299    Mi: TryInto<Minute, Error = Error>,
300    S: TryInto<Second, Error = Error>,
301    N: TryInto<Nanosecond, Error = Error>,
302{
303    type Error = Error;
304
305    fn try_from(
306        (year, month, day, hour, minute, second, nanosecond): (Y, Mo, D, H, Mi, S, N),
307    ) -> Result<Self, Self::Error> {
308        Ok(Self {
309            year: year.try_into()?,
310            month: month.try_into()?,
311            day: day.try_into()?,
312            hour: hour.try_into()?,
313            minute: minute.try_into()?,
314            second: second.try_into()?,
315            nanosecond: nanosecond.try_into()?,
316        })
317    }
318}
319
320#[cfg(feature = "chrono")]
321impl From<PreciseLocalDateTime> for NaiveDateTime {
322    fn from(val: PreciseLocalDateTime) -> Self {
323        NaiveDateTime::new(
324            NaiveDate::from_ymd_opt(val.year.into(), val.month.into(), val.day.into())
325                .expect("internal values are already range checked"),
326            NaiveTime::from_hms_nano_opt(
327                val.hour.into(),
328                val.minute.into(),
329                val.second.into(),
330                val.nanosecond.into(),
331            )
332            .expect("internal values are already range checked"),
333        )
334    }
335}
336
337/// Date and time with time shift information
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub struct ShiftedDateTime<Y = SimpleYear> {
340    pub year: Year<Y>,
341    pub month: Month,
342    pub day: Day,
343    pub hour: Hour,
344    pub minute: Minute,
345    pub second: Second,
346    pub timeshift: Timeshift,
347}
348
349impl<Y> ShiftedDateTime<Y> {
350    pub fn new(
351        year: Year<Y>,
352        month: Month,
353        day: Day,
354        hour: Hour,
355        minute: Minute,
356        second: Second,
357        timeshift: Timeshift,
358    ) -> Self {
359        Self {
360            year,
361            month,
362            day,
363            hour,
364            minute,
365            second,
366            timeshift,
367        }
368    }
369}
370
371impl fmt::Display for ShiftedDateTime {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        write!(
374            f,
375            "{}-{}-{}T{}:{}:{}{}",
376            self.year, self.month, self.day, self.hour, self.minute, self.second, self.timeshift
377        )
378    }
379}
380
381impl<Y, Mo, D, H, Mi, S, T> TryFrom<(Y, Mo, D, H, Mi, S, T)> for ShiftedDateTime
382where
383    Y: TryInto<Year, Error = Error>,
384    Mo: TryInto<Month, Error = Error>,
385    D: TryInto<Day, Error = Error>,
386    H: TryInto<Hour, Error = Error>,
387    Mi: TryInto<Minute, Error = Error>,
388    S: TryInto<Second, Error = Error>,
389    T: TryInto<Timeshift, Error = Error>,
390{
391    type Error = Error;
392
393    fn try_from(
394        (year, month, day, hour, minute, second, timeshift): (Y, Mo, D, H, Mi, S, T),
395    ) -> Result<Self, Self::Error> {
396        Ok(Self {
397            year: year.try_into()?,
398            month: month.try_into()?,
399            day: day.try_into()?,
400            hour: hour.try_into()?,
401            minute: minute.try_into()?,
402            second: second.try_into()?,
403            timeshift: timeshift.try_into()?,
404        })
405    }
406}
407
408#[cfg(feature = "chrono")]
409impl From<ShiftedDateTime> for DateTime<FixedOffset> {
410    fn from(val: ShiftedDateTime) -> Self {
411        DateTime::from_local(
412            NaiveDateTime::new(
413                NaiveDate::from_ymd_opt(val.year.into(), val.month.into(), val.day.into())
414                    .expect("internal values are already range checked"),
415                NaiveTime::from_hms_opt(val.hour.into(), val.minute.into(), val.second.into())
416                    .expect("internal values are already range checked"),
417            ),
418            FixedOffset::east_opt(val.timeshift.seconds_from_east())
419                .expect("internal values are already range checked"),
420        )
421    }
422}
423
424#[cfg(feature = "chrono")]
425impl TryInto<DateTime<Utc>> for ShiftedDateTime {
426    type Error = ();
427
428    fn try_into(self) -> Result<DateTime<Utc>, Self::Error> {
429        match self.timeshift {
430            Timeshift::Utc => Ok(DateTime::<Utc>::from_local(
431                NaiveDateTime::new(
432                    NaiveDate::from_ymd_opt(self.year.into(), self.month.into(), self.day.into())
433                        .expect("internal values are already range checked"),
434                    NaiveTime::from_hms_opt(
435                        self.hour.into(),
436                        self.minute.into(),
437                        self.second.into(),
438                    )
439                    .expect("internal values are already range checked"),
440                ),
441                Utc,
442            )),
443            Timeshift::Offset {
444                non_negative: _,
445                hours: _,
446                minutes: _,
447            } => Err(()),
448        }
449    }
450}
451
452/// Date and with time shift information, with nanosecond precision
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454pub struct PreciseShiftedDateTime<Y = SimpleYear> {
455    pub year: Year<Y>,
456    pub month: Month,
457    pub day: Day,
458    pub hour: Hour,
459    pub minute: Minute,
460    pub second: Second,
461    pub nanosecond: Nanosecond,
462    pub timeshift: Timeshift,
463}
464
465impl<Y> PreciseShiftedDateTime<Y> {
466    pub fn new(
467        year: Year<Y>,
468        month: Month,
469        day: Day,
470        hour: Hour,
471        minute: Minute,
472        second: Second,
473        nanosecond: Nanosecond,
474        timeshift: Timeshift,
475    ) -> Self {
476        Self {
477            year,
478            month,
479            day,
480            hour,
481            minute,
482            second,
483            nanosecond,
484            timeshift,
485        }
486    }
487}
488
489impl fmt::Display for PreciseShiftedDateTime {
490    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491        let ns_string = format!("{:0>9}", self.nanosecond);
492        let ns = if self.nanosecond == Nanosecond::new(0).unwrap() {
493            "0"
494        } else {
495            ns_string.trim_end_matches('0')
496        };
497        write!(
498            f,
499            "{}-{}-{}T{}:{}:{}.{}{}",
500            self.year,
501            self.month,
502            self.day,
503            self.hour,
504            self.minute,
505            self.second,
506            ns,
507            self.timeshift
508        )
509    }
510}
511
512impl<Y, Mo, D, H, Mi, S, N, T> TryFrom<(Y, Mo, D, H, Mi, S, N, T)> for PreciseShiftedDateTime
513where
514    Y: TryInto<Year, Error = Error>,
515    Mo: TryInto<Month, Error = Error>,
516    D: TryInto<Day, Error = Error>,
517    H: TryInto<Hour, Error = Error>,
518    Mi: TryInto<Minute, Error = Error>,
519    S: TryInto<Second, Error = Error>,
520    N: TryInto<Nanosecond, Error = Error>,
521    T: TryInto<Timeshift, Error = Error>,
522{
523    type Error = Error;
524
525    fn try_from(
526        (year, month, day, hour, minute, second, nanosecond, timeshift): (Y, Mo, D, H, Mi, S, N, T),
527    ) -> Result<Self, Self::Error> {
528        Ok(Self {
529            year: year.try_into()?,
530            month: month.try_into()?,
531            day: day.try_into()?,
532            hour: hour.try_into()?,
533            minute: minute.try_into()?,
534            second: second.try_into()?,
535            nanosecond: nanosecond.try_into()?,
536            timeshift: timeshift.try_into()?,
537        })
538    }
539}
540
541#[cfg(feature = "chrono")]
542impl From<PreciseShiftedDateTime> for DateTime<FixedOffset> {
543    fn from(val: PreciseShiftedDateTime) -> Self {
544        DateTime::from_local(
545            NaiveDateTime::new(
546                NaiveDate::from_ymd_opt(val.year.into(), val.month.into(), val.day.into())
547                    .expect("internal values are already range checked"),
548                NaiveTime::from_hms_nano_opt(
549                    val.hour.into(),
550                    val.minute.into(),
551                    val.second.into(),
552                    val.nanosecond.into(),
553                )
554                .expect("internal values are already range checked"),
555            ),
556            FixedOffset::east_opt(val.timeshift.seconds_from_east())
557                .expect("internal values are already range checked"),
558        )
559    }
560}
561
562#[cfg(feature = "chrono")]
563impl TryInto<DateTime<Utc>> for PreciseShiftedDateTime {
564    type Error = ();
565
566    fn try_into(self) -> Result<DateTime<Utc>, Self::Error> {
567        match self.timeshift {
568            Timeshift::Utc => Ok(DateTime::<Utc>::from_local(
569                NaiveDateTime::new(
570                    NaiveDate::from_ymd_opt(self.year.into(), self.month.into(), self.day.into())
571                        .expect("internal values are already range checked"),
572                    NaiveTime::from_hms_nano_opt(
573                        self.hour.into(),
574                        self.minute.into(),
575                        self.second.into(),
576                        self.nanosecond.into(),
577                    )
578                    .expect("internal values are already range checked"),
579                ),
580                Utc,
581            )),
582            Timeshift::Offset {
583                non_negative: _,
584                hours: _,
585                minutes: _,
586            } => Err(()),
587        }
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::{LocalDate, PreciseLocalTime, PreciseShiftedDateTime};
594
595    #[test]
596    fn test_try_from_tuple() {
597        let cd: LocalDate = LocalDate::try_from((2022, 1, 2)).unwrap();
598        assert_eq!(format!("{}", cd), "2022-01-02")
599    }
600
601    #[test]
602    fn test_precise_time() {
603        let pt: PreciseLocalTime = PreciseLocalTime::try_from((20, 12, 0, 0)).unwrap();
604        assert_eq!(format!("{}", pt), "20:12:00.0");
605
606        let pt: PreciseLocalTime = PreciseLocalTime::try_from((20, 12, 0, 123_400_000)).unwrap();
607        assert_eq!(format!("{}", pt), "20:12:00.1234");
608    }
609
610    #[test]
611    fn test_format_full_datetime() {
612        let dt = PreciseShiftedDateTime::try_from((2023, 4, 9, 21, 22, 2, 123_400_000, (12, 2)))
613            .unwrap();
614        assert_eq!(format!("{}", dt), "2023-04-09T21:22:02.1234+12:02");
615        let dt = PreciseShiftedDateTime::try_from((2023, 4, 9, 21, 22, 2, 123_400_000, (-12, 2)))
616            .unwrap();
617        assert_eq!(format!("{}", dt), "2023-04-09T21:22:02.1234-12:02")
618    }
619}