Skip to main content

Time

FunctionDescriptionMeta
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 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 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 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 Go time package documentation 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 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 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() documentation for more information).

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.

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 play

import rego.v1

request_time := time.parse_ns("RFC822Z", input.request_time)

local_hours := data.business_hours[input.tz]

default allow := false

allow if {
[hour, _, _] := time.clock([request_time, input.tz])
hour > local_hours.start
hour < local_hours.end
}
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

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:

    # Using a named timezone identifier
    time.format([1720021249361794300, "Europe/Paris"])
    # Using a timezone code
    time.format([1720021249361794300, "PDT"])
  • A three-element array consisting of:

    # Using time format constant
    time.format([1720021249361794300, "Europe/Paris", "RFC822Z"])
    # Using a custom time layout string
    time.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 play

import rego.v1

request_time := time.parse_ns(
"RFC822Z",
input.request_utc_time,
)

local_time := time.format([
request_time,
input.tz,
"15:04:05",
])

default allow := false

allow if count(reasons) == 0

reasons contains message if {
input.role != "admin"
message := sprintf("E123 %s", [local_time])
}
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

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 play

import rego.v1

# 2006-01-02 is the time format string for yyyy-mm-dd
start_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 := false

allow if {
parsed_time > start_date
parsed_time < end_date
}
input.json
{
"time": "1999-07-02T13:14:46.878235008Z"
}
data.json
{}

Open in OPA Playground

Timestamp Parsing

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

data.json
{}
input.json
{}
package time_format

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

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 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 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 play

import rego.v1

parsed_time := time.parse_rfc3339_ns(input.time)

in_past if parsed_time < time.now_ns()
input.json
{
"time": "2024-07-02T13:14:46.878235008Z"
}
data.json
{}

Open in OPA Playground