mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 17:14:20 +01:00
1021 lines
28 KiB
Rust
1021 lines
28 KiB
Rust
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] <TimeField as ::core::str::FromStr>::Err),
|
|
#[error("rounding error")]
|
|
RoundingError(#[from] chrono::RoundingError),
|
|
#[error("time creation error")]
|
|
CreationError,
|
|
}
|
|
|
|
type Result<T> = core::result::Result<T, TimeError>;
|
|
|
|
#[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()
|
|
}
|