diff --git a/hipcheck/src/policy_exprs/env.rs b/hipcheck/src/policy_exprs/env.rs index f8f10835..c2854b84 100644 --- a/hipcheck/src/policy_exprs/env.rs +++ b/hipcheck/src/policy_exprs/env.rs @@ -522,7 +522,7 @@ fn divz(env: &Env, args: &[Expr]) -> Result { binary_primitive_op(name, env, args, op) } -// Finds the difference in time between two datetimes, in units of hours (chosen for comparision safety) +// Finds the difference in time between two datetimes, in units no larger than hours (chosen for comparision safety) fn duration(env: &Env, args: &[Expr]) -> Result { let name = "duration"; diff --git a/hipcheck/src/policy_exprs/expr.rs b/hipcheck/src/policy_exprs/expr.rs index bde4a575..70042b35 100644 --- a/hipcheck/src/policy_exprs/expr.rs +++ b/hipcheck/src/policy_exprs/expr.rs @@ -60,10 +60,10 @@ pub enum Primitive { /// Span of time using the [jiff] crate, which uses a modified version of ISO8601. /// - /// Can include weeks, days, hours, minutes, and seconds (including decimal fractions of a second). + /// Can include weeks, days, hours, minutes, and seconds. The smallest provided unit of time (but not weeks or days) can have a decimal fraction. /// While spans with months, years, or both are valid under IS08601 and supported by [jiff] in general, we do not allow them in Hipcheck policy expressions. /// This is because spans greater than a day require additional zoned datetime information in [jiff] (to determine e.g. how many days are in a year or month) - /// before we can do time arithematic with them. + /// before we can do time arithmetic with them. /// We *do* allows spans with weeks, even though [jiff] has similar issues with those units. /// We take care of this by converting a week to a period of seven 24-hour days that [jiff] can handle in arithematic without zoned datetime information. /// @@ -327,10 +327,10 @@ mod tests { #[test] fn parse_span() { - let input = "P2W4DT1H30M"; + let input = "P2W4DT1H30.5M"; let result = parse(input).unwrap(); - let raw_span: Span = "P18DT1H30M".parse().unwrap(); + let raw_span: Span = "P18DT1H30.5M".parse().unwrap(); let expected = span(raw_span).into_expr(); assert_eq!(result, expected); diff --git a/hipcheck/src/policy_exprs/token.rs b/hipcheck/src/policy_exprs/token.rs index 2cfb296d..c3bf2657 100644 --- a/hipcheck/src/policy_exprs/token.rs +++ b/hipcheck/src/policy_exprs/token.rs @@ -46,7 +46,7 @@ pub enum Token { // In the future this regex *could* be made more specific to reduce collision // with Ident, or we could introduce a special prefix character like '@' or '#' - #[regex(r"PT?[0-9]+[a-zA-Z][^\s\)]*", lex_span)] + #[regex(r"PT?[0-9.]+[a-zA-Z][^\s\)]*", lex_span)] Span(Box), // Prioritize over span regex, which starts with a 'P' diff --git a/site/content/rfds/0004-plugin-api.md b/site/content/rfds/0004-plugin-api.md index 9ec521ce..80642e37 100644 --- a/site/content/rfds/0004-plugin-api.md +++ b/site/content/rfds/0004-plugin-api.md @@ -607,11 +607,15 @@ The policy expression language is limited. It does not permit user-defined functions, assignment to variables, or the retention of any state. Any policy expression which does _not_ result in a boolean output will produce an error. The primitive types accepted in policy expressions are only -integers, floating point numbers, and booleans. +integers, floating point numbers, booleans, date-times, and spans of time. - `Integer`: 64-bit signed integers. - `Float`: 64-bit IEEE double-precision floating point numbers. - `Boolean`: True (`#t`) or false (`#f`). +- `DateTime`: A date-time value with a date, optional time, and optional UTC + offset. This follows a modified version of ISO8601 (see below for details). +- `Span`: A span of time, in some combination of weeks, days, hours, minutes, + seconds. This follows a modified version of ISO8601 (see below for details). Policy expressions also accept arrays, which are ordered sequences of values of the same type from the list of primitive types above. Arrays may @@ -644,12 +648,17 @@ The following functions are currently defined for policy expressions: - __`neq`__: Not-equal comparison. Example: `(neq 1 1)` is `#f`. This function can be partially-evaluated when passed to a higher-order array function. - Mathematical Functions - - __`add`__: Adds two integers or floats together. This function can be - partially-evaluated when passed to a higher-order array function. - - __`sub`__: Subtracts one integer or float from another. This function - can be partially-evaluated when passed to `foreach`, in which case the - order of the parameters is switched to match the expected result. So + - __`add`__: Adds two integers, floats, or spans together. Adds a date-time and a + span to get new date-time. This function can be partially-evaluated when passed to + a higher-order array function. + - __`sub`__: Subtracts one integer, float, or span from another. Subtracts a + span from a date-time to get a new date-time. This function can be + partially-evaluated when passed to `foreach`, in which case the order of + the parameters is switched to match the expected result. So `(foreach (sub 1) [1 2 3 4 5])` becomes `[0 1 2 3 4]`. +- Date-time Specific Function + - __`duration`__: Returns the span representing the difference between two + date-times. - Logical Functions - __`and`__: Performs the logical "and" of two booleans. Example: `(and #t #f)` becomes `#f`. @@ -702,6 +711,63 @@ array. So `$/items/0`, for example, would select into the `items` field of the provided object, and then into the first (`0`-th) element of that array. +#### Date-time format details +The policy expression Date-time primitive uses a modified version of ISO8601. +A given date-time must include a date in the format `--
`. + +An optional time in the format `T:[MM]:[SS]` can be provided after the date. +**Decimal fractions of hours and minutes are not allowed**; use smaller time units +instead (e.g. T10:30 instead of T10.5). Decimal fractions of seconds are allowed. + +The timezone is always set to UTC, but you can set an offeset from UTC by +including `+{HH}:[MM]` or `-{HH}:[MM]` after the time. All datetimes are +treated as offsets ofUTC and do not include other timezone information (e.g. +daylight savings time adjustments). + +Example of valid datetimes are: +- `2024-09-25` +- `2024-09-25T08` +- `2024-09-25T08:28:35` +- `2024-09-25T08:30-05` +- `2024-09-25T08:28:35-03:30` + +All comparison functions work on date-times, with earlier date-times +considered to be "lesser than" later ones. A span can be added to or +subtraced from a date-time to get a new date-time. The __`duration`__ +function is used to find the difference in time between two date-times, +returned as a span. + +#### Span format details +The policy expression Span primitive uses a modified version of ISO8601. It can +include weeks, days, hours, minutes, and seconds. The smallest provided unit of +time (i.e. hours, minutes, or seconds) can have a decimal fraction. + +While spans with months, years, or both are valid under IS08601, we do not allow +them in Hipcheck policy expressions. This is because spans greater than a day +require additional zoned datetime information (to determine e.g. how many days +are in a year or month) before we can do time arithmetic with them. +Spans with weeks, days, or both **are** allowed. Hipcheck handles this by treating +all days as periods of exactly 24 hours (without worrying about leap seconds) +and converting a week to a period of seven 24-hour days. + +Spans are preceded by the letter "P" with any optional time units separated from +optional date units by the letter "T" (not whitespace, as is also allowed under +ISO8601). All units of dates and times are represented by single case-agnostic +letter abbreviations after the number. + +Examples of valid spans are: +- `P4w` +- `P3d` +- `P1W2D` +- `PT4h15.25m` +- `PT5s` +- `P1w2dT3h4m5.6s` + +All comparison functions work on spans. This is handled the "smart way," with +spans that have different representations but equalling the same amount of time +considred to be equal. e.g. `(eq PT1h PT60m)` will return `#t`. Spans can be added +to or subtracted from each other to return a new span. + #### Limitations of JSON Pointer syntax relative to RFC The [JSON Pointer spec (RFC 6901)](https://datatracker.ietf.org/doc/html/rfc6901)