Files
turso/core/time/mod.rs
2025-06-30 10:01:03 +03:00

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()
}