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#[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#[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#[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#[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#[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#[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#[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}