use super::{Date, DateTimeParseError, ErroredDateTimeComponent, Time, TimeStamp, TimeStampOffset};
use chrono::{
offset::LocalResult, DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime,
TimeZone, Timelike,
};
impl TryFrom<TimeStamp> for NaiveDate {
type Error = DateTimeParseError;
fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
let TimeStamp {
year, month, day, ..
} = value;
let month = month.unwrap_or(1);
let day = day.unwrap_or(1);
let date = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).ok_or(
DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Date),
)?;
Ok(date)
}
}
impl TryFrom<Date> for NaiveDate {
type Error = DateTimeParseError;
fn try_from(value: Date) -> Result<Self, Self::Error> {
let Date { year, month, day } = value;
let month = month.unwrap_or(1);
let day = day.unwrap_or(1);
let date = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).ok_or(
DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Date),
)?;
Ok(date)
}
}
impl TryFrom<Time> for NaiveTime {
type Error = DateTimeParseError;
fn try_from(value: Time) -> Result<Self, Self::Error> {
let Time {
hour,
minute,
second,
microsecond,
..
} = value;
let minute = minute.unwrap_or(0);
let second = second.unwrap_or(0);
let microsecond = microsecond.unwrap_or(0);
let time =
NaiveTime::from_hms_micro_opt(hour as u32, minute as u32, second as u32, microsecond)
.ok_or(DateTimeParseError::InvalidComponentRange(
ErroredDateTimeComponent::Time,
))?;
Ok(time)
}
}
impl From<NaiveDate> for TimeStamp {
fn from(value: NaiveDate) -> Self {
let year = value.year() as u16;
let month = Some(value.month() as u8);
let day = Some(value.day() as u8);
let hour = None;
let minute = None;
let second = None;
let microsecond = None;
let offset = None;
TimeStamp {
year,
month,
day,
hour,
minute,
second,
microsecond,
offset,
}
}
}
impl From<NaiveDate> for Date {
fn from(value: NaiveDate) -> Self {
let year = value.year() as u16;
let month = Some(value.month() as u8);
let day = Some(value.day() as u8);
Date { year, month, day }
}
}
impl From<NaiveTime> for Time {
fn from(value: NaiveTime) -> Self {
let hour = value.hour() as u8;
let minute = Some(value.minute() as u8);
let second = Some(value.second() as u8);
let microsecond = Some(value.nanosecond() / 1000);
Time {
hour,
minute,
second,
microsecond,
offset: None,
}
}
}
impl TryFrom<TimeStamp> for NaiveDateTime {
type Error = DateTimeParseError;
fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
let date = NaiveDate::try_from(value)?;
let time = NaiveTime::from_hms_micro_opt(
value.hour.unwrap_or(0) as u32,
value.minute.unwrap_or(0) as u32,
value.second.unwrap_or(0) as u32,
value.microsecond.unwrap_or(0),
)
.ok_or(DateTimeParseError::InvalidComponentRange(
ErroredDateTimeComponent::Time,
))?;
Ok(NaiveDateTime::new(date, time))
}
}
impl From<NaiveDateTime> for TimeStamp {
fn from(value: NaiveDateTime) -> Self {
let year = value.year() as u16;
let month = Some(value.month() as u8);
let day = Some(value.day() as u8);
let hour = Some(value.hour() as u8);
let minute = Some(value.minute() as u8);
let second = Some(value.second() as u8);
let microsecond = Some(value.nanosecond() / 1000);
let offset = None;
TimeStamp {
year,
month,
day,
hour,
minute,
second,
microsecond,
offset,
}
}
}
impl TryFrom<TimeStamp> for LocalResult<DateTime<FixedOffset>> {
type Error = DateTimeParseError;
fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
let TimeStamp {
year,
month,
day,
hour,
minute,
second,
microsecond,
offset,
} = value;
let month = month.unwrap_or(1);
let day = day.unwrap_or(1);
let date = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32).ok_or(
DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Date),
)?;
let hour = hour.unwrap_or(0);
let minute = minute.unwrap_or(0);
let second = second.unwrap_or(0);
let microsecond = microsecond.unwrap_or(0);
let time =
NaiveTime::from_hms_micro_opt(hour as u32, minute as u32, second as u32, microsecond)
.ok_or(DateTimeParseError::InvalidComponentRange(
ErroredDateTimeComponent::Time,
))?;
let offset = offset.unwrap_or_default();
let offset_hours = offset.hours as i32;
let offset_minutes = offset.minutes as i32;
let offset = FixedOffset::east_opt(offset_hours * 3600 + offset_minutes * 60).ok_or(
DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Offset),
)?;
let datetime = NaiveDateTime::new(date, time);
let datetime = datetime.and_local_timezone(offset);
Ok(datetime)
}
}
impl<Tz> TryFrom<TimeStamp> for DateTime<Tz>
where
Tz: TimeZone,
DateTime<Tz>: From<DateTime<FixedOffset>>,
{
type Error = DateTimeParseError;
fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
let datetime: LocalResult<DateTime<FixedOffset>> = LocalResult::try_from(value)?;
match datetime {
LocalResult::Single(datetime) => Ok(datetime.into()),
LocalResult::Ambiguous(earliest, latest) => Err(DateTimeParseError::AmbiguousTime(
earliest.to_rfc3339(),
latest.to_rfc3339(),
)),
LocalResult::None => Err(DateTimeParseError::InvalidComponentRange(
ErroredDateTimeComponent::DateTime,
)),
}
}
}
impl<Tz> From<DateTime<Tz>> for TimeStamp
where
Tz: TimeZone,
DateTime<Tz>: Into<DateTime<FixedOffset>>,
{
fn from(value: DateTime<Tz>) -> Self {
let datetime: DateTime<FixedOffset> = value.into();
let year = datetime.year() as u16;
let month = Some(datetime.month() as u8);
let day = Some(datetime.day() as u8);
let hour = Some(datetime.hour() as u8);
let minute = Some(datetime.minute() as u8);
let second = Some(datetime.second() as u8);
let microsecond = Some(datetime.nanosecond() / 1000);
let offset = Some(TimeStampOffset {
hours: (datetime.offset().local_minus_utc() / 3600) as i8,
minutes: (datetime.offset().local_minus_utc() % 3600) as u8,
});
TimeStamp {
year,
month,
day,
hour,
minute,
second,
microsecond,
offset,
}
}
}
#[cfg(test)]
mod tests {
use crate::datetime::TimeStampOffset;
use chrono::{Timelike, Utc};
use super::*;
#[test]
fn can_convert_timestamp_to_date() {
let ts = TimeStamp {
year: 2023,
month: Some(3),
day: Some(12),
hour: Some(19),
minute: Some(59),
second: None,
microsecond: None,
offset: None,
};
let actual = NaiveDate::try_from(ts).unwrap();
assert_eq!(actual.year(), 2023);
assert_eq!(actual.month(), 3);
assert_eq!(actual.day(), 12);
}
#[test]
fn can_convert_timestamp_to_datetime_with_fixed_offset() {
let ts = TimeStamp {
year: 2023,
month: Some(3),
day: Some(12),
hour: Some(19),
minute: Some(59),
second: Some(5),
microsecond: Some(1234),
offset: Some(TimeStampOffset {
hours: -7,
minutes: 0,
}),
};
let actual = DateTime::<FixedOffset>::try_from(ts).unwrap();
assert_eq!(actual.year(), 2023);
assert_eq!(actual.month(), 3);
assert_eq!(actual.day(), 12);
assert_eq!(actual.hour(), 19);
assert_eq!(actual.minute(), 59);
assert_eq!(actual.second(), 5);
assert_eq!(actual.nanosecond(), 1234 * 1000);
assert_eq!(actual.offset().local_minus_utc() / 3600, -7);
assert_eq!(actual.offset().local_minus_utc() % 3600, 0);
}
#[test]
fn can_convert_timestamp_datetime_with_utc_offset() {
let ts = TimeStamp {
year: 2023,
month: Some(3),
day: Some(12),
hour: Some(19),
minute: Some(59),
second: Some(5),
microsecond: Some(1234),
offset: Some(TimeStampOffset {
hours: -7,
minutes: 0,
}),
};
let actual = DateTime::<Utc>::try_from(ts).unwrap();
assert_eq!(actual.year(), 2023);
assert_eq!(actual.month(), 3);
assert_eq!(actual.day(), 13);
assert_eq!(actual.hour(), 2);
assert_eq!(actual.minute(), 59);
assert_eq!(actual.second(), 5);
assert_eq!(actual.nanosecond(), 1234 * 1000);
}
#[test]
fn can_convert_datetime_to_timestamp() {
let datetime = Utc.from_utc_datetime(
&NaiveDate::from_ymd_opt(2023, 3, 12)
.unwrap()
.and_hms_opt(19, 59, 5)
.unwrap(),
);
let actual = TimeStamp::from(datetime);
assert_eq!(actual.year, 2023);
assert_eq!(actual.month, Some(3));
assert_eq!(actual.day, Some(12));
assert_eq!(actual.hour, Some(19));
assert_eq!(actual.minute, Some(59));
assert_eq!(actual.second, Some(5));
assert_eq!(actual.microsecond, Some(0));
assert_eq!(
actual.offset,
Some(TimeStampOffset {
hours: 0,
minutes: 0
})
);
}
}