lib/time.go (135 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package lib import ( "net/http" "time" "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" ) // Time returns a cel.EnvOption to configure extended functions for // handling timestamps. // // Now (function) // // Returns a timestamp for when the call was made: // // now() -> <timestamp> // // Examples: // // now() // return "2022-03-30T11:17:57.078390759Z" // // Now (Global Variable) // // Returns a timestamp for when the expression evaluation started: // // now -> <timestamp> // // Examples: // // now // return "2022-03-30T11:17:57.078389559Z" // // # Format // // Returns a string representation of the timestamp formatted according to // the provided layout: // // <timestamp>.format(<string>) -> <string> // // Examples: // // now().format(time_layout.Kitchen) // return "11:17AM" // // # Parse Time // // Returns a timestamp from a string based on a time layout or list of possible // layouts. If a list of formats is provided, the first successful layout is // used: // // <string>.parse_time(<string>) -> <timestamp> // <string>.parse_time(<list<string>>) -> <timestamp> // // Examples: // // "11:17AM".parse_time(time_layout.Kitchen) // return <timestamp> // "11:17AM".parse_time([time_layout.RFC3339,time_layout.Kitchen]) // return <timestamp> // "11:17AM".parse_time(time_layout.RFC3339) // return error // // # Global Variables // // A collection of global variable are provided to give access to the start // time of the evaluation and to the time formatting layouts provided by // the Go standard library time package. // // "now": <timestamp of evaluation start>, // "time_layout": { // "Layout": time.Layout, // "ANSIC": time.ANSIC, // "UnixDate": time.UnixDate, // "RubyDate": time.RubyDate, // "RFC822": time.RFC822, // "RFC822Z": time.RFC822Z, // "RFC850": time.RFC850, // "RFC1123": time.RFC1123, // "RFC1123Z": time.RFC1123Z, // "RFC3339": time.RFC3339, // "RFC3339Nano": time.RFC3339Nano, // "Kitchen": time.Kitchen, // "Stamp": time.Stamp, // "StampMilli": time.StampMilli, // "StampMicro": time.StampMicro, // "StampNano": time.StampNano, // "HTTP": http.TimeFormat, // "DateOnly": time.DateOnly, // "DateTime": time.DateTime, // "TimeOnly": time.TimeOnly // } func Time() cel.EnvOption { return cel.Lib(timeLib{}) } type timeLib struct{} func (timeLib) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Declarations( decls.NewVar("now", decls.Dyn), decls.NewVar("time_layout", decls.NewMapType(decls.String, decls.String)), ), cel.Function("now", cel.Overload( "now_void", nil, cel.TimestampType, cel.FunctionBinding(now), ), ), cel.Function("format", cel.MemberOverload( "timestamp_format_string", []*cel.Type{cel.TimestampType, cel.StringType}, cel.StringType, cel.BinaryBinding(formatTime), ), ), cel.Function("parse_time", cel.MemberOverload( "string_parse_time_string", []*cel.Type{cel.StringType, cel.StringType}, cel.TimestampType, cel.BinaryBinding(parseTimeWithLayout), ), ), cel.Function("parse_time", cel.MemberOverload( "string_parse_time_list_string", []*cel.Type{cel.StringType, listString}, cel.TimestampType, cel.BinaryBinding(parseTimeWithLayouts), ), ), } } func (timeLib) ProgramOptions() []cel.ProgramOption { return []cel.ProgramOption{ cel.Globals(map[string]interface{}{ "now": func() interface{} { return time.Now().In(time.UTC) }, "time_layout": map[string]string{ "Layout": time.Layout, "ANSIC": time.ANSIC, "UnixDate": time.UnixDate, "RubyDate": time.RubyDate, "RFC822": time.RFC822, "RFC822Z": time.RFC822Z, "RFC850": time.RFC850, "RFC1123": time.RFC1123, "RFC1123Z": time.RFC1123Z, "RFC3339": time.RFC3339, "RFC3339Nano": time.RFC3339Nano, "Kitchen": time.Kitchen, "Stamp": time.Stamp, "StampMilli": time.StampMilli, "StampMicro": time.StampMicro, "StampNano": time.StampNano, "HTTP": http.TimeFormat, // TODO: Use the constants from time when go1.19 support is dropped. "DateTime": "2006-01-02 15:04:05", // time.DateTime from future "DateOnly": "2006-01-02", // time.DateOnly from future "TimeOnly": "15:04:05", // time.TimeOnly from future }, }), } } func now(args ...ref.Val) ref.Val { if len(args) != 0 { return types.NewErr("no such overload") } return types.Timestamp{Time: time.Now().In(time.UTC)} } func formatTime(arg, layout ref.Val) ref.Val { obj, ok := arg.(types.Timestamp) if !ok { return types.ValOrErr(obj, "no such overload for time layout: %s", arg.Type()) } l, ok := layout.(types.String) if !ok { return types.ValOrErr(l, "no such overload for time layout: %s", layout.Type()) } return types.String(obj.Format(string(l))) } func parseTimeWithLayout(arg, layout ref.Val) ref.Val { obj, ok := arg.(types.String) if !ok { return types.ValOrErr(obj, "no such overload for time layout: %s", arg.Type()) } l, ok := layout.(types.String) if !ok { return types.ValOrErr(l, "no such overload for time layout: %s", layout.Type()) } t, err := time.Parse(string(l), string(obj)) if err != nil { return types.NewErr("failed %v", err) } return types.Timestamp{Time: t} } func parseTimeWithLayouts(arg, layout ref.Val) ref.Val { obj, ok := arg.(types.String) if !ok { return types.ValOrErr(obj, "no such overload for time layout: %s", arg.Type()) } layouts, ok := layout.(traits.Lister) if !ok { return types.ValOrErr(layouts, "no such overload for time layout: %s", layout.Type()) } it := layouts.Iterator() for it.HasNext() == types.True { l := it.Next().(types.String) t, err := time.Parse(string(l), string(obj)) if err != nil { continue } return types.Timestamp{Time: t} } return types.NewErr("failed to parse %s with any provided layout", obj) }