use std::str::FromStr as _; use chrono::prelude::*; use core::cmp::Ordering; use thiserror::Error; use crate::ext::register_scalar_function; use turso_ext::{scalar, ExtensionApi, ResultCode, Value, ValueType}; mod internal; use internal::*; pub fn register_extension(ext_api: &mut ExtensionApi) { unsafe { register_scalar_function(ext_api.ctx, c"time_now".as_ptr(), time_now); register_scalar_function(ext_api.ctx, c"time_date".as_ptr(), time_date); register_scalar_function(ext_api.ctx, c"make_date".as_ptr(), make_date); register_scalar_function(ext_api.ctx, c"make_timestamp".as_ptr(), make_timestamp); register_scalar_function(ext_api.ctx, c"time_get".as_ptr(), time_get); register_scalar_function(ext_api.ctx, c"time_get_year".as_ptr(), time_get_year); register_scalar_function(ext_api.ctx, c"time_get_month".as_ptr(), time_get_month); register_scalar_function(ext_api.ctx, c"time_get_day".as_ptr(), time_get_day); register_scalar_function(ext_api.ctx, c"time_get_hour".as_ptr(), time_get_hour); register_scalar_function(ext_api.ctx, c"time_get_minute".as_ptr(), time_get_minute); register_scalar_function(ext_api.ctx, c"time_get_second".as_ptr(), time_get_second); register_scalar_function(ext_api.ctx, c"time_get_nano".as_ptr(), time_get_nano); register_scalar_function(ext_api.ctx, c"time_get_weekday".as_ptr(), time_get_weekday); register_scalar_function(ext_api.ctx, c"time_get_yearday".as_ptr(), time_get_yearday); register_scalar_function(ext_api.ctx, c"time_get_isoyear".as_ptr(), time_get_isoyear); register_scalar_function(ext_api.ctx, c"time_get_isoweek".as_ptr(), time_get_isoweek); register_scalar_function(ext_api.ctx, c"time_unix".as_ptr(), time_unix); register_scalar_function(ext_api.ctx, c"to_timestamp".as_ptr(), to_timestamp); register_scalar_function(ext_api.ctx, c"time_milli".as_ptr(), time_milli); register_scalar_function(ext_api.ctx, c"time_micro".as_ptr(), time_micro); register_scalar_function(ext_api.ctx, c"time_nano".as_ptr(), time_nano); register_scalar_function(ext_api.ctx, c"time_to_unix".as_ptr(), time_to_unix); register_scalar_function(ext_api.ctx, c"time_to_milli".as_ptr(), time_to_milli); register_scalar_function(ext_api.ctx, c"time_to_micro".as_ptr(), time_to_micro); register_scalar_function(ext_api.ctx, c"time_to_nano".as_ptr(), time_to_nano); register_scalar_function(ext_api.ctx, c"time_after".as_ptr(), time_after); register_scalar_function(ext_api.ctx, c"time_before".as_ptr(), time_before); register_scalar_function(ext_api.ctx, c"time_compare".as_ptr(), time_compare); register_scalar_function(ext_api.ctx, c"time_equal".as_ptr(), time_equal); register_scalar_function(ext_api.ctx, c"dur_ns".as_ptr(), dur_ns); register_scalar_function(ext_api.ctx, c"dur_us".as_ptr(), dur_us); register_scalar_function(ext_api.ctx, c"dur_ms".as_ptr(), dur_ms); register_scalar_function(ext_api.ctx, c"dur_s".as_ptr(), dur_s); register_scalar_function(ext_api.ctx, c"dur_m".as_ptr(), dur_m); register_scalar_function(ext_api.ctx, c"dur_h".as_ptr(), dur_h); register_scalar_function(ext_api.ctx, c"time_add".as_ptr(), time_add); register_scalar_function(ext_api.ctx, c"time_add_date".as_ptr(), time_add_date); register_scalar_function(ext_api.ctx, c"time_sub".as_ptr(), time_sub); register_scalar_function(ext_api.ctx, c"time_since".as_ptr(), time_since); register_scalar_function(ext_api.ctx, c"time_until".as_ptr(), time_until); register_scalar_function(ext_api.ctx, c"time_trunc".as_ptr(), time_trunc); register_scalar_function(ext_api.ctx, c"time_round".as_ptr(), time_round); register_scalar_function(ext_api.ctx, c"time_fmt_iso".as_ptr(), time_fmt_iso); register_scalar_function( ext_api.ctx, c"time_fmt_datetime".as_ptr(), time_fmt_datetime, ); register_scalar_function(ext_api.ctx, c"time_fmt_date".as_ptr(), time_fmt_date); register_scalar_function(ext_api.ctx, c"time_fmt_time".as_ptr(), time_fmt_time); register_scalar_function(ext_api.ctx, c"time_parse".as_ptr(), time_parse); } } macro_rules! ok_tri { ($e:expr) => { match $e { Some(val) => val, None => return Value::error(ResultCode::Error), } }; ($e:expr, $msg:expr) => { match $e { Some(val) => val, None => return Value::error_with_message($msg.to_string()), } }; } macro_rules! tri { ($e:expr) => { match $e { Ok(val) => val, Err(err) => return Value::error_with_message(err.to_string()), } }; ($e:expr, $msg:expr) => { match $e { Ok(val) => val, Err(_) => return Value::error_with_message($msg.to_string()), } }; } /// Checks to see if e's enum is of type val macro_rules! value_tri { ($e:expr, $val:pat) => { match $e { $val => (), _ => return Value::error(ResultCode::InvalidArgs), } }; ($e:expr, $val:pat, $msg:expr) => { match $e { $val => (), _ => return Value::error_with_message($msg.to_string()), } }; } #[derive(Error, Debug)] pub enum TimeError { /// Timezone offset is invalid #[error("invalid timezone offset")] InvalidOffset, #[error("invalid datetime format")] InvalidFormat, /// Blob is not size of `TIME_BLOB_SIZE` #[error("invalid time blob size")] InvalidSize, /// Blob time version not matching #[error("mismatch time blob version")] MismatchVersion, #[error("unknown field")] UnknownField(#[from] ::Err), #[error("rounding error")] RoundingError(#[from] chrono::RoundingError), #[error("time creation error")] CreationError, } type Result = core::result::Result; #[scalar(name = "time_now", alias = "now")] fn time_now(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } let t = Time::new(); t.into_blob() } /// ```text /// time_date(year, month, day[, hour, min, sec[, nsec[, offset_sec]]]) /// ``` /// /// Returns the Time corresponding to a given date/time. The time part (hour+minute+second), the nanosecond part, and the timezone offset part are all optional. /// /// The `month`, `day`, `hour`, `min`, `sec`, and `nsec` values may be outside their usual ranges and will be normalized during the conversion. For example, October 32 converts to November 1. /// /// If `offset_sec` is not 0, the source time is treated as being in a given timezone (with an offset in seconds east of UTC) and converted back to UTC. fn time_date_internal(args: &[Value]) -> Value { if args.len() != 3 && args.len() != 6 && args.len() != 7 && args.len() != 8 { return Value::error(ResultCode::InvalidArgs); } for arg in args { value_tri!( arg.value_type(), ValueType::Integer, "all parameters should be integers" ); } let year = ok_tri!(args[0].to_integer()); let month = ok_tri!(args[1].to_integer()); let day = ok_tri!(args[2].to_integer()); let mut hour = 0; let mut minutes = 0; let mut seconds = 0; let mut nano_secs = 0; if args.len() >= 6 { hour = ok_tri!(args[3].to_integer()); minutes = ok_tri!(args[4].to_integer()); seconds = ok_tri!(args[5].to_integer()); } if args.len() >= 7 { nano_secs = ok_tri!(args[6].to_integer()); } if args.len() == 8 { let offset_sec = ok_tri!(args[7].to_integer()) as i32; seconds -= offset_sec as i64; } let t = Time::time_date( year as i32, month as i32, day, hour, minutes, seconds, nano_secs, FixedOffset::east_opt(0).unwrap(), ); let t = tri!(t); t.into_blob() } #[scalar(name = "time_date")] fn time_date(args: &[Value]) { time_date_internal(args) } #[scalar(name = "make_date")] fn make_date(args: &[Value]) -> Value { if args.len() != 3 { return Value::error(ResultCode::InvalidArgs); } time_date_internal(args) } #[scalar(name = "make_timestamp")] fn make_timestamp(args: &[Value]) -> Value { if args.len() != 6 { return Value::error(ResultCode::InvalidArgs); } time_date_internal(args) } #[scalar(name = "time_get", alias = "date_part")] fn time_get(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let field = ok_tri!(args[1].to_text(), "2nd parameter: should be a field name"); let field = tri!(TimeField::from_str(field)); t.time_get(field) } #[scalar(name = "time_get_year")] fn time_get_year(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::Year) } #[scalar(name = "time_get_month")] fn time_get_month(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::Month) } #[scalar(name = "time_get_day")] fn time_get_day(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::Day) } #[scalar(name = "time_get_hour")] fn time_get_hour(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::Hour) } #[scalar(name = "time_get_minute")] fn time_get_minute(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::Minute) } #[scalar(name = "time_get_second")] fn time_get_second(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(t.get_second()) } #[scalar(name = "time_get_nano")] fn time_get_nano(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(t.get_nanosecond()) } #[scalar(name = "time_get_weekday")] fn time_get_weekday(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::WeekDay) } #[scalar(name = "time_get_yearday")] fn time_get_yearday(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::YearDay) } #[scalar(name = "time_get_isoyear")] fn time_get_isoyear(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::IsoYear) } #[scalar(name = "time_get_isoweek")] fn time_get_isoweek(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); t.time_get(TimeField::IsoWeek) } fn time_unix_internal(args: &[Value]) -> Value { if args.len() != 1 && args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } for arg in args { value_tri!( arg.value_type(), ValueType::Integer, "all parameters should be integers" ); } let seconds = ok_tri!(args[0].to_integer()); let mut nano_sec = 0; if args.len() == 2 { nano_sec = ok_tri!(args[1].to_integer()); } let dt = ok_tri!(DateTime::from_timestamp(seconds, nano_sec as u32)); let t = Time::from_datetime(dt); t.into_blob() } #[scalar(name = "time_unix")] fn time_unix(args: &[Value]) -> Value { time_unix_internal(args) } #[scalar(name = "to_timestamp")] fn to_timestamp(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } time_unix_internal(args) } #[scalar(name = "time_milli")] fn time_milli(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } value_tri!( &args[0].value_type(), ValueType::Integer, "parameter should be an integer" ); let millis = ok_tri!(args[0].to_integer()); let dt = ok_tri!(DateTime::from_timestamp_millis(millis)); let t = Time::from_datetime(dt); t.into_blob() } #[scalar(name = "time_micro")] fn time_micro(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } value_tri!( &args[0].value_type(), ValueType::Integer, "parameter should be an integer" ); let micros = ok_tri!(args[0].to_integer()); let dt = ok_tri!(DateTime::from_timestamp_micros(micros)); let t = Time::from_datetime(dt); t.into_blob() } #[scalar(name = "time_nano")] fn time_nano(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } value_tri!( &args[0].value_type(), ValueType::Integer, "parameter should be an integer" ); let nanos = ok_tri!(args[0].to_integer()); let dt = DateTime::from_timestamp_nanos(nanos); let t = Time::from_datetime(dt); t.into_blob() } #[scalar(name = "time_to_unix")] fn time_to_unix(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(t.to_unix()) } #[scalar(name = "time_to_milli")] fn time_to_milli(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(t.to_unix_milli()) } #[scalar(name = "time_to_micro")] fn time_to_micro(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(t.to_unix_micro()) } #[scalar(name = "time_to_nano")] fn time_to_nano(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); Value::from_integer(ok_tri!(t.to_unix_nano())) } // Comparisons #[scalar(name = "time_after")] fn time_after(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob"); let u = tri!(Time::try_from(blob)); Value::from_integer((t > u).into()) } #[scalar(name = "time_before")] fn time_before(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob"); let u = tri!(Time::try_from(blob)); Value::from_integer((t < u).into()) } #[scalar(name = "time_compare")] fn time_compare(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob"); let u = tri!(Time::try_from(blob)); let cmp = match ok_tri!(t.partial_cmp(&u)) { Ordering::Less => -1, Ordering::Greater => 1, Ordering::Equal => 0, }; Value::from_integer(cmp) } #[scalar(name = "time_equal")] fn time_equal(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob"); let u = tri!(Time::try_from(blob)); Value::from_integer(t.eq(&u).into()) } // Duration Constants /// 1 nanosecond #[scalar(name = "dur_ns")] fn dur_ns(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::nanoseconds(1).num_nanoseconds().unwrap()) } /// 1 microsecond #[scalar(name = "dur_us")] fn dur_us(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::microseconds(1).num_nanoseconds().unwrap()) } /// 1 millisecond #[scalar(name = "dur_ms")] fn dur_ms(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::milliseconds(1).num_nanoseconds().unwrap()) } /// 1 second #[scalar(name = "dur_s")] fn dur_s(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::seconds(1).num_nanoseconds().unwrap()) } /// 1 minute #[scalar(name = "dur_m")] fn dur_m(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::minutes(1).num_nanoseconds().unwrap()) } /// 1 hour #[scalar(name = "dur_h")] fn dur_h(args: &[Value]) -> Value { if !args.is_empty() { return Value::error(ResultCode::InvalidArgs); } Value::from_integer(chrono::Duration::hours(1).num_nanoseconds().unwrap()) } // Time Arithmetic /// Do not use `time_add` to add days, months or years. Use `time_add_date` instead. #[scalar(name = "time_add", alias = "date_add")] fn time_add(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); let d = ok_tri!(args[1].to_integer()); let d = Duration::from(d); t.add_duration(d).into_blob() } #[scalar(name = "time_add_date")] fn time_add_date(args: &[Value]) -> Value { if args.len() != 2 && args.len() != 3 && args.len() != 4 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); let years = ok_tri!(args[1].to_integer()); let mut months = 0; let mut days = 0; if args.len() >= 3 { value_tri!( args[2].value_type(), ValueType::Integer, "3rd parameter: should be an integer" ); months = ok_tri!(args[2].to_integer()); } if args.len() == 4 { value_tri!( args[3].value_type(), ValueType::Integer, "4th parameter: should be an integer" ); days = ok_tri!(args[3].to_integer()); } let t: Time = tri!(t.time_add_date(years as i32, months as i32, days)); t.into_blob() } /// Returns the duration between two time values t and u (in nanoseconds). /// If the result exceeds the maximum (or minimum) value that can be stored in a Duration, /// the maximum (or minimum) duration will be returned. fn time_sub_internal(t: Time, u: Time) -> Value { let cmp = ok_tri!(t.partial_cmp(&u)); let diff = t - u; let nano_secs = match diff.num_nanoseconds() { Some(nano) => nano, None => match cmp { Ordering::Equal => ok_tri!(diff.num_nanoseconds()), Ordering::Less => i64::MIN, Ordering::Greater => i64::MAX, }, }; Value::from_integer(nano_secs) } #[scalar(name = "time_sub", alias = "age")] fn time_sub(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob"); let u = tri!(Time::try_from(blob)); time_sub_internal(t, u) } #[scalar(name = "time_since")] fn time_since(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let now = Time::new(); let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); time_sub_internal(now, t) } #[scalar(name = "time_until")] fn time_until(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let now = Time::new(); let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob"); let t = tri!(Time::try_from(blob)); time_sub_internal(t, now) } // Rounding #[scalar(name = "time_trunc", alias = "date_trunc")] fn time_trunc(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); match args[1].value_type() { ValueType::Text => { let field = ok_tri!(args[1].to_text()); let field = tri!(TimeRoundField::from_str(field)); tri!(t.trunc_field(field)).into_blob() } ValueType::Integer => { let duration = ok_tri!(args[1].to_integer()); let duration = Duration::from(duration); tri!(t.trunc_duration(duration)).into_blob() } _ => Value::error_with_message("2nd parameter: should be a field name".to_string()), } } #[scalar(name = "time_round")] fn time_round(args: &[Value]) -> Value { if args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); let duration = ok_tri!(args[1].to_integer()); let duration = Duration::from(duration); tri!(t.round_duration(duration)).into_blob() } // Formatting #[scalar(name = "time_fmt_iso")] fn time_fmt_iso(args: &[Value]) -> Value { if args.len() != 1 && args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let offset_sec = { if args.len() == 2 { value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); ok_tri!(args[1].to_integer()) as i32 } else { 0 } }; let fmt_str = tri!(t.fmt_iso(offset_sec)); Value::from_text(fmt_str) } #[scalar(name = "time_fmt_datetime")] fn time_fmt_datetime(args: &[Value]) -> Value { if args.len() != 1 && args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let offset_sec = { if args.len() == 2 { value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); ok_tri!(args[1].to_integer()) as i32 } else { 0 } }; let fmt_str = tri!(t.fmt_datetime(offset_sec)); Value::from_text(fmt_str) } #[scalar(name = "time_fmt_date")] fn time_fmt_date(args: &[Value]) -> Value { if args.len() != 1 && args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let offset_sec = { if args.len() == 2 { value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); ok_tri!(args[1].to_integer()) as i32 } else { 0 } }; let fmt_str = tri!(t.fmt_date(offset_sec)); Value::from_text(fmt_str) } #[scalar(name = "time_fmt_time")] fn time_fmt_time(args: &[Value]) -> Value { if args.len() != 1 && args.len() != 2 { return Value::error(ResultCode::InvalidArgs); } let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob"); let t = tri!(Time::try_from(blob)); let offset_sec = { if args.len() == 2 { value_tri!( args[1].value_type(), ValueType::Integer, "2nd parameter: should be an integer" ); ok_tri!(args[1].to_integer()) as i32 } else { 0 } }; let fmt_str = tri!(t.fmt_time(offset_sec)); Value::from_text(fmt_str) } #[scalar(name = "time_parse")] fn time_parse(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::InvalidArgs); } let dt_str = ok_tri!(args[0].to_text()); if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(dt_str) { return Time::from_datetime(dt.to_utc()).into_blob(); } if let Ok(mut dt) = chrono::NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S") { // Unwrap is safe here dt = dt.with_nanosecond(0).unwrap(); return Time::from_datetime(dt.and_utc()).into_blob(); } if let Ok(date) = chrono::NaiveDate::parse_from_str(dt_str, "%Y-%m-%d") { // Unwrap is safe here let dt = date .and_hms_opt(0, 0, 0) .unwrap() .with_nanosecond(0) .unwrap(); return Time::from_datetime(dt.and_utc()).into_blob(); } let time = tri!( chrono::NaiveTime::parse_from_str(dt_str, "%H:%M:%S"), "error parsing datetime string" ); let dt = NaiveDateTime::new(NaiveDate::from_ymd_opt(1, 1, 1).unwrap(), time) .with_nanosecond(0) .unwrap(); Time::from_datetime(dt.and_utc()).into_blob() }