mirror of
https://github.com/jimeh/go-tyme.git
synced 2026-02-19 01:46:41 +00:00
feat(tyme/dur): add dur package as separate module
This commit is contained in:
2
dur/.gitignore
vendored
Normal file
2
dur/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bin/*
|
||||
coverage.*
|
||||
94
dur/.golangci.yml
Normal file
94
dur/.golangci.yml
Normal file
@@ -0,0 +1,94 @@
|
||||
linters-settings:
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 450
|
||||
golint:
|
||||
min-confidence: 0
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
- shadow
|
||||
lll:
|
||||
line-length: 80
|
||||
tab-width: 4
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
paralleltest:
|
||||
ignore-missing: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- depguard
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- godot
|
||||
- gofumpt
|
||||
- goimports
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- predeclared
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- wastedassign
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
exclude:
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
- Using the variable on range scope `tc` in function literal
|
||||
exclude-rules:
|
||||
- path: "_test\\.go"
|
||||
linters:
|
||||
- funlen
|
||||
- dupl
|
||||
- source: "^//go:generate "
|
||||
linters:
|
||||
- lll
|
||||
- source: "`env:"
|
||||
linters:
|
||||
- lll
|
||||
- source: "`json:"
|
||||
linters:
|
||||
- lll
|
||||
- source: "`xml:"
|
||||
linters:
|
||||
- lll
|
||||
- source: "`yaml:"
|
||||
linters:
|
||||
- lll
|
||||
|
||||
run:
|
||||
timeout: 2m
|
||||
allow-parallel-runners: true
|
||||
modules-download-mode: readonly
|
||||
191
dur/Makefile
Normal file
191
dur/Makefile
Normal file
@@ -0,0 +1,191 @@
|
||||
GOMODNAME := $(shell grep 'module' go.mod | sed -e 's/^module //')
|
||||
SOURCES := $(shell find . -name "*.go" -or -name "go.mod" -or -name "go.sum" \
|
||||
-or -name "Makefile" -or -name "*.golden")
|
||||
|
||||
# Verbose output
|
||||
ifdef VERBOSE
|
||||
V = -v
|
||||
endif
|
||||
|
||||
#
|
||||
# Environment
|
||||
#
|
||||
|
||||
BINDIR := bin
|
||||
TOOLDIR := $(BINDIR)/tools
|
||||
|
||||
# Global environment variables for all targets
|
||||
SHELL ?= /bin/bash
|
||||
SHELL := env \
|
||||
GO111MODULE=on \
|
||||
GOBIN=$(CURDIR)/$(TOOLDIR) \
|
||||
CGO_ENABLED=1 \
|
||||
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
|
||||
$(SHELL)
|
||||
|
||||
#
|
||||
# Defaults
|
||||
#
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL := test
|
||||
|
||||
#
|
||||
# Tools
|
||||
#
|
||||
|
||||
# external tool
|
||||
define tool # 1: binary-name, 2: go-import-path
|
||||
TOOLS += $(TOOLDIR)/$(1)
|
||||
|
||||
$(TOOLDIR)/$(1): Makefile
|
||||
GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
|
||||
endef
|
||||
|
||||
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc@latest))
|
||||
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
|
||||
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports@latest))
|
||||
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50))
|
||||
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
|
||||
$(eval $(call tool,mockgen,github.com/golang/mock/mockgen@v1.6.0))
|
||||
|
||||
.PHONY: tools
|
||||
tools: $(TOOLS)
|
||||
|
||||
#
|
||||
# Development
|
||||
#
|
||||
|
||||
BENCH ?= .
|
||||
TESTARGS ?=
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(TOOLS)
|
||||
rm -f ./coverage.out ./go.mod.tidy-check ./go.sum.tidy-check
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test $(V) -count=1 -race $(TESTARGS) ./...
|
||||
|
||||
.PHONY: test-deps
|
||||
test-deps:
|
||||
go test all
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(TOOLDIR)/golangci-lint
|
||||
golangci-lint $(V) run
|
||||
|
||||
.PHONY: format
|
||||
format: $(TOOLDIR)/goimports $(TOOLDIR)/gofumpt
|
||||
goimports -w . && gofumpt -w .
|
||||
|
||||
.SILENT: bench
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test $(V) -count=1 -bench=$(BENCH) $(TESTARGS) ./...
|
||||
|
||||
#
|
||||
# Code Generation
|
||||
#
|
||||
|
||||
.PHONY: generate
|
||||
generate: $(TOOLDIR)/mockgen
|
||||
go generate ./...
|
||||
|
||||
.PHONY: check-generate
|
||||
check-generate: $(TOOLDIR)/mockgen
|
||||
$(eval CHKDIR := $(shell mktemp -d))
|
||||
cp -av . "$(CHKDIR)"
|
||||
make -C "$(CHKDIR)/" generate
|
||||
( diff -rN . "$(CHKDIR)" && rm -rf "$(CHKDIR)" ) || \
|
||||
( rm -rf "$(CHKDIR)" && exit 1 )
|
||||
|
||||
#
|
||||
# Coverage
|
||||
#
|
||||
|
||||
.PHONY: cov
|
||||
cov: coverage.out
|
||||
|
||||
.PHONY: cov-html
|
||||
cov-html: coverage.out
|
||||
go tool cover -html=./coverage.out
|
||||
|
||||
.PHONY: cov-func
|
||||
cov-func: coverage.out
|
||||
go tool cover -func=./coverage.out
|
||||
|
||||
coverage.out: $(SOURCES)
|
||||
go test $(V) -count=1 -race \
|
||||
-covermode=atomic -coverprofile=./coverage.out ./...
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
go mod download
|
||||
|
||||
.PHONY: deps-update
|
||||
deps-update:
|
||||
go get -u -t ./...
|
||||
|
||||
.PHONY: deps-analyze
|
||||
deps-analyze: $(TOOLDIR)/gomod
|
||||
gomod analyze
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
go mod tidy $(V)
|
||||
|
||||
.PHONY: verify
|
||||
verify:
|
||||
go mod verify
|
||||
|
||||
.SILENT: check-tidy
|
||||
.PHONY: check-tidy
|
||||
check-tidy:
|
||||
cp go.mod go.mod.tidy-check
|
||||
cp go.sum go.sum.tidy-check
|
||||
go mod tidy
|
||||
( \
|
||||
diff go.mod go.mod.tidy-check && \
|
||||
diff go.sum go.sum.tidy-check && \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum \
|
||||
) || ( \
|
||||
rm -f go.mod go.sum && \
|
||||
mv go.mod.tidy-check go.mod && \
|
||||
mv go.sum.tidy-check go.sum; \
|
||||
exit 1 \
|
||||
)
|
||||
|
||||
#
|
||||
# Documentation
|
||||
#
|
||||
|
||||
# Serve docs
|
||||
.PHONY: docs
|
||||
docs: $(TOOLDIR)/godoc
|
||||
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
|
||||
@godoc -http=127.0.0.1:6060
|
||||
|
||||
#
|
||||
# Release
|
||||
#
|
||||
|
||||
.PHONY: new-version
|
||||
new-version: check-npx
|
||||
npx standard-version
|
||||
|
||||
.PHONY: next-version
|
||||
next-version: check-npx
|
||||
npx standard-version --dry-run
|
||||
|
||||
.PHONY: check-npx
|
||||
check-npx:
|
||||
$(if $(shell which npx),,\
|
||||
$(error No npx found in PATH, please install NodeJS))
|
||||
11
dur/dur.go
Normal file
11
dur/dur.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Package dur provides a wrapper dur.Duration around time.Duration, with
|
||||
// sensible JSON/YAML marshaling/unmarshaling support.
|
||||
//
|
||||
// Unmarshaling supports standard time.Duration formats string formats such as
|
||||
// "5s, ""1h30m", all parsed by time.ParseDuration. It also supports integer and
|
||||
// float values which are interpreted as seconds, rather than nanoseconds, like
|
||||
// the regular time.Duration does.
|
||||
//
|
||||
// Marshaling always outputs a string, using the standard time.Duration format,
|
||||
// by calling time.Duration(d).String().
|
||||
package dur
|
||||
79
dur/duration.go
Normal file
79
dur/duration.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package dur
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Duration is a wrapper around time.Duration that implements JSON and YAML
|
||||
// marshaler and unmarshaler interfaces.
|
||||
//
|
||||
// When unmarshaling, string values in JSON and YAML are parsed using
|
||||
// time.ParseDuration. Numeric values are parsed as number of seconds.
|
||||
//
|
||||
// When marshaling, the duration is formatted as a string using time.Duration's
|
||||
// String method.
|
||||
type Duration time.Duration
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface, returning the duration
|
||||
// as a string in the format "1h2m3s", same as time.Duration.String().
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(time.Duration(d).String())
|
||||
}
|
||||
|
||||
// UmarshalJSON implements the json.Unmarshaler interface. Supports string,
|
||||
// numeric JSON types, converting them to a Duration using ParseDuration.
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var x interface{}
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pd, err := Parse(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = pd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the yaml.Marshaler interface, returning the duration
|
||||
// as a string in the format 1h2m3s, same as time.Duration.String().
|
||||
func (d Duration) MarshalYAML() (interface{}, error) {
|
||||
return time.Duration(d).String(), nil
|
||||
}
|
||||
|
||||
// UmarshalYAML implements the yaml.Unmarshaler interface. Supports string, int
|
||||
// and float YAML types, converting them to a Duration using ParseDuration.
|
||||
func (d *Duration) UnmarshalYAML(node *yaml.Node) error {
|
||||
var x interface{}
|
||||
var err error
|
||||
|
||||
switch node.Tag {
|
||||
case "!!str":
|
||||
x = node.Value
|
||||
case "!!int":
|
||||
x, err = strconv.Atoi(node.Value)
|
||||
case "!!float":
|
||||
x, err = strconv.ParseFloat(node.Value, 64)
|
||||
default:
|
||||
return &yaml.TypeError{Errors: []string{"invalid duration"}}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pd, err := Parse(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = pd
|
||||
|
||||
return nil
|
||||
}
|
||||
72
dur/duration_example_test.go
Normal file
72
dur/duration_example_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package dur_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jimeh/go-tyme/dur"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func ExampleDuration_MarshalJSON() {
|
||||
type Connection struct {
|
||||
Timeout dur.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
conn := Connection{Timeout: dur.Duration(5 * time.Second)}
|
||||
b, _ := json.Marshal(conn)
|
||||
|
||||
fmt.Println(string(b))
|
||||
// Output:
|
||||
// {"timeout":"5s"}
|
||||
}
|
||||
|
||||
func ExampleDuration_UnmarshalJSON() {
|
||||
type Connection struct {
|
||||
Timeout dur.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
conn := Connection{}
|
||||
_ = json.Unmarshal([]byte(`{"timeout": "10s"}`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
_ = json.Unmarshal([]byte(`{"timeout": 5}`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
_ = json.Unmarshal([]byte(`{"timeout": 0.5}`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
// Output:
|
||||
// 10000000000 (10s)
|
||||
// 5000000000 (5s)
|
||||
// 500000000 (500ms)
|
||||
}
|
||||
|
||||
func ExampleDuration_MarshalYAML() {
|
||||
type Connection struct {
|
||||
Timeout dur.Duration `yaml:"timeout"`
|
||||
}
|
||||
|
||||
conn := Connection{Timeout: dur.Duration(5 * time.Second)}
|
||||
b, _ := yaml.Marshal(conn)
|
||||
|
||||
fmt.Println(string(b))
|
||||
// Output:
|
||||
// timeout: 5s
|
||||
}
|
||||
|
||||
func ExampleDuration_UnmarshalYAML() {
|
||||
type Connection struct {
|
||||
Timeout dur.Duration `yaml:"timeout"`
|
||||
}
|
||||
|
||||
conn := Connection{}
|
||||
_ = yaml.Unmarshal([]byte(`timeout: 10s`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
_ = yaml.Unmarshal([]byte(`timeout: 5`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
_ = yaml.Unmarshal([]byte(`timeout: 0.5`), &conn)
|
||||
fmt.Printf("%+v (%+v)\n", conn.Timeout, time.Duration(conn.Timeout))
|
||||
// Output:
|
||||
// 10000000000 (10s)
|
||||
// 5000000000 (5s)
|
||||
// 500000000 (500ms)
|
||||
}
|
||||
344
dur/duration_test.go
Normal file
344
dur/duration_test.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package dur
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestDuration_MarshalUnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
d time.Duration
|
||||
}{
|
||||
{name: "zero", d: 0},
|
||||
{name: "1ns", d: 1 * time.Nanosecond},
|
||||
{name: "2ns", d: 2 * time.Nanosecond},
|
||||
{name: "1µs", d: 1 * time.Microsecond},
|
||||
{name: "2µs", d: 2 * time.Microsecond},
|
||||
{name: "1ms", d: 1 * time.Millisecond},
|
||||
{name: "2ms", d: 2 * time.Millisecond},
|
||||
{name: "1s", d: 1 * time.Second},
|
||||
{name: "2s", d: 2 * time.Second},
|
||||
{name: "90s", d: 90 * time.Second},
|
||||
{name: "1m", d: 1 * time.Minute},
|
||||
{name: "2m", d: 2 * time.Minute},
|
||||
{name: "90m", d: 90 * time.Minute},
|
||||
{name: "1h", d: 1 * time.Hour},
|
||||
{name: "2h", d: 2 * time.Hour},
|
||||
{name: "36h", d: 36 * time.Hour},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Duration(tt.d)
|
||||
|
||||
b, err := json.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
var d2 Duration
|
||||
err = json.Unmarshal(b, &d2)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.d, time.Duration(d2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
d time.Duration
|
||||
want string
|
||||
}{
|
||||
{name: "zero", d: 0, want: `"0s"`},
|
||||
{name: "1ns", d: 1 * time.Nanosecond, want: `"1ns"`},
|
||||
{name: "2ns", d: 2 * time.Nanosecond, want: `"2ns"`},
|
||||
{name: "1µs", d: 1 * time.Microsecond, want: `"1µs"`},
|
||||
{name: "2µs", d: 2 * time.Microsecond, want: `"2µs"`},
|
||||
{name: "1ms", d: 1 * time.Millisecond, want: `"1ms"`},
|
||||
{name: "2ms", d: 2 * time.Millisecond, want: `"2ms"`},
|
||||
{name: "1s", d: 1 * time.Second, want: `"1s"`},
|
||||
{name: "2s", d: 2 * time.Second, want: `"2s"`},
|
||||
{name: "90s", d: 90 * time.Second, want: `"1m30s"`},
|
||||
{name: "1m", d: 1 * time.Minute, want: `"1m0s"`},
|
||||
{name: "2m", d: 2 * time.Minute, want: `"2m0s"`},
|
||||
{name: "90m", d: 90 * time.Minute, want: `"1h30m0s"`},
|
||||
{name: "1h", d: 1 * time.Hour, want: `"1h0m0s"`},
|
||||
{name: "2h", d: 2 * time.Hour, want: `"2h0m0s"`},
|
||||
{name: "36h", d: 36 * time.Hour, want: `"36h0m0s"`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Duration(tt.d)
|
||||
|
||||
b, err := json.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want time.Duration
|
||||
wantErr string
|
||||
}{
|
||||
{s: `"1ns"`, want: 1 * time.Nanosecond},
|
||||
{s: `"2ns"`, want: 2 * time.Nanosecond},
|
||||
{s: `"1µs"`, want: 1 * time.Microsecond},
|
||||
{s: `"2µs"`, want: 2 * time.Microsecond},
|
||||
{s: `"1ms"`, want: 1 * time.Millisecond},
|
||||
{s: `"2ms"`, want: 2 * time.Millisecond},
|
||||
{s: `"1s"`, want: 1 * time.Second},
|
||||
{s: `"2s"`, want: 2 * time.Second},
|
||||
{s: `"90s"`, want: 90 * time.Second},
|
||||
{s: `"1m30s"`, want: 90 * time.Second},
|
||||
{s: `"1m0s"`, want: 1 * time.Minute},
|
||||
{s: `"2m0s"`, want: 2 * time.Minute},
|
||||
{s: `"90m"`, want: 90 * time.Minute},
|
||||
{s: `"1h30m"`, want: 90 * time.Minute},
|
||||
{s: `"1h30m0s"`, want: 90 * time.Minute},
|
||||
{s: `"1h0m0s"`, want: 1 * time.Hour},
|
||||
{s: `"2h0m0s"`, want: 2 * time.Hour},
|
||||
{s: `"36h0m0s"`, want: 36 * time.Hour},
|
||||
{s: "0.000000001", want: 1 * time.Nanosecond},
|
||||
{s: "0.000001", want: 1 * time.Microsecond},
|
||||
{s: "0.001", want: 1 * time.Millisecond},
|
||||
{s: "0.1", want: 100 * time.Millisecond},
|
||||
{s: "1", want: 1 * time.Second},
|
||||
{s: "1.0", want: 1 * time.Second},
|
||||
{s: "2.0", want: 2 * time.Second},
|
||||
{s: "90", want: 90 * time.Second},
|
||||
{s: "90.001", want: (90 * time.Second) + (1 * time.Millisecond)},
|
||||
{s: "90.999", want: (90 * time.Second) + (999 * time.Millisecond)},
|
||||
{name: "empty", s: "", wantErr: "unexpected end of JSON input"},
|
||||
{
|
||||
s: "'2ms'",
|
||||
wantErr: "invalid character '\\'' looking for beginning of value",
|
||||
},
|
||||
{
|
||||
s: "nil",
|
||||
wantErr: "invalid character 'i' in literal null (expecting 'u')",
|
||||
},
|
||||
{s: `"nil"`, wantErr: "time: invalid duration \"nil\""},
|
||||
{
|
||||
s: "foo",
|
||||
wantErr: "invalid character 'o' in literal false (expecting 'a')",
|
||||
},
|
||||
{s: `"foo"`, wantErr: "time: invalid duration \"foo\""},
|
||||
{s: "null", wantErr: "time: invalid duration <nil>"},
|
||||
{s: `"null"`, wantErr: "time: invalid duration \"null\""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := tt.name
|
||||
if name == "" {
|
||||
name = tt.s
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var d Duration
|
||||
|
||||
err := json.Unmarshal([]byte(tt.s), &d)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, time.Duration(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration_MarshalUnmarshalYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
d time.Duration
|
||||
}{
|
||||
{name: "zero", d: 0},
|
||||
{name: "1ns", d: 1 * time.Nanosecond},
|
||||
{name: "2ns", d: 2 * time.Nanosecond},
|
||||
{name: "1µs", d: 1 * time.Microsecond},
|
||||
{name: "2µs", d: 2 * time.Microsecond},
|
||||
{name: "1ms", d: 1 * time.Millisecond},
|
||||
{name: "2ms", d: 2 * time.Millisecond},
|
||||
{name: "1s", d: 1 * time.Second},
|
||||
{name: "2s", d: 2 * time.Second},
|
||||
{name: "90s", d: 90 * time.Second},
|
||||
{name: "1m", d: 1 * time.Minute},
|
||||
{name: "2m", d: 2 * time.Minute},
|
||||
{name: "90m", d: 90 * time.Minute},
|
||||
{name: "1h", d: 1 * time.Hour},
|
||||
{name: "2h", d: 2 * time.Hour},
|
||||
{name: "36h", d: 36 * time.Hour},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Duration(tt.d)
|
||||
|
||||
b, err := yaml.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
var d2 Duration
|
||||
err = yaml.Unmarshal(b, &d2)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.d, time.Duration(d2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration_MarshalYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
d time.Duration
|
||||
want string
|
||||
}{
|
||||
{name: "zero", d: 0, want: "0s\n"},
|
||||
{name: "1ns", d: 1 * time.Nanosecond, want: "1ns\n"},
|
||||
{name: "2ns", d: 2 * time.Nanosecond, want: "2ns\n"},
|
||||
{name: "1µs", d: 1 * time.Microsecond, want: "1µs\n"},
|
||||
{name: "2µs", d: 2 * time.Microsecond, want: "2µs\n"},
|
||||
{name: "1ms", d: 1 * time.Millisecond, want: "1ms\n"},
|
||||
{name: "2ms", d: 2 * time.Millisecond, want: "2ms\n"},
|
||||
{name: "1s", d: 1 * time.Second, want: "1s\n"},
|
||||
{name: "2s", d: 2 * time.Second, want: "2s\n"},
|
||||
{name: "90s", d: 90 * time.Second, want: "1m30s\n"},
|
||||
{name: "1m", d: 1 * time.Minute, want: "1m0s\n"},
|
||||
{name: "2m", d: 2 * time.Minute, want: "2m0s\n"},
|
||||
{name: "90m", d: 90 * time.Minute, want: "1h30m0s\n"},
|
||||
{name: "1h", d: 1 * time.Hour, want: "1h0m0s\n"},
|
||||
{name: "2h", d: 2 * time.Hour, want: "2h0m0s\n"},
|
||||
{name: "36h", d: 36 * time.Hour, want: "36h0m0s\n"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Duration(tt.d)
|
||||
|
||||
b, err := yaml.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want, string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration_UnmarshalYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want time.Duration
|
||||
wantErr string
|
||||
}{
|
||||
{s: `"1ns"`, want: 1 * time.Nanosecond},
|
||||
{s: `"2ns"`, want: 2 * time.Nanosecond},
|
||||
{s: `"1µs"`, want: 1 * time.Microsecond},
|
||||
{s: `"2µs"`, want: 2 * time.Microsecond},
|
||||
{s: `"1ms"`, want: 1 * time.Millisecond},
|
||||
{s: `"2ms"`, want: 2 * time.Millisecond},
|
||||
{s: `"1s"`, want: 1 * time.Second},
|
||||
{s: `"2s"`, want: 2 * time.Second},
|
||||
{s: `"90s"`, want: 90 * time.Second},
|
||||
{s: `"1m30s"`, want: 90 * time.Second},
|
||||
{s: `"1m0s"`, want: 1 * time.Minute},
|
||||
{s: `"2m0s"`, want: 2 * time.Minute},
|
||||
{s: `"90m"`, want: 90 * time.Minute},
|
||||
{s: `"1h30m"`, want: 90 * time.Minute},
|
||||
{s: `"1h30m0s"`, want: 90 * time.Minute},
|
||||
{s: `"1h0m0s"`, want: 1 * time.Hour},
|
||||
{s: `"2h0m0s"`, want: 2 * time.Hour},
|
||||
{s: `"36h0m0s"`, want: 36 * time.Hour},
|
||||
{s: "'1ns'", want: 1 * time.Nanosecond},
|
||||
{s: "'2ns'", want: 2 * time.Nanosecond},
|
||||
{s: "'1µs'", want: 1 * time.Microsecond},
|
||||
{s: "'2µs'", want: 2 * time.Microsecond},
|
||||
{s: "'1ms'", want: 1 * time.Millisecond},
|
||||
{s: "'2ms'", want: 2 * time.Millisecond},
|
||||
{s: "'1s'", want: 1 * time.Second},
|
||||
{s: "'2s'", want: 2 * time.Second},
|
||||
{s: "'90s'", want: 90 * time.Second},
|
||||
{s: "'1m30s'", want: 90 * time.Second},
|
||||
{s: "'1m0s'", want: 1 * time.Minute},
|
||||
{s: "'2m0s'", want: 2 * time.Minute},
|
||||
{s: "'90m'", want: 90 * time.Minute},
|
||||
{s: "'1h30m'", want: 90 * time.Minute},
|
||||
{s: "'1h30m0s'", want: 90 * time.Minute},
|
||||
{s: "'1h0m0s'", want: 1 * time.Hour},
|
||||
{s: "'2h0m0s'", want: 2 * time.Hour},
|
||||
{s: "'36h0m0s'", want: 36 * time.Hour},
|
||||
{s: "1ns", want: 1 * time.Nanosecond},
|
||||
{s: "2ns", want: 2 * time.Nanosecond},
|
||||
{s: "1µs", want: 1 * time.Microsecond},
|
||||
{s: "2µs", want: 2 * time.Microsecond},
|
||||
{s: "1ms", want: 1 * time.Millisecond},
|
||||
{s: "2ms", want: 2 * time.Millisecond},
|
||||
{s: "1s", want: 1 * time.Second},
|
||||
{s: "2s", want: 2 * time.Second},
|
||||
{s: "90s", want: 90 * time.Second},
|
||||
{s: "1m30s", want: 90 * time.Second},
|
||||
{s: "1m0s", want: 1 * time.Minute},
|
||||
{s: "2m0s", want: 2 * time.Minute},
|
||||
{s: "90m", want: 90 * time.Minute},
|
||||
{s: "1h30m", want: 90 * time.Minute},
|
||||
{s: "1h30m0s", want: 90 * time.Minute},
|
||||
{s: "1h0m0s", want: 1 * time.Hour},
|
||||
{s: "2h0m0s", want: 2 * time.Hour},
|
||||
{s: "36h0m0s", want: 36 * time.Hour},
|
||||
{name: "empty", s: "", want: 0},
|
||||
{s: "nil", wantErr: "time: invalid duration \"nil\""},
|
||||
{s: "foo", wantErr: "time: invalid duration \"foo\""},
|
||||
{s: "null", want: 0},
|
||||
{s: "0.000000001", want: 1 * time.Nanosecond},
|
||||
{s: "0.000001", want: 1 * time.Microsecond},
|
||||
{s: "0.001", want: 1 * time.Millisecond},
|
||||
{s: "0.1", want: 100 * time.Millisecond},
|
||||
{s: "1", want: 1 * time.Second},
|
||||
{s: "1.0", want: 1 * time.Second},
|
||||
{s: "2.0", want: 2 * time.Second},
|
||||
{s: "90", want: 90 * time.Second},
|
||||
{s: "90.001", want: (90 * time.Second) + (1 * time.Millisecond)},
|
||||
{s: "90.999", want: (90 * time.Second) + (999 * time.Millisecond)},
|
||||
{s: "yes", wantErr: "time: invalid duration \"yes\""},
|
||||
{s: "no", wantErr: "time: invalid duration \"no\""},
|
||||
{s: "true", wantErr: "yaml: unmarshal errors:\n invalid duration"},
|
||||
{s: "false", wantErr: "yaml: unmarshal errors:\n invalid duration"},
|
||||
{
|
||||
s: "[foo, bar]",
|
||||
wantErr: "yaml: unmarshal errors:\n invalid duration",
|
||||
},
|
||||
{
|
||||
s: "{foo: bar}",
|
||||
wantErr: "yaml: unmarshal errors:\n invalid duration",
|
||||
},
|
||||
{
|
||||
s: "2001-12-15T02:59:43.1Z",
|
||||
wantErr: "yaml: unmarshal errors:\n invalid duration",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := tt.name
|
||||
if name == "" {
|
||||
name = tt.s
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var d Duration
|
||||
|
||||
err := yaml.Unmarshal([]byte(tt.s), &d)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, time.Duration(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
13
dur/go.mod
Normal file
13
dur/go.mod
Normal file
@@ -0,0 +1,13 @@
|
||||
module github.com/jimeh/go-tyme/dur
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
||||
17
dur/go.sum
Normal file
17
dur/go.sum
Normal file
@@ -0,0 +1,17 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
33
dur/parse.go
Normal file
33
dur/parse.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package dur
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const floatSecond = float64(time.Second)
|
||||
|
||||
// Parse parses given interface to a Duration.
|
||||
//
|
||||
// If the interface is a string, it will be parsed using time.Parse. If
|
||||
// the interface is a int or float64, it will be parsed as a number of seconds.
|
||||
func Parse(x interface{}) (Duration, error) {
|
||||
var d Duration
|
||||
switch value := x.(type) {
|
||||
case string:
|
||||
td, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
d = Duration(td)
|
||||
case float64:
|
||||
d = Duration(time.Duration(value * floatSecond))
|
||||
case int:
|
||||
d = Duration(time.Duration(value) * time.Second)
|
||||
default:
|
||||
return 0, fmt.Errorf("time: invalid duration %+v", x)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
74
dur/parse_test.go
Normal file
74
dur/parse_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package dur
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
x interface{}
|
||||
want time.Duration
|
||||
wantErr string
|
||||
}{
|
||||
{x: "1ns", want: 1 * time.Nanosecond},
|
||||
{x: "2ns", want: 2 * time.Nanosecond},
|
||||
{x: "1µs", want: 1 * time.Microsecond},
|
||||
{x: "2µs", want: 2 * time.Microsecond},
|
||||
{x: "1ms", want: 1 * time.Millisecond},
|
||||
{x: "2ms", want: 2 * time.Millisecond},
|
||||
{x: "1s", want: 1 * time.Second},
|
||||
{x: "2s", want: 2 * time.Second},
|
||||
{x: "90s", want: 90 * time.Second},
|
||||
{x: "1m30s", want: 90 * time.Second},
|
||||
{x: "1m0s", want: 1 * time.Minute},
|
||||
{x: "2m0s", want: 2 * time.Minute},
|
||||
{x: "90m", want: 90 * time.Minute},
|
||||
{x: "1h30m", want: 90 * time.Minute},
|
||||
{x: "1h30m0s", want: 90 * time.Minute},
|
||||
{x: "1h0m0s", want: 1 * time.Hour},
|
||||
{x: "2h0m0s", want: 2 * time.Hour},
|
||||
{x: "36h0m0s", want: 36 * time.Hour},
|
||||
{x: 0.000000001, want: 1 * time.Nanosecond},
|
||||
{x: 0.000001, want: 1 * time.Microsecond},
|
||||
{x: 0.001, want: 1 * time.Millisecond},
|
||||
{x: 0.1, want: 100 * time.Millisecond},
|
||||
{x: 1, want: 1 * time.Second},
|
||||
{x: 1.0, want: 1 * time.Second},
|
||||
{x: 2.0, want: 2 * time.Second},
|
||||
{x: 90, want: 90 * time.Second},
|
||||
{x: 90.001, want: (90 * time.Second) + (1 * time.Millisecond)},
|
||||
{x: 90.999, want: (90 * time.Second) + (999 * time.Millisecond)},
|
||||
{name: "nil", x: nil, wantErr: "time: invalid duration <nil>"},
|
||||
{name: "empty string", x: "", wantErr: "time: invalid duration \"\""},
|
||||
{x: "'2ms'", wantErr: "time: invalid duration \"'2ms'\""},
|
||||
{x: "nil", wantErr: "time: invalid duration \"nil\""},
|
||||
{x: "foo", wantErr: "time: invalid duration \"foo\""},
|
||||
{x: "\"foo\"", wantErr: "time: invalid duration \"\\\"foo\\\"\""},
|
||||
{x: "null", wantErr: "time: invalid duration \"null\""},
|
||||
{x: "\"null\"", wantErr: "time: invalid duration \"\\\"null\\\"\""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := tt.name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("%#v", tt.x)
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := Parse(tt.x)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, time.Duration(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
4
go.work.sum
Normal file
4
go.work.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4 h1:8qmTC5ByIXO3GP/IzBkxcZ/99VITvnIETDhdFz/om7A=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
Reference in New Issue
Block a user