Skip to content

Commit

Permalink
Add duration strings, and switch to durationSince
Browse files Browse the repository at this point in the history
  • Loading branch information
apg committed Aug 27, 2024
1 parent fcdac27 commit 6398ba4
Showing 1 changed file with 30 additions and 25 deletions.
55 changes: 30 additions & 25 deletions text/0080-datetime-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
};
```

Expand Down Expand Up @@ -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")
};
```

Expand Down Expand Up @@ -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`
Expand All @@ -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`)
Expand All @@ -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

Expand Down Expand Up @@ -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")
};
```

Expand All @@ -468,7 +473,7 @@ 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:

Expand Down

0 comments on commit 6398ba4

Please sign in to comment.