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