datetimeparse/
components.rs

1use core::{fmt, num};
2
3#[derive(Debug)]
4pub enum Error {
5    Range,
6    ParseInt(num::ParseIntError),
7    Parse,
8}
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub struct SimpleYear;
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub struct ExtendedYear<const N: usize>;
13
14pub trait YearDigits {
15    fn digits() -> usize;
16    fn from_digits(digits: i32) -> Result<Year<Self>, Error> where Self: Sized;
17}
18
19impl YearDigits for SimpleYear {
20    fn digits() -> usize {
21        4
22    }
23    fn from_digits(digits: i32) -> Result<Year<Self>, Error> {
24        Year::new(digits)
25    }
26}
27
28impl<const N: usize> YearDigits for ExtendedYear<N> {
29    fn digits() -> usize {
30        N
31    }
32    fn from_digits(digits: i32) -> Result<Year<Self>, Error> {
33        Year::new_extended(digits)
34    }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
38pub struct Year<Y = SimpleYear>(i32, Y);
39
40impl<const N: usize> Year<ExtendedYear<N>> {
41    pub fn new_extended(year: i32) -> Result<Self, Error> {
42        if year == 0 {
43            return Ok(Self(year, ExtendedYear));
44        }
45        let year = match year.checked_abs().and_then(|x| x.checked_ilog10()) {
46            Some(num) if (num as usize) < N => year,
47            Some(_) => return Err(Error::Range),
48            None => return Err(Error::Range),
49        };
50        Ok(Self(year, ExtendedYear))
51    }
52}
53
54impl Year<SimpleYear> {
55    pub fn new(year: i32) -> Result<Self, Error> {
56        if year < 0 || year > 9999 {
57            return Err(Error::Range);
58        }
59
60        Ok(Self(year, SimpleYear))
61    }
62}
63
64#[cfg(test)]
65mod year_test {
66    use super::{ExtendedYear, Year};
67
68    #[test]
69    fn test_year_4_digits() {
70        assert!(Year::new(0).is_ok());
71        assert!(Year::new(1).is_ok());
72        assert!(Year::new(9999).is_ok());
73        assert!(Year::new(10000).is_err());
74        assert_eq!(format!("{}", Year::new(1).unwrap()), "0001");
75    }
76
77    #[test]
78    fn test_negative_years() {
79        assert!(Year::new(-1).is_err());
80        assert!(Year::<ExtendedYear<6>>::new_extended(-1).is_ok());
81        assert_eq!(
82            format!("{}", Year::<ExtendedYear<6>>::new_extended(-1).unwrap()),
83            "-000001"
84        );
85    }
86
87    #[test]
88    fn test_big_years() {
89        assert!(Year::<ExtendedYear<6>>::new_extended(100000).is_ok());
90        assert_eq!(
91            format!("{}", Year::<ExtendedYear<6>>::new_extended(1).unwrap()),
92            "+000001"
93        );
94    }
95}
96
97impl fmt::Display for Year<SimpleYear> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "{:0>width$}", self.0, width = 4)
100    }
101}
102
103impl<const N: usize> fmt::Display for Year<ExtendedYear<N>> {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(
106            f,
107            "{}{:0>width$}",
108            if self.0 < 0 { "-" } else { "+" },
109            (self.0 as i64).abs(),
110            width = N
111        )
112    }
113}
114
115macro_rules! impl_try_from {
116    ($primitive:ty, $structtype:ident) => {
117        impl TryFrom<$primitive> for $structtype {
118            type Error = Error;
119            #[allow(unused_comparisons)]
120            fn try_from(value: $primitive) -> Result<Self, Self::Error> {
121                if value < 0 {
122                    return Err(Error::Range);
123                }
124                $structtype::new(value as i32)
125            }
126        }
127    };
128}
129
130impl_try_from!(u8, Year);
131impl_try_from!(u16, Year);
132impl_try_from!(u32, Year);
133impl_try_from!(u64, Year);
134impl_try_from!(i8, Year);
135impl_try_from!(i16, Year);
136impl_try_from!(i32, Year);
137impl_try_from!(i64, Year);
138
139macro_rules! impl_into {
140    ($primitive:ty, $structtype:ident) => {
141        impl From<$structtype> for $primitive {
142            fn from(value: $structtype) -> $primitive {
143                value.0 as $primitive
144            }
145        }
146    };
147}
148
149impl_into!(u8, Year);
150impl_into!(u16, Year);
151impl_into!(u32, Year);
152impl_into!(u64, Year);
153impl_into!(i16, Year);
154impl_into!(i32, Year);
155impl_into!(i64, Year);
156
157/// Month of the year (1-12)
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
159pub struct Month(u8);
160
161impl Month {
162    pub fn new(month: u64) -> Result<Self, Error> {
163        if month == 0 {
164            return Err(Error::Range);
165        }
166        if month > 12 {
167            return Err(Error::Range);
168        }
169        Ok(Self(month as u8))
170    }
171}
172
173impl fmt::Display for Month {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "{:0>2}", self.0)
176    }
177}
178
179macro_rules! impl_try_from {
180    ($primitive:ty, $structtype:ident) => {
181        impl TryFrom<$primitive> for $structtype {
182            type Error = Error;
183            #[allow(unused_comparisons)]
184            fn try_from(value: $primitive) -> Result<Self, Self::Error> {
185                if value < 0 {
186                    return Err(Error::Range);
187                }
188                $structtype::new(value as u64)
189            }
190        }
191    };
192}
193
194impl_try_from!(u8, Month);
195impl_try_from!(u16, Month);
196impl_try_from!(u32, Month);
197impl_try_from!(u64, Month);
198impl_try_from!(i8, Month);
199impl_try_from!(i16, Month);
200impl_try_from!(i32, Month);
201impl_try_from!(i64, Month);
202
203impl_into!(u8, Month);
204impl_into!(u16, Month);
205impl_into!(u32, Month);
206impl_into!(u64, Month);
207impl_into!(i16, Month);
208impl_into!(i32, Month);
209impl_into!(i64, Month);
210
211/// Week of the year (1-53)
212#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
213pub struct Week(u8);
214
215impl Week {
216    pub fn new(week: u64) -> Result<Self, Error> {
217        if week > 53 {
218            return Err(Error::Range);
219        }
220        Ok(Self(week as u8))
221    }
222}
223
224impl fmt::Display for Week {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(f, "W{:0>2}", self.0)
227    }
228}
229
230impl_try_from!(u8, Week);
231impl_try_from!(u16, Week);
232impl_try_from!(u32, Week);
233impl_try_from!(u64, Week);
234impl_try_from!(i8, Week);
235impl_try_from!(i16, Week);
236impl_try_from!(i32, Week);
237impl_try_from!(i64, Week);
238
239impl_into!(u8, Week);
240impl_into!(u16, Week);
241impl_into!(u32, Week);
242impl_into!(u64, Week);
243impl_into!(i16, Week);
244impl_into!(i32, Week);
245impl_into!(i64, Week);
246
247/// Day of the month (1-31)
248#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
249pub struct Day(u8);
250
251impl Day {
252    pub fn new(day: u64) -> Result<Self, Error> {
253        if day == 0 {
254            return Err(Error::Range);
255        }
256        if day > 31 {
257            return Err(Error::Range);
258        }
259        Ok(Self(day as u8))
260    }
261}
262
263impl fmt::Display for Day {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        write!(f, "{:0>2}", self.0)
266    }
267}
268
269macro_rules! impl_try_from {
270    ($primitive:ty, $structtype:ident) => {
271        impl TryFrom<$primitive> for $structtype {
272            type Error = Error;
273            #[allow(unused_comparisons)]
274            fn try_from(value: $primitive) -> Result<Self, Self::Error> {
275                if value < 0 {
276                    return Err(Error::Range);
277                }
278                $structtype::new(value as u64)
279            }
280        }
281    };
282}
283
284impl_try_from!(u8, Day);
285impl_try_from!(u16, Day);
286impl_try_from!(u32, Day);
287impl_try_from!(u64, Day);
288impl_try_from!(i8, Day);
289impl_try_from!(i16, Day);
290impl_try_from!(i32, Day);
291impl_try_from!(i64, Day);
292
293impl_into!(u8, Day);
294impl_into!(u16, Day);
295impl_into!(u32, Day);
296impl_into!(u64, Day);
297impl_into!(i16, Day);
298impl_into!(i32, Day);
299impl_into!(i64, Day);
300
301/// Hours (0-60)
302#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
303pub struct Hour(u8);
304
305impl Hour {
306    pub fn new(hour: u64) -> Result<Hour, Error> {
307        if hour > 24 {
308            return Err(Error::Range);
309        }
310        Ok(Hour(hour as u8))
311    }
312}
313
314impl fmt::Display for Hour {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(f, "{:0>2}", self.0)
317    }
318}
319
320macro_rules! impl_try_from {
321    ($primitive:ty, $structtype:ident) => {
322        impl TryFrom<$primitive> for $structtype {
323            type Error = Error;
324            #[allow(unused_comparisons)]
325            fn try_from(value: $primitive) -> Result<Self, Self::Error> {
326                if value < 0 {
327                    return Err(Error::Range);
328                }
329                $structtype::new(value as u64)
330            }
331        }
332    };
333}
334
335impl_try_from!(u8, Hour);
336impl_try_from!(u16, Hour);
337impl_try_from!(u32, Hour);
338impl_try_from!(u64, Hour);
339impl_try_from!(i8, Hour);
340impl_try_from!(i16, Hour);
341impl_try_from!(i32, Hour);
342impl_try_from!(i64, Hour);
343
344impl_into!(u8, Hour);
345impl_into!(u16, Hour);
346impl_into!(u32, Hour);
347impl_into!(u64, Hour);
348impl_into!(i16, Hour);
349impl_into!(i32, Hour);
350impl_into!(i64, Hour);
351
352/// Minutes (0-60)
353#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
354pub struct Minute(u8);
355
356impl Minute {
357    pub fn new(minute: u64) -> Result<Minute, Error> {
358        if minute > 60 {
359            return Err(Error::Range);
360        }
361        Ok(Minute(minute as u8))
362    }
363}
364
365impl fmt::Display for Minute {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        write!(f, "{:0>2}", self.0)
368    }
369}
370
371impl_try_from!(u8, Minute);
372impl_try_from!(u16, Minute);
373impl_try_from!(u32, Minute);
374impl_try_from!(u64, Minute);
375impl_try_from!(i8, Minute);
376impl_try_from!(i16, Minute);
377impl_try_from!(i32, Minute);
378impl_try_from!(i64, Minute);
379
380impl_into!(u8, Minute);
381impl_into!(u16, Minute);
382impl_into!(u32, Minute);
383impl_into!(u64, Minute);
384impl_into!(i16, Minute);
385impl_into!(i32, Minute);
386impl_into!(i64, Minute);
387
388/// Seconds (0-61)
389#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
390pub struct Second(u8);
391
392impl Second {
393    pub fn new(second: u64) -> Result<Second, Error> {
394        if second > 61 {
395            return Err(Error::Range);
396        }
397        Ok(Second(second as u8))
398    }
399}
400
401impl fmt::Display for Second {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        write!(f, "{:0>2}", self.0)
404    }
405}
406
407impl_try_from!(u8, Second);
408impl_try_from!(u16, Second);
409impl_try_from!(u32, Second);
410impl_try_from!(u64, Second);
411impl_try_from!(i8, Second);
412impl_try_from!(i16, Second);
413impl_try_from!(i32, Second);
414impl_try_from!(i64, Second);
415
416impl_into!(u8, Second);
417impl_into!(u16, Second);
418impl_into!(u32, Second);
419impl_into!(u64, Second);
420impl_into!(i16, Second);
421impl_into!(i32, Second);
422impl_into!(i64, Second);
423
424/// Used in combination with [`Second`] to signify subsecond fractions
425#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
426pub struct Nanosecond(u32);
427
428impl Nanosecond {
429    pub fn new(nanoseconds: u64) -> Result<Self, Error> {
430        if nanoseconds >= 1_000_000_000 {
431            return Err(Error::Range);
432        }
433        Ok(Self(nanoseconds as u32))
434    }
435}
436
437impl fmt::Display for Nanosecond {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        f.pad_integral(true, "", &self.0.to_string())
440    }
441}
442
443macro_rules! impl_try_from {
444    ($primitive:ty, $structtype:ident) => {
445        impl TryFrom<$primitive> for $structtype {
446            type Error = Error;
447            #[allow(unused_comparisons)]
448            fn try_from(value: $primitive) -> Result<Self, Self::Error> {
449                if value < 0 {
450                    return Err(Error::Range);
451                }
452                $structtype::new(value as u64)
453            }
454        }
455    };
456}
457
458impl_try_from!(u8, Nanosecond);
459impl_try_from!(u16, Nanosecond);
460impl_try_from!(u32, Nanosecond);
461impl_try_from!(i8, Nanosecond);
462impl_try_from!(i16, Nanosecond);
463impl_try_from!(i32, Nanosecond);
464
465impl_into!(u32, Nanosecond);
466impl_into!(u64, Nanosecond);
467impl_into!(i32, Nanosecond);
468impl_into!(i64, Nanosecond);
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq)]
471pub enum Timeshift {
472    Utc,
473    Offset {
474        non_negative: bool,
475        hours: Hour,
476        minutes: Minute,
477    },
478}
479
480impl Timeshift {
481    pub fn utc() -> Self {
482        Self::Utc
483    }
484    pub fn offset(non_negative: bool, hours: Hour, minutes: Minute) -> Self {
485        Self::Offset {
486            non_negative,
487            hours,
488            minutes,
489        }
490    }
491    pub fn positive_offset(hours: Hour, minutes: Minute) -> Self {
492        Self::Offset {
493            non_negative: true,
494            hours,
495            minutes,
496        }
497    }
498    pub fn negative_offset(hours: Hour, minutes: Minute) -> Self {
499        Self::Offset {
500            non_negative: false,
501            hours,
502            minutes,
503        }
504    }
505
506    pub(crate) fn seconds_from_east(&self) -> i32 {
507        match self {
508            Timeshift::Utc => 0,
509            Timeshift::Offset {
510                non_negative,
511                hours,
512                minutes,
513            } => {
514                let hours: i32 = (*hours).into();
515                let minutes: i32 = (*minutes).into();
516                let sign = if *non_negative { 1 } else { -1 };
517
518                sign * hours * 3600 + minutes * 60
519            }
520        }
521    }
522}
523
524impl fmt::Display for Timeshift {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        match self {
527            Self::Utc => write!(f, "Z"),
528            Self::Offset {
529                non_negative,
530                hours,
531                minutes,
532            } if *non_negative => write!(f, "+{}:{}", hours, minutes),
533            Self::Offset {
534                non_negative: _,
535                hours,
536                minutes,
537            } => write!(f, "-{}:{}", hours, minutes),
538        }
539    }
540}
541
542impl TryFrom<(i32, i32)> for Timeshift {
543    type Error = Error;
544
545    fn try_from((h, m): (i32, i32)) -> Result<Self, Self::Error> {
546        if m < 0 {
547            return Err(Error::Range);
548        }
549        if h < 0 {
550            Ok(Timeshift::Offset {
551                non_negative: false,
552                hours: h.abs().try_into()?,
553                minutes: m.try_into()?,
554            })
555        } else {
556            Ok(Timeshift::Offset {
557                non_negative: true,
558                hours: h.abs().try_into()?,
559                minutes: m.try_into()?,
560            })
561        }
562    }
563}