use crate::evaluation::{
temporal_constants::{self, UTC_FIXED_OFFSET},
EvaluationError,
};
use chrono::{DateTime, FixedOffset, TimeZone};
use chrono_tz::Tz;
use core::fmt::{self, Display};
use serde::{Deserialize, Serialize};
use std::hash::Hash;
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ZonedDateTime {
datetime: DateTime<FixedOffset>,
timezone_name: Option<String>, }
impl ZonedDateTime {
pub fn new(datetime: DateTime<FixedOffset>, timezone_name: Option<String>) -> Self {
ZonedDateTime {
datetime,
timezone_name,
}
}
pub fn from_epoch_millis(epoch_millis: i64) -> Self {
let offset = *UTC_FIXED_OFFSET;
let datetime = offset.timestamp_millis_opt(epoch_millis).unwrap();
ZonedDateTime {
datetime,
timezone_name: None,
}
}
pub fn from_string(input: &str) -> Result<Self, EvaluationError> {
if let Some(bracket_pos) = input.find('[') {
let base_str = &input[..bracket_pos];
let end_bracket = match input.find(']') {
Some(pos) => pos,
None => {
return Err(EvaluationError::FormatError {
expected: temporal_constants::INVALID_ZONED_DATETIME_FORMAT_ERROR
.to_string(),
})
}
};
let tz_str = &input[bracket_pos + 1..end_bracket];
let tz = match extract_iana_timezone(tz_str) {
Some(tz) => tz,
None => {
return Err(EvaluationError::FormatError {
expected: temporal_constants::INVALID_ZONED_DATETIME_FORMAT_ERROR
.to_string(),
})
}
};
let base_dt = match time::PrimitiveDateTime::parse(
base_str,
&time::format_description::well_known::Iso8601::DEFAULT,
) {
Ok(dt) => {
DateTime::from_timestamp_nanos(dt.assume_utc().unix_timestamp_nanos() as i64)
.naive_utc()
}
Err(e) => {
return Err(EvaluationError::FormatError {
expected: e.to_string(),
})
}
};
let dt2 = match tz.from_local_datetime(&base_dt) {
chrono::offset::LocalResult::Single(d) => d,
chrono::offset::LocalResult::Ambiguous(d1, _) => d1,
chrono::offset::LocalResult::None => {
return Err(EvaluationError::FormatError {
expected: temporal_constants::INVALID_ZONED_DATETIME_FORMAT_ERROR
.to_string(),
})
}
};
return Ok(ZonedDateTime::new(
dt2.fixed_offset(),
Some(tz.name().to_string()),
));
}
match time::OffsetDateTime::parse(
input,
&time::format_description::well_known::Iso8601::DEFAULT,
) {
Ok(dt) => Ok(Self::from_epoch_millis(
(dt.unix_timestamp_nanos() / 1_000_000) as i64,
)),
Err(e) => Err(EvaluationError::FormatError {
expected: e.to_string(),
}),
}
}
pub fn datetime(&self) -> &DateTime<FixedOffset> {
&self.datetime
}
pub fn timezone_name(&self) -> &Option<String> {
&self.timezone_name
}
}
impl Display for ZonedDateTime {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.datetime.fmt(formatter)
}
}
fn extract_iana_timezone(input: &str) -> Option<Tz> {
let timezone_str = input.replace(' ', "_").replace(['[', ']'], "");
if timezone_str == "Z" || timezone_str.contains('+') || timezone_str.contains('-') {
return Some(Tz::UTC);
}
let tz: Tz = match timezone_str.parse() {
Ok(tz) => tz,
Err(_) => return None,
};
Some(tz)
}