1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
package templates
import (
"errors"
"strings"
"text/template"
"time"
"github.com/Masterminds/sprig/v3"
"go.step.sm/crypto/jose"
)
// GetFuncMap returns the list of functions provided by sprig. It adds the
// functions "toTime", "formatTime", "parseTime", "mustParseTime",
// "toTimeLayout" and changes the function "fail".
//
// The "toTime" function receives a time or a Unix epoch and returns a time.Time
// in UTC. The "formatTime" function uses "toTime" and formats the resulting
// time using RFC3339. The functions "parseTime" and "mustParseTime" parse a
// string and return the time.Time it represents. The "toTimeLayout" function
// converts strings like "time.RFC3339" or "UnixDate" to the actual layout
// represented by the Go constant with the same name. The "fail" function sets
// the provided message, so that template errors are reported directly to the
// template without having the wrapper that text/template adds.
//
// {{ toTime }}
// => time.Now().UTC()
// {{ .Token.nbf | toTime }}
// => time.Unix(.Token.nbf, 0).UTC()
// {{ .Token.nbf | formatTime }}
// => time.Unix(.Token.nbf, 0).UTC().Format(time.RFC3339)
// {{ "2024-07-02T23:16:02Z" | parseTime }}
// => time.Parse(time.RFC3339, "2024-07-02T23:16:02Z")
// {{ parseTime "time.RFC339" "2024-07-02T23:16:02Z" }}
// => time.Parse(time.RFC3339, "2024-07-02T23:16:02Z")
// {{ parseTime "time.UnixDate" "Tue Jul 2 16:20:48 PDT 2024" "America/Los_Angeles" }}
// => loc, _ := time.LoadLocation("America/Los_Angeles")
// time.ParseInLocation(time.UnixDate, "Tue Jul 2 16:20:48 PDT 2024", loc)
// {{ toTimeLayout "RFC3339" }}
// => time.RFC3339
//
// sprig "env" and "expandenv" functions are removed to avoid the leak of
// information.
func GetFuncMap(failMessage *string) template.FuncMap {
m := sprig.TxtFuncMap()
delete(m, "env")
delete(m, "expandenv")
m["fail"] = func(msg string) (string, error) {
*failMessage = msg
return "", errors.New(msg)
}
m["formatTime"] = formatTime
m["toTime"] = toTime
m["parseTime"] = parseTime
m["mustParseTime"] = mustParseTime
m["toTimeLayout"] = toTimeLayout
return m
}
func toTime(v any) time.Time {
var t time.Time
switch date := v.(type) {
case time.Time:
t = date
case *time.Time:
t = *date
case int64:
t = time.Unix(date, 0)
case float64: // from json
t = time.Unix(int64(date), 0)
case int:
t = time.Unix(int64(date), 0)
case int32:
t = time.Unix(int64(date), 0)
case jose.NumericDate:
t = date.Time()
case *jose.NumericDate:
t = date.Time()
default:
t = time.Now()
}
return t.UTC()
}
func formatTime(v any) string {
return toTime(v).Format(time.RFC3339)
}
func parseTime(v ...string) time.Time {
t, _ := mustParseTime(v...)
return t
}
func mustParseTime(v ...string) (time.Time, error) {
switch len(v) {
case 0:
return time.Now().UTC(), nil
case 1:
return time.Parse(time.RFC3339, v[0])
case 2:
layout := toTimeLayout(v[0])
return time.Parse(layout, v[1])
case 3:
layout := toTimeLayout(v[0])
loc, err := time.LoadLocation(v[2])
if err != nil {
return time.Time{}, err
}
return time.ParseInLocation(layout, v[1], loc)
default:
return time.Time{}, errors.New("unsupported number of parameters")
}
}
func toTimeLayout(fmt string) string {
switch strings.ToUpper(strings.TrimPrefix(fmt, "time.")) {
case "LAYOUT":
return time.Layout
case "ANSIC":
return time.ANSIC
case "UNIXDATE":
return time.UnixDate
case "RUBYDATE":
return time.RubyDate
case "RFC822":
return time.RFC822
case "RFC822Z":
return time.RFC822Z
case "RFC850":
return time.RFC850
case "RFC1123":
return time.RFC1123
case "RFC1123Z":
return time.RFC1123Z
case "RFC3339":
return time.RFC3339
case "RFC3339NANO":
return time.RFC3339Nano
// From the ones below, only time.DateTime will parse a complete date.
case "KITCHEN":
return time.Kitchen
case "STAMP":
return time.Stamp
case "STAMPMILLI":
return time.StampMilli
case "STAMPMICRO":
return time.StampMicro
case "STAMPNANO":
return time.StampNano
case "DATETIME":
return time.DateTime
case "DATEONLY":
return time.DateOnly
case "TIMEONLY":
return time.TimeOnly
default:
return fmt
}
}
|