Files
kubesphere/vendor/github.com/open-policy-agent/opa/topdown/time.go
2023-02-12 23:09:20 +08:00

316 lines
7.6 KiB
Go

// Copyright 2017 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package topdown
import (
"encoding/json"
"fmt"
"math"
"math/big"
"strconv"
"sync"
"time"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
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(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
format, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}
value, err := builtins.StringOperand(operands[1].Value, 2)
if err != nil {
return err
}
result, err := time.Parse(string(format), string(value))
if err != nil {
return err
}
return toSafeUnixNano(result, iter)
}
func builtinTimeParseRFC3339Nanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
value, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}
result, err := time.Parse(time.RFC3339, string(value))
if err != nil {
return err
}
return toSafeUnixNano(result, iter)
}
func builtinParseDurationNanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
duration, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}
value, err := time.ParseDuration(string(duration))
if err != nil {
return err
}
return iter(ast.NumberTerm(int64ToJSONNumber(int64(value))))
}
func builtinFormat(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, layout, err := tzTime(operands[0].Value)
if err != nil {
return err
}
// Using RFC3339Nano time formatting as default
if layout == "" {
layout = time.RFC3339Nano
}
timestamp := t.Format(layout)
return iter(ast.StringTerm(timestamp))
}
func builtinDate(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, _, err := tzTime(operands[0].Value)
if err != nil {
return err
}
year, month, day := t.Date()
result := ast.NewArray(ast.IntNumberTerm(year), ast.IntNumberTerm(int(month)), ast.IntNumberTerm(day))
return iter(ast.NewTerm(result))
}
func builtinClock(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, _, err := tzTime(operands[0].Value)
if err != nil {
return err
}
hour, minute, second := t.Clock()
result := ast.NewArray(ast.IntNumberTerm(hour), ast.IntNumberTerm(minute), ast.IntNumberTerm(second))
return iter(ast.NewTerm(result))
}
func builtinWeekday(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, _, err := tzTime(operands[0].Value)
if err != nil {
return err
}
weekday := t.Weekday().String()
return iter(ast.StringTerm(weekday))
}
func builtinAddDate(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t, _, err := tzTime(operands[0].Value)
if err != nil {
return err
}
years, err := builtins.IntOperand(operands[1].Value, 2)
if err != nil {
return err
}
months, err := builtins.IntOperand(operands[2].Value, 3)
if err != nil {
return err
}
days, err := builtins.IntOperand(operands[3].Value, 4)
if err != nil {
return err
}
result := t.AddDate(years, months, days)
return toSafeUnixNano(result, iter)
}
func builtinDiff(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
t1, _, err := tzTime(operands[0].Value)
if err != nil {
return err
}
t2, _, err := tzTime(operands[1].Value)
if err != nil {
return err
}
// The following implementation of this function is taken
// from https://github.com/icza/gox licensed under Apache 2.0.
// The only modification made is to variable names.
//
// For details, see https://stackoverflow.com/a/36531443/1705598
//
// Copyright 2021 icza
// BEGIN REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT
if t1.Location() != t2.Location() {
t2 = t2.In(t1.Location())
}
if t1.After(t2) {
t1, t2 = t2, t1
}
y1, M1, d1 := t1.Date()
y2, M2, d2 := t2.Date()
h1, m1, s1 := t1.Clock()
h2, m2, s2 := t2.Clock()
year := y2 - y1
month := int(M2 - M1)
day := d2 - d1
hour := h2 - h1
min := m2 - m1
sec := s2 - s1
// Normalize negative values
if sec < 0 {
sec += 60
min--
}
if min < 0 {
min += 60
hour--
}
if hour < 0 {
hour += 24
day--
}
if day < 0 {
// Days in month:
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
day += 32 - t.Day()
month--
}
if month < 0 {
month += 12
year--
}
// END REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT
return iter(ast.ArrayTerm(ast.IntNumberTerm(year), ast.IntNumberTerm(month), ast.IntNumberTerm(day),
ast.IntNumberTerm(hour), ast.IntNumberTerm(min), ast.IntNumberTerm(sec)))
}
func tzTime(a ast.Value) (t time.Time, lay string, err error) {
var nVal ast.Value
loc := time.UTC
layout := ""
switch va := a.(type) {
case *ast.Array:
if va.Len() == 0 {
return time.Time{}, layout, builtins.NewOperandTypeErr(1, a, "either number (ns) or [number (ns), string (tz)]")
}
nVal, err = builtins.NumberOperand(va.Elem(0).Value, 1)
if err != nil {
return time.Time{}, layout, err
}
if va.Len() > 1 {
tzVal, err := builtins.StringOperand(va.Elem(1).Value, 1)
if err != nil {
return time.Time{}, layout, err
}
tzName := string(tzVal)
switch tzName {
case "", "UTC":
// loc is already UTC
case "Local":
loc = time.Local
default:
var ok bool
tzCacheMutex.Lock()
loc, ok = tzCache[tzName]
if !ok {
loc, err = time.LoadLocation(tzName)
if err != nil {
tzCacheMutex.Unlock()
return time.Time{}, layout, err
}
tzCache[tzName] = loc
}
tzCacheMutex.Unlock()
}
}
if va.Len() > 2 {
lay, err := builtins.StringOperand(va.Elem(2).Value, 1)
if err != nil {
return time.Time{}, layout, err
}
layout = string(lay)
}
case ast.Number:
nVal = a
default:
return time.Time{}, layout, builtins.NewOperandTypeErr(1, a, "either number (ns) or [number (ns), string (tz)]")
}
value, err := builtins.NumberOperand(nVal, 1)
if err != nil {
return time.Time{}, layout, err
}
f := builtins.NumberToFloat(value)
i64, acc := f.Int64()
if acc != big.Exact {
return time.Time{}, layout, fmt.Errorf("timestamp too big")
}
t = time.Unix(0, i64).In(loc)
return t, layout, nil
}
func int64ToJSONNumber(i int64) json.Number {
return json.Number(strconv.FormatInt(i, 10))
}
func init() {
RegisterBuiltinFunc(ast.NowNanos.Name, builtinTimeNowNanos)
RegisterBuiltinFunc(ast.ParseRFC3339Nanos.Name, builtinTimeParseRFC3339Nanos)
RegisterBuiltinFunc(ast.ParseNanos.Name, builtinTimeParseNanos)
RegisterBuiltinFunc(ast.ParseDurationNanos.Name, builtinParseDurationNanos)
RegisterBuiltinFunc(ast.Format.Name, builtinFormat)
RegisterBuiltinFunc(ast.Date.Name, builtinDate)
RegisterBuiltinFunc(ast.Clock.Name, builtinClock)
RegisterBuiltinFunc(ast.Weekday.Name, builtinWeekday)
RegisterBuiltinFunc(ast.AddDate.Name, builtinAddDate)
RegisterBuiltinFunc(ast.Diff.Name, builtinDiff)
tzCacheMutex = &sync.Mutex{}
tzCache = make(map[string]*time.Location)
}