#![allow(clippy::trivially_copy_pass_by_ref)]
use chrono::{
offset::{Offset, TimeZone}, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc
};
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering, convert::TryInto, error::Error, fmt::{self, Display}, str::FromStr
};
use super::AmadeusOrd;
const JULIAN_DAY_OF_EPOCH: i64 = 2_440_588;
const GREGORIAN_DAY_OF_EPOCH: i64 = 719_163;
const TODO: &str = "not implemented yet";
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ParseDateError;
impl Display for ParseDateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error parsing date")
}
}
impl Error for ParseDateError {}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Timezone {
inner: TimezoneInner,
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
enum TimezoneInner {
Variable(chrono_tz::Tz),
Fixed(i32),
}
impl Timezone {
pub const UTC: Self = Self {
inner: TimezoneInner::Fixed(0),
};
pub const GMT: Self = Self {
inner: TimezoneInner::Fixed(0),
};
pub const GMT_MINUS_1: Self = Self {
inner: TimezoneInner::Fixed(-60 * 60),
};
pub const GMT_MINUS_2: Self = Self {
inner: TimezoneInner::Fixed(-2 * 60 * 60),
};
pub const GMT_MINUS_3: Self = Self {
inner: TimezoneInner::Fixed(-3 * 60 * 60),
};
pub const GMT_MINUS_4: Self = Self {
inner: TimezoneInner::Fixed(-4 * 60 * 60),
};
pub const GMT_MINUS_5: Self = Self {
inner: TimezoneInner::Fixed(-5 * 60 * 60),
};
pub const GMT_MINUS_6: Self = Self {
inner: TimezoneInner::Fixed(-6 * 60 * 60),
};
pub const GMT_MINUS_7: Self = Self {
inner: TimezoneInner::Fixed(-7 * 60 * 60),
};
pub const GMT_MINUS_8: Self = Self {
inner: TimezoneInner::Fixed(-8 * 60 * 60),
};
pub const GMT_MINUS_9: Self = Self {
inner: TimezoneInner::Fixed(-9 * 60 * 60),
};
pub const GMT_MINUS_10: Self = Self {
inner: TimezoneInner::Fixed(-10 * 60 * 60),
};
pub const GMT_MINUS_11: Self = Self {
inner: TimezoneInner::Fixed(-11 * 60 * 60),
};
pub const GMT_MINUS_12: Self = Self {
inner: TimezoneInner::Fixed(-12 * 60 * 60),
};
pub const GMT_PLUS_1: Self = Self {
inner: TimezoneInner::Fixed(60 * 60),
};
pub const GMT_PLUS_2: Self = Self {
inner: TimezoneInner::Fixed(2 * 60 * 60),
};
pub const GMT_PLUS_3: Self = Self {
inner: TimezoneInner::Fixed(3 * 60 * 60),
};
pub const GMT_PLUS_4: Self = Self {
inner: TimezoneInner::Fixed(4 * 60 * 60),
};
pub const GMT_PLUS_5: Self = Self {
inner: TimezoneInner::Fixed(4 * 60 * 60),
};
pub const GMT_PLUS_6: Self = Self {
inner: TimezoneInner::Fixed(5 * 60 * 60),
};
pub const GMT_PLUS_7: Self = Self {
inner: TimezoneInner::Fixed(6 * 60 * 60),
};
pub const GMT_PLUS_8: Self = Self {
inner: TimezoneInner::Fixed(7 * 60 * 60),
};
pub const GMT_PLUS_9: Self = Self {
inner: TimezoneInner::Fixed(8 * 60 * 60),
};
pub const GMT_PLUS_10: Self = Self {
inner: TimezoneInner::Fixed(10 * 60 * 60),
};
pub const GMT_PLUS_11: Self = Self {
inner: TimezoneInner::Fixed(11 * 60 * 60),
};
pub const GMT_PLUS_12: Self = Self {
inner: TimezoneInner::Fixed(12 * 60 * 60),
};
pub const GMT_PLUS_13: Self = Self {
inner: TimezoneInner::Fixed(13 * 60 * 60),
};
pub const GMT_PLUS_14: Self = Self {
inner: TimezoneInner::Fixed(14 * 60 * 60),
};
pub fn from_name(name: &str) -> Option<Self> {
use chrono_tz::*;
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
let inner = match name.parse().ok()? {
UTC | UCT | Universal | Zulu | GMT | GMT0 | GMTMinus0 | GMTPlus0 | Greenwich |
Etc::UTC | Etc::UCT | Etc::Universal | Etc::Zulu | Etc::GMT | Etc::GMT0 | Etc::GMTMinus0 | Etc::GMTPlus0 | Etc::Greenwich => {
TimezoneInner::Fixed(0)
}
Etc::GMTPlus1 => TimezoneInner::Fixed(-60 * 60),
Etc::GMTPlus2 => TimezoneInner::Fixed(-2 * 60 * 60),
Etc::GMTPlus3 => TimezoneInner::Fixed(-3 * 60 * 60),
Etc::GMTPlus4 => TimezoneInner::Fixed(-4 * 60 * 60),
Etc::GMTPlus5 => TimezoneInner::Fixed(-5 * 60 * 60),
Etc::GMTPlus6 => TimezoneInner::Fixed(-6 * 60 * 60),
Etc::GMTPlus7 => TimezoneInner::Fixed(-7 * 60 * 60),
Etc::GMTPlus8 => TimezoneInner::Fixed(-8 * 60 * 60),
Etc::GMTPlus9 => TimezoneInner::Fixed(-9 * 60 * 60),
Etc::GMTPlus10 => TimezoneInner::Fixed(-10 * 60 * 60),
Etc::GMTPlus11 => TimezoneInner::Fixed(-11 * 60 * 60),
Etc::GMTPlus12 => TimezoneInner::Fixed(-12 * 60 * 60),
Etc::GMTMinus1 => TimezoneInner::Fixed(60 * 60),
Etc::GMTMinus2 => TimezoneInner::Fixed(2 * 60 * 60),
Etc::GMTMinus3 => TimezoneInner::Fixed(3 * 60 * 60),
Etc::GMTMinus4 => TimezoneInner::Fixed(4 * 60 * 60),
Etc::GMTMinus5 => TimezoneInner::Fixed(5 * 60 * 60),
Etc::GMTMinus6 => TimezoneInner::Fixed(6 * 60 * 60),
Etc::GMTMinus7 => TimezoneInner::Fixed(7 * 60 * 60),
Etc::GMTMinus8 => TimezoneInner::Fixed(8 * 60 * 60),
Etc::GMTMinus9 => TimezoneInner::Fixed(9 * 60 * 60),
Etc::GMTMinus10 => TimezoneInner::Fixed(10 * 60 * 60),
Etc::GMTMinus11 => TimezoneInner::Fixed(11 * 60 * 60),
Etc::GMTMinus12 => TimezoneInner::Fixed(12 * 60 * 60),
Etc::GMTMinus13 => TimezoneInner::Fixed(13 * 60 * 60),
Etc::GMTMinus14 => TimezoneInner::Fixed(14 * 60 * 60),
tz => TimezoneInner::Variable(tz),
};
Some(Self { inner })
}
pub fn as_name(&self) -> Option<&'static str> {
use chrono_tz::*;
match self.inner {
TimezoneInner::Variable(tz) => Some(tz),
TimezoneInner::Fixed(offset) => match offset {
0 => Some(Etc::GMT),
-3600 => Some(Etc::GMTPlus1),
-7200 => Some(Etc::GMTPlus2),
-10800 => Some(Etc::GMTPlus3),
-14400 => Some(Etc::GMTPlus4),
-18000 => Some(Etc::GMTPlus5),
-21600 => Some(Etc::GMTPlus6),
-25200 => Some(Etc::GMTPlus7),
-28800 => Some(Etc::GMTPlus8),
-32400 => Some(Etc::GMTPlus9),
-36000 => Some(Etc::GMTPlus10),
-39600 => Some(Etc::GMTPlus11),
-43200 => Some(Etc::GMTPlus12),
3600 => Some(Etc::GMTMinus1),
7200 => Some(Etc::GMTMinus2),
10800 => Some(Etc::GMTMinus3),
14400 => Some(Etc::GMTMinus4),
18000 => Some(Etc::GMTMinus5),
21600 => Some(Etc::GMTMinus6),
25200 => Some(Etc::GMTMinus7),
28800 => Some(Etc::GMTMinus8),
32400 => Some(Etc::GMTMinus9),
36000 => Some(Etc::GMTMinus10),
39600 => Some(Etc::GMTMinus11),
43200 => Some(Etc::GMTMinus12),
46800 => Some(Etc::GMTMinus13),
50400 => Some(Etc::GMTMinus14),
_ => None,
},
}
.map(|tz| tz.name())
}
pub fn from_offset(seconds: i32) -> Option<Self> {
FixedOffset::east_opt(seconds).map(|offset| Self {
inner: TimezoneInner::Fixed(offset.local_minus_utc()),
})
}
pub fn as_offset(&self) -> Option<i32> {
match self.inner {
TimezoneInner::Variable(_tz) => None,
TimezoneInner::Fixed(seconds) => Some(seconds),
}
}
pub fn as_offset_at(&self, utc_date_time: &DateTime) -> i32 {
assert_eq!(utc_date_time.timezone, Self::UTC);
match self.inner {
TimezoneInner::Variable(tz) => tz
.offset_from_utc_datetime(&utc_date_time.date_time.as_chrono().expect(TODO))
.fix()
.local_minus_utc(),
TimezoneInner::Fixed(seconds) => seconds,
}
}
#[doc(hidden)]
pub fn from_chrono<Tz>(_timezone: &Tz, offset: &Tz::Offset) -> Self
where
Tz: TimeZone,
{
Self::from_offset(offset.fix().local_minus_utc()).unwrap()
}
#[doc(hidden)]
pub fn as_chrono(&self) -> ChronoTimezone {
ChronoTimezone(*self)
}
}
impl PartialOrd for Timezone {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Ord for Timezone {
fn cmp(&self, other: &Self) -> Ordering {
match (self.inner, other.inner) {
(TimezoneInner::Variable(a), TimezoneInner::Variable(b)) => (a as u32).cmp(&(b as u32)),
(TimezoneInner::Fixed(a), TimezoneInner::Fixed(b)) => a.cmp(&b),
(TimezoneInner::Variable(_), _) => Ordering::Less,
(TimezoneInner::Fixed(_), _) => Ordering::Greater,
}
}
}
impl AmadeusOrd for Timezone {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for Timezone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_chrono().fmt(f)
}
}
impl FromStr for Timezone {
type Err = ParseDateError;
fn from_str(_s: &str) -> Result<Self, Self::Err> {
unimplemented!()
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct ChronoTimezone(Timezone);
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct ChronoTimezoneOffset(Timezone, FixedOffset);
impl TimeZone for ChronoTimezone {
type Offset = ChronoTimezoneOffset;
fn from_offset(offset: &Self::Offset) -> Self {
ChronoTimezone(offset.0)
}
fn offset_from_local_date(&self, _local: &NaiveDate) -> chrono::LocalResult<Self::Offset> {
unimplemented!()
}
fn offset_from_local_datetime(
&self, _local: &NaiveDateTime,
) -> chrono::LocalResult<Self::Offset> {
unimplemented!()
}
fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
unimplemented!()
}
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
ChronoTimezoneOffset(
self.0,
FixedOffset::east(self.0.as_offset_at(&DateTime::from_chrono(
&chrono::DateTime::<Utc>::from_utc(*utc, Utc),
))),
)
}
}
impl Offset for ChronoTimezoneOffset {
fn fix(&self) -> FixedOffset {
self.1
}
}
impl Display for ChronoTimezone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0.inner {
TimezoneInner::Variable(tz) => f.write_str(tz.name()),
TimezoneInner::Fixed(offset) => Display::fmt(&FixedOffset::east(offset), f),
}
}
}
impl Display for ChronoTimezoneOffset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&ChronoTimezone(self.0), f)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct Date {
date: DateWithoutTimezone,
timezone: Timezone,
}
impl Date {
pub fn new(year: i64, month: u8, day: u8, timezone: Timezone) -> Option<Self> {
DateWithoutTimezone::new(year, month, day).map(|date| Self { date, timezone })
}
pub fn from_ordinal(year: i64, day: u16, timezone: Timezone) -> Option<Self> {
DateWithoutTimezone::from_ordinal(year, day).map(|date| Self { date, timezone })
}
pub fn year(&self) -> i64 {
self.date.year()
}
pub fn month(&self) -> u8 {
self.date.month()
}
pub fn day(&self) -> u8 {
self.date.day()
}
pub fn ordinal(&self) -> u16 {
self.date.ordinal()
}
pub fn without_timezone(&self) -> DateWithoutTimezone {
self.date
}
pub fn timezone(&self) -> Timezone {
self.timezone
}
pub fn from_days(days: i64, timezone: Timezone) -> Option<Self> {
DateWithoutTimezone::from_days(days).map(|date| Self { date, timezone })
}
pub fn as_days(&self) -> i64 {
self.date.as_days()
}
#[doc(hidden)]
pub fn from_chrono<Tz>(date: &chrono::Date<Tz>) -> Self
where
Tz: TimeZone,
{
Self::new(
date.year().into(),
date.month().try_into().unwrap(),
date.day().try_into().unwrap(),
Timezone::from_chrono(&date.timezone(), date.offset()),
)
.unwrap()
}
#[doc(hidden)]
pub fn as_chrono(&self) -> Option<chrono::Date<ChronoTimezone>> {
Some(
Utc.ymd(
self.year().try_into().ok()?,
self.month().into(),
self.day().into(),
)
.with_timezone(&ChronoTimezone(self.timezone)),
)
}
}
impl AmadeusOrd for Date {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for Date {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{:04}-{:02}-{:02} {}",
self.year(),
self.month(),
self.day(),
self.timezone()
)
}
}
impl FromStr for Date {
type Err = ParseDateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
chrono::DateTime::parse_from_str(s, "%Y-%m-%d%:z")
.map(|date| Self::from_chrono(&date.date()))
.map_err(|_| ParseDateError)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct Time {
time: TimeWithoutTimezone,
timezone: Timezone,
}
impl Time {
pub fn new(
hour: u8, minute: u8, second: u8, nanosecond: u32, timezone: Timezone,
) -> Option<Self> {
TimeWithoutTimezone::new(hour, minute, second, nanosecond)
.map(|time| Self { time, timezone })
}
pub fn from_seconds(seconds: u32, nanosecond: u32, timezone: Timezone) -> Option<Self> {
TimeWithoutTimezone::from_seconds(seconds, nanosecond).map(|time| Self { time, timezone })
}
pub fn hour(&self) -> u8 {
self.time.hour()
}
pub fn minute(&self) -> u8 {
self.time.minute()
}
pub fn second(&self) -> u8 {
self.time.second()
}
pub fn nanosecond(&self) -> u32 {
self.time.nanosecond()
}
pub fn without_timezone(&self) -> TimeWithoutTimezone {
self.time
}
pub fn timezone(&self) -> Timezone {
self.timezone
}
pub fn truncate_minutes(&self, minutes: u8) -> Self {
Self {
time: self.time.truncate_minutes(minutes),
timezone: self.timezone,
}
}
}
impl AmadeusOrd for Time {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}{}",
self.time.as_chrono().expect(TODO).format("%H:%M:%S%.9f"),
ChronoTimezone(self.timezone)
)
}
}
impl FromStr for Time {
type Err = ParseDateError;
fn from_str(_s: &str) -> Result<Self, Self::Err> {
unimplemented!()
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct DateTime {
date_time: DateTimeWithoutTimezone,
timezone: Timezone,
}
impl DateTime {
#[allow(clippy::too_many_arguments)]
pub fn new(
year: i64, month: u8, day: u8, hour: u8, minute: u8, second: u8, nanosecond: u32,
timezone: Timezone,
) -> Option<Self> {
DateTimeWithoutTimezone::new(year, month, day, hour, minute, second, nanosecond).map(
|date_time| Self {
date_time,
timezone,
},
)
}
pub fn from_date_time(date: Date, time: Time) -> Option<Self> {
let timezone = date.timezone();
if timezone != time.timezone() {
return None;
}
let date_time = DateTimeWithoutTimezone::from_date_time(
date.without_timezone(),
time.without_timezone(),
)?;
Some(Self {
date_time,
timezone,
})
}
pub fn date(&self) -> DateWithoutTimezone {
self.date_time.date()
}
pub fn time(&self) -> TimeWithoutTimezone {
self.date_time.time()
}
pub fn year(&self) -> i64 {
self.date_time.year()
}
pub fn month(&self) -> u8 {
self.date_time.month()
}
pub fn day(&self) -> u8 {
self.date_time.day()
}
pub fn hour(&self) -> u8 {
self.date_time.hour()
}
pub fn minute(&self) -> u8 {
self.date_time.minute()
}
pub fn second(&self) -> u8 {
self.date_time.second()
}
pub fn nanosecond(&self) -> u32 {
self.date_time.nanosecond()
}
#[doc(hidden)]
pub fn from_chrono<Tz>(date_time: &chrono::DateTime<Tz>) -> Self
where
Tz: TimeZone,
{
Self::new(
date_time.year().into(),
date_time.month().try_into().unwrap(),
date_time.day().try_into().unwrap(),
date_time.hour().try_into().unwrap(),
date_time.minute().try_into().unwrap(),
date_time.second().try_into().unwrap(),
date_time.nanosecond(),
Timezone::from_chrono(&date_time.timezone(), date_time.offset()),
)
.unwrap()
}
#[doc(hidden)]
pub fn as_chrono(&self) -> Option<chrono::DateTime<ChronoTimezone>> {
Some(
chrono::DateTime::<Utc>::from_utc(self.date_time.as_chrono()?, Utc)
.with_timezone(&ChronoTimezone(self.timezone)),
)
}
pub fn truncate_minutes(&self, minutes: u8) -> Self {
Self {
date_time: self.date_time.truncate_minutes(minutes),
timezone: self.timezone,
}
}
}
impl AmadeusOrd for DateTime {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.as_chrono()
.expect(TODO)
.format("%Y-%m-%d %H:%M:%S%.9f %:z")
)
}
}
impl FromStr for DateTime {
type Err = ParseDateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
chrono::DateTime::<FixedOffset>::from_str(s)
.map(|date| Self::from_chrono(&date))
.map_err(|_| ParseDateError)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct Duration {
months: i64,
days: i64,
nanos: i64,
}
impl AmadeusOrd for Duration {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct DateWithoutTimezone(i64);
impl DateWithoutTimezone {
pub fn new(year: i64, month: u8, day: u8) -> Option<Self> {
NaiveDate::from_ymd_opt(
year.try_into().ok()?,
month.try_into().ok()?,
day.try_into().ok()?,
)
.as_ref()
.map(Self::from_chrono)
}
pub fn from_ordinal(year: i64, day: u16) -> Option<Self> {
NaiveDate::from_yo_opt(year.try_into().ok()?, day.try_into().ok()?)
.as_ref()
.map(Self::from_chrono)
}
pub fn year(&self) -> i64 {
i64::from(self.as_chrono().expect(TODO).year())
}
pub fn month(&self) -> u8 {
self.as_chrono().expect(TODO).month().try_into().unwrap()
}
pub fn day(&self) -> u8 {
self.as_chrono().expect(TODO).day().try_into().unwrap()
}
pub fn ordinal(&self) -> u16 {
self.as_chrono().expect(TODO).ordinal().try_into().unwrap()
}
pub fn with_timezone(self, timezone: Timezone) -> Date {
Date {
date: self,
timezone,
}
}
pub fn from_days(days: i64) -> Option<Self> {
if JULIAN_DAY_OF_EPOCH + i64::from(i32::min_value()) <= days
&& days <= i64::from(i32::max_value())
{
Some(Self(days))
} else {
None
}
}
pub fn as_days(&self) -> i64 {
self.0
}
#[doc(hidden)]
pub fn from_chrono(date: &NaiveDate) -> Self {
Self::from_days(i64::from(date.num_days_from_ce()) - GREGORIAN_DAY_OF_EPOCH).unwrap()
}
#[doc(hidden)]
pub fn as_chrono(&self) -> Option<NaiveDate> {
NaiveDate::from_num_days_from_ce_opt((self.0 + GREGORIAN_DAY_OF_EPOCH).try_into().ok()?)
}
}
impl AmadeusOrd for DateWithoutTimezone {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for DateWithoutTimezone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_chrono().expect(TODO).fmt(f)
}
}
impl FromStr for DateWithoutTimezone {
type Err = ParseDateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NaiveDate::from_str(s)
.map(|date| Self::from_chrono(&date))
.map_err(|_| ParseDateError)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct TimeWithoutTimezone(NaiveTime);
impl TimeWithoutTimezone {
pub fn new(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Option<Self> {
NaiveTime::from_hms_nano_opt(hour.into(), minute.into(), second.into(), nanosecond)
.map(Self)
}
pub fn from_seconds(seconds: u32, nanosecond: u32) -> Option<Self> {
NaiveTime::from_num_seconds_from_midnight_opt(seconds, nanosecond).map(Self)
}
pub fn hour(&self) -> u8 {
self.0.hour().try_into().unwrap()
}
pub fn minute(&self) -> u8 {
self.0.minute().try_into().unwrap()
}
pub fn second(&self) -> u8 {
self.0.second().try_into().unwrap()
}
pub fn nanosecond(&self) -> u32 {
self.0.nanosecond()
}
pub fn with_timezone(self, timezone: Timezone) -> Time {
Time {
time: self,
timezone,
}
}
#[doc(hidden)]
pub fn from_chrono(time: &NaiveTime) -> Self {
Self(*time)
}
#[doc(hidden)]
pub fn as_chrono(&self) -> Option<NaiveTime> {
Some(self.0)
}
pub fn truncate_minutes(&self, minutes: u8) -> Self {
assert!(
minutes != 0 && 60 % minutes == 0,
"minutes must be a divisor of 60"
);
Self::new(self.hour(), self.minute() / minutes * minutes, 0, 0).unwrap()
}
}
impl AmadeusOrd for TimeWithoutTimezone {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for TimeWithoutTimezone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_chrono().expect(TODO).fmt(f)
}
}
impl FromStr for TimeWithoutTimezone {
type Err = ParseDateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NaiveTime::from_str(s)
.map(|date| Self::from_chrono(&date))
.map_err(|_| ParseDateError)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub struct DateTimeWithoutTimezone {
date: DateWithoutTimezone,
time: TimeWithoutTimezone,
}
impl DateTimeWithoutTimezone {
pub fn new(
year: i64, month: u8, day: u8, hour: u8, minute: u8, second: u8, nanosecond: u32,
) -> Option<Self> {
let date = DateWithoutTimezone::new(year, month, day)?;
let time = TimeWithoutTimezone::new(hour, minute, second, nanosecond)?;
Some(Self { date, time })
}
pub fn from_date_time(date: DateWithoutTimezone, time: TimeWithoutTimezone) -> Option<Self> {
Some(Self { date, time })
}
pub fn date(&self) -> DateWithoutTimezone {
self.date
}
pub fn time(&self) -> TimeWithoutTimezone {
self.time
}
pub fn year(&self) -> i64 {
self.date.year()
}
pub fn month(&self) -> u8 {
self.date.month()
}
pub fn day(&self) -> u8 {
self.date.day()
}
pub fn hour(&self) -> u8 {
self.time.hour()
}
pub fn minute(&self) -> u8 {
self.time.minute()
}
pub fn second(&self) -> u8 {
self.time.second()
}
pub fn nanosecond(&self) -> u32 {
self.time.nanosecond()
}
pub fn with_timezone(self, timezone: Timezone) -> DateTime {
DateTime {
date_time: self,
timezone,
}
}
#[doc(hidden)]
pub fn from_chrono(date_time: &NaiveDateTime) -> Self {
Self::new(
date_time.year().into(),
date_time.month().try_into().unwrap(),
date_time.day().try_into().unwrap(),
date_time.hour().try_into().unwrap(),
date_time.minute().try_into().unwrap(),
date_time.second().try_into().unwrap(),
date_time.nanosecond(),
)
.unwrap()
}
#[doc(hidden)]
pub fn as_chrono(&self) -> Option<NaiveDateTime> {
Some(self.date.as_chrono()?.and_time(self.time.as_chrono()?))
}
pub fn truncate_minutes(&self, minutes: u8) -> Self {
Self {
date: self.date,
time: self.time.truncate_minutes(minutes),
}
}
}
impl AmadeusOrd for DateTimeWithoutTimezone {
fn amadeus_cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self, other)
}
}
impl Display for DateTimeWithoutTimezone {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_chrono().expect(TODO).fmt(f)
}
}
impl FromStr for DateTimeWithoutTimezone {
type Err = ParseDateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NaiveDateTime::from_str(s)
.map(|date| Self::from_chrono(&date))
.map_err(|_| ParseDateError)
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
const SECONDS_PER_DAY: i64 = 86_400;
#[test]
fn timezone() {
assert_eq!(
Timezone::from_name("Etc/GMT-14")
.unwrap()
.as_offset()
.unwrap(),
14 * 60 * 60
);
}
#[test]
fn test_convert_date_to_string() {
fn check_date_conversion(y: i32, m: u32, d: u32) {
let chrono_date = NaiveDate::from_ymd(y, m, d);
let chrono_datetime = chrono_date.and_hms(0, 0, 0);
assert_eq!(chrono_datetime.timestamp() % SECONDS_PER_DAY, 0);
let date =
DateWithoutTimezone::from_days(chrono_datetime.timestamp() / SECONDS_PER_DAY)
.unwrap();
assert_eq!(date.to_string(), chrono_date.to_string());
let date2 = DateWithoutTimezone::from_chrono(&date.as_chrono().unwrap());
assert_eq!(date, date2);
}
check_date_conversion(-262_144, 1, 1);
check_date_conversion(1969, 12, 31);
check_date_conversion(1970, 1, 1);
check_date_conversion(2010, 1, 2);
check_date_conversion(2014, 5, 1);
check_date_conversion(2016, 2, 29);
check_date_conversion(2017, 9, 12);
check_date_conversion(2018, 3, 31);
check_date_conversion(262_143, 12, 31);
}
#[test]
fn test_convert_time_to_string() {
fn check_time_conversion(h: u32, mi: u32, s: u32) {
let chrono_time = NaiveTime::from_hms(h, mi, s);
let time = TimeWithoutTimezone::from_chrono(&chrono_time);
assert_eq!(time.to_string(), chrono_time.to_string());
}
check_time_conversion(13, 12, 54);
check_time_conversion(8, 23, 1);
check_time_conversion(11, 6, 32);
check_time_conversion(16, 38, 0);
check_time_conversion(21, 15, 12);
}
#[test]
fn test_convert_timestamp_to_string() {
#[allow(clippy::many_single_char_names)]
fn check_datetime_conversion(y: i32, m: u32, d: u32, h: u32, mi: u32, s: u32) {
let dt = NaiveDate::from_ymd(y, m, d).and_hms(h, mi, s);
let res = DateTimeWithoutTimezone::from_chrono(&dt).to_string();
let exp = dt.to_string();
assert_eq!(res, exp);
}
check_datetime_conversion(2010, 1, 2, 13, 12, 54);
check_datetime_conversion(2011, 1, 3, 8, 23, 1);
check_datetime_conversion(2012, 4, 5, 11, 6, 32);
check_datetime_conversion(2013, 5, 12, 16, 38, 0);
check_datetime_conversion(2014, 11, 28, 21, 15, 12);
}
}