diff --git a/text/0080-datetime-extension.md b/text/0080-datetime-extension.md index 4dde787c..4177598f 100644 --- a/text/0080-datetime-extension.md +++ b/text/0080-datetime-extension.md @@ -30,7 +30,7 @@ permit( when { principal.department == "HardwareEngineering" && principal.jobLevel >= 10 && - (context.now.timestamp - principal.hireDate) > duration(365, "days") + (context.now.timestamp - principal.hireDate) > duration("365d") }; ``` @@ -62,7 +62,7 @@ permit( resource is Photo ) when { resource.fileType == "JPEG" && - (context.now.timestamp - resource.creationTime) <= duration(7, "days") + (context.now.timestamp - resource.creationTime) <= duration("7d") }; ``` @@ -142,26 +142,11 @@ The `datetime` type does not provide a way for a policy author to create a `date Values of type `datetime` have the following methods: - `.offset(duration)` returns a new `datetime`, offset by duration. +- `.durationSince(DT2)` returns the difference between `DT` and `DT2` as a `duration`. (Note that the inverse of `durationSince` is `DT1.offset(duration)`). + An invariant for `DT1.durationSince(DT2)` is that when `DT1` is before `DT2` the resulting duration is negative. - `.toDate()` returns a new `datetime`, truncating to the day, such that printing the `datetime` would have `00:00:00` as the time. - `.toTime()` returns a new `duration`, removing the days, such that only milliseconds since `.toDate()` are left. This is equivalent to `DT - DT.toDate()` -Values of type `datetime` can also be used with the `-` operator. - -- `DT1 - DT2` returns the difference between `DT1` and `DT2` as a `duration`. (The inverse of `-` is `DT1.offset(duration)`) - -An invariant for the `-` operator is that when `DT1` is before `DT2` the resulting duration is negative. - -The internal representation of `DT1` may be negative (_i.e._ representing milliseconds _before_ `1970-01-01T00:00:00Z`) requiring that `-` be computed specially: - -```cedar -if DT1 < 0 && DT2 >= 0 then - result = -(DT1 - DT2) -else - result = DT1 - DT2 -``` - -There is not a use case for adding, or multiplying `datetime` values together. As such, `DT1 * DT2` and `DT1 + DT2` are errors. - Values of type `datetime` can be used with comparison operators: - `DT1 < DT2` returns `true` when `DT1` is before `DT2` @@ -177,11 +162,31 @@ Equality is based on the underlying representation (see below) so, for example, The `datetime` type is internally represented as a `long` and contains a Unix Time in milliseconds. This is the number of non-leap seconds that have passed since `1970-01-01T00:00:00Z` in milliseconds. Unix Time days are always 86,400 seconds and handle leap seconds by absorbing them at the start of the day. Due to using Unix Time, and not providing a "current time" function, Cedar avoids the complexities of leap second handling, pushing them to the system and application. -Negative Unix Time values represent the number of milliseconds before `1970-01-01T00:00:00Z`. +Negative Unix Time values represent the number of milliseconds before `1970-01-01T00:00:00Z`. This means that in order for the `durationSince` invariant to hold, we must compute it as such: + +```cedar +if DT1.ms < 0 && DT2.ms >= 0 then + result = -(DT1.ms - DT2.ms) +else + result = DT1.ms - DT2.ms +``` + ### Durations of Time (`duration`) -The `duration(long, string)` function constructs a duration value. The string argument must be one of `"days", "hours", "minutes", "seconds", "milliseconds"`. Strict validation requires `long` and `string` to be literals, although evaluation/authorization support any appropriately-typed expressions. Values of type `duration` have the following methods: +The `duration(string)` function constructs a duration value from a duration string. Strict validation requires the argument to be a literal, although evaluation/authorization support any appropriately-typed expressions. The `string` is a concatenated sequence of quantity-unit pairs. For example, `"1d2h3m4s5ms"` is a valid duration string. + +The quantity part is a positive or negative integer. The unit is one of the following: + +* `d`: days +* `h`: hours +* `m`: minutes +* `s`: seconds +* `ms`: milliseconds + +By convention, duration strings should be ordered from largest unit to smallest unit, and contain one quantity per unit. Units with 0 quantity can be omitted. `"1h"`, `"-10h"`, `"5d3ms"`, and `"3m5h"` are all valid duration strings. `"3m5h"`, while valid, is more clearly expressed as `"5h3m"`. + +Values of type `duration` have the following methods: - `.toMilliseconds()` returns a `long` describing the number of milliseconds in this duration. (the value as a long, itself) - `.toSeconds()` returns a `long` describing the number of seconds in this duration. (`.toMilliseconds() / 1000`) @@ -198,7 +203,7 @@ Values with type `duration` can also be used with comparison operators: - `DUR1 == DUR2` returns `true` when `DUR1` is equal to `DUR2` - `DUR1 != DUR2` returns `true` when `DUR1` is not equal to `DUR2` -Equality is based on the underlying representation (see below) so, for example, `duration(1, "days") == duration(24, "hours")` is true. +Equality is based on the underlying representation (see below) so, for example, `duration("1d") == duration("24h")` is true. #### Representation @@ -446,8 +451,8 @@ permit( action == Action::"access", resource ) when { - context.now.timestamp.offset(principal.timeZoneOffset).toTime() >= duration(9, "hours") && - context.now.timestamp.offset(principal.timeZoneOffset).toTime() <= duration(17, "hours") + context.now.timestamp.offset(principal.timeZoneOffset).toTime() >= duration("9h") && + context.now.timestamp.offset(principal.timeZoneOffset).toTime() <= duration("17h") }; ``` @@ -468,13 +473,13 @@ Note that names like `.lessThan()` are already reserved by the decimal extension The current proposal supports both positive and negative durations. A negative duration may be useful when a user wants to use `.offset()` to shift a date backwards. -For example: `context.now.offset(duration(-3, "days"))` expresses "three days before the current date". +For example: `context.now.offset(duration("-3d"))` expresses "three days before the current date". But some users may find the concept of negative durations confusing, so we have also considered some alternative definition of subtraction (`-`) that will allow us to avoid them: -- Define `DT1 - DT2` as the _absolute value_ of the difference between `DT1` and `DT2`. -- Define `DT1 - DT2` to be zero when `DT2 > DT1`. -- Define `DT1 - DT2` to be an error when `DT2 > DT1`. +- Define `DT1.durationSince(DT2)` as the _absolute value_ of the difference between `DT1` and `DT2`. +- Define `DT1.durationSince(DT2)` to be zero when `DT2 > DT1`. +- Define `DT1.durationSince(DT2)` to be an error when `DT2 > DT1`. ### Is millisecond precision enough? (Or too much?)