use crate::script::*;
use crate::value::dt::DT;
use crate::value::temporal::{
Date, DateTime, Duration, LocalDateTime, Temporal, TemporalError, Time, arith, zone::Zone,
};
pub(crate) fn temporal_to_dynamic(value: Temporal) -> Dynamic {
match value {
Temporal::Date(v) => Dynamic::from(v),
Temporal::LocalDateTime(v) => Dynamic::from(v),
Temporal::DateTime(v) => Dynamic::from(v),
Temporal::Time(v) => Dynamic::from(v),
Temporal::Duration(v) => Dynamic::from(v),
}
}
pub(crate) fn dynamic_temporal(arg: &Dynamic) -> Option<Temporal> {
if let Some(v) = arg.read_lock::<DateTime>() {
return Some(Temporal::DateTime(*v));
}
if let Some(v) = arg.read_lock::<Date>() {
return Some(Temporal::Date(*v));
}
if let Some(v) = arg.read_lock::<LocalDateTime>() {
return Some(Temporal::LocalDateTime(*v));
}
if let Some(v) = arg.read_lock::<Time>() {
return Some(Temporal::Time(*v));
}
if let Some(v) = arg.read_lock::<Duration>() {
return Some(Temporal::Duration(*v));
}
arg.read_lock::<Temporal>().map(|v| *v)
}
pub(crate) fn is_temporal(arg: &Dynamic) -> bool {
arg.is::<DateTime>()
|| arg.is::<Date>()
|| arg.is::<LocalDateTime>()
|| arg.is::<Time>()
|| arg.is::<Duration>()
|| arg.is::<Temporal>()
}
fn temporal_failure(name: &'static str, error: TemporalError) -> Box<rhai::EvalAltResult> {
script_error(Box::new(crate::error::TraverserError::Value(
crate::error::ValueError::Temporal { step: name, error },
)))
}
type Rhai<T> = Result<T, Box<rhai::EvalAltResult>>;
pub(crate) fn script_datetime(text: &str) -> Rhai<DateTime> {
text.parse()
.map_err(|error| temporal_failure("datetime", error))
}
fn script_date(text: &str) -> Rhai<Date> {
text.parse()
.map_err(|error| temporal_failure("date", error))
}
fn script_local(text: &str) -> Rhai<LocalDateTime> {
text.parse()
.map_err(|error| temporal_failure("localdatetime", error))
}
fn script_time(text: &str) -> Rhai<Time> {
text.parse()
.map_err(|error| temporal_failure("time", error))
}
fn script_duration(text: &str) -> Rhai<Duration> {
text.parse()
.map_err(|error| temporal_failure("duration", error))
}
fn script_today(ctx: &NativeCallContext, zone: Option<&str>) -> Rhai<Date> {
let now = access::now_of(ctx);
let at = match zone {
None => now.with_offset(0),
Some(name) => Zone::parse(name).and_then(|zone| zone.at(now)),
};
let at = at.map_err(|error| temporal_failure("today", error))?;
Ok(at.local().date())
}
fn difference(a: Temporal, b: Temporal) -> Result<Duration, TemporalError> {
let elapsed: i128 = match (a, b) {
(Temporal::DateTime(a), Temporal::DateTime(b)) => {
a.epoch_micros() as i128 - b.epoch_micros() as i128
}
(Temporal::LocalDateTime(a), Temporal::LocalDateTime(b)) => {
a.micros() as i128 - b.micros() as i128
}
(Temporal::Date(a), Temporal::Date(b)) => {
let days = a.days_since_epoch() as i64 - b.days_since_epoch() as i64;
return Ok(Duration::new(0, days as i32, 0));
}
(Temporal::Time(a), Temporal::Time(b)) => {
return Ok(Duration::from_micros(a.micros_of_day() - b.micros_of_day()));
}
(Temporal::Duration(a), Temporal::Duration(b)) => {
return b.checked_neg().and_then(|b| a.checked_add(b));
}
_ => {
return Err(TemporalError::unsupported(
format!("subtracting a {}", b.kind()),
a.kind(),
));
}
};
const DAY: i128 = 86_400_000_000;
let days = elapsed / DAY;
let micros = elapsed % DAY;
match i32::try_from(days) {
Ok(days) => Ok(Duration::new(0, days, micros as i64)),
Err(_) => Err(TemporalError::out_of_range("the result")),
}
}
fn plus(value: Temporal, duration: Duration) -> Rhai<Dynamic> {
arith::add_duration(value, duration, None)
.map(temporal_to_dynamic)
.map_err(|error| temporal_failure("+", error))
}
fn minus(value: Temporal, duration: Duration) -> Rhai<Dynamic> {
let negated = duration
.checked_neg()
.map_err(|error| temporal_failure("-", error))?;
arith::add_duration(value, negated, None)
.map(temporal_to_dynamic)
.map_err(|error| temporal_failure("-", error))
}
macro_rules! register_temporal_type {
($registry:expr, $ty:ty, $name:literal, $variant:ident) => {{
$registry
.register_type_with_name::<$ty>($name)
.register_fn_as(&["to_string", "to_debug"], |v: &mut $ty| {
crate::value::literal::rhai_temporal(&Temporal::$variant(*v))
})
.register_fn_as(&["as_string", "asString"], |v: &mut $ty| {
Temporal::$variant(*v).to_string()
})
.register_fn("==", |a: $ty, b: $ty| a == b)
.register_fn("!=", |a: $ty, b: $ty| a != b)
.register_fn("<", |a: $ty, b: $ty| a < b)
.register_fn("<=", |a: $ty, b: $ty| a <= b)
.register_fn(">", |a: $ty, b: $ty| a > b)
.register_fn(">=", |a: $ty, b: $ty| a >= b)
.register_fn("-", |a: $ty, b: $ty| {
difference(Temporal::$variant(a), Temporal::$variant(b))
.map_err(|error| temporal_failure("-", error))
})
.register_fn("==", |_: $ty, _: rhai::ImmutableString| false)
.register_fn("!=", |_: $ty, _: rhai::ImmutableString| true);
}};
}
pub(crate) fn register_temporal(registry: &mut crate::script::register::Registry) {
register_temporal_type!(registry, DateTime, "datetime", DateTime);
register_temporal_type!(registry, Date, "date", Date);
register_temporal_type!(registry, LocalDateTime, "localdatetime", LocalDateTime);
register_temporal_type!(registry, Time, "time", Time);
register_temporal_type!(registry, Duration, "duration", Duration);
registry
.register_fn("+", |a: DateTime, d: Duration| {
plus(Temporal::DateTime(a), d)
})
.register_fn("+", |a: Date, d: Duration| plus(Temporal::Date(a), d))
.register_fn("+", |a: LocalDateTime, d: Duration| {
plus(Temporal::LocalDateTime(a), d)
})
.register_fn("+", |a: Time, d: Duration| plus(Temporal::Time(a), d))
.register_fn("+", |a: Duration, d: Duration| {
plus(Temporal::Duration(a), d)
})
.register_fn("-", |a: DateTime, d: Duration| {
minus(Temporal::DateTime(a), d)
})
.register_fn("-", |a: Date, d: Duration| minus(Temporal::Date(a), d))
.register_fn("-", |a: LocalDateTime, d: Duration| {
minus(Temporal::LocalDateTime(a), d)
})
.register_fn("-", |a: Time, d: Duration| minus(Temporal::Time(a), d))
.register_fn("-", |d: Duration| {
d.checked_neg()
.map_err(|error| temporal_failure("-", error))
});
registry
.register_fn_as(&["datetime", "DateTime"], |text: &str| {
script_datetime(text)
})
.register_fn("date", |text: &str| script_date(text))
.register_fn_as(&["localdatetime", "localDateTime"], |text: &str| {
script_local(text)
})
.register_fn("time", |text: &str| script_time(text))
.register_fn("duration", |text: &str| script_duration(text))
.register_fn("now", |ctx: NativeCallContext| access::now_of(&ctx))
.register_fn("today", |ctx: NativeCallContext| script_today(&ctx, None))
.register_fn("today", |ctx: NativeCallContext, zone: &str| {
script_today(&ctx, Some(zone))
});
registry
.register_get("year", |v: &mut DateTime| v.local().date().year() as i64)
.register_get("month", |v: &mut DateTime| v.local().date().month() as i64)
.register_get("day", |v: &mut DateTime| v.local().date().day() as i64)
.register_get("hour", |v: &mut DateTime| v.local().time().hour() as i64)
.register_get("minute", |v: &mut DateTime| {
v.local().time().minute() as i64
})
.register_get("second", |v: &mut DateTime| {
v.local().time().second() as i64
})
.register_get("microsecond", |v: &mut DateTime| {
v.local().time().microsecond() as i64
})
.register_get("offset", |v: &mut DateTime| v.offset_seconds() as i64)
.register_get("epoch_millis", |v: &mut DateTime| v.epoch_millis())
.register_get("epochMillis", |v: &mut DateTime| v.epoch_millis())
.register_get("year", |v: &mut LocalDateTime| v.date().year() as i64)
.register_get("month", |v: &mut LocalDateTime| v.date().month() as i64)
.register_get("day", |v: &mut LocalDateTime| v.date().day() as i64)
.register_get("hour", |v: &mut LocalDateTime| v.time().hour() as i64)
.register_get("minute", |v: &mut LocalDateTime| v.time().minute() as i64)
.register_get("second", |v: &mut LocalDateTime| v.time().second() as i64)
.register_get("microsecond", |v: &mut LocalDateTime| {
v.time().microsecond() as i64
})
.register_get("year", |v: &mut Date| v.year() as i64)
.register_get("month", |v: &mut Date| v.month() as i64)
.register_get("day", |v: &mut Date| v.day() as i64)
.register_get("hour", |v: &mut Time| v.hour() as i64)
.register_get("minute", |v: &mut Time| v.minute() as i64)
.register_get("second", |v: &mut Time| v.second() as i64)
.register_get("microsecond", |v: &mut Time| v.microsecond() as i64)
.register_get("months", |v: &mut Duration| v.months() as i64)
.register_get("days", |v: &mut Duration| v.days() as i64)
.register_get("micros", |v: &mut Duration| v.micros());
registry
.register_type_with_name::<DT>("DT")
.register_type_with_name::<ScriptDT>("DTTokens");
for &dt in DT::ALL {
registry.register_get(dt.name(), move |_: &mut ScriptDT| dt);
if dt.camel_name() != dt.name() {
registry.register_get(dt.camel_name(), move |_: &mut ScriptDT| dt);
}
}
registry.register_static_module("DT", exported_module!(DTModule).into());
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScriptDT;
#[export_module]
#[allow(non_snake_case, non_upper_case_globals)]
pub(crate) mod DTModule {
use super::DT;
use crate::value::literal::TokenLiteral;
pub const microsecond: DT = DT::Microsecond;
pub const millisecond: DT = DT::Millisecond;
pub const second: DT = DT::Second;
pub const minute: DT = DT::Minute;
pub const hour: DT = DT::Hour;
pub const day: DT = DT::Day;
pub const week: DT = DT::Week;
pub const month: DT = DT::Month;
pub const quarter: DT = DT::Quarter;
pub const year: DT = DT::Year;
pub const decade: DT = DT::Decade;
pub const century: DT = DT::Century;
pub const millennium: DT = DT::Millennium;
pub const day_of_week: DT = DT::DayOfWeek;
pub const iso_day_of_week: DT = DT::IsoDayOfWeek;
pub const day_of_year: DT = DT::DayOfYear;
pub const iso_year: DT = DT::IsoYear;
pub const epoch: DT = DT::Epoch;
pub const offset: DT = DT::Offset;
pub const dayOfWeek: DT = DT::DayOfWeek;
pub const isoDayOfWeek: DT = DT::IsoDayOfWeek;
pub const dayOfYear: DT = DT::DayOfYear;
pub const isoYear: DT = DT::IsoYear;
#[rhai_fn(global, name = "to_string", name = "to_debug", pure)]
pub fn to_string(dt: &mut DT) -> String {
dt.token_literal()
}
#[rhai_fn(global, name = "==", pure)]
pub fn eq(left: &mut DT, right: DT) -> bool {
*left == right
}
#[rhai_fn(global, name = "!=", pure)]
pub fn ne(left: &mut DT, right: DT) -> bool {
*left != right
}
}