1#![allow(clippy::trivially_copy_pass_by_ref)]
4
5use chrono::{
6 offset::{Offset, TimeZone}, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc
7};
8use serde::{Deserialize, Serialize};
9use std::{
10 cmp::Ordering, convert::TryInto, error::Error, fmt::{self, Display}, str::FromStr
11};
12
13use super::AmadeusOrd;
14
15const JULIAN_DAY_OF_EPOCH: i64 = 2_440_588;
16const GREGORIAN_DAY_OF_EPOCH: i64 = 719_163;
17
18const TODO: &str = "not implemented yet";
19
20#[derive(Clone, PartialEq, Eq, Debug)]
21pub struct ParseDateError;
22impl Display for ParseDateError {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "error parsing date")
25 }
26}
27impl Error for ParseDateError {}
28
29#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
32pub struct Timezone {
33 inner: TimezoneInner,
34}
35#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
36enum TimezoneInner {
37 Variable(chrono_tz::Tz), Fixed(i32), }
40impl Timezone {
41 pub const UTC: Self = Self {
42 inner: TimezoneInner::Fixed(0),
43 };
44 pub const GMT: Self = Self {
45 inner: TimezoneInner::Fixed(0),
46 };
47 pub const GMT_MINUS_1: Self = Self {
48 inner: TimezoneInner::Fixed(-60 * 60),
49 };
50 pub const GMT_MINUS_2: Self = Self {
51 inner: TimezoneInner::Fixed(-2 * 60 * 60),
52 };
53 pub const GMT_MINUS_3: Self = Self {
54 inner: TimezoneInner::Fixed(-3 * 60 * 60),
55 };
56 pub const GMT_MINUS_4: Self = Self {
57 inner: TimezoneInner::Fixed(-4 * 60 * 60),
58 };
59 pub const GMT_MINUS_5: Self = Self {
60 inner: TimezoneInner::Fixed(-5 * 60 * 60),
61 };
62 pub const GMT_MINUS_6: Self = Self {
63 inner: TimezoneInner::Fixed(-6 * 60 * 60),
64 };
65 pub const GMT_MINUS_7: Self = Self {
66 inner: TimezoneInner::Fixed(-7 * 60 * 60),
67 };
68 pub const GMT_MINUS_8: Self = Self {
69 inner: TimezoneInner::Fixed(-8 * 60 * 60),
70 };
71 pub const GMT_MINUS_9: Self = Self {
72 inner: TimezoneInner::Fixed(-9 * 60 * 60),
73 };
74 pub const GMT_MINUS_10: Self = Self {
75 inner: TimezoneInner::Fixed(-10 * 60 * 60),
76 };
77 pub const GMT_MINUS_11: Self = Self {
78 inner: TimezoneInner::Fixed(-11 * 60 * 60),
79 };
80 pub const GMT_MINUS_12: Self = Self {
81 inner: TimezoneInner::Fixed(-12 * 60 * 60),
82 };
83 pub const GMT_PLUS_1: Self = Self {
84 inner: TimezoneInner::Fixed(60 * 60),
85 };
86 pub const GMT_PLUS_2: Self = Self {
87 inner: TimezoneInner::Fixed(2 * 60 * 60),
88 };
89 pub const GMT_PLUS_3: Self = Self {
90 inner: TimezoneInner::Fixed(3 * 60 * 60),
91 };
92 pub const GMT_PLUS_4: Self = Self {
93 inner: TimezoneInner::Fixed(4 * 60 * 60),
94 };
95 pub const GMT_PLUS_5: Self = Self {
96 inner: TimezoneInner::Fixed(4 * 60 * 60),
97 };
98 pub const GMT_PLUS_6: Self = Self {
99 inner: TimezoneInner::Fixed(5 * 60 * 60),
100 };
101 pub const GMT_PLUS_7: Self = Self {
102 inner: TimezoneInner::Fixed(6 * 60 * 60),
103 };
104 pub const GMT_PLUS_8: Self = Self {
105 inner: TimezoneInner::Fixed(7 * 60 * 60),
106 };
107 pub const GMT_PLUS_9: Self = Self {
108 inner: TimezoneInner::Fixed(8 * 60 * 60),
109 };
110 pub const GMT_PLUS_10: Self = Self {
111 inner: TimezoneInner::Fixed(10 * 60 * 60),
112 };
113 pub const GMT_PLUS_11: Self = Self {
114 inner: TimezoneInner::Fixed(11 * 60 * 60),
115 };
116 pub const GMT_PLUS_12: Self = Self {
117 inner: TimezoneInner::Fixed(12 * 60 * 60),
118 };
119 pub const GMT_PLUS_13: Self = Self {
120 inner: TimezoneInner::Fixed(13 * 60 * 60),
121 };
122 pub const GMT_PLUS_14: Self = Self {
123 inner: TimezoneInner::Fixed(14 * 60 * 60),
124 };
125
126 pub fn from_name(name: &str) -> Option<Self> {
128 use chrono_tz::*;
129 #[rustfmt::skip]
130 #[allow(non_upper_case_globals)] let inner = match name.parse().ok()? {
132 UTC | UCT | Universal | Zulu | GMT | GMT0 | GMTMinus0 | GMTPlus0 | Greenwich |
133 Etc::UTC | Etc::UCT | Etc::Universal | Etc::Zulu | Etc::GMT | Etc::GMT0 | Etc::GMTMinus0 | Etc::GMTPlus0 | Etc::Greenwich => {
134 TimezoneInner::Fixed(0)
135 }
136 Etc::GMTPlus1 => TimezoneInner::Fixed(-60 * 60),
137 Etc::GMTPlus2 => TimezoneInner::Fixed(-2 * 60 * 60),
138 Etc::GMTPlus3 => TimezoneInner::Fixed(-3 * 60 * 60),
139 Etc::GMTPlus4 => TimezoneInner::Fixed(-4 * 60 * 60),
140 Etc::GMTPlus5 => TimezoneInner::Fixed(-5 * 60 * 60),
141 Etc::GMTPlus6 => TimezoneInner::Fixed(-6 * 60 * 60),
142 Etc::GMTPlus7 => TimezoneInner::Fixed(-7 * 60 * 60),
143 Etc::GMTPlus8 => TimezoneInner::Fixed(-8 * 60 * 60),
144 Etc::GMTPlus9 => TimezoneInner::Fixed(-9 * 60 * 60),
145 Etc::GMTPlus10 => TimezoneInner::Fixed(-10 * 60 * 60),
146 Etc::GMTPlus11 => TimezoneInner::Fixed(-11 * 60 * 60),
147 Etc::GMTPlus12 => TimezoneInner::Fixed(-12 * 60 * 60),
148 Etc::GMTMinus1 => TimezoneInner::Fixed(60 * 60),
149 Etc::GMTMinus2 => TimezoneInner::Fixed(2 * 60 * 60),
150 Etc::GMTMinus3 => TimezoneInner::Fixed(3 * 60 * 60),
151 Etc::GMTMinus4 => TimezoneInner::Fixed(4 * 60 * 60),
152 Etc::GMTMinus5 => TimezoneInner::Fixed(5 * 60 * 60),
153 Etc::GMTMinus6 => TimezoneInner::Fixed(6 * 60 * 60),
154 Etc::GMTMinus7 => TimezoneInner::Fixed(7 * 60 * 60),
155 Etc::GMTMinus8 => TimezoneInner::Fixed(8 * 60 * 60),
156 Etc::GMTMinus9 => TimezoneInner::Fixed(9 * 60 * 60),
157 Etc::GMTMinus10 => TimezoneInner::Fixed(10 * 60 * 60),
158 Etc::GMTMinus11 => TimezoneInner::Fixed(11 * 60 * 60),
159 Etc::GMTMinus12 => TimezoneInner::Fixed(12 * 60 * 60),
160 Etc::GMTMinus13 => TimezoneInner::Fixed(13 * 60 * 60),
161 Etc::GMTMinus14 => TimezoneInner::Fixed(14 * 60 * 60),
162 tz => TimezoneInner::Variable(tz),
163 };
164 Some(Self { inner })
165 }
166 pub fn as_name(&self) -> Option<&'static str> {
168 use chrono_tz::*;
169 match self.inner {
170 TimezoneInner::Variable(tz) => Some(tz),
171 TimezoneInner::Fixed(offset) => match offset {
172 0 => Some(Etc::GMT),
173 -3600 => Some(Etc::GMTPlus1),
174 -7200 => Some(Etc::GMTPlus2),
175 -10800 => Some(Etc::GMTPlus3),
176 -14400 => Some(Etc::GMTPlus4),
177 -18000 => Some(Etc::GMTPlus5),
178 -21600 => Some(Etc::GMTPlus6),
179 -25200 => Some(Etc::GMTPlus7),
180 -28800 => Some(Etc::GMTPlus8),
181 -32400 => Some(Etc::GMTPlus9),
182 -36000 => Some(Etc::GMTPlus10),
183 -39600 => Some(Etc::GMTPlus11),
184 -43200 => Some(Etc::GMTPlus12),
185 3600 => Some(Etc::GMTMinus1),
186 7200 => Some(Etc::GMTMinus2),
187 10800 => Some(Etc::GMTMinus3),
188 14400 => Some(Etc::GMTMinus4),
189 18000 => Some(Etc::GMTMinus5),
190 21600 => Some(Etc::GMTMinus6),
191 25200 => Some(Etc::GMTMinus7),
192 28800 => Some(Etc::GMTMinus8),
193 32400 => Some(Etc::GMTMinus9),
194 36000 => Some(Etc::GMTMinus10),
195 39600 => Some(Etc::GMTMinus11),
196 43200 => Some(Etc::GMTMinus12),
197 46800 => Some(Etc::GMTMinus13),
198 50400 => Some(Etc::GMTMinus14),
199 _ => None,
200 },
201 }
202 .map(Tz::name)
203 }
204 pub fn from_offset(seconds: i32) -> Option<Self> {
206 FixedOffset::east_opt(seconds).map(|offset| Self {
207 inner: TimezoneInner::Fixed(offset.local_minus_utc()),
208 })
209 }
210 pub fn as_offset(&self) -> Option<i32> {
212 match self.inner {
213 TimezoneInner::Variable(_tz) => None,
214 TimezoneInner::Fixed(seconds) => Some(seconds),
215 }
216 }
217 pub fn as_offset_at(&self, utc_date_time: &DateTime) -> i32 {
219 assert_eq!(utc_date_time.timezone, Self::UTC);
220 match self.inner {
221 TimezoneInner::Variable(tz) => tz
222 .offset_from_utc_datetime(&utc_date_time.date_time.as_chrono().expect(TODO))
223 .fix()
224 .local_minus_utc(),
225 TimezoneInner::Fixed(seconds) => seconds,
226 }
227 }
228 #[doc(hidden)]
229 pub fn from_chrono<Tz>(_timezone: &Tz, offset: &Tz::Offset) -> Self
230 where
231 Tz: TimeZone,
232 {
233 Self::from_offset(offset.fix().local_minus_utc()).unwrap() }
235 #[doc(hidden)]
236 pub fn as_chrono(&self) -> ChronoTimezone {
237 ChronoTimezone(*self)
238 }
239}
240impl PartialOrd for Timezone {
241 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
242 Some(Ord::cmp(self, other))
243 }
244}
245impl Ord for Timezone {
246 fn cmp(&self, other: &Self) -> Ordering {
247 match (self.inner, other.inner) {
248 (TimezoneInner::Variable(a), TimezoneInner::Variable(b)) => (a as u32).cmp(&(b as u32)),
249 (TimezoneInner::Fixed(a), TimezoneInner::Fixed(b)) => a.cmp(&b),
250 (TimezoneInner::Variable(_), _) => Ordering::Less,
251 (TimezoneInner::Fixed(_), _) => Ordering::Greater,
252 }
253 }
254}
255impl AmadeusOrd for Timezone {
256 fn amadeus_cmp(&self, other: &Self) -> Ordering {
257 Ord::cmp(self, other)
258 }
259}
260impl Display for Timezone {
261 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262 self.as_chrono().fmt(f)
263 }
264}
265impl FromStr for Timezone {
266 type Err = ParseDateError;
267
268 fn from_str(_s: &str) -> Result<Self, Self::Err> {
269 unimplemented!()
270 }
271}
272
273#[doc(hidden)]
274#[derive(Clone, Debug)]
275pub struct ChronoTimezone(Timezone);
276#[doc(hidden)]
277#[derive(Clone, Debug)]
278pub struct ChronoTimezoneOffset(Timezone, FixedOffset);
279impl TimeZone for ChronoTimezone {
280 type Offset = ChronoTimezoneOffset;
281
282 fn from_offset(offset: &Self::Offset) -> Self {
283 ChronoTimezone(offset.0)
284 }
285 fn offset_from_local_date(&self, _local: &NaiveDate) -> chrono::LocalResult<Self::Offset> {
286 unimplemented!()
287 }
288 fn offset_from_local_datetime(
289 &self, _local: &NaiveDateTime,
290 ) -> chrono::LocalResult<Self::Offset> {
291 unimplemented!()
292 }
293 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
294 unimplemented!()
295 }
296 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
297 ChronoTimezoneOffset(
298 self.0,
299 FixedOffset::east(self.0.as_offset_at(&DateTime::from_chrono(
300 &chrono::DateTime::<Utc>::from_utc(*utc, Utc),
301 ))),
302 )
303 }
304}
305impl Offset for ChronoTimezoneOffset {
306 fn fix(&self) -> FixedOffset {
307 self.1
308 }
309}
310impl Display for ChronoTimezone {
311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312 match self.0.inner {
313 TimezoneInner::Variable(tz) => f.write_str(tz.name()),
314 TimezoneInner::Fixed(offset) => Display::fmt(&FixedOffset::east(offset), f),
315 }
316 }
317}
318impl Display for ChronoTimezoneOffset {
319 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320 Display::fmt(&ChronoTimezone(self.0), f)
321 }
322}
323
324#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
325pub struct Date {
326 date: DateWithoutTimezone, timezone: Timezone,
328}
329impl Date {
330 pub fn new(year: i64, month: u8, day: u8, timezone: Timezone) -> Option<Self> {
331 DateWithoutTimezone::new(year, month, day).map(|date| Self { date, timezone })
332 }
333 pub fn from_ordinal(year: i64, day: u16, timezone: Timezone) -> Option<Self> {
334 DateWithoutTimezone::from_ordinal(year, day).map(|date| Self { date, timezone })
335 }
336 pub fn year(&self) -> i64 {
337 self.date.year()
338 }
339 pub fn month(&self) -> u8 {
340 self.date.month()
341 }
342 pub fn day(&self) -> u8 {
343 self.date.day()
344 }
345 pub fn ordinal(&self) -> u16 {
346 self.date.ordinal()
347 }
348 pub fn without_timezone(&self) -> DateWithoutTimezone {
349 self.date
350 }
351 pub fn timezone(&self) -> Timezone {
352 self.timezone
353 }
354 pub fn from_days(days: i64, timezone: Timezone) -> Option<Self> {
356 DateWithoutTimezone::from_days(days).map(|date| Self { date, timezone })
357 }
358 pub fn as_days(&self) -> i64 {
360 self.date.as_days()
361 }
362 #[doc(hidden)]
363 pub fn from_chrono<Tz>(date: &chrono::Date<Tz>) -> Self
364 where
365 Tz: TimeZone,
366 {
367 Self::new(
368 date.year().into(),
369 date.month().try_into().unwrap(),
370 date.day().try_into().unwrap(),
371 Timezone::from_chrono(&date.timezone(), date.offset()),
372 )
373 .unwrap()
374 }
375 #[doc(hidden)]
376 pub fn as_chrono(&self) -> Option<chrono::Date<ChronoTimezone>> {
377 Some(
378 Utc.ymd(
379 self.year().try_into().ok()?,
380 self.month().into(),
381 self.day().into(),
382 )
383 .with_timezone(&ChronoTimezone(self.timezone)),
384 )
385 }
386}
387impl AmadeusOrd for Date {
388 fn amadeus_cmp(&self, other: &Self) -> Ordering {
389 Ord::cmp(self, other)
390 }
391}
392impl Display for Date {
394 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395 write!(
396 f,
397 "{:04}-{:02}-{:02} {}",
398 self.year(),
399 self.month(),
400 self.day(),
401 self.timezone()
402 )
403 }
404}
405impl FromStr for Date {
406 type Err = ParseDateError;
407
408 fn from_str(s: &str) -> Result<Self, Self::Err> {
409 chrono::DateTime::parse_from_str(s, "%Y-%m-%d%:z")
410 .map(|date| Self::from_chrono(&date.date()))
411 .map_err(|_| ParseDateError)
412 }
413}
414
415#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
416pub struct Time {
417 time: TimeWithoutTimezone, timezone: Timezone,
419}
420impl Time {
421 pub fn new(
427 hour: u8, minute: u8, second: u8, nanosecond: u32, timezone: Timezone,
428 ) -> Option<Self> {
429 TimeWithoutTimezone::new(hour, minute, second, nanosecond)
430 .map(|time| Self { time, timezone })
431 }
432 pub fn from_seconds(seconds: u32, nanosecond: u32, timezone: Timezone) -> Option<Self> {
438 TimeWithoutTimezone::from_seconds(seconds, nanosecond).map(|time| Self { time, timezone })
439 }
440 pub fn hour(&self) -> u8 {
441 self.time.hour()
442 }
443 pub fn minute(&self) -> u8 {
444 self.time.minute()
445 }
446 pub fn second(&self) -> u8 {
447 self.time.second()
448 }
449 pub fn nanosecond(&self) -> u32 {
450 self.time.nanosecond()
451 }
452 pub fn without_timezone(&self) -> TimeWithoutTimezone {
453 self.time
454 }
455 pub fn timezone(&self) -> Timezone {
456 self.timezone
457 }
458 pub fn truncate_minutes(&self, minutes: u8) -> Self {
459 Self {
460 time: self.time.truncate_minutes(minutes),
461 timezone: self.timezone,
462 }
463 }
464}
465impl AmadeusOrd for Time {
466 fn amadeus_cmp(&self, other: &Self) -> Ordering {
467 Ord::cmp(self, other)
468 }
469}
470impl Display for Time {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 write!(
474 f,
475 "{}{}",
476 self.time.as_chrono().expect(TODO).format("%H:%M:%S%.9f"),
477 ChronoTimezone(self.timezone)
478 )
479 }
480}
481impl FromStr for Time {
482 type Err = ParseDateError;
483
484 fn from_str(_s: &str) -> Result<Self, Self::Err> {
485 unimplemented!()
486 }
487}
488
489#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
490pub struct DateTime {
491 date_time: DateTimeWithoutTimezone, timezone: Timezone,
493}
494impl DateTime {
495 #[allow(clippy::too_many_arguments)]
496 pub fn new(
497 year: i64, month: u8, day: u8, hour: u8, minute: u8, second: u8, nanosecond: u32,
498 timezone: Timezone,
499 ) -> Option<Self> {
500 DateTimeWithoutTimezone::new(year, month, day, hour, minute, second, nanosecond).map(
501 |date_time| Self {
502 date_time,
503 timezone,
504 },
505 )
506 }
507 pub fn from_date_time(date: Date, time: Time) -> Option<Self> {
509 let timezone = date.timezone();
510 if timezone != time.timezone() {
511 return None;
512 }
513 let date_time = DateTimeWithoutTimezone::from_date_time(
514 date.without_timezone(),
515 time.without_timezone(),
516 )?;
517 Some(Self {
518 date_time,
519 timezone,
520 })
521 }
522 pub fn date(&self) -> DateWithoutTimezone {
523 self.date_time.date()
524 }
525 pub fn time(&self) -> TimeWithoutTimezone {
526 self.date_time.time()
527 }
528 pub fn year(&self) -> i64 {
529 self.date_time.year()
530 }
531 pub fn month(&self) -> u8 {
532 self.date_time.month()
533 }
534 pub fn day(&self) -> u8 {
535 self.date_time.day()
536 }
537 pub fn hour(&self) -> u8 {
538 self.date_time.hour()
539 }
540 pub fn minute(&self) -> u8 {
541 self.date_time.minute()
542 }
543 pub fn second(&self) -> u8 {
544 self.date_time.second()
545 }
546 pub fn nanosecond(&self) -> u32 {
547 self.date_time.nanosecond()
548 }
549 #[doc(hidden)]
550 pub fn from_chrono<Tz>(date_time: &chrono::DateTime<Tz>) -> Self
551 where
552 Tz: TimeZone,
553 {
554 Self::new(
555 date_time.year().into(),
556 date_time.month().try_into().unwrap(),
557 date_time.day().try_into().unwrap(),
558 date_time.hour().try_into().unwrap(),
559 date_time.minute().try_into().unwrap(),
560 date_time.second().try_into().unwrap(),
561 date_time.nanosecond(),
562 Timezone::from_chrono(&date_time.timezone(), date_time.offset()),
563 )
564 .unwrap()
565 }
566 #[doc(hidden)]
567 pub fn as_chrono(&self) -> Option<chrono::DateTime<ChronoTimezone>> {
568 Some(
569 chrono::DateTime::<Utc>::from_utc(self.date_time.as_chrono()?, Utc)
570 .with_timezone(&ChronoTimezone(self.timezone)),
571 )
572 }
573 pub fn truncate_minutes(&self, minutes: u8) -> Self {
574 Self {
575 date_time: self.date_time.truncate_minutes(minutes),
576 timezone: self.timezone,
577 }
578 }
579}
580impl AmadeusOrd for DateTime {
581 fn amadeus_cmp(&self, other: &Self) -> Ordering {
582 Ord::cmp(self, other)
583 }
584}
585impl Display for DateTime {
587 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
588 write!(
589 f,
590 "{}",
591 self.as_chrono()
592 .expect(TODO)
593 .format("%Y-%m-%d %H:%M:%S%.9f %:z")
594 )
595 }
596}
597impl FromStr for DateTime {
598 type Err = ParseDateError;
599
600 fn from_str(s: &str) -> Result<Self, Self::Err> {
601 chrono::DateTime::<FixedOffset>::from_str(s)
602 .map(|date| Self::from_chrono(&date))
603 .map_err(|_| ParseDateError)
604 }
605}
606
607#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
609pub struct Duration {
610 months: i64,
611 days: i64,
612 nanos: i64,
613}
614impl AmadeusOrd for Duration {
615 fn amadeus_cmp(&self, other: &Self) -> Ordering {
616 Ord::cmp(self, other)
617 }
618}
619
620#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
626pub struct DateWithoutTimezone(i64);
627impl DateWithoutTimezone {
628 pub fn new(year: i64, month: u8, day: u8) -> Option<Self> {
629 NaiveDate::from_ymd_opt(
630 year.try_into().ok()?,
631 month.try_into().ok()?,
632 day.try_into().ok()?,
633 )
634 .as_ref()
635 .map(Self::from_chrono)
636 }
637 pub fn from_ordinal(year: i64, day: u16) -> Option<Self> {
638 NaiveDate::from_yo_opt(year.try_into().ok()?, day.try_into().ok()?)
639 .as_ref()
640 .map(Self::from_chrono)
641 }
642 pub fn year(&self) -> i64 {
643 i64::from(self.as_chrono().expect(TODO).year())
644 }
645 pub fn month(&self) -> u8 {
646 self.as_chrono().expect(TODO).month().try_into().unwrap()
647 }
648 pub fn day(&self) -> u8 {
649 self.as_chrono().expect(TODO).day().try_into().unwrap()
650 }
651 pub fn ordinal(&self) -> u16 {
652 self.as_chrono().expect(TODO).ordinal().try_into().unwrap()
653 }
654 pub fn with_timezone(self, timezone: Timezone) -> Date {
655 Date {
656 date: self,
657 timezone,
658 }
659 }
660 pub fn from_days(days: i64) -> Option<Self> {
662 if JULIAN_DAY_OF_EPOCH + i64::from(i32::min_value()) <= days
663 && days <= i64::from(i32::max_value())
664 {
665 Some(Self(days))
666 } else {
667 None
668 }
669 }
670 pub fn as_days(&self) -> i64 {
672 self.0
673 }
674 #[doc(hidden)]
675 pub fn from_chrono(date: &NaiveDate) -> Self {
676 Self::from_days(i64::from(date.num_days_from_ce()) - GREGORIAN_DAY_OF_EPOCH).unwrap()
677 }
678 #[doc(hidden)]
679 pub fn as_chrono(&self) -> Option<NaiveDate> {
680 NaiveDate::from_num_days_from_ce_opt((self.0 + GREGORIAN_DAY_OF_EPOCH).try_into().ok()?)
681 }
682}
683impl AmadeusOrd for DateWithoutTimezone {
684 fn amadeus_cmp(&self, other: &Self) -> Ordering {
685 Ord::cmp(self, other)
686 }
687}
688impl Display for DateWithoutTimezone {
689 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
690 self.as_chrono().expect(TODO).fmt(f)
691 }
692}
693impl FromStr for DateWithoutTimezone {
694 type Err = ParseDateError;
695
696 fn from_str(s: &str) -> Result<Self, Self::Err> {
697 NaiveDate::from_str(s)
698 .map(|date| Self::from_chrono(&date))
699 .map_err(|_| ParseDateError)
700 }
701}
702
703#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
707pub struct TimeWithoutTimezone(NaiveTime);
708impl TimeWithoutTimezone {
709 pub fn new(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Option<Self> {
715 NaiveTime::from_hms_nano_opt(hour.into(), minute.into(), second.into(), nanosecond)
716 .map(Self)
717 }
718 pub fn from_seconds(seconds: u32, nanosecond: u32) -> Option<Self> {
724 NaiveTime::from_num_seconds_from_midnight_opt(seconds, nanosecond).map(Self)
725 }
726 pub fn hour(&self) -> u8 {
727 self.0.hour().try_into().unwrap()
728 }
729 pub fn minute(&self) -> u8 {
730 self.0.minute().try_into().unwrap()
731 }
732 pub fn second(&self) -> u8 {
733 self.0.second().try_into().unwrap()
734 }
735 pub fn nanosecond(&self) -> u32 {
736 self.0.nanosecond()
737 }
738 pub fn with_timezone(self, timezone: Timezone) -> Time {
739 Time {
740 time: self,
741 timezone,
742 }
743 }
744 #[doc(hidden)]
745 pub fn from_chrono(time: &NaiveTime) -> Self {
746 Self(*time)
747 }
748 #[doc(hidden)]
749 pub fn as_chrono(&self) -> Option<NaiveTime> {
750 Some(self.0)
751 }
752 pub fn truncate_minutes(&self, minutes: u8) -> Self {
753 assert!(
754 minutes != 0 && 60 % minutes == 0,
755 "minutes must be a divisor of 60"
756 );
757 Self::new(self.hour(), self.minute() / minutes * minutes, 0, 0).unwrap()
758 }
759 }
809impl AmadeusOrd for TimeWithoutTimezone {
810 fn amadeus_cmp(&self, other: &Self) -> Ordering {
811 Ord::cmp(self, other)
812 }
813}
814impl Display for TimeWithoutTimezone {
815 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
816 self.as_chrono().expect(TODO).fmt(f)
817 }
818}
819impl FromStr for TimeWithoutTimezone {
820 type Err = ParseDateError;
821
822 fn from_str(s: &str) -> Result<Self, Self::Err> {
823 NaiveTime::from_str(s)
824 .map(|date| Self::from_chrono(&date))
825 .map_err(|_| ParseDateError)
826 }
827}
828
829#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
831pub struct DateTimeWithoutTimezone {
832 date: DateWithoutTimezone,
833 time: TimeWithoutTimezone,
834}
835impl DateTimeWithoutTimezone {
836 pub fn new(
837 year: i64, month: u8, day: u8, hour: u8, minute: u8, second: u8, nanosecond: u32,
838 ) -> Option<Self> {
839 let date = DateWithoutTimezone::new(year, month, day)?;
840 let time = TimeWithoutTimezone::new(hour, minute, second, nanosecond)?;
841 Some(Self { date, time })
842 }
843 pub fn from_date_time(date: DateWithoutTimezone, time: TimeWithoutTimezone) -> Option<Self> {
845 Some(Self { date, time })
846 }
847 pub fn date(&self) -> DateWithoutTimezone {
848 self.date
849 }
850 pub fn time(&self) -> TimeWithoutTimezone {
851 self.time
852 }
853 pub fn year(&self) -> i64 {
854 self.date.year()
855 }
856 pub fn month(&self) -> u8 {
857 self.date.month()
858 }
859 pub fn day(&self) -> u8 {
860 self.date.day()
861 }
862 pub fn hour(&self) -> u8 {
863 self.time.hour()
864 }
865 pub fn minute(&self) -> u8 {
866 self.time.minute()
867 }
868 pub fn second(&self) -> u8 {
869 self.time.second()
870 }
871 pub fn nanosecond(&self) -> u32 {
872 self.time.nanosecond()
873 }
874 pub fn with_timezone(self, timezone: Timezone) -> DateTime {
875 DateTime {
876 date_time: self,
877 timezone,
878 }
879 }
880 #[doc(hidden)]
881 pub fn from_chrono(date_time: &NaiveDateTime) -> Self {
882 Self::new(
883 date_time.year().into(),
884 date_time.month().try_into().unwrap(),
885 date_time.day().try_into().unwrap(),
886 date_time.hour().try_into().unwrap(),
887 date_time.minute().try_into().unwrap(),
888 date_time.second().try_into().unwrap(),
889 date_time.nanosecond(),
890 )
891 .unwrap()
892 }
893 #[doc(hidden)]
894 pub fn as_chrono(&self) -> Option<NaiveDateTime> {
895 Some(self.date.as_chrono()?.and_time(self.time.as_chrono()?))
896 }
897 pub fn truncate_minutes(&self, minutes: u8) -> Self {
898 Self {
899 date: self.date,
900 time: self.time.truncate_minutes(minutes),
901 }
902 }
903 }
981impl AmadeusOrd for DateTimeWithoutTimezone {
982 fn amadeus_cmp(&self, other: &Self) -> Ordering {
983 Ord::cmp(self, other)
984 }
985}
986impl Display for DateTimeWithoutTimezone {
987 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
988 self.as_chrono().expect(TODO).fmt(f)
989 }
990}
991impl FromStr for DateTimeWithoutTimezone {
992 type Err = ParseDateError;
993
994 fn from_str(s: &str) -> Result<Self, Self::Err> {
995 NaiveDateTime::from_str(s)
996 .map(|date| Self::from_chrono(&date))
997 .map_err(|_| ParseDateError)
998 }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::*;
1004
1005 use chrono::NaiveDate;
1006
1007 const SECONDS_PER_DAY: i64 = 86_400;
1008
1009 #[test]
1022 fn timezone() {
1023 assert_eq!(
1024 Timezone::from_name("Etc/GMT-14")
1025 .unwrap()
1026 .as_offset()
1027 .unwrap(),
1028 14 * 60 * 60
1029 );
1030 }
1031
1032 #[test]
1033 fn test_convert_date_to_string() {
1034 fn check_date_conversion(y: i32, m: u32, d: u32) {
1035 let chrono_date = NaiveDate::from_ymd(y, m, d);
1036 let chrono_datetime = chrono_date.and_hms(0, 0, 0);
1037 assert_eq!(chrono_datetime.timestamp() % SECONDS_PER_DAY, 0);
1038 let date =
1039 DateWithoutTimezone::from_days(chrono_datetime.timestamp() / SECONDS_PER_DAY)
1040 .unwrap();
1041 assert_eq!(date.to_string(), chrono_date.to_string());
1042 let date2 = DateWithoutTimezone::from_chrono(&date.as_chrono().unwrap());
1043 assert_eq!(date, date2);
1044 }
1045
1046 check_date_conversion(-262_144, 1, 1);
1047 check_date_conversion(1969, 12, 31);
1048 check_date_conversion(1970, 1, 1);
1049 check_date_conversion(2010, 1, 2);
1050 check_date_conversion(2014, 5, 1);
1051 check_date_conversion(2016, 2, 29);
1052 check_date_conversion(2017, 9, 12);
1053 check_date_conversion(2018, 3, 31);
1054 check_date_conversion(262_143, 12, 31);
1055 }
1056
1057 #[test]
1058 fn test_convert_time_to_string() {
1059 fn check_time_conversion(h: u32, mi: u32, s: u32) {
1060 let chrono_time = NaiveTime::from_hms(h, mi, s);
1061 let time = TimeWithoutTimezone::from_chrono(&chrono_time);
1062 assert_eq!(time.to_string(), chrono_time.to_string());
1063 }
1064
1065 check_time_conversion(13, 12, 54);
1066 check_time_conversion(8, 23, 1);
1067 check_time_conversion(11, 6, 32);
1068 check_time_conversion(16, 38, 0);
1069 check_time_conversion(21, 15, 12);
1070 }
1071
1072 #[test]
1073 fn test_convert_timestamp_to_string() {
1074 #[allow(clippy::many_single_char_names)]
1075 fn check_datetime_conversion(y: i32, m: u32, d: u32, h: u32, mi: u32, s: u32) {
1076 let dt = NaiveDate::from_ymd(y, m, d).and_hms(h, mi, s);
1077 let res = DateTimeWithoutTimezone::from_chrono(&dt).to_string();
1079 let exp = dt.to_string();
1080 assert_eq!(res, exp);
1081 }
1082
1083 check_datetime_conversion(2010, 1, 2, 13, 12, 54);
1084 check_datetime_conversion(2011, 1, 3, 8, 23, 1);
1085 check_datetime_conversion(2012, 4, 5, 11, 6, 32);
1086 check_datetime_conversion(2013, 5, 12, 16, 38, 0);
1087 check_datetime_conversion(2014, 11, 28, 21, 15, 12);
1088 }
1089}