use chrono::{DateTime, Datelike, Duration, Local, NaiveDateTime, TimeZone, Timelike, Utc};
use chrono_tz::Tz;
use rhai::{Engine, EvalAltResult, Position};
use std::cell::RefCell;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct DateTimeWrapper {
pub inner: DateTime<Tz>,
}
impl DateTimeWrapper {
pub fn new(dt: DateTime<Tz>) -> Self {
Self { inner: dt }
}
pub fn from_utc(dt: DateTime<Utc>) -> Self {
Self {
inner: dt.with_timezone(&Tz::UTC),
}
}
pub fn from_local(dt: DateTime<Local>) -> Self {
Self {
inner: dt.with_timezone(&Tz::UTC),
}
}
}
impl fmt::Display for DateTimeWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner.to_rfc3339())
}
}
#[derive(Debug, Clone)]
pub struct DurationWrapper {
pub inner: Duration,
}
impl DurationWrapper {
pub fn new(dur: Duration) -> Self {
Self { inner: dur.abs() }
}
pub fn from_seconds(secs: i64) -> Self {
Self::new(Duration::seconds(secs))
}
pub fn from_minutes(mins: i64) -> Self {
Self::new(Duration::minutes(mins))
}
pub fn from_hours(hours: i64) -> Self {
Self::new(Duration::hours(hours))
}
pub fn from_days(days: i64) -> Self {
Self::new(Duration::days(days))
}
pub fn from_milliseconds(ms: i64) -> Self {
Self::new(Duration::milliseconds(ms))
}
pub fn from_nanoseconds(ns: i64) -> Self {
Self::new(Duration::nanoseconds(ns))
}
}
impl fmt::Display for DurationWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total_seconds = self.inner.num_seconds();
if total_seconds < 60 {
write!(f, "{}s", total_seconds)
} else if total_seconds < 3600 {
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
if seconds == 0 {
write!(f, "{}m", minutes)
} else {
write!(f, "{}m {}s", minutes, seconds)
}
} else if total_seconds < 86400 {
let hours = total_seconds / 3600;
let remaining = total_seconds % 3600;
let minutes = remaining / 60;
if minutes == 0 {
write!(f, "{}h", hours)
} else {
write!(f, "{}h {}m", hours, minutes)
}
} else {
let days = total_seconds / 86400;
let remaining = total_seconds % 86400;
let hours = remaining / 3600;
if hours == 0 {
write!(f, "{}d", days)
} else {
write!(f, "{}d {}h", days, hours)
}
}
}
}
thread_local! {
static RHAI_TS_PARSER: RefCell<crate::timestamp::AdaptiveTsParser> =
RefCell::new(crate::timestamp::AdaptiveTsParser::new());
}
pub fn parse_ts(
s: &str,
format: Option<&str>,
tz: Option<&str>,
) -> Result<DateTimeWrapper, Box<EvalAltResult>> {
let default_tz = if let Some(tz_str) = tz {
tz_str.parse::<Tz>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid timezone '{}': {}", tz_str, e).into(),
Position::NONE,
))
})?
} else {
Tz::UTC
};
if let Some(fmt) = format {
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(s, fmt) {
return Ok(DateTimeWrapper::new(
default_tz.from_utc_datetime(&naive_dt),
));
}
if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
return Ok(DateTimeWrapper::new(dt.with_timezone(&default_tz)));
}
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to parse '{}' with format '{}'", s, fmt).into(),
Position::NONE,
)));
}
let parsed_utc = RHAI_TS_PARSER.with(|parser| {
parser
.borrow_mut()
.parse_ts_with_config(s, None, Some("UTC"))
});
if let Some(utc_dt) = parsed_utc {
let tz_dt = if default_tz == Tz::UTC {
utc_dt.with_timezone(&Tz::UTC)
} else {
utc_dt.with_timezone(&default_tz)
};
return Ok(DateTimeWrapper::new(tz_dt));
}
Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Unable to parse timestamp: '{}'", s).into(),
Position::NONE,
)))
}
pub fn parse_dur(s: &str) -> Result<DurationWrapper, Box<EvalAltResult>> {
let mut total_duration = Duration::zero();
let mut current_number = String::new();
let mut found_unit = false;
let chars = s.chars();
for ch in chars {
if ch.is_numeric() {
current_number.push(ch);
} else if ch.is_alphabetic() || ch == ' ' {
if !current_number.is_empty() {
let number: i64 = current_number.parse().map_err(|_| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid number in duration: '{}'", current_number).into(),
Position::NONE,
))
})?;
let unit = ch.to_lowercase().next().unwrap();
let duration_part = match unit {
's' => Duration::seconds(number),
'm' => Duration::minutes(number),
'h' => Duration::hours(number),
'd' => Duration::days(number),
_ => {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Unknown duration unit: '{}'", unit).into(),
Position::NONE,
)))
}
};
total_duration += duration_part;
current_number.clear();
found_unit = true;
}
} else if ch == ' ' {
continue;
} else {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid character in duration: '{}'", ch).into(),
Position::NONE,
)));
}
}
if !found_unit {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Unable to parse duration: '{}'", s).into(),
Position::NONE,
)));
}
Ok(DurationWrapper::new(total_duration))
}
pub fn register_functions(engine: &mut Engine) {
engine.register_fn(
"parse_ts",
|s: &str| -> Result<DateTimeWrapper, Box<EvalAltResult>> { parse_ts(s, None, None) },
);
engine.register_fn(
"parse_ts",
|s: &str, format: &str| -> Result<DateTimeWrapper, Box<EvalAltResult>> {
parse_ts(s, Some(format), None)
},
);
engine.register_fn(
"parse_ts",
|s: &str, format: &str, tz: &str| -> Result<DateTimeWrapper, Box<EvalAltResult>> {
parse_ts(s, Some(format), Some(tz))
},
);
engine.register_fn("parse_dur", parse_dur);
engine.register_fn("now_utc", || DateTimeWrapper::from_utc(Utc::now()));
engine.register_fn("now_local", || DateTimeWrapper::from_local(Local::now()));
engine.register_fn("dur_from_seconds", DurationWrapper::from_seconds);
engine.register_fn("dur_from_minutes", DurationWrapper::from_minutes);
engine.register_fn("dur_from_hours", DurationWrapper::from_hours);
engine.register_fn("dur_from_days", DurationWrapper::from_days);
engine.register_fn("dur_from_milliseconds", DurationWrapper::from_milliseconds);
engine.register_fn("dur_from_nanoseconds", DurationWrapper::from_nanoseconds);
engine
.register_type::<DateTimeWrapper>()
.register_type::<DurationWrapper>();
engine.register_fn("to_utc", |dt: &mut DateTimeWrapper| {
DateTimeWrapper::new(dt.inner.with_timezone(&Tz::UTC))
});
engine.register_fn("to_local", |dt: &mut DateTimeWrapper| {
let local_tz = chrono_tz::Tz::from_str("UTC").unwrap(); DateTimeWrapper::new(dt.inner.with_timezone(&local_tz))
});
engine.register_fn(
"to_timezone",
|dt: &mut DateTimeWrapper, tz: &str| -> Result<DateTimeWrapper, Box<EvalAltResult>> {
let timezone = tz.parse::<Tz>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid timezone '{}': {}", tz, e).into(),
Position::NONE,
))
})?;
Ok(DateTimeWrapper::new(dt.inner.with_timezone(&timezone)))
},
);
engine.register_fn("format", |dt: &mut DateTimeWrapper, fmt: &str| -> String {
dt.inner.format(fmt).to_string()
});
engine.register_fn("year", |dt: &mut DateTimeWrapper| dt.inner.year() as i64);
engine.register_fn("month", |dt: &mut DateTimeWrapper| dt.inner.month() as i64);
engine.register_fn("day", |dt: &mut DateTimeWrapper| dt.inner.day() as i64);
engine.register_fn("hour", |dt: &mut DateTimeWrapper| dt.inner.hour() as i64);
engine.register_fn("minute", |dt: &mut DateTimeWrapper| {
dt.inner.minute() as i64
});
engine.register_fn("second", |dt: &mut DateTimeWrapper| {
dt.inner.second() as i64
});
engine.register_fn("ts_nanos", |dt: &mut DateTimeWrapper| {
dt.inner.timestamp_nanos_opt().unwrap_or(0)
});
engine.register_fn("timezone_name", |dt: &mut DateTimeWrapper| {
dt.inner.timezone().to_string()
});
engine.register_fn("as_seconds", |dur: &mut DurationWrapper| {
dur.inner.num_seconds()
});
engine.register_fn("as_milliseconds", |dur: &mut DurationWrapper| {
dur.inner.num_milliseconds()
});
engine.register_fn("as_nanoseconds", |dur: &mut DurationWrapper| {
dur.inner.num_nanoseconds().unwrap_or(0)
});
engine.register_fn("as_minutes", |dur: &mut DurationWrapper| {
dur.inner.num_minutes()
});
engine.register_fn("as_hours", |dur: &mut DurationWrapper| {
dur.inner.num_hours()
});
engine.register_fn("as_days", |dur: &mut DurationWrapper| dur.inner.num_days());
engine.register_fn("+", |dt: DateTimeWrapper, dur: DurationWrapper| {
DateTimeWrapper::new(dt.inner + dur.inner)
});
engine.register_fn("-", |dt: DateTimeWrapper, dur: DurationWrapper| {
DateTimeWrapper::new(dt.inner - dur.inner)
});
engine.register_fn("-", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
DurationWrapper::new((dt1.inner - dt2.inner).abs())
});
engine.register_fn("+", |dur1: DurationWrapper, dur2: DurationWrapper| {
DurationWrapper::new(dur1.inner + dur2.inner)
});
engine.register_fn("-", |dur1: DurationWrapper, dur2: DurationWrapper| {
DurationWrapper::new((dur1.inner - dur2.inner).abs())
});
engine.register_fn("*", |dur: DurationWrapper, n: i64| {
DurationWrapper::new(dur.inner * n as i32)
});
engine.register_fn("/", |dur: DurationWrapper, n: i64| {
DurationWrapper::new(dur.inner / n as i32)
});
engine.register_fn("==", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner == dur2.inner
});
engine.register_fn("!=", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner != dur2.inner
});
engine.register_fn(">", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner > dur2.inner
});
engine.register_fn("<", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner < dur2.inner
});
engine.register_fn(">=", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner >= dur2.inner
});
engine.register_fn("<=", |dur1: DurationWrapper, dur2: DurationWrapper| {
dur1.inner <= dur2.inner
});
engine.register_fn("==", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner == dt2.inner
});
engine.register_fn("!=", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner != dt2.inner
});
engine.register_fn(">", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner > dt2.inner
});
engine.register_fn("<", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner < dt2.inner
});
engine.register_fn(">=", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner >= dt2.inner
});
engine.register_fn("<=", |dt1: DateTimeWrapper, dt2: DateTimeWrapper| {
dt1.inner <= dt2.inner
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_duration_wrapper_always_positive() {
let negative_dur = Duration::seconds(-100);
let wrapper = DurationWrapper::new(negative_dur);
assert_eq!(wrapper.inner.num_seconds(), 100);
}
#[test]
fn test_duration_display_formatting() {
assert_eq!(DurationWrapper::from_seconds(30).to_string(), "30s");
assert_eq!(DurationWrapper::from_seconds(60).to_string(), "1m");
assert_eq!(DurationWrapper::from_seconds(90).to_string(), "1m 30s");
assert_eq!(DurationWrapper::from_seconds(3600).to_string(), "1h");
assert_eq!(DurationWrapper::from_seconds(3660).to_string(), "1h 1m");
assert_eq!(DurationWrapper::from_seconds(86400).to_string(), "1d");
assert_eq!(DurationWrapper::from_seconds(90000).to_string(), "1d 1h");
}
#[test]
fn test_parse_ts_edge_cases() {
assert!(parse_ts("", None, None).is_err());
assert!(parse_ts("not-a-date", None, None).is_err());
assert!(parse_ts("2023-13-01T12:00:00Z", None, None).is_err()); assert!(parse_ts("2023-02-30T12:00:00Z", None, None).is_err());
assert!(parse_ts("2023-01-01T00:00:00Z", None, None).is_ok());
assert!(parse_ts("2023-12-31T23:59:59Z", None, None).is_ok());
}
#[test]
fn test_parse_ts_with_explicit_format() {
let result = parse_ts("2023/07/04 12:34:56", Some("%Y/%m/%d %H:%M:%S"), None);
assert!(result.is_ok());
let dt = result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert!(parse_ts("2023-07-04", Some("%Y/%m/%d"), None).is_err());
}
#[test]
fn test_parse_ts_with_timezone() {
let result = parse_ts(
"2023-07-04 12:34:56",
Some("%Y-%m-%d %H:%M:%S"),
Some("UTC"),
);
assert!(result.is_ok());
let result = parse_ts(
"2023-07-04 12:34:56",
Some("%Y-%m-%d %H:%M:%S"),
Some("INVALID"),
);
assert!(result.is_err());
}
#[test]
fn test_parse_dur_edge_cases() {
assert!(parse_dur("").is_err());
assert!(parse_dur("1x").is_err());
assert!(parse_dur("1h@30m").is_err());
assert!(parse_dur("ah").is_err());
assert!(parse_dur("0s").is_ok());
assert!(parse_dur("1d 2h 3m 4s").is_ok());
assert!(parse_dur("100h").is_ok());
}
#[test]
fn test_parse_dur_various_formats() {
let dur_s = parse_dur("30s").unwrap();
assert_eq!(dur_s.inner.num_seconds(), 30);
let dur_m = parse_dur("5m").unwrap();
assert_eq!(dur_m.inner.num_minutes(), 5);
let dur_h = parse_dur("2h").unwrap();
assert_eq!(dur_h.inner.num_hours(), 2);
let dur_d = parse_dur("3d").unwrap();
assert_eq!(dur_d.inner.num_days(), 3);
let dur_mixed = parse_dur("1h 30m").unwrap();
assert_eq!(dur_mixed.inner.num_minutes(), 90);
let dur_spaced = parse_dur(" 1h 30m ").unwrap();
assert_eq!(dur_spaced.inner.num_minutes(), 90);
}
#[test]
fn test_duration_arithmetic_non_negative() {
let dur1 = DurationWrapper::from_hours(2);
let dur2 = DurationWrapper::from_hours(3);
let result = DurationWrapper::new((dur1.inner - dur2.inner).abs());
assert_eq!(result.inner.num_hours(), 1);
}
#[test]
fn test_datetime_wrapper_display() {
let dt = parse_ts("2023-07-04T12:34:56Z", None, None).unwrap();
let display_str = dt.to_string();
assert!(display_str.contains("2023-07-04"));
assert!(display_str.contains("12:34:56"));
}
#[test]
fn test_datetime_component_access() {
let dt = parse_ts("2023-07-04T12:34:56Z", None, None).unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
}
#[test]
fn test_duration_conversions() {
let dur = DurationWrapper::from_hours(2);
assert_eq!(dur.inner.num_hours(), 2);
assert_eq!(dur.inner.num_minutes(), 120);
assert_eq!(dur.inner.num_seconds(), 7200);
let dur_ms = DurationWrapper::from_milliseconds(5000);
assert_eq!(dur_ms.inner.num_seconds(), 5);
}
#[test]
fn test_rfc3339_and_rfc2822_parsing() {
let rfc3339_result = parse_ts("2023-07-04T12:34:56+00:00", None, None);
assert!(rfc3339_result.is_ok());
let rfc2822_result = parse_ts("Tue, 04 Jul 2023 12:34:56 +0000", None, None);
assert!(rfc2822_result.is_ok());
}
#[test]
fn test_standard_format_parsing() {
let apache_result = parse_ts("04/Jul/2023:12:34:56 +0000", None, None);
assert!(apache_result.is_ok());
let common_result = parse_ts("2023-07-04 12:34:56", None, None);
assert!(common_result.is_ok());
let iso_result = parse_ts("2023-07-04T12:34:56.123Z", None, None);
assert!(iso_result.is_ok());
}
#[test]
fn test_large_duration_values() {
let large_dur = DurationWrapper::from_days(365);
assert_eq!(large_dur.inner.num_days(), 365);
let nano_dur = DurationWrapper::from_nanoseconds(1_000_000_000);
assert_eq!(nano_dur.inner.num_seconds(), 1);
}
#[test]
fn test_boundary_conditions() {
let leap_year_result = parse_ts("2024-02-29T12:00:00Z", None, None);
assert!(leap_year_result.is_ok());
let non_leap_result = parse_ts("2023-02-29T12:00:00Z", None, None);
assert!(non_leap_result.is_err());
let y2k_result = parse_ts("2000-01-01T00:00:00Z", None, None);
assert!(y2k_result.is_ok());
}
#[test]
fn test_unix_timestamp_parsing() {
let unix_seconds = parse_ts("1735566123", None, None);
assert!(unix_seconds.is_ok());
let dt = unix_seconds.unwrap();
assert_eq!(dt.inner.year(), 2024);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 30);
let unix_millis = parse_ts("1735566123000", None, None);
assert!(unix_millis.is_ok());
let dt = unix_millis.unwrap();
assert_eq!(dt.inner.year(), 2024);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 30);
let unix_micros = parse_ts("1735566123000000", None, None);
assert!(unix_micros.is_ok());
let dt = unix_micros.unwrap();
assert_eq!(dt.inner.year(), 2024);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 30);
let unix_nanos = parse_ts("1735566123000000000", None, None);
assert!(unix_nanos.is_ok());
let dt = unix_nanos.unwrap();
assert_eq!(dt.inner.year(), 2024);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 30);
let invalid_unix = parse_ts("12345", None, None);
assert!(invalid_unix.is_err());
let invalid_chars = parse_ts("1735566123a", None, None);
assert!(invalid_chars.is_err());
}
#[test]
fn test_new_timestamp_formats() {
let python_result = parse_ts("2023-07-04 12:34:56,123", None, None);
assert!(python_result.is_ok());
let dt = python_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let mysql_result = parse_ts("230704 12:34:56", None, None);
assert!(mysql_result.is_ok());
let dt = mysql_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let nginx_result = parse_ts("2023/07/04 12:34:56", None, None);
assert!(nginx_result.is_ok());
let dt = nginx_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let bsd_result = parse_ts("Jul 04 2023 12:34:56", None, None);
assert!(bsd_result.is_ok());
let dt = bsd_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let java_result = parse_ts("Jul 04, 2023 12:34:56 PM", None, None);
assert!(java_result.is_ok());
let dt = java_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let german_result = parse_ts("04.07.2023 12:34:56", None, None);
assert!(german_result.is_ok());
let dt = german_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
}
#[test]
fn test_klp_inspired_formats() {
let space_iso_frac_z = parse_ts("2023-07-04 12:34:56.123Z", None, None);
assert!(space_iso_frac_z.is_ok());
let dt = space_iso_frac_z.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let space_iso_z = parse_ts("2023-07-04 12:34:56Z", None, None);
assert!(space_iso_z.is_ok());
let dt = space_iso_z.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let space_iso_tz = parse_ts("2023-07-04 12:34:56+0000", None, None);
assert!(space_iso_tz.is_ok());
let dt = space_iso_tz.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let space_iso_frac_tz = parse_ts("2023-07-04 12:34:56.123+0000", None, None);
assert!(space_iso_frac_tz.is_ok());
let dt = space_iso_frac_tz.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
let unix_weekday = parse_ts("Tue Jul 04 12:34:56 2023", None, None);
assert!(unix_weekday.is_ok());
let dt = unix_weekday.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
}
#[test]
fn test_oracle_format_parsing() {
let oracle_result = parse_ts("04-JUL-23 12:34:56.123 PM", None, None);
assert!(oracle_result.is_ok());
let dt = oracle_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7);
assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
assert_eq!(dt.inner.minute(), 34);
assert_eq!(dt.inner.second(), 56);
}
#[test]
fn test_adaptive_parsing_in_rhai() {
let result1 = parse_ts("2023-07-04 12:34:56", None, None);
assert!(result1.is_ok());
let dt1 = result1.unwrap();
assert_eq!(dt1.inner.year(), 2023);
assert_eq!(dt1.inner.month(), 7);
assert_eq!(dt1.inner.day(), 4);
let result2 = parse_ts("2023-07-05 13:45:07", None, None);
assert!(result2.is_ok());
let dt2 = result2.unwrap();
assert_eq!(dt2.inner.year(), 2023);
assert_eq!(dt2.inner.month(), 7);
assert_eq!(dt2.inner.day(), 5);
let result3 = parse_ts("2023-07-06 14:56:08", None, None);
assert!(result3.is_ok());
let dt3 = result3.unwrap();
assert_eq!(dt3.inner.year(), 2023);
assert_eq!(dt3.inner.month(), 7);
assert_eq!(dt3.inner.day(), 6);
}
#[test]
fn test_explicit_format_for_ambiguous_dates() {
let us_result = parse_ts("7/4/2023 12:34:56 PM", Some("%m/%d/%Y %I:%M:%S %p"), None);
assert!(us_result.is_ok());
let dt = us_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 7); assert_eq!(dt.inner.day(), 4);
assert_eq!(dt.inner.hour(), 12);
let eu_result = parse_ts("7/4/2023 12:34:56", Some("%d/%m/%Y %H:%M:%S"), None);
assert!(eu_result.is_ok());
let dt = eu_result.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 4); assert_eq!(dt.inner.day(), 7);
assert_eq!(dt.inner.hour(), 12);
let ambiguous_result = parse_ts("7/4/2023 12:34:56 PM", None, None);
assert!(
ambiguous_result.is_err(),
"Ambiguous date format should fail without explicit format"
);
let windows_us = parse_ts("12/31/2023 11:59:59 PM", Some("%m/%d/%Y %I:%M:%S %p"), None);
assert!(windows_us.is_ok());
let dt = windows_us.unwrap();
assert_eq!(dt.inner.year(), 2023);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 31);
assert_eq!(dt.inner.hour(), 23); }
#[test]
fn test_unix_timestamp_edge_cases() {
let epoch_result = parse_ts("0", None, None);
assert!(epoch_result.is_err());
let epoch_result = parse_ts("0000000000", None, None);
assert!(epoch_result.is_ok());
let dt = epoch_result.unwrap();
assert_eq!(dt.inner.year(), 1970);
assert_eq!(dt.inner.month(), 1);
assert_eq!(dt.inner.day(), 1);
let y2038_result = parse_ts("2147483647", None, None);
assert!(y2038_result.is_ok());
let dt = y2038_result.unwrap();
assert_eq!(dt.inner.year(), 2038);
assert_eq!(dt.inner.month(), 1);
assert_eq!(dt.inner.day(), 19);
let millis_result = parse_ts("1735566123456", None, None);
assert!(millis_result.is_ok());
let dt = millis_result.unwrap();
assert_eq!(dt.inner.year(), 2024);
assert_eq!(dt.inner.month(), 12);
assert_eq!(dt.inner.day(), 30);
}
}