Support for basic expr functions (num, date, str)
This commit is contained in:
@@ -2,6 +2,7 @@ package values
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
@@ -21,7 +22,7 @@ func Formatter() *formatter {
|
||||
|
||||
func (f formatter) Run(m *types.Module, vv types.RecordValueSet) types.RecordValueSet {
|
||||
var (
|
||||
exprParser = parser()
|
||||
exprParser = expr.Parser()
|
||||
|
||||
log = logger.Default().
|
||||
WithOptions(zap.AddStacktrace(zap.PanicLevel)).
|
||||
|
||||
@@ -2,6 +2,7 @@ package values
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
@@ -31,7 +32,7 @@ func Sanitizer() *sanitizer {
|
||||
// Existing data (when updating record) is not yet loaded at this point
|
||||
func (s sanitizer) Run(m *types.Module, vv types.RecordValueSet) (out types.RecordValueSet) {
|
||||
var (
|
||||
exprParser = parser()
|
||||
exprParser = expr.Parser()
|
||||
)
|
||||
|
||||
out = make([]*types.RecordValue, 0, len(vv))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza-server/pkg/slice"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"math/big"
|
||||
@@ -97,7 +98,7 @@ func (vldtr validator) Run(ctx context.Context, s store.Storer, m *types.Module,
|
||||
var (
|
||||
f *types.ModuleField
|
||||
|
||||
valParser = parser()
|
||||
valParser = expr.Parser()
|
||||
valDict = r.Values.Dict(m.Fields)
|
||||
)
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@@ -45,6 +45,7 @@ require (
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0
|
||||
github.com/lestrrat-go/strftime v1.0.3
|
||||
github.com/lib/pq v1.1.0
|
||||
github.com/markbates/goth v1.50.0
|
||||
github.com/mattn/go-shellwords v1.0.10 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@@ -176,6 +176,9 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||
github.com/lestrrat-go/strftime v1.0.3 h1:qqOPU7y+TM8Y803I8fG9c/DyKG3xH/xkng6keC1015Q=
|
||||
github.com/lestrrat-go/strftime v1.0.3/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
package expr
|
||||
|
||||
import "github.com/PaesslerAG/gval"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/PaesslerAG/gval"
|
||||
)
|
||||
|
||||
func Parser() gval.Language {
|
||||
return gval.Full()
|
||||
func Parser(ll ...gval.Language) gval.Language {
|
||||
return gval.Full(append(AllFunctions(), ll...)...)
|
||||
}
|
||||
|
||||
func AllFunctions() []gval.Language {
|
||||
ff := make([]gval.Language, 0, 100)
|
||||
|
||||
//ff = append(ff, GenericFunctions()...)
|
||||
ff = append(ff, StringFunctions()...)
|
||||
ff = append(ff, NumericFunctions()...)
|
||||
ff = append(ff, TimeFunctions()...)
|
||||
|
||||
return ff
|
||||
}
|
||||
|
||||
// utility function for examples
|
||||
func eval(e string, p interface{}) {
|
||||
result, err := Parser().Evaluate(e, p)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
} else {
|
||||
fmt.Printf("%v", result)
|
||||
}
|
||||
}
|
||||
|
||||
30
pkg/expr/expr_test.go
Normal file
30
pkg/expr/expr_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
ctx = context.Background()
|
||||
p = Parser()
|
||||
e, err = p.NewEvaluable("0 == 0")
|
||||
|
||||
result bool
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
|
||||
result, err = e.EvalBool(ctx, nil)
|
||||
req.NoError(err)
|
||||
req.True(result)
|
||||
}
|
||||
|
||||
func Example_simpleExpresion() {
|
||||
eval(`40 + 2`, nil)
|
||||
// output:
|
||||
// 42
|
||||
}
|
||||
34
pkg/expr/func_gen.go
Normal file
34
pkg/expr/func_gen.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"github.com/PaesslerAG/gval"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func GenericFunctions() []gval.Language {
|
||||
return []gval.Language{
|
||||
gval.Function("coalesce", coalesce),
|
||||
}
|
||||
}
|
||||
|
||||
func coalesce(aa ...interface{}) interface{} {
|
||||
for _, a := range aa {
|
||||
if !isNil(a) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isNil(i interface{}) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
switch reflect.TypeOf(i).Kind() {
|
||||
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Map:
|
||||
return reflect.ValueOf(i).IsNil()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
73
pkg/expr/func_num.go
Normal file
73
pkg/expr/func_num.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"github.com/PaesslerAG/gval"
|
||||
"math"
|
||||
)
|
||||
|
||||
func NumericFunctions() []gval.Language {
|
||||
return []gval.Language{
|
||||
gval.Function("min", min),
|
||||
gval.Function("max", max),
|
||||
gval.Function("round", round),
|
||||
gval.Function("floor", floor),
|
||||
gval.Function("ceil", ceil),
|
||||
}
|
||||
}
|
||||
|
||||
func min(aa ...interface{}) (min float64) {
|
||||
return findMinMax(-1, aa...)
|
||||
}
|
||||
|
||||
func max(aa ...interface{}) (min float64) {
|
||||
return findMinMax(1, aa...)
|
||||
}
|
||||
|
||||
func findMinMax(dir int, aa ...interface{}) (mm float64) {
|
||||
var (
|
||||
set bool
|
||||
flt float64
|
||||
)
|
||||
for i := range aa {
|
||||
switch conv := aa[i].(type) {
|
||||
case int:
|
||||
flt = float64(conv)
|
||||
case int64:
|
||||
flt = float64(conv)
|
||||
case uint:
|
||||
flt = float64(conv)
|
||||
case uint64:
|
||||
flt = float64(conv)
|
||||
case float32:
|
||||
flt = float64(conv)
|
||||
case float64:
|
||||
flt = conv
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if !set {
|
||||
set = true
|
||||
mm = flt
|
||||
} else if dir < 0 {
|
||||
mm = math.Min(mm, flt)
|
||||
} else if dir > 0 {
|
||||
mm = math.Max(mm, flt)
|
||||
}
|
||||
}
|
||||
|
||||
return mm
|
||||
}
|
||||
|
||||
func round(f float64, d float64) float64 {
|
||||
p := math.Pow(10, d)
|
||||
return math.Round(f*p) / p
|
||||
}
|
||||
|
||||
func floor(f float64) float64 {
|
||||
return math.Floor(f)
|
||||
}
|
||||
|
||||
func ceil(f float64) float64 {
|
||||
return math.Ceil(f)
|
||||
}
|
||||
36
pkg/expr/func_num_test.go
Normal file
36
pkg/expr/func_num_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package expr
|
||||
|
||||
func Example_min() {
|
||||
eval(`min(2, 1, 3)`, nil)
|
||||
|
||||
// output
|
||||
// float64(1)
|
||||
}
|
||||
|
||||
func Example_max() {
|
||||
eval(`max(2, 1, 3)`, nil)
|
||||
|
||||
// output
|
||||
// float64(3)
|
||||
}
|
||||
|
||||
func Example_round() {
|
||||
eval(`round(3.14,1)`, nil)
|
||||
|
||||
// output
|
||||
// 3.1
|
||||
}
|
||||
|
||||
func Example_floor() {
|
||||
eval(`floor(3.14)`, nil)
|
||||
|
||||
// output
|
||||
// float64(3)
|
||||
}
|
||||
|
||||
func Example_ceil() {
|
||||
eval(`ceil(3.14)`, nil)
|
||||
|
||||
// output
|
||||
// float64(4)
|
||||
}
|
||||
43
pkg/expr/func_str.go
Normal file
43
pkg/expr/func_str.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"github.com/PaesslerAG/gval"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func StringFunctions() []gval.Language {
|
||||
return []gval.Language{
|
||||
gval.Function("trim", strings.TrimSpace),
|
||||
gval.Function("trimLeft", strings.TrimLeft),
|
||||
gval.Function("trimRight", strings.TrimRight),
|
||||
gval.Function("length", length),
|
||||
gval.Function("toLower", strings.ToLower),
|
||||
gval.Function("toUpper", strings.ToUpper),
|
||||
gval.Function("shortest", shortest),
|
||||
gval.Function("longest", longest),
|
||||
}
|
||||
}
|
||||
|
||||
func shortest(f string, aa ...string) string {
|
||||
for _, s := range aa {
|
||||
if len(f) > len(s) {
|
||||
f = s
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func longest(f string, aa ...string) string {
|
||||
for _, s := range aa {
|
||||
if len(f) < len(s) {
|
||||
f = s
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func length(s string) int {
|
||||
return len(s)
|
||||
}
|
||||
44
pkg/expr/func_str_test.go
Normal file
44
pkg/expr/func_str_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package expr
|
||||
|
||||
func Example_trim() {
|
||||
eval(`trim(" foo ")`, nil)
|
||||
|
||||
// output:
|
||||
// foo
|
||||
}
|
||||
func Example_trimLeft() {
|
||||
eval(`trimLeft(" foo ", " ")`, nil)
|
||||
|
||||
// output:
|
||||
// foo
|
||||
}
|
||||
func Example_trimRight() {
|
||||
eval(`trimRight(" foo ", " ")`, nil)
|
||||
|
||||
// output:
|
||||
// foo
|
||||
}
|
||||
func Example_toLower() {
|
||||
eval(`toLower("FOO")`, nil)
|
||||
|
||||
// output:
|
||||
// foo
|
||||
}
|
||||
func Example_toUpper() {
|
||||
eval(`toUpper("foo")`, nil)
|
||||
|
||||
// output:
|
||||
// FOO
|
||||
}
|
||||
func Example_shortest() {
|
||||
eval(`shortest("foo", "foobar")`, nil)
|
||||
|
||||
// output:
|
||||
// foo
|
||||
}
|
||||
func Example_longest() {
|
||||
eval(`longest("foo", "foobar")`, nil)
|
||||
|
||||
// output:
|
||||
// foobar
|
||||
}
|
||||
69
pkg/expr/func_time.go
Normal file
69
pkg/expr/func_time.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"github.com/PaesslerAG/gval"
|
||||
"github.com/lestrrat-go/strftime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TimeFunctions() []gval.Language {
|
||||
return []gval.Language{
|
||||
gval.Function("earliest", earliest),
|
||||
gval.Function("latest", latest),
|
||||
gval.Function("parseISOTime", parseISOTime),
|
||||
gval.Function("modTime", modTime),
|
||||
gval.Function("parseDuration", time.ParseDuration),
|
||||
gval.Function("strftime", strfTime),
|
||||
}
|
||||
}
|
||||
|
||||
func earliest(f time.Time, aa ...time.Time) time.Time {
|
||||
for _, s := range aa {
|
||||
if s.Before(f) {
|
||||
f = s
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func latest(f time.Time, aa ...time.Time) time.Time {
|
||||
for _, s := range aa {
|
||||
if s.After(f) {
|
||||
f = s
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func parseISOTime(s string) (time.Time, error) {
|
||||
return time.Parse(time.RFC3339, s)
|
||||
}
|
||||
|
||||
func modTime(t time.Time, mod interface{}) (time.Time, error) {
|
||||
var (
|
||||
err error
|
||||
d time.Duration
|
||||
)
|
||||
switch c := mod.(type) {
|
||||
case time.Duration:
|
||||
d = c
|
||||
case string:
|
||||
d, err = time.ParseDuration(c)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
return t.Add(d), nil
|
||||
}
|
||||
|
||||
// Strftime formats time with POSIX standard format
|
||||
// More details here:
|
||||
// https://github.com/lestrrat-go/strftime#supported-conversion-specifications
|
||||
func strfTime(t time.Time, f string) string {
|
||||
o, _ := strftime.Format(f, t)
|
||||
return o
|
||||
}
|
||||
70
pkg/expr/func_time_test.go
Normal file
70
pkg/expr/func_time_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package expr
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
|
||||
// human genome project end
|
||||
hgp = time.Date(2003, 4, 14, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// world's first antibiotic
|
||||
wfa = time.Date(1928, 9, 128, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// groundhog day
|
||||
ghd = time.Date(1993, 2, 2, 6, 0, 0, 0, time.FixedZone("", -5*60*60))
|
||||
|
||||
exampleTimeParams = map[string]interface{}{
|
||||
"hgp": hgp,
|
||||
"wfa": wfa,
|
||||
"ghd": ghd,
|
||||
}
|
||||
)
|
||||
|
||||
func Example_strftime() {
|
||||
eval(`strftime(ghd, "%Y-%m-%dT%H:%M:%S")`, exampleTimeParams)
|
||||
|
||||
// output:
|
||||
// 1993-02-02T06:00:00
|
||||
}
|
||||
|
||||
func Example_strftimeWithModTime() {
|
||||
eval(`strftime(modTime(ghd, "+30m"), "%Y-%m-%dT%H:%M:%S")`, exampleTimeParams)
|
||||
|
||||
// output:
|
||||
// 1993-02-02T06:30:00
|
||||
}
|
||||
|
||||
func Example_parseISODate() {
|
||||
eval(`date("1993-02-02T06:00:00-05:00")`, nil)
|
||||
|
||||
// output:
|
||||
// 1993-02-02 06:00:00 -0500 -0500
|
||||
}
|
||||
|
||||
func Example_parseDate() {
|
||||
eval(`date("1993-02-02 06:00:00+00:00")`, nil)
|
||||
|
||||
// output:
|
||||
// 1993-02-02 06:00:00 +0000 +0000
|
||||
}
|
||||
|
||||
func Example_parseDuration() {
|
||||
eval(`parseDuration("2h")`, nil)
|
||||
|
||||
// output:
|
||||
// 2h0m0s
|
||||
}
|
||||
|
||||
func Example_earliest() {
|
||||
eval(`earliest(hgp, wfa)`, exampleTimeParams)
|
||||
|
||||
// output:
|
||||
// 1929-01-06 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func Example_latest() {
|
||||
eval(`latest(ghd, wfa)`, exampleTimeParams)
|
||||
|
||||
// output:
|
||||
// 1993-02-02 06:00:00 -0500 -0500
|
||||
}
|
||||
24
vendor/github.com/lestrrat-go/strftime/.gitignore
generated
vendored
Normal file
24
vendor/github.com/lestrrat-go/strftime/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
5
vendor/github.com/lestrrat-go/strftime/.golangci.yml
generated
vendored
Normal file
5
vendor/github.com/lestrrat-go/strftime/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
21
vendor/github.com/lestrrat-go/strftime/LICENSE
generated
vendored
Normal file
21
vendor/github.com/lestrrat-go/strftime/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 lestrrat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
25
vendor/github.com/lestrrat-go/strftime/Makefile
generated
vendored
Normal file
25
vendor/github.com/lestrrat-go/strftime/Makefile
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.PHONY: bench realclean cover viewcover test lint
|
||||
|
||||
bench:
|
||||
go test -tags bench -benchmem -bench .
|
||||
@git checkout go.mod
|
||||
@rm go.sum
|
||||
|
||||
realclean:
|
||||
rm coverage.out
|
||||
|
||||
test:
|
||||
go test -v -race ./...
|
||||
|
||||
cover:
|
||||
go test -v -race -coverpkg=./... -coverprofile=coverage.out ./...
|
||||
|
||||
viewcover:
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
imports:
|
||||
goimports -w ./
|
||||
|
||||
227
vendor/github.com/lestrrat-go/strftime/README.md
generated
vendored
Normal file
227
vendor/github.com/lestrrat-go/strftime/README.md
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
# strftime
|
||||
|
||||
Fast strftime for Go
|
||||
|
||||
[](https://travis-ci.org/lestrrat-go/strftime)
|
||||
|
||||
[](https://godoc.org/github.com/lestrrat-go/strftime)
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
```go
|
||||
f := strftime.New(`.... pattern ...`)
|
||||
if err := f.Format(buf, time.Now()); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
```
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The goals for this library are
|
||||
|
||||
* Optimized for the same pattern being called repeatedly
|
||||
* Be flexible about destination to write the results out
|
||||
* Be as complete as possible in terms of conversion specifications
|
||||
|
||||
# API
|
||||
|
||||
## Format(string, time.Time) (string, error)
|
||||
|
||||
Takes the pattern and the time, and formats it. This function is a utility function that recompiles the pattern every time the function is called. If you know beforehand that you will be formatting the same pattern multiple times, consider using `New` to create a `Strftime` object and reuse it.
|
||||
|
||||
## New(string) (\*Strftime, error)
|
||||
|
||||
Takes the pattern and creates a new `Strftime` object.
|
||||
|
||||
## obj.Pattern() string
|
||||
|
||||
Returns the pattern string used to create this `Strftime` object
|
||||
|
||||
## obj.Format(io.Writer, time.Time) error
|
||||
|
||||
Formats the time according to the pre-compiled pattern, and writes the result to the specified `io.Writer`
|
||||
|
||||
## obj.FormatString(time.Time) string
|
||||
|
||||
Formats the time according to the pre-compiled pattern, and returns the result string.
|
||||
|
||||
# SUPPORTED CONVERSION SPECIFICATIONS
|
||||
|
||||
| pattern | description |
|
||||
|:--------|:------------|
|
||||
| %A | national representation of the full weekday name |
|
||||
| %a | national representation of the abbreviated weekday |
|
||||
| %B | national representation of the full month name |
|
||||
| %b | national representation of the abbreviated month name |
|
||||
| %C | (year / 100) as decimal number; single digits are preceded by a zero |
|
||||
| %c | national representation of time and date |
|
||||
| %D | equivalent to %m/%d/%y |
|
||||
| %d | day of the month as a decimal number (01-31) |
|
||||
| %e | the day of the month as a decimal number (1-31); single digits are preceded by a blank |
|
||||
| %F | equivalent to %Y-%m-%d |
|
||||
| %H | the hour (24-hour clock) as a decimal number (00-23) |
|
||||
| %h | same as %b |
|
||||
| %I | the hour (12-hour clock) as a decimal number (01-12) |
|
||||
| %j | the day of the year as a decimal number (001-366) |
|
||||
| %k | the hour (24-hour clock) as a decimal number (0-23); single digits are preceded by a blank |
|
||||
| %l | the hour (12-hour clock) as a decimal number (1-12); single digits are preceded by a blank |
|
||||
| %M | the minute as a decimal number (00-59) |
|
||||
| %m | the month as a decimal number (01-12) |
|
||||
| %n | a newline |
|
||||
| %p | national representation of either "ante meridiem" (a.m.) or "post meridiem" (p.m.) as appropriate. |
|
||||
| %R | equivalent to %H:%M |
|
||||
| %r | equivalent to %I:%M:%S %p |
|
||||
| %S | the second as a decimal number (00-60) |
|
||||
| %T | equivalent to %H:%M:%S |
|
||||
| %t | a tab |
|
||||
| %U | the week number of the year (Sunday as the first day of the week) as a decimal number (00-53) |
|
||||
| %u | the weekday (Monday as the first day of the week) as a decimal number (1-7) |
|
||||
| %V | the week number of the year (Monday as the first day of the week) as a decimal number (01-53) |
|
||||
| %v | equivalent to %e-%b-%Y |
|
||||
| %W | the week number of the year (Monday as the first day of the week) as a decimal number (00-53) |
|
||||
| %w | the weekday (Sunday as the first day of the week) as a decimal number (0-6) |
|
||||
| %X | national representation of the time |
|
||||
| %x | national representation of the date |
|
||||
| %Y | the year with century as a decimal number |
|
||||
| %y | the year without century as a decimal number (00-99) |
|
||||
| %Z | the time zone name |
|
||||
| %z | the time zone offset from UTC |
|
||||
| %% | a '%' |
|
||||
|
||||
# EXTENSIONS / CUSTOM SPECIFICATIONS
|
||||
|
||||
This library in general tries to be POSIX compliant, but sometimes you just need that
|
||||
extra specification or two that is relatively widely used but is not included in the
|
||||
POSIX specification.
|
||||
|
||||
For example, POSIX does not specify how to print out milliseconds,
|
||||
but popular implementations allow `%f` or `%L` to achieve this.
|
||||
|
||||
For those instances, `strftime.Strftime` can be configured to use a custom set of
|
||||
specifications:
|
||||
|
||||
```
|
||||
ss := strftime.NewSpecificationSet()
|
||||
ss.Set('L', ...) // provide implementation for `%L`
|
||||
|
||||
// pass this new specification set to the strftime instance
|
||||
p, err := strftime.New(`%L`, strftime.WithSpecificationSet(ss))
|
||||
p.Format(..., time.Now())
|
||||
```
|
||||
|
||||
The implementation must implement the `Appender` interface, which is
|
||||
|
||||
```
|
||||
type Appender interface {
|
||||
Append([]byte, time.Time) []byte
|
||||
}
|
||||
```
|
||||
|
||||
For commonly used extensions such as the millisecond example and Unix timestamp, we provide a default
|
||||
implementation so the user can do one of the following:
|
||||
|
||||
```
|
||||
// (1) Pass a specification byte and the Appender
|
||||
// This allows you to pass arbitrary Appenders
|
||||
p, err := strftime.New(
|
||||
`%L`,
|
||||
strftime.WithSpecification('L', strftime.Milliseconds),
|
||||
)
|
||||
|
||||
// (2) Pass an option that knows to use strftime.Milliseconds
|
||||
p, err := strftime.New(
|
||||
`%L`,
|
||||
strftime.WithMilliseconds('L'),
|
||||
)
|
||||
```
|
||||
|
||||
Similarly for Unix Timestamp:
|
||||
```
|
||||
// (1) Pass a specification byte and the Appender
|
||||
// This allows you to pass arbitrary Appenders
|
||||
p, err := strftime.New(
|
||||
`%s`,
|
||||
strftime.WithSpecification('s', strftime.UnixSeconds),
|
||||
)
|
||||
|
||||
// (2) Pass an option that knows to use strftime.UnixSeconds
|
||||
p, err := strftime.New(
|
||||
`%s`,
|
||||
strftime.WithUnixSeconds('s'),
|
||||
)
|
||||
```
|
||||
|
||||
If a common specification is missing, please feel free to submit a PR
|
||||
(but please be sure to be able to defend how "common" it is)
|
||||
|
||||
## List of available extensions
|
||||
|
||||
- [`Milliseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#Milliseconds) (related option: [`WithMilliseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithMilliseconds));
|
||||
|
||||
- [`Microseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#Microseconds) (related option: [`WithMicroseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithMicroseconds));
|
||||
|
||||
- [`UnixSeconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#UnixSeconds) (related option: [`WithUnixSeconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithUnixSeconds)).
|
||||
|
||||
|
||||
# PERFORMANCE / OTHER LIBRARIES
|
||||
|
||||
The following benchmarks were run separately because some libraries were using cgo on specific platforms (notabley, the fastly version)
|
||||
|
||||
```
|
||||
// On my OS X 10.14.6, 2.3 GHz Intel Core i5, 16GB memory.
|
||||
// go version go1.13.4 darwin/amd64
|
||||
hummingbird% go test -tags bench -benchmem -bench .
|
||||
<snip>
|
||||
BenchmarkTebeka-4 297471 3905 ns/op 257 B/op 20 allocs/op
|
||||
BenchmarkJehiah-4 818444 1773 ns/op 256 B/op 17 allocs/op
|
||||
BenchmarkFastly-4 2330794 550 ns/op 80 B/op 5 allocs/op
|
||||
BenchmarkLestrrat-4 916365 1458 ns/op 80 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedString-4 2527428 546 ns/op 128 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedWriter-4 537422 2155 ns/op 192 B/op 3 allocs/op
|
||||
PASS
|
||||
ok github.com/lestrrat-go/strftime 25.618s
|
||||
```
|
||||
|
||||
```
|
||||
// On a host on Google Cloud Platform, machine-type: f1-micro (vCPU x 1, memory: 0.6GB)
|
||||
// (Yes, I was being skimpy)
|
||||
// Linux <snip> 4.9.0-11-amd64 #1 SMP Debian 4.9.189-3+deb9u1 (2019-09-20) x86_64 GNU/Linux
|
||||
// go version go1.13.4 linux/amd64
|
||||
hummingbird% go test -tags bench -benchmem -bench .
|
||||
<snip>
|
||||
BenchmarkTebeka 254997 4726 ns/op 256 B/op 20 allocs/op
|
||||
BenchmarkJehiah 659289 1882 ns/op 256 B/op 17 allocs/op
|
||||
BenchmarkFastly 389150 3044 ns/op 224 B/op 13 allocs/op
|
||||
BenchmarkLestrrat 699069 1780 ns/op 80 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedString 2081594 589 ns/op 128 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedWriter 825763 1480 ns/op 192 B/op 3 allocs/op
|
||||
PASS
|
||||
ok github.com/lestrrat-go/strftime 11.355s
|
||||
```
|
||||
|
||||
This library is much faster than other libraries *IF* you can reuse the format pattern.
|
||||
|
||||
Here's the annotated list from the benchmark results. You can clearly see that (re)using a `Strftime` object
|
||||
and producing a string is the fastest. Writing to an `io.Writer` seems a bit sluggish, but since
|
||||
the one producing the string is doing almost exactly the same thing, we believe this is purely the overhead of
|
||||
writing to an `io.Writer`
|
||||
|
||||
| Import Path | Score | Note |
|
||||
|:------------------------------------|--------:|:--------------------------------|
|
||||
| github.com/lestrrat-go/strftime | 3000000 | Using `FormatString()` (cached) |
|
||||
| github.com/fastly/go-utils/strftime | 2000000 | Pure go version on OS X |
|
||||
| github.com/lestrrat-go/strftime | 1000000 | Using `Format()` (NOT cached) |
|
||||
| github.com/jehiah/go-strftime | 1000000 | |
|
||||
| github.com/fastly/go-utils/strftime | 1000000 | cgo version on Linux |
|
||||
| github.com/lestrrat-go/strftime | 500000 | Using `Format()` (cached) |
|
||||
| github.com/tebeka/strftime | 300000 | |
|
||||
|
||||
However, depending on your pattern, this speed may vary. If you find a particular pattern that seems sluggish,
|
||||
please send in patches or tests.
|
||||
|
||||
Please also note that this benchmark only uses the subset of conversion specifications that are supported by *ALL* of the libraries compared.
|
||||
|
||||
Somethings to consider when making performance comparisons in the future:
|
||||
|
||||
* Can it write to io.Writer?
|
||||
* Which `%specification` does it handle?
|
||||
346
vendor/github.com/lestrrat-go/strftime/appenders.go
generated
vendored
Normal file
346
vendor/github.com/lestrrat-go/strftime/appenders.go
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These are all of the standard, POSIX compliant specifications.
|
||||
// Extensions should be in extensions.go
|
||||
var (
|
||||
fullWeekDayName = StdlibFormat("Monday")
|
||||
abbrvWeekDayName = StdlibFormat("Mon")
|
||||
fullMonthName = StdlibFormat("January")
|
||||
abbrvMonthName = StdlibFormat("Jan")
|
||||
centuryDecimal = AppendFunc(appendCentury)
|
||||
timeAndDate = StdlibFormat("Mon Jan _2 15:04:05 2006")
|
||||
mdy = StdlibFormat("01/02/06")
|
||||
dayOfMonthZeroPad = StdlibFormat("02")
|
||||
dayOfMonthSpacePad = StdlibFormat("_2")
|
||||
ymd = StdlibFormat("2006-01-02")
|
||||
twentyFourHourClockZeroPad = &hourPadded{twelveHour: false, pad: '0'}
|
||||
twelveHourClockZeroPad = &hourPadded{twelveHour: true, pad: '0'}
|
||||
dayOfYear = AppendFunc(appendDayOfYear)
|
||||
twentyFourHourClockSpacePad = &hourPadded{twelveHour: false, pad: ' '}
|
||||
twelveHourClockSpacePad = &hourPadded{twelveHour: true, pad: ' '}
|
||||
minutesZeroPad = StdlibFormat("04")
|
||||
monthNumberZeroPad = StdlibFormat("01")
|
||||
newline = Verbatim("\n")
|
||||
ampm = StdlibFormat("PM")
|
||||
hm = StdlibFormat("15:04")
|
||||
imsp = hmsWAMPM{}
|
||||
secondsNumberZeroPad = StdlibFormat("05")
|
||||
hms = StdlibFormat("15:04:05")
|
||||
tab = Verbatim("\t")
|
||||
weekNumberSundayOrigin = weeknumberOffset(0) // week number of the year, Sunday first
|
||||
weekdayMondayOrigin = weekday(1)
|
||||
// monday as the first day, and 01 as the first value
|
||||
weekNumberMondayOriginOneOrigin = AppendFunc(appendWeekNumber)
|
||||
eby = StdlibFormat("_2-Jan-2006")
|
||||
// monday as the first day, and 00 as the first value
|
||||
weekNumberMondayOrigin = weeknumberOffset(1) // week number of the year, Monday first
|
||||
weekdaySundayOrigin = weekday(0)
|
||||
natReprTime = StdlibFormat("15:04:05") // national representation of the time XXX is this correct?
|
||||
natReprDate = StdlibFormat("01/02/06") // national representation of the date XXX is this correct?
|
||||
year = StdlibFormat("2006") // year with century
|
||||
yearNoCentury = StdlibFormat("06") // year w/o century
|
||||
timezone = StdlibFormat("MST") // time zone name
|
||||
timezoneOffset = StdlibFormat("-0700") // time zone ofset from UTC
|
||||
percent = Verbatim("%")
|
||||
)
|
||||
|
||||
// Appender is the interface that must be fulfilled by components that
|
||||
// implement the translation of specifications to actual time value.
|
||||
//
|
||||
// The Append method takes the accumulated byte buffer, and the time to
|
||||
// use to generate the textual representation. The resulting byte
|
||||
// sequence must be returned by this method, normally by using the
|
||||
// append() builtin function.
|
||||
type Appender interface {
|
||||
Append([]byte, time.Time) []byte
|
||||
}
|
||||
|
||||
// AppendFunc is an utility type to allow users to create a
|
||||
// function-only version of an Appender
|
||||
type AppendFunc func([]byte, time.Time) []byte
|
||||
|
||||
func (af AppendFunc) Append(b []byte, t time.Time) []byte {
|
||||
return af(b, t)
|
||||
}
|
||||
|
||||
type appenderList []Appender
|
||||
|
||||
type dumper interface {
|
||||
dump(io.Writer)
|
||||
}
|
||||
|
||||
func (l appenderList) dump(out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
ll := len(l)
|
||||
for i, a := range l {
|
||||
if dumper, ok := a.(dumper); ok {
|
||||
dumper.dump(&buf)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%#v", a)
|
||||
}
|
||||
|
||||
if i < ll-1 {
|
||||
fmt.Fprintf(&buf, ",\n")
|
||||
}
|
||||
}
|
||||
if _, err := buf.WriteTo(out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// does the time.Format thing
|
||||
type stdlibFormat struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// StdlibFormat returns an Appender that simply goes through `time.Format()`
|
||||
// For example, if you know you want to display the abbreviated month name for %b,
|
||||
// you can create a StdlibFormat with the pattern `Jan` and register that
|
||||
// for specification `b`:
|
||||
//
|
||||
// a := StdlibFormat(`Jan`)
|
||||
// ss := NewSpecificationSet()
|
||||
// ss.Set('b', a) // does %b -> abbreviated month name
|
||||
func StdlibFormat(s string) Appender {
|
||||
return &stdlibFormat{s: s}
|
||||
}
|
||||
|
||||
func (v stdlibFormat) Append(b []byte, t time.Time) []byte {
|
||||
return t.AppendFormat(b, v.s)
|
||||
}
|
||||
|
||||
func (v stdlibFormat) str() string {
|
||||
return v.s
|
||||
}
|
||||
|
||||
func (v stdlibFormat) canCombine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v stdlibFormat) combine(w combiner) Appender {
|
||||
return StdlibFormat(v.s + w.str())
|
||||
}
|
||||
|
||||
func (v stdlibFormat) dump(out io.Writer) {
|
||||
fmt.Fprintf(out, "stdlib: %s", v.s)
|
||||
}
|
||||
|
||||
type verbatimw struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// Verbatim returns an Appender suitable for generating static text.
|
||||
// For static text, this method is slightly favorable than creating
|
||||
// your own appender, as adjacent verbatim blocks will be combined
|
||||
// at compile time to produce more efficient Appenders
|
||||
func Verbatim(s string) Appender {
|
||||
return &verbatimw{s: s}
|
||||
}
|
||||
|
||||
func (v verbatimw) Append(b []byte, _ time.Time) []byte {
|
||||
return append(b, v.s...)
|
||||
}
|
||||
|
||||
func (v verbatimw) canCombine() bool {
|
||||
return canCombine(v.s)
|
||||
}
|
||||
|
||||
func (v verbatimw) combine(w combiner) Appender {
|
||||
if _, ok := w.(*stdlibFormat); ok {
|
||||
return StdlibFormat(v.s + w.str())
|
||||
}
|
||||
return Verbatim(v.s + w.str())
|
||||
}
|
||||
|
||||
func (v verbatimw) str() string {
|
||||
return v.s
|
||||
}
|
||||
|
||||
func (v verbatimw) dump(out io.Writer) {
|
||||
fmt.Fprintf(out, "verbatim: %s", v.s)
|
||||
}
|
||||
|
||||
// These words below, as well as any decimal character
|
||||
var combineExclusion = []string{
|
||||
"Mon",
|
||||
"Monday",
|
||||
"Jan",
|
||||
"January",
|
||||
"MST",
|
||||
"PM",
|
||||
"pm",
|
||||
}
|
||||
|
||||
func canCombine(s string) bool {
|
||||
if strings.ContainsAny(s, "0123456789") {
|
||||
return false
|
||||
}
|
||||
for _, word := range combineExclusion {
|
||||
if strings.Contains(s, word) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type combiner interface {
|
||||
canCombine() bool
|
||||
combine(combiner) Appender
|
||||
str() string
|
||||
}
|
||||
|
||||
// this is container for the compiler to keep track of appenders,
|
||||
// and combine them as we parse and compile the pattern
|
||||
type combiningAppend struct {
|
||||
list appenderList
|
||||
prev Appender
|
||||
prevCanCombine bool
|
||||
}
|
||||
|
||||
func (ca *combiningAppend) Append(w Appender) {
|
||||
if ca.prevCanCombine {
|
||||
if wc, ok := w.(combiner); ok && wc.canCombine() {
|
||||
ca.prev = ca.prev.(combiner).combine(wc)
|
||||
ca.list[len(ca.list)-1] = ca.prev
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ca.list = append(ca.list, w)
|
||||
ca.prev = w
|
||||
ca.prevCanCombine = false
|
||||
if comb, ok := w.(combiner); ok {
|
||||
if comb.canCombine() {
|
||||
ca.prevCanCombine = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendCentury(b []byte, t time.Time) []byte {
|
||||
n := t.Year() / 100
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
type weekday int
|
||||
|
||||
func (v weekday) Append(b []byte, t time.Time) []byte {
|
||||
n := int(t.Weekday())
|
||||
if n < int(v) {
|
||||
n += 7
|
||||
}
|
||||
return append(b, byte(n+48))
|
||||
}
|
||||
|
||||
type weeknumberOffset int
|
||||
|
||||
func (v weeknumberOffset) Append(b []byte, t time.Time) []byte {
|
||||
yd := t.YearDay()
|
||||
offset := int(t.Weekday()) - int(v)
|
||||
if offset < 0 {
|
||||
offset += 7
|
||||
}
|
||||
|
||||
if yd < offset {
|
||||
return append(b, '0', '0')
|
||||
}
|
||||
|
||||
n := ((yd - offset) / 7) + 1
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
func appendWeekNumber(b []byte, t time.Time) []byte {
|
||||
_, n := t.ISOWeek()
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
func appendDayOfYear(b []byte, t time.Time) []byte {
|
||||
n := t.YearDay()
|
||||
if n < 10 {
|
||||
b = append(b, '0', '0')
|
||||
} else if n < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
type hourPadded struct {
|
||||
pad byte
|
||||
twelveHour bool
|
||||
}
|
||||
|
||||
func (v hourPadded) Append(b []byte, t time.Time) []byte {
|
||||
h := t.Hour()
|
||||
if v.twelveHour && h > 12 {
|
||||
h = h - 12
|
||||
}
|
||||
|
||||
if h < 10 {
|
||||
b = append(b, v.pad)
|
||||
b = append(b, byte(h+48))
|
||||
} else {
|
||||
b = unrollTwoDigits(b, h)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func unrollTwoDigits(b []byte, v int) []byte {
|
||||
b = append(b, byte((v/10)+48))
|
||||
b = append(b, byte((v%10)+48))
|
||||
return b
|
||||
}
|
||||
|
||||
type hmsWAMPM struct{}
|
||||
|
||||
func (v hmsWAMPM) Append(b []byte, t time.Time) []byte {
|
||||
h := t.Hour()
|
||||
var am bool
|
||||
|
||||
if h == 0 {
|
||||
b = append(b, '1')
|
||||
b = append(b, '2')
|
||||
am = true
|
||||
} else {
|
||||
switch {
|
||||
case h == 12:
|
||||
// no op
|
||||
case h > 12:
|
||||
h = h -12
|
||||
default:
|
||||
am = true
|
||||
}
|
||||
b = unrollTwoDigits(b, h)
|
||||
}
|
||||
b = append(b, ':')
|
||||
b = unrollTwoDigits(b, t.Minute())
|
||||
b = append(b, ':')
|
||||
b = unrollTwoDigits(b, t.Second())
|
||||
|
||||
b = append(b, ' ')
|
||||
if am {
|
||||
b = append(b, 'A')
|
||||
} else {
|
||||
b = append(b, 'P')
|
||||
}
|
||||
b = append(b, 'M')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
67
vendor/github.com/lestrrat-go/strftime/extension.go
generated
vendored
Normal file
67
vendor/github.com/lestrrat-go/strftime/extension.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NOTE: declare private variable and iniitalize once in init(),
|
||||
// and leave the Milliseconds() function as returning static content.
|
||||
// This way, `go doc -all` does not show the contents of the
|
||||
// milliseconds function
|
||||
var milliseconds Appender
|
||||
var microseconds Appender
|
||||
var unixseconds Appender
|
||||
|
||||
func init() {
|
||||
milliseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
millisecond := int(t.Nanosecond()) / int(time.Millisecond)
|
||||
if millisecond < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if millisecond < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(millisecond)...)
|
||||
})
|
||||
microseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
microsecond := int(t.Nanosecond()) / int(time.Microsecond)
|
||||
if microsecond < 100000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 10000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 1000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(microsecond)...)
|
||||
})
|
||||
unixseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
return append(b, strconv.FormatInt(t.Unix(), 10)...)
|
||||
})
|
||||
}
|
||||
|
||||
// Milliseconds returns the Appender suitable for creating a zero-padded,
|
||||
// 3-digit millisecond textual representation.
|
||||
func Milliseconds() Appender {
|
||||
return milliseconds
|
||||
}
|
||||
|
||||
// Microsecond returns the Appender suitable for creating a zero-padded,
|
||||
// 6-digit microsecond textual representation.
|
||||
func Microseconds() Appender {
|
||||
return microseconds
|
||||
}
|
||||
|
||||
// UnixSeconds returns the Appender suitable for creating
|
||||
// unix timestamp textual representation.
|
||||
func UnixSeconds() Appender {
|
||||
return unixseconds
|
||||
}
|
||||
9
vendor/github.com/lestrrat-go/strftime/go.mod
generated
vendored
Normal file
9
vendor/github.com/lestrrat-go/strftime/go.mod
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/lestrrat-go/strftime
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/stretchr/testify v1.3.0
|
||||
)
|
||||
67
vendor/github.com/lestrrat-go/strftime/options.go
generated
vendored
Normal file
67
vendor/github.com/lestrrat-go/strftime/options.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package strftime
|
||||
|
||||
type Option interface {
|
||||
Name() string
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
type option struct {
|
||||
name string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (o *option) Name() string { return o.name }
|
||||
func (o *option) Value() interface{} { return o.value }
|
||||
|
||||
const optSpecificationSet = `opt-specification-set`
|
||||
|
||||
// WithSpecification allows you to specify a custom specification set
|
||||
func WithSpecificationSet(ds SpecificationSet) Option {
|
||||
return &option{
|
||||
name: optSpecificationSet,
|
||||
value: ds,
|
||||
}
|
||||
}
|
||||
|
||||
type optSpecificationPair struct {
|
||||
name byte
|
||||
appender Appender
|
||||
}
|
||||
|
||||
const optSpecification = `opt-specification`
|
||||
|
||||
// WithSpecification allows you to create a new specification set on the fly,
|
||||
// to be used only for that invocation.
|
||||
func WithSpecification(b byte, a Appender) Option {
|
||||
return &option{
|
||||
name: optSpecification,
|
||||
value: &optSpecificationPair{
|
||||
name: b,
|
||||
appender: a,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithMilliseconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the zero-padded, 3 letter milliseconds of the time.
|
||||
func WithMilliseconds(b byte) Option {
|
||||
return WithSpecification(b, Milliseconds())
|
||||
}
|
||||
|
||||
// WithMicroseconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the zero-padded, 3 letter microseconds of the time.
|
||||
func WithMicroseconds(b byte) Option {
|
||||
return WithSpecification(b, Microseconds())
|
||||
}
|
||||
|
||||
// WithUnixSeconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the unix timestamp in seconds
|
||||
func WithUnixSeconds(b byte) Option {
|
||||
return WithSpecification(b, UnixSeconds())
|
||||
}
|
||||
152
vendor/github.com/lestrrat-go/strftime/specifications.go
generated
vendored
Normal file
152
vendor/github.com/lestrrat-go/strftime/specifications.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// because there is no such thing was a sync.RWLocker
|
||||
type rwLocker interface {
|
||||
RLock()
|
||||
RUnlock()
|
||||
sync.Locker
|
||||
}
|
||||
|
||||
// SpecificationSet is a container for patterns that Strftime uses.
|
||||
// If you want a custom strftime, you can copy the default
|
||||
// SpecificationSet and tweak it
|
||||
type SpecificationSet interface {
|
||||
Lookup(byte) (Appender, error)
|
||||
Delete(byte) error
|
||||
Set(byte, Appender) error
|
||||
}
|
||||
|
||||
type specificationSet struct {
|
||||
mutable bool
|
||||
lock rwLocker
|
||||
store map[byte]Appender
|
||||
}
|
||||
|
||||
// The default specification set does not need any locking as it is never
|
||||
// accessed from the outside, and is never mutated.
|
||||
var defaultSpecificationSet SpecificationSet
|
||||
|
||||
func init() {
|
||||
defaultSpecificationSet = newImmutableSpecificationSet()
|
||||
}
|
||||
|
||||
func newImmutableSpecificationSet() SpecificationSet {
|
||||
// Create a mutable one so that populateDefaultSpecifications work through
|
||||
// its magic, then copy the associated map
|
||||
// (NOTE: this is done this way because there used to be
|
||||
// two struct types for specification set, united under an interface.
|
||||
// it can now be removed, but we would need to change the entire
|
||||
// populateDefaultSpecifications method, and I'm currently too lazy
|
||||
// PRs welcome)
|
||||
tmp := NewSpecificationSet()
|
||||
|
||||
ss := &specificationSet{
|
||||
mutable: false,
|
||||
lock: nil, // never used, so intentionally not initialized
|
||||
store: tmp.(*specificationSet).store,
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// NewSpecificationSet creates a specification set with the default specifications.
|
||||
func NewSpecificationSet() SpecificationSet {
|
||||
ds := &specificationSet{
|
||||
mutable: true,
|
||||
lock: &sync.RWMutex{},
|
||||
store: make(map[byte]Appender),
|
||||
}
|
||||
populateDefaultSpecifications(ds)
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
var defaultSpecifications = map[byte]Appender{
|
||||
'A': fullWeekDayName,
|
||||
'a': abbrvWeekDayName,
|
||||
'B': fullMonthName,
|
||||
'b': abbrvMonthName,
|
||||
'C': centuryDecimal,
|
||||
'c': timeAndDate,
|
||||
'D': mdy,
|
||||
'd': dayOfMonthZeroPad,
|
||||
'e': dayOfMonthSpacePad,
|
||||
'F': ymd,
|
||||
'H': twentyFourHourClockZeroPad,
|
||||
'h': abbrvMonthName,
|
||||
'I': twelveHourClockZeroPad,
|
||||
'j': dayOfYear,
|
||||
'k': twentyFourHourClockSpacePad,
|
||||
'l': twelveHourClockSpacePad,
|
||||
'M': minutesZeroPad,
|
||||
'm': monthNumberZeroPad,
|
||||
'n': newline,
|
||||
'p': ampm,
|
||||
'R': hm,
|
||||
'r': imsp,
|
||||
'S': secondsNumberZeroPad,
|
||||
'T': hms,
|
||||
't': tab,
|
||||
'U': weekNumberSundayOrigin,
|
||||
'u': weekdayMondayOrigin,
|
||||
'V': weekNumberMondayOriginOneOrigin,
|
||||
'v': eby,
|
||||
'W': weekNumberMondayOrigin,
|
||||
'w': weekdaySundayOrigin,
|
||||
'X': natReprTime,
|
||||
'x': natReprDate,
|
||||
'Y': year,
|
||||
'y': yearNoCentury,
|
||||
'Z': timezone,
|
||||
'z': timezoneOffset,
|
||||
'%': percent,
|
||||
}
|
||||
|
||||
func populateDefaultSpecifications(ds SpecificationSet) {
|
||||
for c, handler := range defaultSpecifications {
|
||||
if err := ds.Set(c, handler); err != nil {
|
||||
panic(fmt.Sprintf("failed to set default specification for %c: %s", c, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Lookup(b byte) (Appender, error) {
|
||||
if ds.mutable {
|
||||
ds.lock.RLock()
|
||||
defer ds.lock.RLock()
|
||||
}
|
||||
v, ok := ds.store[b]
|
||||
if !ok {
|
||||
return nil, errors.Errorf(`lookup failed: '%%%c' was not found in specification set`, b)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Delete(b byte) error {
|
||||
if !ds.mutable {
|
||||
return errors.New(`delete failed: this specification set is marked immutable`)
|
||||
}
|
||||
|
||||
ds.lock.Lock()
|
||||
defer ds.lock.Unlock()
|
||||
delete(ds.store, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Set(b byte, a Appender) error {
|
||||
if !ds.mutable {
|
||||
return errors.New(`set failed: this specification set is marked immutable`)
|
||||
}
|
||||
|
||||
ds.lock.Lock()
|
||||
defer ds.lock.Unlock()
|
||||
ds.store[b] = a
|
||||
return nil
|
||||
}
|
||||
221
vendor/github.com/lestrrat-go/strftime/strftime.go
generated
vendored
Normal file
221
vendor/github.com/lestrrat-go/strftime/strftime.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type compileHandler interface {
|
||||
handle(Appender)
|
||||
}
|
||||
|
||||
// compile, and create an appender list
|
||||
type appenderListBuilder struct {
|
||||
list *combiningAppend
|
||||
}
|
||||
|
||||
func (alb *appenderListBuilder) handle(a Appender) {
|
||||
alb.list.Append(a)
|
||||
}
|
||||
|
||||
// compile, and execute the appenders on the fly
|
||||
type appenderExecutor struct {
|
||||
t time.Time
|
||||
dst []byte
|
||||
}
|
||||
|
||||
func (ae *appenderExecutor) handle(a Appender) {
|
||||
ae.dst = a.Append(ae.dst, ae.t)
|
||||
}
|
||||
|
||||
func compile(handler compileHandler, p string, ds SpecificationSet) error {
|
||||
for l := len(p); l > 0; l = len(p) {
|
||||
// This is a really tight loop, so we don't even calls to
|
||||
// Verbatim() to cuase extra stuff
|
||||
var verbatim verbatimw
|
||||
|
||||
i := strings.IndexByte(p, '%')
|
||||
if i < 0 {
|
||||
verbatim.s = p
|
||||
handler.handle(&verbatim)
|
||||
// this is silly, but I don't trust break keywords when there's a
|
||||
// possibility of this piece of code being rearranged
|
||||
p = p[l:]
|
||||
continue
|
||||
}
|
||||
if i == l-1 {
|
||||
return errors.New(`stray % at the end of pattern`)
|
||||
}
|
||||
|
||||
// we found a '%'. we need the next byte to decide what to do next
|
||||
// we already know that i < l - 1
|
||||
// everything up to the i is verbatim
|
||||
if i > 0 {
|
||||
verbatim.s = p[:i]
|
||||
handler.handle(&verbatim)
|
||||
p = p[i:]
|
||||
}
|
||||
|
||||
specification, err := ds.Lookup(p[1])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `pattern compilation failed`)
|
||||
}
|
||||
|
||||
handler.handle(specification)
|
||||
p = p[2:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSpecificationSetFor(options ...Option) (SpecificationSet, error) {
|
||||
var ds SpecificationSet = defaultSpecificationSet
|
||||
var extraSpecifications []*optSpecificationPair
|
||||
for _, option := range options {
|
||||
switch option.Name() {
|
||||
case optSpecificationSet:
|
||||
ds = option.Value().(SpecificationSet)
|
||||
case optSpecification:
|
||||
extraSpecifications = append(extraSpecifications, option.Value().(*optSpecificationPair))
|
||||
}
|
||||
}
|
||||
|
||||
if len(extraSpecifications) > 0 {
|
||||
// If ds is immutable, we're going to need to create a new
|
||||
// one. oh what a waste!
|
||||
if raw, ok := ds.(*specificationSet); ok && !raw.mutable {
|
||||
ds = NewSpecificationSet()
|
||||
}
|
||||
for _, v := range extraSpecifications {
|
||||
if err := ds.Set(v.name, v.appender); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
var fmtAppendExecutorPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var h appenderExecutor
|
||||
h.dst = make([]byte, 0, 32)
|
||||
return &h
|
||||
},
|
||||
}
|
||||
|
||||
func getFmtAppendExecutor() *appenderExecutor {
|
||||
return fmtAppendExecutorPool.Get().(*appenderExecutor)
|
||||
}
|
||||
|
||||
func releasdeFmtAppendExecutor(v *appenderExecutor) {
|
||||
// TODO: should we discard the buffer if it's too long?
|
||||
v.dst = v.dst[:0]
|
||||
fmtAppendExecutorPool.Put(v)
|
||||
}
|
||||
|
||||
// Format takes the format `s` and the time `t` to produce the
|
||||
// format date/time. Note that this function re-compiles the
|
||||
// pattern every time it is called.
|
||||
//
|
||||
// If you know beforehand that you will be reusing the pattern
|
||||
// within your application, consider creating a `Strftime` object
|
||||
// and reusing it.
|
||||
func Format(p string, t time.Time, options ...Option) (string, error) {
|
||||
// TODO: this may be premature optimization
|
||||
ds, err := getSpecificationSetFor(options...)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, `failed to get specification set`)
|
||||
}
|
||||
h := getFmtAppendExecutor()
|
||||
defer releasdeFmtAppendExecutor(h)
|
||||
|
||||
h.t = t
|
||||
if err := compile(h, p, ds); err != nil {
|
||||
return "", errors.Wrap(err, `failed to compile format`)
|
||||
}
|
||||
|
||||
return string(h.dst), nil
|
||||
}
|
||||
|
||||
// Strftime is the object that represents a compiled strftime pattern
|
||||
type Strftime struct {
|
||||
pattern string
|
||||
compiled appenderList
|
||||
}
|
||||
|
||||
// New creates a new Strftime object. If the compilation fails, then
|
||||
// an error is returned in the second argument.
|
||||
func New(p string, options ...Option) (*Strftime, error) {
|
||||
// TODO: this may be premature optimization
|
||||
ds, err := getSpecificationSetFor(options...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, `failed to get specification set`)
|
||||
}
|
||||
|
||||
var h appenderListBuilder
|
||||
h.list = &combiningAppend{}
|
||||
|
||||
if err := compile(&h, p, ds); err != nil {
|
||||
return nil, errors.Wrap(err, `failed to compile format`)
|
||||
}
|
||||
|
||||
return &Strftime{
|
||||
pattern: p,
|
||||
compiled: h.list.list,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Pattern returns the original pattern string
|
||||
func (f *Strftime) Pattern() string {
|
||||
return f.pattern
|
||||
}
|
||||
|
||||
// Format takes the destination `dst` and time `t`. It formats the date/time
|
||||
// using the pre-compiled pattern, and outputs the results to `dst`
|
||||
func (f *Strftime) Format(dst io.Writer, t time.Time) error {
|
||||
const bufSize = 64
|
||||
var b []byte
|
||||
max := len(f.pattern) + 10
|
||||
if max < bufSize {
|
||||
var buf [bufSize]byte
|
||||
b = buf[:0]
|
||||
} else {
|
||||
b = make([]byte, 0, max)
|
||||
}
|
||||
if _, err := dst.Write(f.format(b, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dump outputs the internal structure of the formatter, for debugging purposes.
|
||||
// Please do NOT assume the output format to be fixed: it is expected to change
|
||||
// in the future.
|
||||
func (f *Strftime) Dump(out io.Writer) {
|
||||
f.compiled.dump(out)
|
||||
}
|
||||
|
||||
func (f *Strftime) format(b []byte, t time.Time) []byte {
|
||||
for _, w := range f.compiled {
|
||||
b = w.Append(b, t)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// FormatString takes the time `t` and formats it, returning the
|
||||
// string containing the formated data.
|
||||
func (f *Strftime) FormatString(t time.Time) string {
|
||||
const bufSize = 64
|
||||
var b []byte
|
||||
max := len(f.pattern) + 10
|
||||
if max < bufSize {
|
||||
var buf [bufSize]byte
|
||||
b = buf[:0]
|
||||
} else {
|
||||
b = make([]byte, 0, max)
|
||||
}
|
||||
return string(f.format(b, t))
|
||||
}
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -139,6 +139,9 @@ github.com/joho/godotenv/autoload
|
||||
github.com/lann/builder
|
||||
# github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0
|
||||
github.com/lann/ps
|
||||
# github.com/lestrrat-go/strftime v1.0.3
|
||||
## explicit
|
||||
github.com/lestrrat-go/strftime
|
||||
# github.com/lib/pq v1.1.0
|
||||
## explicit
|
||||
github.com/lib/pq
|
||||
|
||||
Reference in New Issue
Block a user