Repository: kyverno/kyverno
Pull request #5950 (2023-01-09 15:28:36)

feat: add more time jmespath filters
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

## Explanation

This PR adds a couple more time related jmespath filters:
- `time_utc`
- `time_now_utc`
- `time_diff` (the ultimate replacement for `time_since`)
- `time_before`
- `time_after`
- `time_between`
- `time_truncate`

I also took the opportunity to refactor existing funcs and restructure the package to put all time related filters in a separate file.

## Proof manifests

- time_utc

`time_utc('2023-02-02T15:04:05-07:00')` -> `2023-02-02T22:04:05Z`

- time_now_utc

same as `time_now` but result is in UTC

- time_diff

`time_diff('2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')` -> `180h10m0s`


eddycharly: cc @Vishal-Chdhry 
codecov[bot]: # [Codecov](https://codecov.io/gh/kyverno/kyverno/pull/5950?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) Report
> Merging [#5950](https://codecov.io/gh/kyverno/kyverno/pull/5950?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) (a9dbb6f) into [main](https://codecov.io/gh/kyverno/kyverno/commit/6ce8e16884e44de67b4187fd5603699c0c28116c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) (6ce8e16) will **increase** coverage by `0.04%`.
> The diff coverage is `62.79%`.

```diff
@@            Coverage Diff             @@
##             main    #5950      +/-   ##
==========================================
+ Coverage   35.71%   35.76%   +0.04%     
==========================================
  Files         188      189       +1     
  Lines       21043    21064      +21     
==========================================
+ Hits         7515     7533      +18     
+ Misses      12699    12696       -3     
- Partials      829      835       +6     
```


| [Impacted Files](https://codecov.io/gh/kyverno/kyverno/pull/5950?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno) | Coverage Δ | |
|---|---|---|
| [pkg/engine/jmespath/time.go](https://codecov.io/gh/kyverno/kyverno/pull/5950?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno#diff-cGtnL2VuZ2luZS9qbWVzcGF0aC90aW1lLmdv) | `50.00% <50.00%> (ø)` | |
| [pkg/engine/jmespath/functions.go](https://codecov.io/gh/kyverno/kyverno/pull/5950?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno#diff-cGtnL2VuZ2luZS9qbWVzcGF0aC9mdW5jdGlvbnMuZ28=) | `76.09% <100.00%> (+2.87%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kyverno)

chipzoller: Couldn't `time_since()` be used where `time_diff()` is as shown?
eddycharly: `time_diff` does not have a `format` argument.
chipzoller: Ok, so this is the replacement for `time_since()` that we discussed a few days ago, right?
eddycharly: @chipzoller yes, the goal is to deprecate `time_since`.
chipzoller: Ok, so we need a deprecation notice printed if `time_since()` is found in a rule. That could be in this PR or another.

eddycharly: Probably in another PR, this is not going to be easy :(
chipzoller: Since we're talking about a warning, we could start with being "dumb" and just looking for that as a substring anywhere. Not ideal but it's better than nothing.
eddycharly: > Since we're talking about a warning, we could start with being "dumb" and just looking for that as a substring anywhere. Not ideal but it's better than nothing.

Detecting a function call should be relatively easy.
realshuting: /cherry-pick release-1.9

Patch:
diff --git a/pkg/engine/jmespath/functions.go b/pkg/engine/jmespath/functions.go
index 8675e398c..abb32e0e6 100644
--- a/pkg/engine/jmespath/functions.go
+++ b/pkg/engine/jmespath/functions.go
@@ -70,8 +70,6 @@ var (
 	modulo                 = "modulo"
 	base64Decode           = "base64_decode"
 	base64Encode           = "base64_encode"
-	timeSince              = "time_since"
-	timeNow                = "time_now"
 	pathCanonicalize       = "path_canonicalize"
 	truncate               = "truncate"
 	semverCompare          = "semver_compare"
@@ -81,9 +79,6 @@ var (
 	objectFromLists        = "object_from_lists"
 	random                 = "random"
 	x509_decode            = "x509_decode"
-	timeAdd                = "time_add"
-	timeParse              = "time_parse"
-	timeToCron             = "time_to_cron"
 )
 
 const (
@@ -386,6 +381,14 @@ func GetFunctions() []*FunctionEntry {
 			ReturnType: []JpType{JpString},
 			Note:       "returns current time in RFC 3339 format",
 		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name:    timeNowUtc,
+				Handler: jpTimeNowUtc,
+			},
+			ReturnType: []JpType{JpString},
+			Note:       "returns current UTC time in RFC 3339 format",
+		},
 		{
 			Entry: &gojmespath.FunctionEntry{
 				Name: pathCanonicalize,
@@ -499,7 +502,7 @@ func GetFunctions() []*FunctionEntry {
 				Handler: jpTimeToCron,
 			},
 			ReturnType: []JpType{JpString},
-			Note:       "converts an absolute time (RFC 3339) to a cron expression (string).",
+			Note:       "converts a time (RFC 3339) to a cron expression (string).",
 		},
 		{
 			Entry: &gojmespath.FunctionEntry{
@@ -507,12 +510,11 @@ func GetFunctions() []*FunctionEntry {
 				Arguments: []ArgSpec{
 					{Types: []JpType{JpString}},
 					{Types: []JpType{JpString}},
-					{Types: []JpType{JpString}},
 				},
 				Handler: jpTimeAdd,
 			},
 			ReturnType: []JpType{JpString},
-			Note:       "adds duration (third string) to a time value (second string) of a specified layout (first string)",
+			Note:       "adds duration (second string) to a time value (first string)",
 		},
 		{
 			Entry: &gojmespath.FunctionEntry{
@@ -526,6 +528,78 @@ func GetFunctions() []*FunctionEntry {
 			ReturnType: []JpType{JpString},
 			Note:       "changes a time value of a given layout to RFC 3339",
 		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeUtc,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeUtc,
+			},
+			ReturnType: []JpType{JpString},
+			Note:       "calcutes time in UTC from a given time in RFC 3339 format",
+		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeDiff,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeDiff,
+			},
+			ReturnType: []JpType{JpString},
+			Note:       "calculate the difference between a start and end date in RFC3339 format",
+		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeBefore,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeBefore,
+			},
+			ReturnType: []JpType{JpBool},
+			Note:       "checks if a time is before another time, both in RFC3339 format",
+		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeAfter,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeAfter,
+			},
+			ReturnType: []JpType{JpBool},
+			Note:       "checks if a time is after another time, both in RFC3339 format",
+		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeBetween,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeBetween,
+			},
+			ReturnType: []JpType{JpBool},
+			Note:       "checks if a time is between a start and end time, all in RFC3339 format",
+		},
+		{
+			Entry: &gojmespath.FunctionEntry{
+				Name: timeTruncate,
+				Arguments: []ArgSpec{
+					{Types: []JpType{JpString}},
+					{Types: []JpType{JpString}},
+				},
+				Handler: jpTimeTruncate,
+			},
+			ReturnType: []JpType{JpString},
+			Note:       "returns the result of rounding time down to a multiple of duration",
+		},
 	}
 }
 
@@ -830,55 +904,6 @@ func jpBase64Encode(arguments []interface{}) (interface{}, error) {
 	return base64.StdEncoding.EncodeToString([]byte(str.String())), nil
 }
 
-func jpTimeSince(arguments []interface{}) (interface{}, error) {
-	var err error
-	layout, err := validateArg("", arguments, 0, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	ts1, err := validateArg("", arguments, 1, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	ts2, err := validateArg("", arguments, 2, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	var t1, t2 time.Time
-	if layout.String() != "" {
-		t1, err = time.Parse(layout.String(), ts1.String())
-	} else {
-		t1, err = time.Parse(time.RFC3339, ts1.String())
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	t2 = time.Now()
-	if ts2.String() != "" {
-		if layout.String() != "" {
-			t2, err = time.Parse(layout.String(), ts2.String())
-		} else {
-			t2, err = time.Parse(time.RFC3339, ts2.String())
-		}
-
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return t2.Sub(t1).String(), nil
-}
-
-func jpTimeNow(arguments []interface{}) (interface{}, error) {
-	var ts time.Time = time.Now()
-	var t string = ts.Format(time.RFC3339)
-	return t, nil
-}
-
 func jpPathCanonicalize(arguments []interface{}) (interface{}, error) {
 	var err error
 	str, err := validateArg(pathCanonicalize, arguments, 0, reflect.String)
@@ -1113,85 +1138,3 @@ func jpX509Decode(arguments []interface{}) (interface{}, error) {
 
 	return res, nil
 }
-
-func jpTimeToCron(arguments []interface{}) (interface{}, error) {
-	var err error
-
-	ts, err := validateArg("", arguments, 0, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	var t time.Time
-	t, err = time.Parse(time.RFC3339, ts.String())
-	if err != nil {
-		return nil, err
-	}
-	t = t.UTC()
-
-	var cron string = ""
-	cron += strconv.Itoa(t.Minute()) + " "
-	cron += strconv.Itoa(t.Hour()) + " "
-	cron += strconv.Itoa(t.Day()) + " "
-	cron += strconv.Itoa(int(t.Month())) + " "
-	cron += strconv.Itoa(int(t.Weekday()))
-
-	return cron, nil
-}
-
-func jpTimeAdd(arguments []interface{}) (interface{}, error) {
-	var err error
-	layout, err := validateArg("", arguments, 0, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	ts, err := validateArg("", arguments, 1, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	dr, err := validateArg("", arguments, 2, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	var t time.Time
-	if layout.String() != "" {
-		t, err = time.Parse(layout.String(), ts.String())
-	} else {
-		t, err = time.Parse(time.RFC3339, ts.String())
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	var d time.Duration
-	d, err = time.ParseDuration(dr.String())
-	if err != nil {
-		return nil, err
-	}
-
-	return t.Add(d).Format(time.RFC3339), nil
-}
-
-func jpTimeParse(arguments []interface{}) (interface{}, error) {
-	var err error
-	layout, err := validateArg("", arguments, 0, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	ts, err := validateArg("", arguments, 1, reflect.String)
-	if err != nil {
-		return nil, err
-	}
-
-	var t time.Time
-	t, err = time.Parse(layout.String(), ts.String())
-	if err != nil {
-		return nil, err
-	}
-
-	return t.Format(time.RFC3339), nil
-}
diff --git a/pkg/engine/jmespath/time.go b/pkg/engine/jmespath/time.go
new file mode 100644
index 000000000..8f7ab8415
--- /dev/null
+++ b/pkg/engine/jmespath/time.go
@@ -0,0 +1,182 @@
+package jmespath
+
+import (
+	"reflect"
+	"strconv"
+	"time"
+)
+
+// function names
+var (
+	timeSince    = "time_since"
+	timeNow      = "time_now"
+	timeNowUtc   = "time_now_utc"
+	timeAdd      = "time_add"
+	timeParse    = "time_parse"
+	timeToCron   = "time_to_cron"
+	timeUtc      = "time_utc"
+	timeDiff     = "time_diff"
+	timeBefore   = "time_before"
+	timeAfter    = "time_after"
+	timeBetween  = "time_between"
+	timeTruncate = "time_truncate"
+)
+
+func getTimeArg(f string, arguments []interface{}, index int) (time.Time, error) {
+	var empty time.Time
+	arg, err := validateArg(f, arguments, index, reflect.String)
+	if err != nil {
+		return empty, err
+	}
+	return time.Parse(time.RFC3339, arg.String())
+}
+
+func getDurationArg(f string, arguments []interface{}, index int) (time.Duration, error) {
+	var empty time.Duration
+	arg, err := validateArg(f, arguments, index, reflect.String)
+	if err != nil {
+		return empty, err
+	}
+	return time.ParseDuration(arg.String())
+}
+
+func jpTimeSince(arguments []interface{}) (interface{}, error) {
+	var err error
+	layout, err := validateArg(timeSince, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+	ts1, err := validateArg(timeSince, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+	ts2, err := validateArg(timeSince, arguments, 2, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+	var t1, t2 time.Time
+	if layout.String() != "" {
+		t1, err = time.Parse(layout.String(), ts1.String())
+	} else {
+		t1, err = time.Parse(time.RFC3339, ts1.String())
+	}
+	if err != nil {
+		return nil, err
+	}
+	t2 = time.Now()
+	if ts2.String() != "" {
+		if layout.String() != "" {
+			t2, err = time.Parse(layout.String(), ts2.String())
+		} else {
+			t2, err = time.Parse(time.RFC3339, ts2.String())
+		}
+		if err != nil {
+			return nil, err
+		}
+	}
+	return t2.Sub(t1).String(), nil
+}
+
+func jpTimeNow(arguments []interface{}) (interface{}, error) {
+	return time.Now().Format(time.RFC3339), nil
+}
+
+func jpTimeNowUtc(arguments []interface{}) (interface{}, error) {
+	return time.Now().UTC().Format(time.RFC3339), nil
+}
+
+func jpTimeToCron(arguments []interface{}) (interface{}, error) {
+	if t, err := getTimeArg(timeToCron, arguments, 0); err != nil {
+		return nil, err
+	} else {
+		var cron string = ""
+		cron += strconv.Itoa(t.Minute()) + " "
+		cron += strconv.Itoa(t.Hour()) + " "
+		cron += strconv.Itoa(t.Day()) + " "
+		cron += strconv.Itoa(int(t.Month())) + " "
+		cron += strconv.Itoa(int(t.Weekday()))
+		return cron, nil
+	}
+}
+
+func jpTimeAdd(arguments []interface{}) (interface{}, error) {
+	if t, err := getTimeArg(timeToCron, arguments, 0); err != nil {
+		return nil, err
+	} else if d, err := getDurationArg(timeToCron, arguments, 1); err != nil {
+		return nil, err
+	} else {
+		return t.Add(d).Format(time.RFC3339), nil
+	}
+}
+
+func jpTimeParse(arguments []interface{}) (interface{}, error) {
+	if layout, err := validateArg(timeParse, arguments, 0, reflect.String); err != nil {
+		return nil, err
+	} else if ts, err := validateArg(timeParse, arguments, 1, reflect.String); err != nil {
+		return nil, err
+	} else if t, err := time.Parse(layout.String(), ts.String()); err != nil {
+		return nil, err
+	} else {
+		return t.Format(time.RFC3339), nil
+	}
+}
+
+func jpTimeUtc(arguments []interface{}) (interface{}, error) {
+	if t, err := getTimeArg(timeUtc, arguments, 0); err != nil {
+		return nil, err
+	} else {
+		return t.UTC().Format(time.RFC3339), nil
+	}
+}
+
+func jpTimeDiff(arguments []interface{}) (interface{}, error) {
+	if t1, err := getTimeArg(timeDiff, arguments, 0); err != nil {
+		return nil, err
+	} else if t2, err := getTimeArg(timeDiff, arguments, 1); err != nil {
+		return nil, err
+	} else {
+		return t2.Sub(t1).String(), nil
+	}
+}
+
+func jpTimeBefore(arguments []interface{}) (interface{}, error) {
+	if t1, err := getTimeArg(timeBefore, arguments, 0); err != nil {
+		return nil, err
+	} else if t2, err := getTimeArg(timeBefore, arguments, 1); err != nil {
+		return nil, err
+	} else {
+		return t1.Before(t2), nil
+	}
+}
+
+func jpTimeAfter(arguments []interface{}) (interface{}, error) {
+	if t1, err := getTimeArg(timeAfter, arguments, 0); err != nil {
+		return nil, err
+	} else if t2, err := getTimeArg(timeAfter, arguments, 1); err != nil {
+		return nil, err
+	} else {
+		return t1.After(t2), nil
+	}
+}
+
+func jpTimeBetween(arguments []interface{}) (interface{}, error) {
+	if t, err := getTimeArg(timeBetween, arguments, 0); err != nil {
+		return nil, err
+	} else if start, err := getTimeArg(timeBetween, arguments, 1); err != nil {
+		return nil, err
+	} else if end, err := getTimeArg(timeBetween, arguments, 2); err != nil {
+		return nil, err
+	} else {
+		return t.After(start) && t.Before(end), nil
+	}
+}
+
+func jpTimeTruncate(arguments []interface{}) (interface{}, error) {
+	if t, err := getTimeArg(timeTruncate, arguments, 0); err != nil {
+		return nil, err
+	} else if d, err := getDurationArg(timeTruncate, arguments, 1); err != nil {
+		return nil, err
+	} else {
+		return t.Truncate(d).Format(time.RFC3339), nil
+	}
+}


Test patch:
diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go
index ae420b5ab..0c6fd4410 100644
--- a/pkg/engine/jmespath/functions_test.go
+++ b/pkg/engine/jmespath/functions_test.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"runtime"
 	"testing"
-	"time"
 
 	"gotest.tools/assert"
 )
@@ -1162,36 +1161,6 @@ func Test_Base64Decode_Secret(t *testing.T) {
 	assert.Equal(t, string(result), "Hello, world!")
 }
 
-func Test_TimeSince(t *testing.T) {
-	testCases := []struct {
-		test           string
-		expectedResult string
-	}{
-		{
-			test:           "time_since('', '2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
-			expectedResult: "180h10m0s",
-		},
-		{
-			test:           "time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021')",
-			expectedResult: "180h10m11s",
-		},
-	}
-	for i, tc := range testCases {
-		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
-			query, err := New(tc.test)
-			assert.NilError(t, err)
-
-			res, err := query.Search("")
-			assert.NilError(t, err)
-
-			result, ok := res.(string)
-			assert.Assert(t, ok)
-
-			assert.Equal(t, result, tc.expectedResult)
-		})
-	}
-}
-
 func Test_PathCanonicalize(t *testing.T) {
 	testCases := []struct {
 		jmesPath       string
@@ -1550,118 +1519,3 @@ UFOZZVoELaasWS559wy8og39Eq21dDMynb8Bndn/
 		})
 	}
 }
-
-func Test_TimeToCron(t *testing.T) {
-	testCases := []struct {
-		test           string
-		expectedResult string
-	}{
-		{
-			test:           "time_to_cron('2023-02-02T15:04:05Z')",
-			expectedResult: "4 15 2 2 4",
-		},
-		{
-			test:           "time_to_cron('2023-02-02T15:04:05-07:00')",
-			expectedResult: "4 22 2 2 4",
-		},
-	}
-	for i, tc := range testCases {
-		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
-			query, err := New(tc.test)
-			assert.NilError(t, err)
-
-			res, err := query.Search("")
-			assert.NilError(t, err)
-
-			result, ok := res.(string)
-			assert.Assert(t, ok)
-
-			assert.Equal(t, result, tc.expectedResult)
-		})
-	}
-}
-
-func Test_TimeAdd(t *testing.T) {
-	testCases := []struct {
-		test           string
-		expectedResult string
-	}{
-		{
-			test:           "time_add('', '2021-01-02T15:04:05-07:00', '3h')",
-			expectedResult: "2021-01-02T18:04:05-07:00",
-		},
-		{
-			test:           "time_add('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021', '5h30m40s')",
-			expectedResult: "2021-01-02T20:34:45Z",
-		},
-	}
-	for i, tc := range testCases {
-		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
-			query, err := New(tc.test)
-			assert.NilError(t, err)
-
-			res, err := query.Search("")
-			assert.NilError(t, err)
-
-			result, ok := res.(string)
-			assert.Assert(t, ok)
-
-			assert.Equal(t, result, tc.expectedResult)
-		})
-	}
-}
-
-func Test_TimeParse(t *testing.T) {
-	testCases := []struct {
-		test           string
-		expectedResult string
-	}{
-		{
-			test:           "time_parse('2006-01-02T15:04:05Z07:00', '2021-01-02T15:04:05-07:00')",
-			expectedResult: "2021-01-02T15:04:05-07:00",
-		},
-		{
-			test:           "time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021')",
-			expectedResult: "2021-01-02T15:04:05Z",
-		},
-	}
-	for i, tc := range testCases {
-		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
-			query, err := New(tc.test)
-			assert.NilError(t, err)
-
-			res, err := query.Search("")
-			assert.NilError(t, err)
-
-			result, ok := res.(string)
-			assert.Assert(t, ok)
-
-			assert.Equal(t, result, tc.expectedResult)
-		})
-	}
-}
-
-func Test_TimeNow(t *testing.T) {
-	testCases := []struct {
-		test   string
-		layout string
-	}{
-		{
-			test: "time_now()",
-		},
-	}
-	for i, tc := range testCases {
-		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
-			query, err := New(tc.test)
-			assert.NilError(t, err)
-
-			res, err := query.Search("")
-			assert.NilError(t, err)
-
-			result, ok := res.(string)
-			assert.Assert(t, ok)
-
-			assert.Equal(t, result, time.Now().Format(time.RFC3339))
-		})
-	}
-}
diff --git a/pkg/engine/jmespath/time_test.go b/pkg/engine/jmespath/time_test.go
new file mode 100644
index 000000000..3e237344b
--- /dev/null
+++ b/pkg/engine/jmespath/time_test.go
@@ -0,0 +1,184 @@
+package jmespath
+
+import (
+	"fmt"
+	"testing"
+
+	"gotest.tools/assert"
+)
+
+func Test_TimeSince(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_since('', '2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
+			expectedResult: "180h10m0s",
+		},
+		{
+			test:           "time_since('Mon Jan _2 15:04:05 MST 2006', 'Mon Jan 02 15:04:05 MST 2021', 'Mon Jan 10 03:14:16 MST 2021')",
+			expectedResult: "180h10m11s",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}
+
+func Test_TimeToCron(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_to_cron('2023-02-02T15:04:05Z')",
+			expectedResult: "4 15 2 2 4",
+		},
+		{
+			test:           "time_to_cron(time_utc('2023-02-02T15:04:05-07:00'))",
+			expectedResult: "4 22 2 2 4",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}
+
+func Test_TimeAdd(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_add('2021-01-02T15:04:05-07:00', '3h')",
+			expectedResult: "2021-01-02T18:04:05-07:00",
+		},
+		{
+			test:           "time_add(time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021'), '5h30m40s')",
+			expectedResult: "2021-01-02T20:34:45Z",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}
+
+func Test_TimeParse(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_parse('2006-01-02T15:04:05Z07:00', '2021-01-02T15:04:05-07:00')",
+			expectedResult: "2021-01-02T15:04:05-07:00",
+		},
+		{
+			test:           "time_parse('Mon Jan 02 15:04:05 MST 2006', 'Sat Jan 02 15:04:05 MST 2021')",
+			expectedResult: "2021-01-02T15:04:05Z",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}
+
+func Test_TimeUtc(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_utc('2023-02-02T15:04:05Z')",
+			expectedResult: "2023-02-02T15:04:05Z",
+		},
+		{
+			test:           "time_utc('2023-02-02T15:04:05-07:00')",
+			expectedResult: "2023-02-02T22:04:05Z",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}
+
+func Test_TimeDiff(t *testing.T) {
+	testCases := []struct {
+		test           string
+		expectedResult string
+	}{
+		{
+			test:           "time_diff('2021-01-02T15:04:05-07:00', '2021-01-10T03:14:05-07:00')",
+			expectedResult: "180h10m0s",
+		},
+	}
+	for i, tc := range testCases {
+		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+			query, err := New(tc.test)
+			assert.NilError(t, err)
+
+			res, err := query.Search("")
+			assert.NilError(t, err)
+
+			result, ok := res.(string)
+			assert.Assert(t, ok)
+
+			assert.Equal(t, result, tc.expectedResult)
+		})
+	}
+}