use hamelin_lib::func::defs::{
AtTimezone, Day, DayOfWeek, FromUnixtimeMicros, FromUnixtimeMillis, FromUnixtimeNanos,
FromUnixtimeSeconds, Hour, Minute, Month, Now, Second, ToMillis, ToUnixtime, Today, Tomorrow,
Ts, Year, Yesterday,
};
use crate::null_propagate;
use crate::registry::EvalRegistry;
use crate::reverse_eval::domain::Constraint;
use crate::value::Value;
use crate::value::{TimeZone, TimestampValue};
pub fn register(registry: &mut EvalRegistry) {
registry.register_eval::<Now>(|_params| Ok(TimestampValue::utc(chrono::Utc::now()).into()));
registry.register_eval::<Today>(|_params| {
let now = chrono::Utc::now();
let date = now.date_naive();
let midnight = date
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to create midnight timestamp"))?;
Ok(TimestampValue::utc(midnight.and_utc()).into())
});
registry.register_eval::<Yesterday>(|_params| {
let now = chrono::Utc::now();
let yesterday_date = now.date_naive() - chrono::Duration::days(1);
let midnight = yesterday_date
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to create midnight timestamp"))?;
Ok(TimestampValue::utc(midnight.and_utc()).into())
});
registry.register_eval::<Tomorrow>(|_params| {
let now = chrono::Utc::now();
let tomorrow_date = now.date_naive() + chrono::Duration::days(1);
let midnight = tomorrow_date
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to create midnight timestamp"))?;
Ok(TimestampValue::utc(midnight.and_utc()).into())
});
registry.register_eval::<Ts>(|mut params| {
let s = null_propagate!(params.take()?).require_string()?;
if let Ok(dt_with_tz) = chrono::DateTime::parse_from_rfc3339(&s) {
let instant = dt_with_tz.with_timezone(&chrono::Utc);
let timezone = TimeZone::FixedOffset(*dt_with_tz.offset());
return Ok(TimestampValue::new(instant, timezone).into());
}
let tz_formats = &["%Y-%m-%d %H:%M:%S%z", "%Y-%m-%dT%H:%M:%S%z"];
for fmt in tz_formats {
if let Ok(dt) = chrono::DateTime::parse_from_str(&s, fmt) {
let instant = dt.with_timezone(&chrono::Utc);
let timezone = TimeZone::FixedOffset(*dt.offset());
return Ok(TimestampValue::new(instant, timezone).into());
}
}
let naive_formats = &[
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S%.f",
"%Y-%m-%dT%H:%M:%S%.f",
"%Y-%m-%d",
"%Y/%m/%d %H:%M:%S",
"%Y/%m/%d",
];
for fmt in naive_formats {
if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(&s, fmt) {
return Ok(TimestampValue::utc(naive.and_utc()).into());
}
}
if let Ok(date) = chrono::NaiveDate::parse_from_str(&s, "%Y-%m-%d") {
let naive = date
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to create midnight timestamp"))?;
return Ok(TimestampValue::utc(naive.and_utc()).into());
}
if let Ok(date) = chrono::NaiveDate::parse_from_str(&s, "%Y/%m/%d") {
let naive = date
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to create midnight timestamp"))?;
return Ok(TimestampValue::utc(naive.and_utc()).into());
}
Err(anyhow::anyhow!(
"Failed to parse timestamp from string: {}",
s
))
});
registry.register_eval::<Year>(|mut params| {
let timestamp = null_propagate!(params.take()?);
let ts = timestamp.try_timestamp()?;
Ok(Value::Int(ts.year()? as i64))
});
registry.register_eval::<Month>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
Ok(Value::Int(ts.month()? as i64))
});
registry.register_eval::<Day>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
Ok(Value::Int(ts.day()? as i64))
});
registry.register_eval::<DayOfWeek>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
let weekday = match ts.weekday()? {
chrono::Weekday::Sun => 1,
chrono::Weekday::Mon => 2,
chrono::Weekday::Tue => 3,
chrono::Weekday::Wed => 4,
chrono::Weekday::Thu => 5,
chrono::Weekday::Fri => 6,
chrono::Weekday::Sat => 7,
};
Ok(Value::Int(weekday))
});
registry.register_eval::<Hour>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
Ok(Value::Int(ts.hour()? as i64))
});
registry.register_eval::<Minute>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
Ok(Value::Int(ts.minute()? as i64))
});
registry.register_eval::<Second>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
Ok(Value::Int(ts.second()? as i64))
});
registry.register_eval::<AtTimezone>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
let tz_str = null_propagate!(params.take()?).require_string()?;
let tz: chrono_tz::Tz = tz_str
.parse()
.map_err(|_| anyhow::anyhow!("Invalid timezone name: {}", tz_str))?;
Ok(ts.with_timezone(TimeZone::Named(tz)).into())
});
registry.register_reverse::<AtTimezone>(|output_constraint, mut params| {
let tz_str = match params.take()? {
Some(Value::String(s)) => s,
Some(Value::Null) => return Ok(Constraint::Universal),
None => return Ok(Constraint::Universal), _ => return Ok(Constraint::Universal),
};
let tz: chrono_tz::Tz = match tz_str.parse() {
Ok(tz) => tz,
Err(_) => return Ok(Constraint::Empty), };
output_constraint.map(|val| match val {
Value::Timestamp(ts) => Ok(ts.clone().with_timezone(TimeZone::Named(tz)).into()),
_ => Err(anyhow::anyhow!(
"Expected timestamp value for at_timezone reverse"
)),
})
});
registry.register_eval::<ToMillis>(|mut params| {
let interval_value = null_propagate!(params.take()?).require_interval()?;
let millis = interval_value.num_milliseconds();
Ok(Value::Int(millis))
});
registry.register_eval::<FromUnixtimeSeconds>(|mut params| {
let secs = null_propagate!(params.take()?).require_int()?;
let dt = chrono::DateTime::from_timestamp(secs, 0)
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp seconds: {}", secs))?;
Ok(TimestampValue::utc(dt).into())
});
registry.register_reverse::<FromUnixtimeSeconds>(|output_constraint, _params| {
output_constraint.map(|timestamp_val| match timestamp_val {
Value::Timestamp(ts) => Ok(Value::Int(ts.timestamp())),
_ => Err(anyhow::anyhow!(
"Expected timestamp value for reverse evaluation of from_unixtime_seconds"
)),
})
});
registry.register_eval::<FromUnixtimeMillis>(|mut params| {
let ms = null_propagate!(params.take()?).require_int()?;
let dt = chrono::DateTime::from_timestamp_millis(ms)
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp milliseconds: {}", ms))?;
Ok(TimestampValue::utc(dt).into())
});
registry.register_reverse::<FromUnixtimeMillis>(|output_constraint, _params| {
output_constraint.map(|timestamp_val| match timestamp_val {
Value::Timestamp(ts) => Ok(Value::Int(ts.timestamp_millis())),
_ => Err(anyhow::anyhow!(
"Expected timestamp value for reverse evaluation of from_unixtime_millis"
)),
})
});
registry.register_eval::<FromUnixtimeMicros>(|mut params| {
let us = null_propagate!(params.take()?).require_int()?;
let dt = chrono::DateTime::from_timestamp_micros(us)
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp microseconds: {}", us))?;
Ok(TimestampValue::utc(dt).into())
});
registry.register_reverse::<FromUnixtimeMicros>(|output_constraint, _params| {
output_constraint.map(|timestamp_val| match timestamp_val {
Value::Timestamp(ts) => Ok(Value::Int(ts.timestamp_micros())),
_ => Err(anyhow::anyhow!(
"Expected timestamp value for reverse evaluation of from_unixtime_micros"
)),
})
});
registry.register_eval::<FromUnixtimeNanos>(|mut params| {
let ns = null_propagate!(params.take()?).require_int()?;
let dt = chrono::DateTime::from_timestamp_nanos(ns);
Ok(TimestampValue::utc(dt).into())
});
registry.register_reverse::<FromUnixtimeNanos>(|output_constraint, _params| {
output_constraint.map(|timestamp_val| match timestamp_val {
Value::Timestamp(ts) => Ok(Value::Int(ts.timestamp_nanos_opt().unwrap_or(0))),
_ => Err(anyhow::anyhow!(
"Expected timestamp value for reverse evaluation of from_unixtime_nanos"
)),
})
});
registry.register_eval::<ToUnixtime>(|mut params| {
let ts = null_propagate!(params.take()?).require_timestamp()?;
let secs = ts.timestamp() as f64 + (ts.timestamp_subsec_micros() as f64 / 1_000_000.0);
Ok(Value::Double(secs))
});
registry.register_reverse::<ToUnixtime>(|output_constraint, _params| {
output_constraint.map(|double_val| match double_val {
Value::Double(secs) => {
let micros = (*secs * 1_000_000.0).round() as i64;
let ts = chrono::DateTime::from_timestamp_micros(micros)
.ok_or_else(|| anyhow::anyhow!("Invalid unix timestamp: {}", secs))?;
Ok(TimestampValue::utc(ts).into())
}
Value::Int(secs) => {
let ts = chrono::DateTime::from_timestamp(*secs, 0)
.ok_or_else(|| anyhow::anyhow!("Invalid unix timestamp: {}", secs))?;
Ok(TimestampValue::utc(ts).into())
}
_ => Err(anyhow::anyhow!(
"Expected numeric value for reverse evaluation of to_unixtime"
)),
})
});
}