# Time Built-ins

| Function | Description | Meta |
| --- | --- | --- |
| `time.add_date` | `output := time.add_date(ns, years, months, days)`  Returns the nanoseconds since epoch after adding years, months and days to nanoseconds. Month & day values outside their usual ranges after the operation and will be normalized - for example, October 32 would become November 1. `undefined` if the result would be outside the valid time range that can fit within an `int64`.  **Arguments:**  `ns` (number)  nanoseconds since the epoch  `years` (number)  number of years to add  `months` (number)  number of months to add  `days` (number)  number of days to add  **Returns:**  `output` (number)  nanoseconds since the epoch representing the input time, with years, months and days added | [v0.19.0](https://github.com/open-policy-agent/opa/releases/v0.19.0) SDK-dependent |
| `time.clock` | `output := time.clock(x)`  Returns the `[hour, minute, second]` of the day for the nanoseconds since epoch.  **Arguments:**  `x` (any<number, array<number, string>>)  a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string  **Returns:**  `output` (array<number, number, number>)  the `hour`, `minute` (0-59), and `second` (0-59) representing the time of day for the nanoseconds since epoch in the supplied timezone (or UTC) | SDK-dependent |
| `time.date` | `date := time.date(x)`  Returns the `[year, month, day]` for the nanoseconds since epoch.  **Arguments:**  `x` (any<number, array<number, string>>)  a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string  **Returns:**  `date` (array<number, number, number>)  an array of `year`, `month` (1-12), and `day` (1-31) | SDK-dependent |
| `time.diff` | `output := time.diff(ns1, ns2)`  Returns the difference between two unix timestamps in nanoseconds (with optional timezone strings).  **Arguments:**  `ns1` (any<number, array<number, string>>)  nanoseconds since the epoch; or a two-element array of the nanoseconds, and a timezone string  `ns2` (any<number, array<number, string>>)  nanoseconds since the epoch; or a two-element array of the nanoseconds, and a timezone string  **Returns:**  `output` (array<number, number, number, number, number, number>)  difference between `ns1` and `ns2` (in their supplied timezones, if supplied, or UTC) as array of numbers: `[years, months, days, hours, minutes, seconds]` | [v0.28.0](https://github.com/open-policy-agent/opa/releases/v0.28.0) SDK-dependent |
| `time.format` | `formatted timestamp := time.format(x)`  Returns the formatted timestamp for the nanoseconds since epoch.  **Arguments:**  `x` (any<number, array<number, string>, array<number, string, string>>)  a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string; or a three-element array of ns, timezone string and a layout string or golang defined formatting constant (see golang supported time formats)  **Returns:**  `formatted timestamp` (string)  the formatted timestamp represented for the nanoseconds since the epoch in the supplied timezone (or UTC) | [v0.48.0](https://github.com/open-policy-agent/opa/releases/v0.48.0) SDK-dependent |
| `time.now_ns` | `now := time.now_ns()`  Returns the current time since epoch in nanoseconds.  **Returns:**  `now` (number)  nanoseconds since epoch | SDK-dependent |
| `time.parse_duration_ns` | `ns := time.parse_duration_ns(duration)`  Returns the duration in nanoseconds represented by a string.  **Arguments:**  `duration` (string)  a duration like "3m"; see the [OPA `Duration Parsing` documentation](https://www.openpolicyagent.org/docs/latest/policy-reference/builtins/time#duration-parsing) for more details  **Returns:**  `ns` (number)  the `duration` in nanoseconds | SDK-dependent |
| `time.parse_ns` | `ns := time.parse_ns(layout, value)`  Returns the time in nanoseconds parsed from the string in the given format. `undefined` if the result would be outside the valid time range that can fit within an `int64`.  **Arguments:**  `layout` (string)  format used for parsing, see the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details  `value` (string)  input to parse according to `layout`  **Returns:**  `ns` (number)  `value` in nanoseconds since epoch | SDK-dependent |
| `time.parse_rfc3339_ns` | `ns := time.parse_rfc3339_ns(value)`  Returns the time in nanoseconds parsed from the string in RFC3339 format. `undefined` if the result would be outside the valid time range that can fit within an `int64`.  **Arguments:**  `value` (string)  input string to parse in RFC3339 format  **Returns:**  `ns` (number)  `value` in nanoseconds since epoch | SDK-dependent |
| `time.weekday` | `day := time.weekday(x)`  Returns the day of the week (Monday, Tuesday, ...) for the nanoseconds since epoch.  **Arguments:**  `x` (any<number, array<number, string>>)  a number representing the nanoseconds since the epoch (UTC); or a two-element array of the nanoseconds, and a timezone string  **Returns:**  `day` (string)  the weekday represented by `ns` nanoseconds since the epoch in the supplied timezone (or UTC) | SDK-dependent |

info

Multiple calls to the `time.now_ns` built-in function within a single policy evaluation query will always return the same value.

Timezones can be specified as

*   an [IANA Time Zone](https://www.iana.org/time-zones) string e.g. "America/New\_York"
*   "UTC" or "", which are equivalent to not passing a timezone (i.e. will return as UTC)
*   "Local", which will use the local timezone.

Note that OPA will use the `time/tzdata` data if none is present on the runtime filesystem (see the [Go `time.LoadLocation()`](https://pkg.go.dev/time#LoadLocation) documentation for more information).

Duration Parsing

OPA supports the following time units for `time.parse_duration_ns`:

*   `ns` - NanoSeconds
*   `us` (or `µs`) - MicroSeconds
*   `ms` - MilliSeconds
*   `s` - Seconds
*   `m` - Minutes (ignoring leap seconds)
*   `h` - Hours
*   `d` - Days (ignoring so-called daylight saving time)
*   `w` - Weeks
*   `y` - Years (ignoring leap days)

Timestamp Parsing

OPA can parse timestamps of nearly arbitrary formats, and currently accepts the same inputs as Go's `time.Parse()` utility. As a result, either you will pass a supported constant, or you **must** describe the format of your timestamps using the Reference Timestamp that Go's `time` module expects:

2006-01-02T15:04:05Z07:00

In other date formats, that same value is rendered as:

*   January 2, 15:04:05, 2006, in time zone seven hours west of GMT
*   Unix time: `1136239445`
*   Unix `date` command output: `Mon Jan 2 15:04:05 MST 2006`
*   RFC3339 timestamp: `2006-01-02T15:04:05Z07:00`

Examples of valid values for each timestamp field:

*   Year: `"2006"` `"06"`
*   Month: `"Jan"` `"January"` `"01"` `"1"`
*   Day of the week: `"Mon"` `"Monday"`
*   Day of the month: `"2"` `"_2"` `"02"`
*   Day of the year: `"__2"` `"002"`
*   Hour: `"15"` `"3"` `"03"` (PM or AM)
*   Minute: `"4"` `"04"`
*   Second: `"5"` `"05"`
*   AM/PM mark: `"PM"`

For supported constants, formatting of nanoseconds, time zones, and other fields, see the [Go `time/format` module documentation](https://cs.opensource.google/go/go/+/master:src/time/format.go;l=9-113).

## Examples

### `clock`

`time.clock` is Rego's built-in function that returns the 'clock time' (hours, minutes, seconds) for a given time in nanoseconds and timezone.

This is most useful for creating policy that's relative to a user's local time, or showing time-based information in a human-readable format in error messages that are shown to the user.

Grant access during local business hours

A common attribute-based access control (ABAC) requirement is to grant access based on time. This is typically done by determining the user's local time and ensuring it falls within a given period.

In this example we show how to allow requests when made by a user in their local business hours.

policy.rego

```
package playimport rego.v1request_time := time.parse_ns("RFC822Z", input.request_time)local_hours := data.business_hours[input.tz]default allow := falseallow if {	[hour, _, _] := time.clock([request_time, input.tz])	hour > local_hours.start	hour < local_hours.end}
```

Output

{
  "allow": false,
  "local\_hours": {
    "end": 18,
    "start": 9
  },
  "request\_time": 1720015440000000000
}

input.json

```
{  "user": "y.hanako@example.com",  "tz": "Asia/Tokyo",  "request_time": "03 Jul 24 14:04 +0000"}
```

data.json

```
{  "business_hours": {    "Asia/Tokyo": {      "start": 9,      "end": 18    }  }}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInVzZXJcIjogXCJ5LmhhbmFrb0BleGFtcGxlLmNvbVwiLFxuICBcInR6XCI6IFwiQXNpYS9Ub2t5b1wiLFxuICBcInJlcXVlc3RfdGltZVwiOiBcIjAzIEp1bCAyNCAxNDowNCArMDAwMFwiXG59IiwiZCI6IntcbiAgXCJidXNpbmVzc19ob3Vyc1wiOiB7XG4gICAgXCJBc2lhL1Rva3lvXCI6IHtcbiAgICAgIFwic3RhcnRcIjogOSxcbiAgICAgIFwiZW5kXCI6IDE4XG4gICAgfVxuICB9XG59IiwicCI6InBhY2thZ2UgcGxheVxuXG5pbXBvcnQgcmVnby52MVxuXG5yZXF1ZXN0X3RpbWUgOj0gdGltZS5wYXJzZV9ucyhcIlJGQzgyMlpcIiwgaW5wdXQucmVxdWVzdF90aW1lKVxuXG5sb2NhbF9ob3VycyA6PSBkYXRhLmJ1c2luZXNzX2hvdXJzW2lucHV0LnR6XVxuXG5kZWZhdWx0IGFsbG93IDo9IGZhbHNlXG5cbmFsbG93IGlmIHtcblx0W2hvdXIsIF8sIF9dIDo9IHRpbWUuY2xvY2soW3JlcXVlc3RfdGltZSwgaW5wdXQudHpdKVxuXHRob3VyID4gbG9jYWxfaG91cnMuc3RhcnRcblx0aG91ciA8IGxvY2FsX2hvdXJzLmVuZFxufVxuIn0=)

### `format`

`time.format` is Rego's built-in function that takes a time in nanoseconds since the Unix epoch and formats it as a string. This is useful for displaying timestamps in a human-readable format and presenting times in a given timezone.

The function accepts arguments in three formats, making it slightly more complicated to use. These are either:

*   A single value representing the nanoseconds since the Unix epoch
    
    ```
    time.format(1720021249361794300)time.format(1712345679361794300)
    ```
    
*   A two-element array consisting of:
    
    *   The number of nanoseconds since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)
    *   A timezone ([TZ identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones))
    
    ```
    # Using a named timezone identifiertime.format([1720021249361794300, "Europe/Paris"])# Using a timezone codetime.format([1720021249361794300, "PDT"])
    ```
    
*   A three-element array consisting of:
    
    *   The number of nanoseconds since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)
    *   A timezone ([TZ identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones))
    *   Golang time layout string or formatting constant, see [time package documentation](https://pkg.go.dev/time#pkg-constants).
    
    ```
    # Using time format constanttime.format([1720021249361794300, "Europe/Paris", "RFC822Z"])# Using a custom time layout stringtime.format([1720021249361794300, "Europe/Paris", "2006_01_02_15_04_05"])
    ```
    

Show the local time in a response

`time.format` can be used to provide information to the user in a human-readable format in error messages. Error codes and local times can be useful when debugging or troubleshooting and so in many cases returning them from policy decisions can be helpful.

In this example we see a user is not an admin and is denied access, the policy response is a message that includes the current time and an error code to help them debug.

policy.rego

```
package playimport rego.v1request_time := time.parse_ns(	"RFC822Z",	input.request_utc_time,)local_time := time.format([	request_time,	input.tz,	"15:04:05",])default allow := falseallow if count(reasons) == 0reasons contains message if {	input.role != "admin"	message := sprintf("E123 %s", [local_time])}
```

Output

{
  "allow": false,
  "local\_time": "23:04:00",
  "reasons": \[
    "E123 23:04:00"
  \],
  "request\_time": 1720015440000000000
}

input.json

```
{  "user": "yamada-hanako@example.com",  "tz": "Asia/Tokyo",  "role": "developer",  "request_utc_time": "03 Jul 24 14:04 +0000"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInVzZXJcIjogXCJ5YW1hZGEtaGFuYWtvQGV4YW1wbGUuY29tXCIsXG4gIFwidHpcIjogXCJBc2lhL1Rva3lvXCIsXG4gIFwicm9sZVwiOiBcImRldmVsb3BlclwiLFxuICBcInJlcXVlc3RfdXRjX3RpbWVcIjogXCIwMyBKdWwgMjQgMTQ6MDQgKzAwMDBcIlxufSIsImQiOiJ7fSIsInAiOiJwYWNrYWdlIHBsYXlcblxuaW1wb3J0IHJlZ28udjFcblxucmVxdWVzdF90aW1lIDo9IHRpbWUucGFyc2VfbnMoXG5cdFwiUkZDODIyWlwiLFxuXHRpbnB1dC5yZXF1ZXN0X3V0Y190aW1lLFxuKVxuXG5sb2NhbF90aW1lIDo9IHRpbWUuZm9ybWF0KFtcblx0cmVxdWVzdF90aW1lLFxuXHRpbnB1dC50eixcblx0XCIxNTowNDowNVwiLFxuXSlcblxuZGVmYXVsdCBhbGxvdyA6PSBmYWxzZVxuXG5hbGxvdyBpZiBjb3VudChyZWFzb25zKSA9PSAwXG5cbnJlYXNvbnMgY29udGFpbnMgbWVzc2FnZSBpZiB7XG5cdGlucHV0LnJvbGUgIT0gXCJhZG1pblwiXG5cdG1lc3NhZ2UgOj0gc3ByaW50ZihcIkUxMjMgJXNcIiwgW2xvY2FsX3RpbWVdKVxufVxuIn0=)

### `parse_ns`

`time.parse_ns` is Rego's built-in function that parses a string into a timestamp in nanoseconds since the Unix epoch. This is useful converting input data to a format that can be compared with other timestamps or used in time calculations.

Check if a time is within a period

In order to check if a time is in a period of time, we need to know the start and the end of the period first. This policy defines two dates in time to mark the start and the end of the period, before testing if the supplied time is in the period.

policy.rego

```
package playimport rego.v1# 2006-01-02 is the time format string for yyyy-mm-ddstart_date := time.parse_ns("2006-01-02", "1999-01-01")end_date := time.parse_ns("2006-01-02", "2000-01-01")parsed_time := time.parse_rfc3339_ns(input.time)default allow := falseallow if {	parsed_time > start_date	parsed_time < end_date}
```

Output

{
  "allow": true,
  "end\_date": 946684800000000000,
  "parsed\_time": 930921286878235000,
  "start\_date": 915148800000000000
}

input.json

```
{  "time": "1999-07-02T13:14:46.878235008Z"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInRpbWVcIjogXCIxOTk5LTA3LTAyVDEzOjE0OjQ2Ljg3ODIzNTAwOFpcIlxufSIsImQiOiJ7fSIsInAiOiJwYWNrYWdlIHBsYXlcblxuaW1wb3J0IHJlZ28udjFcblxuIyAyMDA2LTAxLTAyIGlzIHRoZSB0aW1lIGZvcm1hdCBzdHJpbmcgZm9yIHl5eXktbW0tZGRcbnN0YXJ0X2RhdGUgOj0gdGltZS5wYXJzZV9ucyhcIjIwMDYtMDEtMDJcIiwgXCIxOTk5LTAxLTAxXCIpXG5cbmVuZF9kYXRlIDo9IHRpbWUucGFyc2VfbnMoXCIyMDA2LTAxLTAyXCIsIFwiMjAwMC0wMS0wMVwiKVxuXG5wYXJzZWRfdGltZSA6PSB0aW1lLnBhcnNlX3JmYzMzMzlfbnMoaW5wdXQudGltZSlcblxuZGVmYXVsdCBhbGxvdyA6PSBmYWxzZVxuXG5hbGxvdyBpZiB7XG5cdHBhcnNlZF90aW1lID4gc3RhcnRfZGF0ZVxuXHRwYXJzZWRfdGltZSA8IGVuZF9kYXRlXG59XG4ifQ==)

Timestamp Parsing

In OPA, we can parse a simple YYYY-MM-DD timestamp as follows:

data.json

```
{}
```

input.json

```
{}
```

```
package time_formatts := "1985-10-27"result := time.parse_ns("2006-01-02", ts)
```

Output

{
  "result": 499219200000000000,
  "ts": "1985-10-27"
}

### `now_ns`

`time.now_ns` is Rego's built-in function that returns the current time in nanoseconds since the Unix epoch. This is useful for comparing timestamps or calculating time differences.

Check if a time is in the past

In this example, we see compare an [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) timestamp with the current time to determine if the timestamp is in the past.

If you have a time in a different format, you might want to use [`time.parse_ns`](https://www.openpolicyagent.org/docs/policy-reference/#builtin-time-timeparse_ns) function to convert it to nanoseconds before comparing it with the current time.

Observe that in Rego, comparing time can be done using the `<` and `>` operators just like comparing numbers and times in many other languages.

policy.rego

```
package playimport rego.v1parsed_time := time.parse_rfc3339_ns(input.time)in_past if parsed_time < time.now_ns()
```

Output

{
  "in\_past": true,
  "parsed\_time": 1719926086878235100
}

input.json

```
{  "time": "2024-07-02T13:14:46.878235008Z"}
```

data.json

```
{}
```

[Open in OPA Playground](https://play.openpolicyagent.org/?state=eyJpIjoie1xuICBcInRpbWVcIjogXCIyMDI0LTA3LTAyVDEzOjE0OjQ2Ljg3ODIzNTAwOFpcIlxufSIsImQiOiJ7fSIsInAiOiJwYWNrYWdlIHBsYXlcblxuaW1wb3J0IHJlZ28udjFcblxucGFyc2VkX3RpbWUgOj0gdGltZS5wYXJzZV9yZmMzMzM5X25zKGlucHV0LnRpbWUpXG5cbmluX3Bhc3QgaWYgcGFyc2VkX3RpbWUgPCB0aW1lLm5vd19ucygpXG4ifQ==)