mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Implement most of the builtin time module (#82)
* Implement builtin `time.add_date` method * Implement builtin `time.clock` method * Implement builtin `time.date` method * Implement builtin `time.diff` method * Implement builtin `time.format` method * Migrate `time.now_ns` to `chrono` * Implement builtin `time.parse_ns` method * Implement builtin `time.parse_rfc3339_ns` method * Implement builtin `time.weekday` method * Add conditional `test` module for OPA tests * Cache result of `time.now_ns` * Fail in strict mode if timestamp is outside of range * Add `ensure_i32` util * Move `diff_between_datetimes` into its own file and include appropriate license
This commit is contained in:
23
Cargo.toml
23
Cargo.toml
@@ -20,8 +20,27 @@ regex = ["dep:regex"]
|
||||
semver = ["dep:semver"]
|
||||
uuid = ["dep:uuid"]
|
||||
urlquery = ["dep:url"]
|
||||
time = ["dep:chrono", "dep:chrono-tz"]
|
||||
yaml = ["serde_yaml"]
|
||||
full-opa = ["base64", "base64url", "crypto", "deprecated", "glob", "graph", "hex", "jsonschema", "regex", "semver", "uuid", "urlquery", "yaml"]
|
||||
full-opa = [
|
||||
"base64",
|
||||
"base64url",
|
||||
"crypto",
|
||||
"deprecated",
|
||||
"glob",
|
||||
"graph",
|
||||
"hex",
|
||||
"jsonschema",
|
||||
"regex",
|
||||
"semver",
|
||||
"uuid",
|
||||
"urlquery",
|
||||
"time",
|
||||
"yaml"
|
||||
]
|
||||
|
||||
# This feature enables some testing utils for OPA tests.
|
||||
opa-testutil = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = {version = "1.0.66", features = ["backtrace"] }
|
||||
@@ -51,6 +70,8 @@ wax = { version = "0.6.0", features = [], default-features = false, optional = t
|
||||
url = { version = "2.5.0", optional = true }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.17.1", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.31", optional = true }
|
||||
chrono-tz = { version = "0.8.5", optional = true }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -33,6 +33,9 @@ mod utils;
|
||||
#[cfg(feature = "uuid")]
|
||||
mod uuid;
|
||||
|
||||
#[cfg(feature = "opa-testutil")]
|
||||
mod test;
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
@@ -76,6 +79,7 @@ lazy_static! {
|
||||
encoding::register(&mut m);
|
||||
//token_signing::register(&mut m);
|
||||
//token_verification::register(&mut m);
|
||||
#[cfg(feature = "time")]
|
||||
time::register(&mut m);
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
@@ -92,6 +96,10 @@ lazy_static! {
|
||||
debugging::register(&mut m);
|
||||
tracing::register(&mut m);
|
||||
units::register(&mut m);
|
||||
|
||||
#[cfg(feature = "opa-testutil")]
|
||||
test::register(&mut m);
|
||||
|
||||
m
|
||||
};
|
||||
}
|
||||
@@ -100,6 +108,7 @@ pub fn must_cache(path: &str) -> Option<&'static str> {
|
||||
match path {
|
||||
"rand.intn" => Some("rand.intn"),
|
||||
"uuid.rfc4122" => Some("uuid.rfc4122"),
|
||||
"time.now_ns" => Some("time.now_ns"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
40
src/builtins/test.rs
Normal file
40
src/builtins/test.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_string};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Ok, Result};
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("test.sleep", (sleep, 1));
|
||||
}
|
||||
|
||||
fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "test.sleep";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let val = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let duration = if let Some(millis) = val.strip_suffix("ms").and_then(|v| v.parse().ok()) {
|
||||
Duration::from_millis(millis)
|
||||
} else if let Some(secs) = val.strip_suffix("s").and_then(|v| v.parse().ok()) {
|
||||
Duration::from_secs(secs)
|
||||
} else {
|
||||
bail!(params[0].span().error(
|
||||
format!("`{name}` expects a simple duration ends with `ms` or `s`. Got {val} instead")
|
||||
.as_str()
|
||||
))
|
||||
};
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
@@ -3,28 +3,258 @@
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::ensure_args_count;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use chrono::{
|
||||
DateTime, Datelike, Days, FixedOffset, Local, Months, NaiveDateTime, SecondsFormat, TimeZone,
|
||||
Timelike, Utc, Weekday,
|
||||
};
|
||||
use chrono_tz::Tz;
|
||||
|
||||
mod diff;
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("time.add_date", (add_date, 4));
|
||||
m.insert("time.clock", (clock, 1));
|
||||
m.insert("time.date", (date, 1));
|
||||
m.insert("time.diff", (diff, 2));
|
||||
m.insert("time.format", (format, 1));
|
||||
m.insert("time.now_ns", (now_ns, 0));
|
||||
m.insert("time.parse_ns", (parse_ns, 2));
|
||||
m.insert("time.parse_rfc3339_ns", (parse_rfc3339_ns, 1));
|
||||
m.insert("time.weekday", (weekday, 1));
|
||||
}
|
||||
|
||||
fn now_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
fn add_date(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "time.add_date";
|
||||
ensure_args_count(span, name, params, args, 4)?;
|
||||
|
||||
let (datetime, _) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
let years = ensure_i32(name, ¶ms[1], &args[1])?;
|
||||
let months = ensure_i32(name, ¶ms[2], &args[2])?;
|
||||
let days = ensure_i32(name, ¶ms[3], &args[3])?;
|
||||
|
||||
let Some(new_year) = datetime.year().checked_add(years) else {
|
||||
return Ok(Value::Undefined);
|
||||
};
|
||||
|
||||
datetime
|
||||
.with_year(new_year)
|
||||
.and_then(|d| {
|
||||
let rhs = Months::new(months.unsigned_abs());
|
||||
if months >= 0 {
|
||||
d.checked_add_months(rhs)
|
||||
} else {
|
||||
d.checked_sub_months(rhs)
|
||||
}
|
||||
})
|
||||
.and_then(|d| {
|
||||
let rhs = Days::new(days.unsigned_abs() as u64);
|
||||
if days >= 0 {
|
||||
d.checked_add_days(rhs)
|
||||
} else {
|
||||
d.checked_sub_days(rhs)
|
||||
}
|
||||
})
|
||||
.map_or(Ok(Value::Undefined), |d| {
|
||||
safe_timestamp_nanos(span, strict, d.timestamp_nanos_opt())
|
||||
})
|
||||
}
|
||||
|
||||
fn clock(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "time.clock";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let (datetime, _) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
|
||||
Ok(Vec::from([
|
||||
(datetime.hour() as u64).into(),
|
||||
(datetime.minute() as u64).into(),
|
||||
(datetime.second() as u64).into(),
|
||||
])
|
||||
.into())
|
||||
}
|
||||
|
||||
fn date(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "time.date";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let (datetime, _) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
|
||||
Ok(Vec::from([
|
||||
(datetime.year() as u64).into(),
|
||||
(datetime.month() as u64).into(),
|
||||
(datetime.day() as u64).into(),
|
||||
])
|
||||
.into())
|
||||
}
|
||||
|
||||
fn diff(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "time.diff";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
let (datetime1, _) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
let (datetime2, _) = parse_epoch(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let (year, month, day, hour, min, sec) = diff::diff_between_datetimes(datetime1, datetime2)?;
|
||||
|
||||
Ok(Vec::from([
|
||||
(year as i64).into(),
|
||||
(month as i64).into(),
|
||||
(day as i64).into(),
|
||||
(hour as i64).into(),
|
||||
(min as i64).into(),
|
||||
(sec as i64).into(),
|
||||
])
|
||||
.into())
|
||||
}
|
||||
|
||||
fn format(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "time.format";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let (datetime, format) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let result = match format {
|
||||
Some(format) => datetime.format(&format).to_string(),
|
||||
None => datetime.to_rfc3339_opts(SecondsFormat::AutoSi, true),
|
||||
};
|
||||
|
||||
Ok(Value::String(result.into()))
|
||||
}
|
||||
|
||||
fn now_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "time.now_ns";
|
||||
ensure_args_count(span, name, params, args, 0)?;
|
||||
|
||||
let now = SystemTime::now();
|
||||
let elapsed = match now.duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(e) => e,
|
||||
Err(e) => bail!(span.error(format!("could not fetch elapsed time. {e}").as_str())),
|
||||
};
|
||||
let nanos = elapsed.as_nanos();
|
||||
Ok(Value::from(nanos))
|
||||
safe_timestamp_nanos(span, strict, Utc::now().timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
fn parse_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "time.parse_ns";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
let layout = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let value = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let datetime = NaiveDateTime::parse_from_str(&value, &layout)?;
|
||||
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
fn parse_rfc3339_ns(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "time.parse_rfc3339_ns";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let value = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let datetime = DateTime::parse_from_rfc3339(&value)?;
|
||||
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
fn weekday(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "time.weekday";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
let (datetime, _) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let weekday = match datetime.weekday() {
|
||||
Weekday::Mon => "Monday",
|
||||
Weekday::Tue => "Tuesday",
|
||||
Weekday::Wed => "Wednesday",
|
||||
Weekday::Thu => "Thursday",
|
||||
Weekday::Fri => "Friday",
|
||||
Weekday::Sat => "Saturday",
|
||||
Weekday::Sun => "Sunday",
|
||||
};
|
||||
|
||||
Ok(Value::String(weekday.into()))
|
||||
}
|
||||
|
||||
fn ensure_i32(name: &str, arg: &Expr, v: &Value) -> Result<i32> {
|
||||
ensure_numeric(name, arg, v)?
|
||||
.as_i64()
|
||||
.and_then(|n| n.try_into().ok())
|
||||
.ok_or_else(|| arg.span().error("could not convert to int32"))
|
||||
}
|
||||
|
||||
fn safe_timestamp_nanos(span: &Span, strict: bool, nanos: Option<i64>) -> Result<Value> {
|
||||
match nanos {
|
||||
Some(ns) => Ok(Value::Number(ns.into())),
|
||||
None if strict => {
|
||||
bail!(span.error("time outside of valid range"))
|
||||
}
|
||||
None => Ok(Value::Undefined),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_epoch(
|
||||
fcn: &str,
|
||||
arg: &Expr,
|
||||
val: &Value,
|
||||
) -> Result<(DateTime<FixedOffset>, Option<String>)> {
|
||||
match val {
|
||||
Value::Number(num) => {
|
||||
let ns = num.as_i64().ok_or_else(|| {
|
||||
arg.span()
|
||||
.error("could not convert numeric value of `ns` to int64")
|
||||
})?;
|
||||
|
||||
return Ok((Utc.timestamp_nanos(ns).fixed_offset(), None));
|
||||
}
|
||||
|
||||
Value::Array(arr) => match arr.as_slice() {
|
||||
[Value::Number(num)] => {
|
||||
let ns = num.as_i64().ok_or_else(|| {
|
||||
arg.span()
|
||||
.error("could not convert numeric value of `ns` to int64")
|
||||
})?;
|
||||
|
||||
return Ok((Utc.timestamp_nanos(ns).fixed_offset(), None));
|
||||
}
|
||||
[Value::Number(num), Value::String(tz), rest @ ..] => {
|
||||
let ns = num.as_i64().ok_or_else(|| {
|
||||
arg.span()
|
||||
.error("could not convert numeric value of `ns` to int64")
|
||||
})?;
|
||||
|
||||
let datetime = match tz.as_ref() {
|
||||
"UTC" | "" => Utc.timestamp_nanos(ns).fixed_offset(),
|
||||
"Local" => Local.timestamp_nanos(ns).fixed_offset(),
|
||||
_ => {
|
||||
let tz: Tz = tz.parse().map_err(|err: String| anyhow!(err))?;
|
||||
tz.timestamp_nanos(ns).fixed_offset()
|
||||
}
|
||||
};
|
||||
|
||||
let format = match rest.first() {
|
||||
Some(Value::String(format)) => Some(format.to_string()),
|
||||
Some(other) => {
|
||||
bail!(arg.span().error(&format!(
|
||||
"`{fcn}` expects 3rd element of `ns` to be a `string`. Got `{other}` instead"
|
||||
)))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
return Ok((datetime, format));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
bail!(arg.span().error(&format!(
|
||||
"`{fcn}` expects `ns` to be a `number` or `array[number, string]`. Got `{val}` instead"
|
||||
)))
|
||||
}
|
||||
|
||||
69
src/builtins/time/diff.rs
Normal file
69
src/builtins/time/diff.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT and Apache 2.0 License.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike, Utc};
|
||||
|
||||
// Adapted from the official Go implementation:
|
||||
// https://github.com/open-policy-agent/opa/blob/eb17a716b97720a27c6569395ba7c4b7409aae87/topdown/time.go#L179-L243
|
||||
pub fn diff_between_datetimes(
|
||||
datetime1: DateTime<FixedOffset>,
|
||||
datetime2: DateTime<FixedOffset>,
|
||||
) -> Result<(i32, i32, i32, i32, i32, i32)> {
|
||||
// The following implementation of this function is taken
|
||||
// from https://github.com/icza/gox licensed under Apache 2.0.
|
||||
// The only modification made is to variable names.
|
||||
//
|
||||
// For details, see https://stackoverflow.com/a/36531443/1705598
|
||||
//
|
||||
// Copyright 2021 icza
|
||||
// BEGIN REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT
|
||||
|
||||
// Make sure both datetimes in the same timezone
|
||||
let datetime2 = datetime2.with_timezone(&datetime1.timezone());
|
||||
|
||||
// Make sure `datetime1` is always the smallest one
|
||||
let (datetime1, datetime2) = if datetime1 > datetime2 {
|
||||
(datetime2, datetime1)
|
||||
} else {
|
||||
(datetime1, datetime2)
|
||||
};
|
||||
|
||||
let mut year = datetime2.year() - datetime1.year();
|
||||
let mut month = datetime2.month() as i32 - datetime1.month() as i32;
|
||||
let mut day = datetime2.day() as i32 - datetime1.day() as i32;
|
||||
let mut hour = datetime2.hour() as i32 - datetime1.hour() as i32;
|
||||
let mut min = datetime2.minute() as i32 - datetime1.minute() as i32;
|
||||
let mut sec = datetime2.second() as i32 - datetime1.second() as i32;
|
||||
|
||||
// Normalize negative values
|
||||
if sec < 0 {
|
||||
sec += 60;
|
||||
min -= 1;
|
||||
}
|
||||
if min < 0 {
|
||||
min += 60;
|
||||
hour -= 1;
|
||||
}
|
||||
if hour < 0 {
|
||||
hour += 24;
|
||||
day -= 1;
|
||||
}
|
||||
if day < 0 {
|
||||
// Days in month:
|
||||
let t = Utc
|
||||
.with_ymd_and_hms(datetime1.year(), datetime1.month(), 32, 0, 0, 0)
|
||||
.single()
|
||||
.ok_or(anyhow!("Could not convert `ns1` to datetime"))?;
|
||||
day += 32 - t.day() as i32;
|
||||
month -= 1;
|
||||
}
|
||||
if month < 0 {
|
||||
month += 12;
|
||||
year -= 1;
|
||||
}
|
||||
|
||||
// END REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT
|
||||
|
||||
Ok((year, month, day, hour, min, sec))
|
||||
}
|
||||
253
tests/interpreter/cases/builtins/time/add_date.yaml
Normal file
253
tests/interpreter/cases/builtins/time/add_date.yaml
Normal file
@@ -0,0 +1,253 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: adding-nothing
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 0, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1703444325734390000
|
||||
|
||||
- note: adding-years
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 1, 0, 0)
|
||||
b := time.add_date(1703444325734390000, 60, 0, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1735066725734390000
|
||||
b: 3596900325734390000
|
||||
|
||||
- note: subtracting-years
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, -1, 0, 0)
|
||||
b := time.add_date(1703444325734390000, -60, 0, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1671908325734390000
|
||||
b: -190011674265610000
|
||||
|
||||
- note: adding-overflowing-years
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 2147483647, 0, 0)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: subtracting-overflowing-years
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, -2147483647, 0, 0)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: adding-months
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 1, 0)
|
||||
b := time.add_date(1703444325734390000, 0, 12, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1706122725734390000
|
||||
b: 1735066725734390000
|
||||
|
||||
- note: subtracting-months
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, -1, 0)
|
||||
b := time.add_date(1703444325734390000, 0, -12, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1700852325734390000
|
||||
b: 1671908325734390000
|
||||
|
||||
- note: adding-overflowing-months
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 15, 0)
|
||||
b := time.add_date(1703444325734390000, 0, 19, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1742842725734390000
|
||||
b: 1753383525734390000
|
||||
|
||||
- note: subtracting-overflowing-months
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, -15, 0)
|
||||
b := time.add_date(1703444325734390000, 0, -19, 0)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1664045925734390000
|
||||
b: 1653418725734390000
|
||||
|
||||
- note: adding-days
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 0, 5)
|
||||
b := time.add_date(1703444325734390000, 0, 0, 16)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1703876325734390000
|
||||
b: 1704826725734390000
|
||||
|
||||
- note: subtracting-days
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 0, -5)
|
||||
b := time.add_date(1703444325734390000, 0, 0, -16)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1703012325734390000
|
||||
b: 1702061925734390000
|
||||
|
||||
- note: adding-overflowing-days
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 0, 31)
|
||||
b := time.add_date(1703444325734390000, 0, 0, 37)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1706122725734390000
|
||||
b: 1706641125734390000
|
||||
|
||||
- note: subtracting-overflowing-days
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 0, 0, -31)
|
||||
b := time.add_date(1703444325734390000, 0, 0, -37)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1700765925734390000
|
||||
b: 1700247525734390000
|
||||
|
||||
- note: adding-mixed
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, 213, 6, 5)
|
||||
b := time.add_date(1703444325734390000, 5, 0, 6)
|
||||
c := time.add_date(1703444325734390000, 0, 3, 16)
|
||||
d := time.add_date(1703444325734390000, 7, 3, 0)
|
||||
e := time.add_date(1703444325734390000, 10, 2, 16)
|
||||
f := time.add_date(1703444325734390000, 10, 14, 16)
|
||||
g := time.add_date(1703444325734390000, 10, 14, 75)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 8441261925734390000
|
||||
b: 1861815525734390000
|
||||
c: 1712689125734390000
|
||||
d: 1932145125734390000
|
||||
e: 2025802725734390000
|
||||
f: 2057338725734390000
|
||||
g: 2062436325734390000
|
||||
|
||||
- note: subtracting-mixed
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, -213, -6, -5)
|
||||
b := time.add_date(1703444325734390000, -5, 0, -6)
|
||||
c := time.add_date(1703444325734390000, 0, -3, -16)
|
||||
d := time.add_date(1703444325734390000, -7, -3, 0)
|
||||
e := time.add_date(1703444325734390000, -10, -2, -16)
|
||||
f := time.add_date(1703444325734390000, -10, -14, -16)
|
||||
g := time.add_date(1703444325734390000, -10, -14, -75)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: -5034459674265610000
|
||||
b: 1545159525734390000
|
||||
c: 1694199525734390000
|
||||
d: 1474743525734390000
|
||||
e: 1381258725734390000
|
||||
f: 1349722725734390000
|
||||
g: 1344625125734390000
|
||||
|
||||
- note: mixed-operations
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date(1703444325734390000, -213, 6, -5)
|
||||
b := time.add_date(1703444325734390000, 5, 0, -6)
|
||||
c := time.add_date(1703444325734390000, 0, -3, -16)
|
||||
d := time.add_date(1703444325734390000, 8, -3, 0)
|
||||
e := time.add_date(1703444325734390000, -10, 2, 16)
|
||||
f := time.add_date(1703444325734390000, -10, 14, -16)
|
||||
g := time.add_date(1703444325734390000, 10, 14, -75)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: -5002923674265610000
|
||||
b: 1860778725734390000
|
||||
c: 1694199525734390000
|
||||
d: 1948042725734390000
|
||||
e: 1394650725734390000
|
||||
f: 1423421925734390000
|
||||
g: 2049476325734390000
|
||||
|
||||
- note: missing-arguments
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date()
|
||||
query: data.test
|
||||
error: '`time.add_date` expects 4 arguments'
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.add_date("1703444325734390000", 0, 0, 0)
|
||||
query: data.test
|
||||
error: '`time.add_date` expects `ns` to be a `number` or `array[number, string]`. Got `"1703444325734390000"` instead'
|
||||
71
tests/interpreter/cases/builtins/time/clock.yaml
Normal file
71
tests/interpreter/cases/builtins/time/clock.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: without-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.clock(1703444325734390000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 18
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: with-utc-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.clock([1703444325734390000, "UTC"])
|
||||
b := time.clock([1703444325734390000, ""])
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 18
|
||||
- 58
|
||||
- 45
|
||||
b:
|
||||
- 18
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: with-cet-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.clock([1703444325734390000, "CET"])
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 19
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: with-local-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.clock([1703444325734390000, "Local"]) != null
|
||||
query: data.test
|
||||
want_result:
|
||||
a: true
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.clock("1703444325734390000")
|
||||
query: data.test
|
||||
error: '`time.clock` expects `ns` to be a `number` or `array[number, string]`. Got `"1703444325734390000"` instead'
|
||||
46
tests/interpreter/cases/builtins/time/date.yaml
Normal file
46
tests/interpreter/cases/builtins/time/date.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: without-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.date(1703444325734390000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 2023
|
||||
- 12
|
||||
- 24
|
||||
|
||||
- note: with-utc-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.date([1257894000000000000, "UTC"])
|
||||
b := time.date([1703896119423491000, ""])
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 2009
|
||||
- 11
|
||||
- 10
|
||||
b:
|
||||
- 2023
|
||||
- 12
|
||||
- 30
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.date("1703444325734390000")
|
||||
query: data.test
|
||||
error: '`time.date` expects `ns` to be a `number` or `array[number, string]`. Got `"1703444325734390000"` instead'
|
||||
64
tests/interpreter/cases/builtins/time/diff.yaml
Normal file
64
tests/interpreter/cases/builtins/time/diff.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: without-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.diff(1703444325734390000, 1257894000000000000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 14
|
||||
- 1
|
||||
- 13
|
||||
- 19
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: with-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.diff([1703444325734390000, "UTC"], [1257894000000000000, ""])
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 14
|
||||
- 1
|
||||
- 13
|
||||
- 19
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: tz1-greater-than-tz2
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.diff(1257894000000000000, 1703444325734390000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 14
|
||||
- 1
|
||||
- 13
|
||||
- 19
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.diff("1703444325734390000", 1257894000000000000)
|
||||
query: data.test
|
||||
error: '`time.diff` expects `ns` to be a `number` or `array[number, string]`. Got `"1703444325734390000"` instead'
|
||||
52
tests/interpreter/cases/builtins/time/format.yaml
Normal file
52
tests/interpreter/cases/builtins/time/format.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: without-timezone-and-format
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.format(1703444325734390000)
|
||||
b := time.format(1257894000000000000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "2023-12-24T18:58:45.734390Z"
|
||||
b: "2009-11-10T23:00:00Z"
|
||||
|
||||
- note: with-timezone-and-no-format
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.format([1703444325734390000, "UTC"])
|
||||
b := time.format([1257894000000000000, ""])
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "2023-12-24T18:58:45.734390Z"
|
||||
b: "2009-11-10T23:00:00Z"
|
||||
|
||||
- note: with-timezone-and-format
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.format([1703444325734390000, "UTC", "%Y-%m-%dT%H:%M:%S"])
|
||||
b := time.format([1257894000000000000, "", "%d/%m/%Y %H:%M"])
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "2023-12-24T18:58:45"
|
||||
b: "10/11/2009 23:00"
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.format([1703444325734390000, "UTC", 42])
|
||||
query: data.test
|
||||
error: '`time.format` expects 3rd element of `ns` to be a `string`. Got `42` instead'
|
||||
29
tests/interpreter/cases/builtins/time/now_ns.yaml
Normal file
29
tests/interpreter/cases/builtins/time/now_ns.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: now-is-greater-than-2009
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.now_ns() > 1257894000000000000
|
||||
query: data.test
|
||||
want_result:
|
||||
a: true
|
||||
|
||||
- note: now-is-cached
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := res {
|
||||
first := time.now_ns()
|
||||
second := time.now_ns()
|
||||
res := second == first
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
a: true
|
||||
40
tests/interpreter/cases/builtins/time/parse_ns.yaml
Normal file
40
tests/interpreter/cases/builtins/time/parse_ns.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: parse
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_ns("%Y-%m-%dT%H:%M:%S", "2006-01-02T15:04:05")
|
||||
b := time.parse_ns("%Y-%m-%d %H:%M:%S", "2015-09-05 23:56:04")
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1136214245000000000
|
||||
b: 1441497364000000000
|
||||
|
||||
- note: format-and-parse-back
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := res {
|
||||
date := time.format([1703444325734390000, "UTC", "%Y-%m-%dT%H:%M:%S%.f"])
|
||||
res := time.parse_ns("%Y-%m-%dT%H:%M:%S%.f", date)
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1703444325734390000
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_ns("%Y-%m-%dT%H:%M:%S%.f", 1703444325734390000)
|
||||
query: data.test
|
||||
error: '`time.parse_ns` expects string argument. Got `1703444325734390000` instead'
|
||||
27
tests/interpreter/cases/builtins/time/parse_rfc3339_ns.yaml
Normal file
27
tests/interpreter/cases/builtins/time/parse_rfc3339_ns.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: parse-rfc3339
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_rfc3339_ns("1985-04-12T23:20:50.52Z")
|
||||
b := time.parse_rfc3339_ns("1996-12-19T16:39:57-08:00")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 482196050520000000
|
||||
b: 851042397000000000
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_rfc3339_ns(482196050520000000)
|
||||
query: data.test
|
||||
error: '`time.parse_rfc3339_ns` expects string argument. Got `482196050520000000` instead'
|
||||
39
tests/interpreter/cases/builtins/time/weekday.yaml
Normal file
39
tests/interpreter/cases/builtins/time/weekday.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: without-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.weekday(1703444325734390000)
|
||||
b := time.weekday(1257894000000000000)
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "Sunday"
|
||||
b: "Tuesday"
|
||||
|
||||
- note: with-timezone
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.weekday([1703444325734390000, "UTC"])
|
||||
b := time.weekday([1257894000000000000, ""])
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "Sunday"
|
||||
b: "Tuesday"
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.weekday("1703444325734390000")
|
||||
query: data.test
|
||||
error: '`time.weekday` expects `ns` to be a `number` or `array[number, string]`. Got `"1703444325734390000"` instead'
|
||||
Reference in New Issue
Block a user