use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Datelike, TimeZone, Timelike, Utc, Weekday};
use chrono_tz::Tz;
use chronoutil::RelativeDuration;
use serde::{Deserialize, Serialize};
use crate::EvaluationContext;
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum TimestampWithOptionalTimezone {
Timestamp(i64),
TimestampAndTimezone(i64, String),
}
impl TimestampWithOptionalTimezone {
fn into_datetime(self) -> Result<DateTime<Tz>> {
let (ts, tz) = match self {
Self::Timestamp(ts) => (ts, Tz::UTC),
Self::TimestampAndTimezone(ts, tz) => (
ts,
tz.parse()
.map_err(|e| anyhow!("Could not parse timezone: {e}"))?,
),
};
Ok(tz.timestamp_nanos(ts))
}
}
#[tracing::instrument(name = "time.add_date", err)]
pub fn add_date(ns: i64, years: i32, months: i32, days: i64) -> Result<i64> {
let date_time = {
Utc.timestamp_nanos(ns)
+ RelativeDuration::years(years)
+ RelativeDuration::months(months)
+ RelativeDuration::days(days)
};
date_time.timestamp_nanos_opt().context("Invalid date")
}
#[tracing::instrument(name = "time.clock", err)]
pub fn clock(x: TimestampWithOptionalTimezone) -> Result<(u32, u32, u32)> {
let date_time = x.into_datetime()?;
Ok((date_time.hour(), date_time.minute(), date_time.second()))
}
#[tracing::instrument(name = "time.date", err)]
pub fn date(x: TimestampWithOptionalTimezone) -> Result<(i32, u32, u32)> {
let date_time = x.into_datetime()?;
Ok((date_time.year(), date_time.month(), date_time.day()))
}
#[tracing::instrument(name = "time.diff", err)]
pub fn diff(ns1: serde_json::Value, ns2: serde_json::Value) -> Result<(u8, u8, u8, u8, u8, u8)> {
Err(anyhow!("not implemented"))
}
#[tracing::instrument(name = "time.now_ns", skip(ctx))]
pub fn now_ns<C: EvaluationContext>(ctx: &mut C) -> Result<i64> {
ctx.now()
.timestamp_nanos_opt()
.context("Timestamp out of range")
}
#[tracing::instrument(name = "time.parse_duration_ns", err)]
pub fn parse_duration_ns(duration: String) -> Result<u128> {
Ok(duration_str::parse(duration.as_str())
.map_err(|e| anyhow!("{e}"))?
.as_nanos())
}
#[tracing::instrument(name = "time.parse_ns", err)]
pub fn parse_ns(layout: String, value: String) -> Result<i64> {
Err(anyhow!("not implemented"))
}
#[tracing::instrument(name = "time.parse_rfc3339_ns", err)]
pub fn parse_rfc3339_ns(value: String) -> Result<i64> {
DateTime::parse_from_rfc3339(value.as_ref())?
.timestamp_nanos_opt()
.context("Invalid date")
}
#[tracing::instrument(name = "time.weekday", err)]
pub fn weekday(x: TimestampWithOptionalTimezone) -> Result<&'static str> {
let date_time = x.into_datetime()?;
Ok(match date_time.weekday() {
Weekday::Mon => "Monday",
Weekday::Tue => "Tuesday",
Weekday::Wed => "Wednesday",
Weekday::Thu => "Thursday",
Weekday::Fri => "Friday",
Weekday::Sat => "Saturday",
Weekday::Sun => "Sunday",
})
}