diff --git a/docs/content/policy-reference.md b/docs/content/policy-reference.md
index ec0f19b08e..f134f00d1d 100644
--- a/docs/content/policy-reference.md
+++ b/docs/content/policy-reference.md
@@ -800,13 +800,13 @@ result_valid_hs256 := io.jwt.verify_hs256(result_hs256, "foo")
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| ``output := time.now_ns()`` | ``output`` is a ``number`` representing the current time since epoch in nanoseconds. | ``SDK-dependent`` |
-| ``output := time.parse_ns(layout, value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch. See the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details on ``layout``. | ``SDK-dependent`` |
-| ``output := time.parse_rfc3339_ns(value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch. | ``SDK-dependent`` |
+| ``output := time.parse_ns(layout, value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details on ``layout``. | ``SDK-dependent`` |
+| ``output := time.parse_rfc3339_ns(value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. | ``SDK-dependent`` |
| ``output := time.parse_duration_ns(duration)`` | ``output`` is a ``number`` representing the duration ``duration`` in nanoseconds. See the [Go `time` package documentation](https://golang.org/pkg/time/#ParseDuration) for more details on ``duration``. | ``SDK-dependent`` |
| ``output := time.date(ns)``
``output := time.date([ns, tz])`` | ``output`` is of the form ``[year, month, day]``, which includes the ``year``, ``month`` (0-12), and ``day`` (0-31) as ``number``s representing the date from the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC.| ``SDK-dependent`` |
| ``output := time.clock(ns)``
``output := time.clock([ns, tz])`` | ``output`` is of the form ``[hour, minute, second]``, which outputs the ``hour``, ``minute`` (0-59), and ``second`` (0-59) as ``number``s representing the time of day for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` |
| ``day := time.weekday(ns)``
``day := time.weekday([ns, tz])`` | outputs the ``day`` as ``string`` representing the day of the week for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` |
-| ``output := time.add_date(ns, years, months, days)`` | ``output`` is a ``number`` representing the time since epoch in nanoseconds after adding the ``years``, ``months`` and ``days`` to ``ns``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Time.AddDate) for more details on ``add_date``. | ``SDK-dependent`` |
+| ``output := time.add_date(ns, years, months, days)`` | ``output`` is a ``number`` representing the time since epoch in nanoseconds after adding the ``years``, ``months`` and ``days`` to ``ns``; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Time.AddDate) for more details on ``add_date``. | ``SDK-dependent`` |
| ``output := time.diff(ns1, ns2)``
``output := time.diff([ns1, tz1], [ns2, tz2])`` | ``output`` is of the form ``[year(s), month(s), day(s), hour(s), minute(s), second(s)]``, which outputs ``year(s)``, ``month(s)`` (0-11), ``day(s)`` (0-30), ``hour(s)``(0-23), ``minute(s)``(0-59) and ``second(s)``(0-59) as ``number``s representing the difference between the the two timestamps in nanoseconds since epoch (``ns1`` and ``ns2``), in the timezones (``tz1`` and ``tz2``, respectively), if supplied, or as UTC. | ``SDK-dependent`` |
> Multiple calls to the `time.now_ns` built-in function within a single policy
diff --git a/test/cases/testdata/time/test-time-0948.yaml b/test/cases/testdata/time/test-time-0948.yaml
index a3593ff0a1..5eadffb222 100644
--- a/test/cases/testdata/time/test-time-0948.yaml
+++ b/test/cases/testdata/time/test-time-0948.yaml
@@ -1,95 +1,50 @@
cases:
-- data:
- a:
- - 1
- - 2
- - 3
- - 4
- b:
- v1: hello
- v2: goodbye
- c:
- - x:
- - true
- - false
- - foo
- "y":
- - null
- - 3.14159
- z:
- p: true
- q: false
- d:
- e:
- - bar
- - baz
- f:
- - xs:
- - 1
- ys:
- - 2
- - xs:
- - 2
- ys:
- - 3
- g:
- a:
- - 1
- - 0
- - 0
- - 0
- b:
- - 0
- - 2
- - 0
- - 0
- c:
- - 0
- - 0
- - 0
- - 4
- h:
- - - 1
- - 2
- - 3
- - - 2
- - 3
- - 4
- l:
- - a: bob
- b: -1
- c:
- - 1
- - 2
- - 3
- - 4
- - a: alice
- b: 1
- c:
- - 2
- - 3
- - 4
- - 5
- d: null
- m: []
- numbers:
- - "1"
- - "2"
- - "3"
- - "4"
- strings:
- bar: 2
- baz: 3
- foo: 1
- three: 3
+- note: time/parse_nanos
modules:
- |
package generated
-
- p = ns {
- time.parse_ns("2006-01-02T15:04:05Z07:00", "2017-06-02T19:00:00-07:00", ns)
+
+ p[case_id] = ns {
+ case := input.cases[case_id]
+ time.parse_ns(case.layout, case.value, ns)
}
- note: time/parse nanos
query: data.generated.p = x
+ input: { cases: {
+ 1: { layout: "2006-01-02T15:04:05Z07:00", value: "2017-06-02T19:00:00-07:00" }, # RFC3339
+ 2: { layout: "2006-01-02T15:04:05Z07:00", value: "1677-09-21T00:12:43.145224192-00:00" }, # Earliest valid time
+ 3: { layout: "2006-01-02T15:04:05Z07:00", value: "2262-04-11T23:47:16.854775807-00:00" }, # Latest valid time
+ 4: { layout: "01/02 03:04:05PM '06 -0700", value: "06/02 07:00:00PM '17 -0700" }, # Layout
+ 5: { layout: "02 Jan 06 15:04 -0700", value: "02 Jun 17 19:00 -0700" }, # RFC822Z
+ }}
want_result:
- - x: 1496455200000000000
+ - x: {
+ 1: 1496455200000000000,
+ 2: -9223372036854775808,
+ 3: 9223372036854775807,
+ 4: 1496455200000000000,
+ 5: 1496455200000000000
+ }
+- note: time/parse_nanos_too_small
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.parse_ns("2006-01-02T15:04:05Z07:00", "1677-09-21T00:12:43.145224191-00:00", ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
+- note: time/parse_nanos_too_large
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.parse_ns("2006-01-02T15:04:05Z07:00", "2262-04-11T23:47:16.854775808-00:00", ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
diff --git a/test/cases/testdata/time/test-time-0949.yaml b/test/cases/testdata/time/test-time-0949.yaml
index cf73b1f501..73ad63e322 100644
--- a/test/cases/testdata/time/test-time-0949.yaml
+++ b/test/cases/testdata/time/test-time-0949.yaml
@@ -1,95 +1,48 @@
cases:
-- data:
- a:
- - 1
- - 2
- - 3
- - 4
- b:
- v1: hello
- v2: goodbye
- c:
- - x:
- - true
- - false
- - foo
- "y":
- - null
- - 3.14159
- z:
- p: true
- q: false
- d:
- e:
- - bar
- - baz
- f:
- - xs:
- - 1
- ys:
- - 2
- - xs:
- - 2
- ys:
- - 3
- g:
- a:
- - 1
- - 0
- - 0
- - 0
- b:
- - 0
- - 2
- - 0
- - 0
- c:
- - 0
- - 0
- - 0
- - 4
- h:
- - - 1
- - 2
- - 3
- - - 2
- - 3
- - 4
- l:
- - a: bob
- b: -1
- c:
- - 1
- - 2
- - 3
- - 4
- - a: alice
- b: 1
- c:
- - 2
- - 3
- - 4
- - 5
- d: null
- m: []
- numbers:
- - "1"
- - "2"
- - "3"
- - "4"
- strings:
- bar: 2
- baz: 3
- foo: 1
- three: 3
+- note: time/parse_rfc3339_nanos
modules:
- - |
- package generated
-
- p = ns {
- time.parse_rfc3339_ns("2017-06-02T19:00:00-07:00", ns)
- }
- note: time/parse rfc3339 nanos
+ - |
+ package generated
+
+ p[t] = ns {
+ t = input.cases[_]
+ time.parse_rfc3339_ns(t, ns)
+ }
query: data.generated.p = x
+ input: { cases: [
+ "1677-09-21T00:12:43.145224192-00:00", # Earliest valid time
+ "1970-01-01T00:00:00-00:00",
+ "2017-06-02T19:00:00-07:00",
+ "2262-04-11T23:47:16.854775807-00:00" # Latest valid time
+ ]}
want_result:
- - x: 1496455200000000000
+ - x: {
+ "1677-09-21T00:12:43.145224192-00:00": -9223372036854775808,
+ "1970-01-01T00:00:00-00:00": 0,
+ "2017-06-02T19:00:00-07:00": 1496455200000000000,
+ "2262-04-11T23:47:16.854775807-00:00": 9223372036854775807,
+ }
+- note: time/parse_rfc3339_nanos_too_small
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.parse_rfc3339_ns("1677-09-21T00:12:43.145224191-00:00", ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
+- note: time/parse_rfc3339_nanos_too_large
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.parse_rfc3339_ns("2262-04-11T23:47:16.854775808-00:00", ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
diff --git a/test/cases/testdata/time/test-time-0968.yaml b/test/cases/testdata/time/test-time-0968.yaml
index 08bc623d71..f7196da07d 100644
--- a/test/cases/testdata/time/test-time-0968.yaml
+++ b/test/cases/testdata/time/test-time-0968.yaml
@@ -1,87 +1,5 @@
cases:
-- data:
- a:
- - 1
- - 2
- - 3
- - 4
- b:
- v1: hello
- v2: goodbye
- c:
- - x:
- - true
- - false
- - foo
- "y":
- - null
- - 3.14159
- z:
- p: true
- q: false
- d:
- e:
- - bar
- - baz
- f:
- - xs:
- - 1
- ys:
- - 2
- - xs:
- - 2
- ys:
- - 3
- g:
- a:
- - 1
- - 0
- - 0
- - 0
- b:
- - 0
- - 2
- - 0
- - 0
- c:
- - 0
- - 0
- - 0
- - 4
- h:
- - - 1
- - 2
- - 3
- - - 2
- - 3
- - 4
- l:
- - a: bob
- b: -1
- c:
- - 1
- - 2
- - 3
- - 4
- - a: alice
- b: 1
- c:
- - 2
- - 3
- - 4
- - 5
- d: null
- m: []
- numbers:
- - "1"
- - "2"
- - "3"
- - "4"
- strings:
- bar: 2
- baz: 3
- foo: 1
- three: 3
+- note: time/add_date year month day
modules:
- |
package generated
@@ -90,7 +8,19 @@ cases:
time.add_date(1585852421593912000, 3, 9, 12, __local1__)
__local0__ = __local1__
}
- note: time/add_date year month day
+
query: data.generated.p = x
want_result:
- x: 1705257221593912000
+- note: time/add_date too large result
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.add_date(0, 2262, 1, 1, ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
\ No newline at end of file
diff --git a/test/cases/testdata/time/test-time-0969.yaml b/test/cases/testdata/time/test-time-0969.yaml
index b554093f65..09929f4ff9 100644
--- a/test/cases/testdata/time/test-time-0969.yaml
+++ b/test/cases/testdata/time/test-time-0969.yaml
@@ -1,87 +1,5 @@
cases:
-- data:
- a:
- - 1
- - 2
- - 3
- - 4
- b:
- v1: hello
- v2: goodbye
- c:
- - x:
- - true
- - false
- - foo
- "y":
- - null
- - 3.14159
- z:
- p: true
- q: false
- d:
- e:
- - bar
- - baz
- f:
- - xs:
- - 1
- ys:
- - 2
- - xs:
- - 2
- ys:
- - 3
- g:
- a:
- - 1
- - 0
- - 0
- - 0
- b:
- - 0
- - 2
- - 0
- - 0
- c:
- - 0
- - 0
- - 0
- - 4
- h:
- - - 1
- - 2
- - 3
- - - 2
- - 3
- - 4
- l:
- - a: bob
- b: -1
- c:
- - 1
- - 2
- - 3
- - 4
- - a: alice
- b: 1
- c:
- - 2
- - 3
- - 4
- - 5
- d: null
- m: []
- numbers:
- - "1"
- - "2"
- - "3"
- - "4"
- strings:
- bar: 2
- baz: 3
- foo: 1
- three: 3
+- note: time/add_date negative values
modules:
- |
package generated
@@ -90,7 +8,18 @@ cases:
time.add_date(1585852421593912000, -1, -1, -1, __local1__)
__local0__ = __local1__
}
- note: time/add_date negative values
query: data.generated.p = x
want_result:
- x: 1551465221593912000
+- note: time/add_date too small result
+ modules:
+ - |
+ package generated
+
+ p = ns {
+ time.add_date(-9223372036854775808, 0, 0, -1, ns)
+ }
+ query: data.generated.p = x
+ strict_error: true
+ want_error_code: eval_builtin_error
+ want_error: 'time outside of valid range'
diff --git a/topdown/time.go b/topdown/time.go
index b1e94eb651..481326dade 100644
--- a/topdown/time.go
+++ b/topdown/time.go
@@ -7,6 +7,7 @@ package topdown
import (
"encoding/json"
"fmt"
+ "math"
"math/big"
"strconv"
"sync"
@@ -19,43 +20,58 @@ import (
var tzCache map[string]*time.Location
var tzCacheMutex *sync.Mutex
+// 1677-09-21T00:12:43.145224192-00:00
+var minDateAllowedForNsConversion = time.Unix(0, math.MinInt64)
+
+// 2262-04-11T23:47:16.854775807-00:00
+var maxDateAllowedForNsConversion = time.Unix(0, math.MaxInt64)
+
+func toSafeUnixNano(t time.Time, iter func(*ast.Term) error) error {
+ if t.Before(minDateAllowedForNsConversion) || t.After(maxDateAllowedForNsConversion) {
+ return fmt.Errorf("time outside of valid range")
+ }
+
+ return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(t.UnixNano()))))
+}
+
func builtinTimeNowNanos(bctx BuiltinContext, _ []*ast.Term, iter func(*ast.Term) error) error {
return iter(bctx.Time)
}
-func builtinTimeParseNanos(a, b ast.Value) (ast.Value, error) {
-
+func builtinTimeParseNanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
+ a := operands[0].Value
format, err := builtins.StringOperand(a, 1)
if err != nil {
- return nil, err
+ return err
}
+ b := operands[1].Value
value, err := builtins.StringOperand(b, 2)
if err != nil {
- return nil, err
+ return err
}
result, err := time.Parse(string(format), string(value))
if err != nil {
- return nil, err
+ return err
}
- return ast.Number(int64ToJSONNumber(result.UnixNano())), nil
+ return toSafeUnixNano(result, iter)
}
-func builtinTimeParseRFC3339Nanos(a ast.Value) (ast.Value, error) {
-
+func builtinTimeParseRFC3339Nanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
+ a := operands[0].Value
value, err := builtins.StringOperand(a, 1)
if err != nil {
- return nil, err
+ return err
}
result, err := time.Parse(time.RFC3339, string(value))
if err != nil {
- return nil, err
+ return err
}
- return ast.Number(int64ToJSONNumber(result.UnixNano())), nil
+ return toSafeUnixNano(result, iter)
}
func builtinParseDurationNanos(a ast.Value) (ast.Value, error) {
@@ -99,7 +115,7 @@ func builtinWeekday(a ast.Value) (ast.Value, error) {
return ast.String(weekday), nil
}
-func builtinAddDate(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
+func builtinAddDate(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, err := tzTime(operands[0].Value)
if err != nil {
return err
@@ -121,10 +137,11 @@ func builtinAddDate(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Te
}
result := t.AddDate(years, months, days)
- return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(result.UnixNano()))))
+
+ return toSafeUnixNano(result, iter)
}
-func builtinDiff(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
+func builtinDiff(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t1, err := tzTime(operands[0].Value)
if err != nil {
return err
@@ -267,8 +284,8 @@ func int64ToJSONNumber(i int64) json.Number {
func init() {
RegisterBuiltinFunc(ast.NowNanos.Name, builtinTimeNowNanos)
- RegisterFunctionalBuiltin1(ast.ParseRFC3339Nanos.Name, builtinTimeParseRFC3339Nanos)
- RegisterFunctionalBuiltin2(ast.ParseNanos.Name, builtinTimeParseNanos)
+ RegisterBuiltinFunc(ast.ParseRFC3339Nanos.Name, builtinTimeParseRFC3339Nanos)
+ RegisterBuiltinFunc(ast.ParseNanos.Name, builtinTimeParseNanos)
RegisterFunctionalBuiltin1(ast.ParseDurationNanos.Name, builtinParseDurationNanos)
RegisterFunctionalBuiltin1(ast.Date.Name, builtinDate)
RegisterFunctionalBuiltin1(ast.Clock.Name, builtinClock)