mirror of
https://github.com/jimeh/go-tyme.git
synced 2026-02-19 01:46:41 +00:00
feat(tyme/ts): add ts package
This commit is contained in:
2
ts/.gitignore
vendored
Normal file
2
ts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bin/*
|
||||||
|
coverage.*
|
||||||
94
ts/.golangci.yml
Normal file
94
ts/.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
ts/Makefile
Normal file
191
ts/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))
|
||||||
14
ts/go.mod
Normal file
14
ts/go.mod
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module github.com/jimeh/go-tyme/ts
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jimeh/go-tyme/dur v0.0.0-20221030033507-5d31aa674303
|
||||||
|
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
|
||||||
|
)
|
||||||
19
ts/go.sum
Normal file
19
ts/go.sum
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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/jimeh/go-tyme/dur v0.0.0-20221030033507-5d31aa674303 h1:nTg0rfEObislvl5SOmEyjE2iV/BTNCG2ts36cjXMNfk=
|
||||||
|
github.com/jimeh/go-tyme/dur v0.0.0-20221030033507-5d31aa674303/go.mod h1:9zwXRzQlr7JTL5wUVkdCnZY0NR08ZtFMaehZNpZNV+w=
|
||||||
|
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=
|
||||||
248
ts/marshal_test.go
Normal file
248
ts/marshal_test.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var (
|
||||||
|
marshalUnmarshalTestCases = []struct {
|
||||||
|
name string
|
||||||
|
t time.Time
|
||||||
|
second string
|
||||||
|
millisecond string
|
||||||
|
microsecond string
|
||||||
|
nanosecond string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "UTC-8",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 6, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC-8", -8*60*60),
|
||||||
|
),
|
||||||
|
second: "1667054434",
|
||||||
|
millisecond: "1667054434934",
|
||||||
|
microsecond: "1667054434934349",
|
||||||
|
nanosecond: "1667054434934349003",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTC-2",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 12, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC-2", -2*60*60),
|
||||||
|
),
|
||||||
|
second: "1667054434",
|
||||||
|
millisecond: "1667054434934",
|
||||||
|
microsecond: "1667054434934349",
|
||||||
|
nanosecond: "1667054434934349003",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTC",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 14, 40, 34, 934349003, time.UTC,
|
||||||
|
),
|
||||||
|
second: "1667054434",
|
||||||
|
millisecond: "1667054434934",
|
||||||
|
microsecond: "1667054434934349",
|
||||||
|
nanosecond: "1667054434934349003",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTC+2",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 16, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC+2", 2*60*60),
|
||||||
|
),
|
||||||
|
second: "1667054434",
|
||||||
|
millisecond: "1667054434934",
|
||||||
|
microsecond: "1667054434934349",
|
||||||
|
nanosecond: "1667054434934349003",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTC+8",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 22, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC+8", 8*60*60),
|
||||||
|
),
|
||||||
|
second: "1667054434",
|
||||||
|
millisecond: "1667054434934",
|
||||||
|
microsecond: "1667054434934349",
|
||||||
|
nanosecond: "1667054434934349003",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "epoch",
|
||||||
|
t: time.Date(
|
||||||
|
1970, 1, 1, 0, 0, 0, 0, time.UTC,
|
||||||
|
),
|
||||||
|
second: "0",
|
||||||
|
millisecond: "0",
|
||||||
|
microsecond: "0",
|
||||||
|
nanosecond: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min second",
|
||||||
|
t: time.Date(292277026596, 12, 4, 15, 30, 8, 0, time.UTC),
|
||||||
|
second: "-9223372036854775808",
|
||||||
|
millisecond: "0",
|
||||||
|
microsecond: "0",
|
||||||
|
nanosecond: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max second",
|
||||||
|
t: time.Date(292277026596, 12, 4, 15, 30, 7, 0, time.UTC),
|
||||||
|
second: "9223372036854775807",
|
||||||
|
millisecond: "-1000",
|
||||||
|
microsecond: "-1000000",
|
||||||
|
nanosecond: "-1000000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min millisecond",
|
||||||
|
t: time.Date(
|
||||||
|
-292275055, 5, 16, 16, 47, 4, 192000000, time.UTC,
|
||||||
|
),
|
||||||
|
second: "-9223372036854776",
|
||||||
|
millisecond: "-9223372036854775808",
|
||||||
|
microsecond: "0",
|
||||||
|
nanosecond: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max millisecond",
|
||||||
|
t: time.Date(
|
||||||
|
292278994, 8, 17, 7, 12, 55, 807000000, time.UTC,
|
||||||
|
),
|
||||||
|
second: "9223372036854775",
|
||||||
|
millisecond: "9223372036854775807",
|
||||||
|
microsecond: "-1000",
|
||||||
|
nanosecond: "-1000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min microseconds",
|
||||||
|
t: time.Date(
|
||||||
|
-290308, 12, 21, 19, 59, 5, 224192000, time.UTC,
|
||||||
|
),
|
||||||
|
second: "-9223372036855",
|
||||||
|
millisecond: "-9223372036854776",
|
||||||
|
microsecond: "-9223372036854775808",
|
||||||
|
nanosecond: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max microseconds",
|
||||||
|
t: time.Date(
|
||||||
|
294247, 1, 10, 4, 0, 54, 775807000, time.UTC,
|
||||||
|
),
|
||||||
|
second: "9223372036854",
|
||||||
|
millisecond: "9223372036854775",
|
||||||
|
microsecond: "9223372036854775807",
|
||||||
|
nanosecond: "-1000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "min nanoseconds",
|
||||||
|
t: time.Date(1677, 9, 21, 0, 12, 43, 145224192, time.UTC),
|
||||||
|
second: "-9223372037",
|
||||||
|
millisecond: "-9223372036855",
|
||||||
|
microsecond: "-9223372036854776",
|
||||||
|
nanosecond: "-9223372036854775808",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max nanoseconds",
|
||||||
|
t: time.Date(
|
||||||
|
2262, 4, 11, 23, 47, 16, 854775807, time.UTC,
|
||||||
|
),
|
||||||
|
second: "9223372036",
|
||||||
|
millisecond: "9223372036854",
|
||||||
|
microsecond: "9223372036854775",
|
||||||
|
nanosecond: "9223372036854775807",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "year 1092",
|
||||||
|
t: time.Date(1092, 3, 23, 3, 52, 8, 734829384, time.UTC),
|
||||||
|
second: "-27699912472",
|
||||||
|
millisecond: "-27699912471266",
|
||||||
|
microsecond: "-27699912471265171",
|
||||||
|
nanosecond: "9193575676153932616",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "year -1000",
|
||||||
|
t: time.Date(
|
||||||
|
-1000, 10, 29, 22, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC+8", 8*60*60),
|
||||||
|
),
|
||||||
|
second: "-93698068766",
|
||||||
|
millisecond: "-93698068765066",
|
||||||
|
microsecond: "-93698068765065651",
|
||||||
|
nanosecond: "-1464348396517892917",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "year 10449",
|
||||||
|
t: time.Date(
|
||||||
|
10449, 10, 29, 22, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC+8", 8*60*60),
|
||||||
|
),
|
||||||
|
second: "267597528034",
|
||||||
|
millisecond: "267597528034934",
|
||||||
|
microsecond: "267597528034934349",
|
||||||
|
nanosecond: "-9103633070708925237",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "year 1044938",
|
||||||
|
t: time.Date(
|
||||||
|
1044938, 10, 29, 22, 40, 34, 934349003,
|
||||||
|
time.FixedZone("UTC+8", 8*60*60),
|
||||||
|
),
|
||||||
|
second: "32912917195234",
|
||||||
|
millisecond: "32912917195234934",
|
||||||
|
microsecond: "-3980570952184168883",
|
||||||
|
nanosecond: "3925767737094266059",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unmarshalTestCases = []struct {
|
||||||
|
name string
|
||||||
|
second string
|
||||||
|
millisecond string
|
||||||
|
microsecond string
|
||||||
|
nanosecond string
|
||||||
|
t time.Time
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
second: `"2019-01-01T00:00:00Z"`,
|
||||||
|
millisecond: `"2019-01-01T00:00:00Z"`,
|
||||||
|
microsecond: `"2019-01-01T00:00:00Z"`,
|
||||||
|
nanosecond: `"2019-01-01T00:00:00Z"`,
|
||||||
|
wantErr: "invalid numeric timestamp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array",
|
||||||
|
second: `[1, "true", false]`,
|
||||||
|
millisecond: `[1, "true", false]`,
|
||||||
|
microsecond: `[1, "true", false]`,
|
||||||
|
nanosecond: `[1, "true", false]`,
|
||||||
|
wantErr: "invalid numeric timestamp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "object",
|
||||||
|
second: `{"object": "Object"}`,
|
||||||
|
millisecond: `{"object": "Object"}`,
|
||||||
|
microsecond: `{"object": "Object"}`,
|
||||||
|
nanosecond: `{"object": "Object"}`,
|
||||||
|
wantErr: "invalid numeric timestamp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace",
|
||||||
|
second: " 1667054434 ",
|
||||||
|
millisecond: " 1667054434934 ",
|
||||||
|
microsecond: " 1667054434934349 ",
|
||||||
|
nanosecond: " 1667054434934349003 ",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 14, 40, 34, 934349003, time.UTC,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float",
|
||||||
|
second: "1667054434.123456789",
|
||||||
|
millisecond: "1667054434934.123456",
|
||||||
|
microsecond: "1667054434934349.123",
|
||||||
|
nanosecond: "",
|
||||||
|
t: time.Date(
|
||||||
|
2022, 10, 29, 14, 40, 34, 934349003, time.UTC,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
88
ts/microsecond.go
Normal file
88
ts/microsecond.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Microsecond is a wrapper around time.Time for marshaling to/from JSON/YAML as
|
||||||
|
// microsecond-based numeric Unix timestamps.
|
||||||
|
//
|
||||||
|
// It marshals to a JSON/YAML number representing the number of microseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
//
|
||||||
|
// It unmarshals from a JSON/YAML number representing the number of microseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
type Microsecond time.Time
|
||||||
|
|
||||||
|
// Time returns the time.Time corresponding to the microsecond instant s.
|
||||||
|
func (ms Microsecond) Time() time.Time {
|
||||||
|
return time.Time(ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local returns the local time corresponding to the microsecond instant s.
|
||||||
|
func (ms Microsecond) Local() Microsecond {
|
||||||
|
return Microsecond(time.Time(ms).Local())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString implements the fmt.GoStringer interface.
|
||||||
|
func (ms Microsecond) GoString() string {
|
||||||
|
return time.Time(ms).GoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDST reports whether the microsecond instant s occurs within Daylight Saving
|
||||||
|
// Time.
|
||||||
|
func (ms Microsecond) IsDST() bool {
|
||||||
|
return time.Time(ms).IsDST()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the Microsecond is the zero value.
|
||||||
|
func (ms Microsecond) IsZero() bool {
|
||||||
|
return time.Time(ms).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String calls time.Time.String.
|
||||||
|
func (ms Microsecond) String() string {
|
||||||
|
return time.Time(ms).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTC returns a copy of the Microsecond with the location set to UTC.
|
||||||
|
func (ms Microsecond) UTC() Microsecond {
|
||||||
|
return Microsecond(time.Time(ms).UTC())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (ms Microsecond) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(time.Time(ms).UnixMicro(), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (ms *Microsecond) UnmarshalJSON(data []byte) error {
|
||||||
|
i, err := unmarshalBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ms = UnixMicro(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the yaml.Marshaler interface.
|
||||||
|
func (ms Microsecond) MarshalYAML() (interface{}, error) {
|
||||||
|
return time.Time(ms).UnixMicro(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
|
func (ms *Microsecond) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
i, err := unmarshalYAMLNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ms = UnixMicro(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
168
ts/microsecond_test.go
Normal file
168
ts/microsecond_test.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
minMicro = time.UnixMicro(-9223372036854775808).UTC()
|
||||||
|
maxMicro = time.UnixMicro(9223372036854775807).UTC()
|
||||||
|
)
|
||||||
|
|
||||||
|
func microsecondSkipTestCase(t *testing.T, ti time.Time) bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return ti.Before(minMicro) || ti.After(maxMicro)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_MarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Microsecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.microsecond, string(b))
|
||||||
|
|
||||||
|
if microsecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Microsecond
|
||||||
|
err = json.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_MarshalUnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Microsecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.microsecond+"\n", string(b))
|
||||||
|
|
||||||
|
if microsecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Microsecond
|
||||||
|
err = yaml.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_MarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Microsecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.microsecond, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_MarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Microsecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.microsecond+"\n", string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_UnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if microsecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Microsecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.microsecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Microsecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.microsecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMicrosecond_UnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if microsecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Microsecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.microsecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Microsecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.microsecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ts/millisecond.go
Normal file
88
ts/millisecond.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Millisecond is a wrapper around time.Time for marshaling to/from JSON/YAML as
|
||||||
|
// millisecond-based numeric Unix timestamps.
|
||||||
|
//
|
||||||
|
// It marshals to a JSON/YAML number representing the number of milliseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
//
|
||||||
|
// It unmarshals from a JSON/YAML number representing the number of milliseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
type Millisecond time.Time
|
||||||
|
|
||||||
|
// Time returns the time.Time corresponding to the millisecond instant s.
|
||||||
|
func (ms Millisecond) Time() time.Time {
|
||||||
|
return time.Time(ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local returns the local time corresponding to the millisecond instant s.
|
||||||
|
func (ms Millisecond) Local() Millisecond {
|
||||||
|
return Millisecond(time.Time(ms).Local())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString implements the fmt.GoStringer interface.
|
||||||
|
func (ms Millisecond) GoString() string {
|
||||||
|
return time.Time(ms).GoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDST reports whether the millisecond instant s occurs within Daylight Saving
|
||||||
|
// Time.
|
||||||
|
func (ms Millisecond) IsDST() bool {
|
||||||
|
return time.Time(ms).IsDST()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the Millisecond is the zero value.
|
||||||
|
func (ms Millisecond) IsZero() bool {
|
||||||
|
return time.Time(ms).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String calls time.Time.String.
|
||||||
|
func (ms Millisecond) String() string {
|
||||||
|
return time.Time(ms).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTC returns a copy of the Millisecond with the location set to UTC.
|
||||||
|
func (ms Millisecond) UTC() Millisecond {
|
||||||
|
return Millisecond(time.Time(ms).UTC())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (ms Millisecond) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(time.Time(ms).UnixMilli(), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (ms *Millisecond) UnmarshalJSON(data []byte) error {
|
||||||
|
i, err := unmarshalBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ms = UnixMilli(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the yaml.Marshaler interface.
|
||||||
|
func (ms Millisecond) MarshalYAML() (interface{}, error) {
|
||||||
|
return time.Time(ms).UnixMilli(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
|
func (ms *Millisecond) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
i, err := unmarshalYAMLNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ms = UnixMilli(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
168
ts/millisecond_test.go
Normal file
168
ts/millisecond_test.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
minMilli = time.UnixMilli(-9223372036854775808).UTC()
|
||||||
|
maxMilli = time.UnixMilli(9223372036854775807).UTC()
|
||||||
|
)
|
||||||
|
|
||||||
|
func millisecondSkipTestCase(t *testing.T, ti time.Time) bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return ti.Before(minMilli) || ti.After(maxMilli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_MarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Millisecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.millisecond, string(b))
|
||||||
|
|
||||||
|
if millisecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Millisecond
|
||||||
|
err = json.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_MarshalUnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Millisecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.millisecond+"\n", string(b))
|
||||||
|
|
||||||
|
if millisecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Millisecond
|
||||||
|
err = yaml.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_MarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Millisecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.millisecond, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_MarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Millisecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.millisecond+"\n", string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_UnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if millisecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Millisecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.millisecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Millisecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.millisecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMillisecond_UnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if millisecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Millisecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.millisecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Millisecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.millisecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
92
ts/nanosecond.go
Normal file
92
ts/nanosecond.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nanosecond is a wrapper around time.Time for marshaling to/from JSON/YAML as
|
||||||
|
// nanosecond-based numeric Unix timestamps.
|
||||||
|
//
|
||||||
|
// It marshals to a JSON/YAML number representing the number of nanoseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
//
|
||||||
|
// It unmarshals from a JSON/YAML number representing the number of nanoseconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
type Nanosecond time.Time
|
||||||
|
|
||||||
|
// Time returns the time.Time corresponding to the nanosecond instant s.
|
||||||
|
func (ns Nanosecond) Time() time.Time {
|
||||||
|
return time.Time(ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local returns the local time corresponding to the nanosecond instant s.
|
||||||
|
func (ns Nanosecond) Local() Nanosecond {
|
||||||
|
return Nanosecond(time.Time(ns).Local())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString implements the fmt.GoStringer interface.
|
||||||
|
func (ns Nanosecond) GoString() string {
|
||||||
|
return time.Time(ns).GoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDST reports whether the nanosecond instant s occurs within Daylight Saving
|
||||||
|
// Time.
|
||||||
|
func (ns Nanosecond) IsDST() bool {
|
||||||
|
return time.Time(ns).IsDST()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the Nanosecond is the zero value.
|
||||||
|
func (ns Nanosecond) IsZero() bool {
|
||||||
|
return time.Time(ns).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String calls time.Time.String.
|
||||||
|
func (ns Nanosecond) String() string {
|
||||||
|
return time.Time(ns).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTC returns a copy of the Nanosecond with the location set to UTC.
|
||||||
|
func (ns Nanosecond) UTC() Nanosecond {
|
||||||
|
return Nanosecond(time.Time(ns).UTC())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (ns Nanosecond) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(time.Time(ns).UnixNano(), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (ns *Nanosecond) UnmarshalJSON(data []byte) error {
|
||||||
|
i, err := unmarshalBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ns = UnixNano(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the yaml.Marshaler interface.
|
||||||
|
func (ns Nanosecond) MarshalYAML() (interface{}, error) {
|
||||||
|
return time.Time(ns).UnixNano(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
|
func (ns *Nanosecond) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
i, err := unmarshalYAMLNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ns = UnixNano(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unixNano(ts int64) time.Time {
|
||||||
|
return time.Unix(ts/1e9, ts%1e9)
|
||||||
|
}
|
||||||
174
ts/nanosecond_test.go
Normal file
174
ts/nanosecond_test.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
minNano = unixNano(-9223372036854775808).UTC()
|
||||||
|
maxNano = unixNano(9223372036854775807).UTC()
|
||||||
|
)
|
||||||
|
|
||||||
|
func nanosecondSkipTestCase(t *testing.T, ti time.Time) bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return ti.Before(minNano) || ti.After(maxNano)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_MarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Nanosecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.nanosecond, string(b))
|
||||||
|
|
||||||
|
if nanosecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Nanosecond
|
||||||
|
err = json.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_MarshalUnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Nanosecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.nanosecond+"\n", string(b))
|
||||||
|
|
||||||
|
if nanosecondSkipTestCase(t, tt.t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Nanosecond
|
||||||
|
err = yaml.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_MarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Nanosecond(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.nanosecond, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_MarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Nanosecond(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.nanosecond+"\n", string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_UnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if nanosecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Nanosecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.nanosecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
if tt.nanosecond == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Nanosecond
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.nanosecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanosecond_UnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
if nanosecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Nanosecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.nanosecond), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
if tt.nanosecond == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Nanosecond
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.nanosecond), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ts/second.go
Normal file
88
ts/second.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Second is a wrapper around time.Time for marshaling to/from JSON/YAML as
|
||||||
|
// second-based numeric Unix timestamps.
|
||||||
|
//
|
||||||
|
// It marshals to a JSON/YAML number representing the number of seconds since
|
||||||
|
// the Unix time epoch.
|
||||||
|
//
|
||||||
|
// It unmarshals from a JSON/YAML number representing the number of seconds
|
||||||
|
// since the Unix time epoch.
|
||||||
|
type Second time.Time
|
||||||
|
|
||||||
|
// Time returns the time.Time corresponding to the second instant s.
|
||||||
|
func (s Second) Time() time.Time {
|
||||||
|
return time.Time(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local returns the local time corresponding to the second instant s.
|
||||||
|
func (s Second) Local() Second {
|
||||||
|
return Second(time.Time(s).Local())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString implements the fmt.GoStringer interface.
|
||||||
|
func (s Second) GoString() string {
|
||||||
|
return time.Time(s).GoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDST reports whether the second instant s occurs within Daylight Saving
|
||||||
|
// Time.
|
||||||
|
func (s Second) IsDST() bool {
|
||||||
|
return time.Time(s).IsDST()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the Second is the zero value.
|
||||||
|
func (s Second) IsZero() bool {
|
||||||
|
return time.Time(s).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String calls time.Time.String.
|
||||||
|
func (s Second) String() string {
|
||||||
|
return time.Time(s).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTC returns a copy of the Second with the location set to UTC.
|
||||||
|
func (s Second) UTC() Second {
|
||||||
|
return Second(time.Time(s).UTC())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (s Second) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(time.Time(s).Unix(), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (s *Second) UnmarshalJSON(data []byte) error {
|
||||||
|
i, err := unmarshalBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = UnixSecond(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the yaml.Marshaler interface.
|
||||||
|
func (s Second) MarshalYAML() (interface{}, error) {
|
||||||
|
return time.Time(s).Unix(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
|
func (s *Second) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
i, err := unmarshalYAMLNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = UnixSecond(i)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
143
ts/second_test.go
Normal file
143
ts/second_test.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecond_MarshalUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Second(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.second, string(b))
|
||||||
|
|
||||||
|
var got Second
|
||||||
|
err = json.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond_MarshalUnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Second(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.second+"\n", string(b))
|
||||||
|
|
||||||
|
var got Second
|
||||||
|
err = yaml.Unmarshal(b, &got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
assert.Equal(t, time.Local, time.Time(got).Location())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond_MarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Second(tt.t)
|
||||||
|
|
||||||
|
b, err := json.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.second, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond_MarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts := Second(tt.t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.second+"\n", string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond_UnmarshalJSON(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Second
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.second), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Second
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.second), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecond_UnmarshalYAML(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Second
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.second), &ts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range unmarshalTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var ts Second
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(tt.second), &ts)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(ts).UTC())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
69
ts/ts.go
Normal file
69
ts/ts.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// Package ts provides wrapper types around time.Time, with support for
|
||||||
|
// JSON/YAML marshaling to/from numeric Unix timestamps of various precisions.
|
||||||
|
//
|
||||||
|
// Unmarshaling supports both numeric and string values. Marshaling always
|
||||||
|
// produces a integer value.
|
||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jimeh/go-tyme/dur"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timestamp is a type constraint that matches against time.Time, Second,
|
||||||
|
// Millisecond, Microsecond, and Nanosecond.
|
||||||
|
type Timestamp interface {
|
||||||
|
time.Time | Second | Millisecond | Microsecond | Nanosecond
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration is a type constraint that matches against time.Duration and
|
||||||
|
// dur.Duration.
|
||||||
|
type Duration interface {
|
||||||
|
time.Duration | dur.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns a new Timestamp with given Duration added to it, using
|
||||||
|
// time.Time.Add.
|
||||||
|
func Add[T Timestamp, D Duration](ts T, d D) T {
|
||||||
|
var t time.Time
|
||||||
|
t = time.Time(ts)
|
||||||
|
t = t.Add(time.Duration(d))
|
||||||
|
|
||||||
|
return T(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub returns the dur.Duration between two Timestamps, using time.Time.Sub.
|
||||||
|
func Sub[T, U Timestamp](t T, u U) dur.Duration {
|
||||||
|
return dur.Duration(time.Time(t).Sub(time.Time(u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// After reports whether the Timestamp instant t is after u, using
|
||||||
|
// time.Time.After.
|
||||||
|
func After[T, U Timestamp](t T, u U) bool {
|
||||||
|
return time.Time(t).After(time.Time(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before reports whether the Timestamp instant t is before u, using
|
||||||
|
// time.Time.Before.
|
||||||
|
func Before[T, U Timestamp](t T, u U) bool {
|
||||||
|
return time.Time(t).Before(time.Time(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether t and u represent the same Timestamp instant, using
|
||||||
|
// time.Time.Equal.
|
||||||
|
func Equal[T, U Timestamp](t T, u U) bool {
|
||||||
|
return time.Time(t).Equal(time.Time(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round returns the result of rounding t to the nearest multiple of d, using
|
||||||
|
// time.Time.Round.
|
||||||
|
func Round[T Timestamp, D Duration](t T, d D) T {
|
||||||
|
return T(time.Time(t).Round(time.Duration(d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate returns the result of trucating t down to the nearest multiple of d,
|
||||||
|
// using time.Time.Truncate.
|
||||||
|
func Truncate[T Timestamp, D Duration](t T, d D) T {
|
||||||
|
return T(time.Time(t).Truncate(time.Duration(d)))
|
||||||
|
}
|
||||||
524
ts/ts_test.go
Normal file
524
ts/ts_test.go
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jimeh/go-tyme/dur"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdd(t *testing.T) {
|
||||||
|
testAdd[time.Time, time.Duration](t)
|
||||||
|
testAdd[time.Time, dur.Duration](t)
|
||||||
|
|
||||||
|
testAdd[Second, time.Duration](t)
|
||||||
|
testAdd[Second, dur.Duration](t)
|
||||||
|
|
||||||
|
testAdd[Millisecond, time.Duration](t)
|
||||||
|
testAdd[Millisecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testAdd[Microsecond, time.Duration](t)
|
||||||
|
testAdd[Microsecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testAdd[Nanosecond, time.Duration](t)
|
||||||
|
testAdd[Nanosecond, dur.Duration](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAdd[T Timestamp, D Duration](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, D %T]", T(time.Time{}), D(0)),
|
||||||
|
func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
t T
|
||||||
|
d D
|
||||||
|
want T
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "add 1s",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Second),
|
||||||
|
want: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 3, 0, time.UTC),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove 1s",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(-time.Second),
|
||||||
|
want: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 1, 0, time.UTC),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add 250ms",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(250 * time.Millisecond),
|
||||||
|
want: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 250000000, time.UTC),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove 250ms",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(-250 * time.Millisecond),
|
||||||
|
want: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 1, 750000000, time.UTC),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Add(tt.t, tt.d)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSub(t *testing.T) {
|
||||||
|
testSub[time.Time, time.Time](t)
|
||||||
|
testSub[time.Time, Second](t)
|
||||||
|
testSub[time.Time, Millisecond](t)
|
||||||
|
testSub[time.Time, Microsecond](t)
|
||||||
|
testSub[time.Time, Nanosecond](t)
|
||||||
|
|
||||||
|
testSub[Second, time.Time](t)
|
||||||
|
testSub[Second, Second](t)
|
||||||
|
testSub[Second, Millisecond](t)
|
||||||
|
testSub[Second, Microsecond](t)
|
||||||
|
testSub[Second, Nanosecond](t)
|
||||||
|
|
||||||
|
testSub[Millisecond, time.Time](t)
|
||||||
|
testSub[Millisecond, Millisecond](t)
|
||||||
|
testSub[Millisecond, Second](t)
|
||||||
|
testSub[Millisecond, Microsecond](t)
|
||||||
|
testSub[Millisecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testSub[Microsecond, time.Time](t)
|
||||||
|
testSub[Microsecond, Second](t)
|
||||||
|
testSub[Microsecond, Millisecond](t)
|
||||||
|
testSub[Microsecond, Microsecond](t)
|
||||||
|
testSub[Microsecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testSub[Nanosecond, time.Time](t)
|
||||||
|
testSub[Nanosecond, Second](t)
|
||||||
|
testSub[Nanosecond, Millisecond](t)
|
||||||
|
testSub[Nanosecond, Microsecond](t)
|
||||||
|
testSub[Nanosecond, Nanosecond](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSub[T, U Timestamp](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, U %T]", T(time.Time{}), U(time.Time{})),
|
||||||
|
func(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
t T
|
||||||
|
u U
|
||||||
|
want time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "-1s",
|
||||||
|
t: T(now),
|
||||||
|
u: U(now.Add(time.Second)),
|
||||||
|
want: -time.Second,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1s",
|
||||||
|
t: T(now),
|
||||||
|
u: U(now.Add(-time.Second)),
|
||||||
|
want: time.Second,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "-250ms",
|
||||||
|
t: T(now),
|
||||||
|
u: U(now.Add(250 * time.Millisecond)),
|
||||||
|
want: -250 * time.Millisecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "250ms",
|
||||||
|
t: T(now),
|
||||||
|
u: U(now.Add(-250 * time.Millisecond)),
|
||||||
|
want: 250 * time.Millisecond,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Sub(tt.t, tt.u)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, time.Duration(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t1 := T(now)
|
||||||
|
u1 := U(now.Add(-time.Second))
|
||||||
|
got1 := Sub(t1, u1)
|
||||||
|
assert.Equal(t, time.Second, time.Duration(got1))
|
||||||
|
|
||||||
|
t2 := T(now)
|
||||||
|
u2 := U(now.Add(time.Second))
|
||||||
|
got2 := Sub(t2, u2)
|
||||||
|
assert.Equal(t, -time.Second, time.Duration(got2))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfter(t *testing.T) {
|
||||||
|
testAfter[time.Time, time.Time](t)
|
||||||
|
testAfter[time.Time, Second](t)
|
||||||
|
testAfter[time.Time, Millisecond](t)
|
||||||
|
testAfter[time.Time, Microsecond](t)
|
||||||
|
testAfter[time.Time, Nanosecond](t)
|
||||||
|
|
||||||
|
testAfter[Second, time.Time](t)
|
||||||
|
testAfter[Second, Second](t)
|
||||||
|
testAfter[Second, Millisecond](t)
|
||||||
|
testAfter[Second, Microsecond](t)
|
||||||
|
testAfter[Second, Nanosecond](t)
|
||||||
|
|
||||||
|
testAfter[Millisecond, time.Time](t)
|
||||||
|
testAfter[Millisecond, Millisecond](t)
|
||||||
|
testAfter[Millisecond, Second](t)
|
||||||
|
testAfter[Millisecond, Microsecond](t)
|
||||||
|
testAfter[Millisecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testAfter[Microsecond, time.Time](t)
|
||||||
|
testAfter[Microsecond, Second](t)
|
||||||
|
testAfter[Microsecond, Millisecond](t)
|
||||||
|
testAfter[Microsecond, Microsecond](t)
|
||||||
|
testAfter[Microsecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testAfter[Nanosecond, time.Time](t)
|
||||||
|
testAfter[Nanosecond, Second](t)
|
||||||
|
testAfter[Nanosecond, Millisecond](t)
|
||||||
|
testAfter[Nanosecond, Microsecond](t)
|
||||||
|
testAfter[Nanosecond, Nanosecond](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAfter[T, u Timestamp](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, U %T]", T(time.Time{}), u(time.Time{})),
|
||||||
|
func(t *testing.T) {
|
||||||
|
t1 := T(time.Now())
|
||||||
|
o1 := u(time.Now().Add(-time.Second))
|
||||||
|
got1 := After(t1, o1)
|
||||||
|
assert.True(t, got1)
|
||||||
|
|
||||||
|
t2 := T(time.Now())
|
||||||
|
o2 := u(time.Now().Add(time.Second))
|
||||||
|
got2 := After(t2, o2)
|
||||||
|
assert.False(t, got2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBefore(t *testing.T) {
|
||||||
|
testBefore[time.Time, time.Time](t)
|
||||||
|
testBefore[time.Time, Second](t)
|
||||||
|
testBefore[time.Time, Millisecond](t)
|
||||||
|
testBefore[time.Time, Microsecond](t)
|
||||||
|
testBefore[time.Time, Nanosecond](t)
|
||||||
|
|
||||||
|
testBefore[Second, time.Time](t)
|
||||||
|
testBefore[Second, Second](t)
|
||||||
|
testBefore[Second, Millisecond](t)
|
||||||
|
testBefore[Second, Microsecond](t)
|
||||||
|
testBefore[Second, Nanosecond](t)
|
||||||
|
|
||||||
|
testBefore[Millisecond, time.Time](t)
|
||||||
|
testBefore[Millisecond, Millisecond](t)
|
||||||
|
testBefore[Millisecond, Second](t)
|
||||||
|
testBefore[Millisecond, Microsecond](t)
|
||||||
|
testBefore[Millisecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testBefore[Microsecond, time.Time](t)
|
||||||
|
testBefore[Microsecond, Second](t)
|
||||||
|
testBefore[Microsecond, Millisecond](t)
|
||||||
|
testBefore[Microsecond, Microsecond](t)
|
||||||
|
testBefore[Microsecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testBefore[Nanosecond, time.Time](t)
|
||||||
|
testBefore[Nanosecond, Second](t)
|
||||||
|
testBefore[Nanosecond, Millisecond](t)
|
||||||
|
testBefore[Nanosecond, Microsecond](t)
|
||||||
|
testBefore[Nanosecond, Nanosecond](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBefore[T, U Timestamp](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, U %T]", T(time.Time{}), U(time.Time{})),
|
||||||
|
func(t *testing.T) {
|
||||||
|
t1 := T(time.Now())
|
||||||
|
o1 := U(time.Now().Add(-time.Second))
|
||||||
|
got1 := Before(t1, o1)
|
||||||
|
assert.False(t, got1)
|
||||||
|
|
||||||
|
t2 := T(time.Now())
|
||||||
|
o2 := U(time.Now().Add(time.Second))
|
||||||
|
got2 := Before(t2, o2)
|
||||||
|
assert.True(t, got2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqual(t *testing.T) {
|
||||||
|
testEqual[time.Time, time.Time](t)
|
||||||
|
testEqual[time.Time, Second](t)
|
||||||
|
testEqual[time.Time, Millisecond](t)
|
||||||
|
testEqual[time.Time, Microsecond](t)
|
||||||
|
testEqual[time.Time, Nanosecond](t)
|
||||||
|
|
||||||
|
testEqual[Second, time.Time](t)
|
||||||
|
testEqual[Second, Second](t)
|
||||||
|
testEqual[Second, Millisecond](t)
|
||||||
|
testEqual[Second, Microsecond](t)
|
||||||
|
testEqual[Second, Nanosecond](t)
|
||||||
|
|
||||||
|
testEqual[Millisecond, time.Time](t)
|
||||||
|
testEqual[Millisecond, Millisecond](t)
|
||||||
|
testEqual[Millisecond, Second](t)
|
||||||
|
testEqual[Millisecond, Microsecond](t)
|
||||||
|
testEqual[Millisecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testEqual[Microsecond, time.Time](t)
|
||||||
|
testEqual[Microsecond, Second](t)
|
||||||
|
testEqual[Microsecond, Millisecond](t)
|
||||||
|
testEqual[Microsecond, Microsecond](t)
|
||||||
|
testEqual[Microsecond, Nanosecond](t)
|
||||||
|
|
||||||
|
testEqual[Nanosecond, time.Time](t)
|
||||||
|
testEqual[Nanosecond, Second](t)
|
||||||
|
testEqual[Nanosecond, Millisecond](t)
|
||||||
|
testEqual[Nanosecond, Microsecond](t)
|
||||||
|
testEqual[Nanosecond, Nanosecond](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEqual[T, U Timestamp](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, U %T]", T(time.Time{}), U(time.Time{})),
|
||||||
|
func(t *testing.T) {
|
||||||
|
t1 := T(time.Now())
|
||||||
|
o1 := U(t1)
|
||||||
|
got1 := Equal(t1, o1)
|
||||||
|
assert.True(t, got1)
|
||||||
|
|
||||||
|
t2 := T(time.Now())
|
||||||
|
o2 := U(time.Now().Add(time.Second))
|
||||||
|
got2 := Equal(t2, o2)
|
||||||
|
assert.False(t, got2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRound(t *testing.T) {
|
||||||
|
testRound[time.Time, time.Duration](t)
|
||||||
|
testRound[time.Time, dur.Duration](t)
|
||||||
|
|
||||||
|
testRound[Second, time.Duration](t)
|
||||||
|
testRound[Second, dur.Duration](t)
|
||||||
|
|
||||||
|
testRound[Millisecond, time.Duration](t)
|
||||||
|
testRound[Millisecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testRound[Microsecond, time.Duration](t)
|
||||||
|
testRound[Microsecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testRound[Nanosecond, time.Duration](t)
|
||||||
|
testRound[Nanosecond, dur.Duration](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRound[T Timestamp, D Duration](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, D %T]", T(time.Time{}), D(0)),
|
||||||
|
func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
t T
|
||||||
|
d D
|
||||||
|
want T
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "round up to nearest hour",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 30, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Hour),
|
||||||
|
want: T(time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round down to nearest hour",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 29, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Hour),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round up to nearest minute",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 30, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Minute),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 1, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round down to nearest minute",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 29, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Minute),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round up to nearest second",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 500000000, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Second),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 3, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round down to nearest second",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499999999, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Second),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round up to nearest millisecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 500000, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Millisecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 1000000, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round down to nearest millisecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499999, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Millisecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round up to nearest microsecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 500, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Microsecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 1000, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round down to nearest microsecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Microsecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Round(tt.t, tt.d)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
testTruncate[time.Time, time.Duration](t)
|
||||||
|
testTruncate[time.Time, dur.Duration](t)
|
||||||
|
|
||||||
|
testTruncate[Second, time.Duration](t)
|
||||||
|
testTruncate[Second, dur.Duration](t)
|
||||||
|
|
||||||
|
testTruncate[Millisecond, time.Duration](t)
|
||||||
|
testTruncate[Millisecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testTruncate[Microsecond, time.Duration](t)
|
||||||
|
testTruncate[Microsecond, dur.Duration](t)
|
||||||
|
|
||||||
|
testTruncate[Nanosecond, time.Duration](t)
|
||||||
|
testTruncate[Nanosecond, dur.Duration](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTruncate[T Timestamp, D Duration](t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("[T %T, D %T]", T(time.Time{}), D(0)),
|
||||||
|
func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
t T
|
||||||
|
d D
|
||||||
|
want T
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "truncate down to nearest hour",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 29, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Hour),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncate down to nearest minute",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 29, 0, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Minute),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncate down to nearest second",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499999999, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Second),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncate down to nearest millisecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499999, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Millisecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncate down to nearest microsecond",
|
||||||
|
t: T(
|
||||||
|
time.Date(2020, 1, 1, 0, 0, 2, 499, time.UTC),
|
||||||
|
),
|
||||||
|
d: D(time.Microsecond),
|
||||||
|
want: T(time.Date(2020, 1, 1, 0, 0, 2, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Truncate(tt.t, tt.d)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
23
ts/unix.go
Normal file
23
ts/unix.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// UnixSecond parses a given int64 as a Unix timestamp with second accuracy.
|
||||||
|
func UnixSecond(ts int64) Second {
|
||||||
|
return Second(time.Unix(ts, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnixMilli parses a given int64 as a Unix timestamp with millisecond accuracy.
|
||||||
|
func UnixMilli(ts int64) Millisecond {
|
||||||
|
return Millisecond(time.UnixMilli(ts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnixMicro parses a given int64 as a Unix timestamp with microsecond accuracy.
|
||||||
|
func UnixMicro(ts int64) Microsecond {
|
||||||
|
return Microsecond(time.UnixMicro(ts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnixNano parses a given int64 as a Unix timestamp with nanosecond accuracy.
|
||||||
|
func UnixNano(ts int64) Nanosecond {
|
||||||
|
return Nanosecond(unixNano(ts))
|
||||||
|
}
|
||||||
81
ts/unix_test.go
Normal file
81
ts/unix_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnixSecond(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
i, err := strconv.ParseInt(tt.second, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := UnixSecond(i)
|
||||||
|
|
||||||
|
assert.IsType(t, Second(time.Time{}), got)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Second)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixMilli(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
i, err := strconv.ParseInt(tt.millisecond, 10, 64)
|
||||||
|
if err != nil || millisecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := UnixMilli(i)
|
||||||
|
|
||||||
|
assert.IsType(t, Millisecond(time.Time{}), got)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Millisecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixMicro(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
i, err := strconv.ParseInt(tt.microsecond, 10, 64)
|
||||||
|
if err != nil || microsecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := UnixMicro(i)
|
||||||
|
|
||||||
|
assert.IsType(t, Microsecond(time.Time{}), got)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Microsecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixNano(t *testing.T) {
|
||||||
|
for _, tt := range marshalUnmarshalTestCases {
|
||||||
|
i, err := strconv.ParseInt(tt.nanosecond, 10, 64)
|
||||||
|
if err != nil || nanosecondSkipTestCase(t, tt.t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := UnixNano(i)
|
||||||
|
|
||||||
|
assert.IsType(t, Nanosecond(time.Time{}), got)
|
||||||
|
|
||||||
|
want := tt.t.Truncate(time.Nanosecond)
|
||||||
|
assert.Equal(t, want.UTC(), time.Time(got).UTC())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
51
ts/unmarshal.go
Normal file
51
ts/unmarshal.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unmarshalBytes(data []byte) (int64, error) {
|
||||||
|
s, err := strconv.Unquote(string(data))
|
||||||
|
if err == nil {
|
||||||
|
data = []byte(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := strconv.ParseInt(string(data), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
var f float64
|
||||||
|
f, err = strconv.ParseFloat(string(data), 64)
|
||||||
|
i = int64(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid numeric timestamp: %s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalYAMLNode(node *yaml.Node) (int64, error) {
|
||||||
|
var i int64
|
||||||
|
var err error
|
||||||
|
var invalid bool
|
||||||
|
|
||||||
|
switch node.Tag {
|
||||||
|
case "!!int", "!!str":
|
||||||
|
i, err = strconv.ParseInt(node.Value, 10, 64)
|
||||||
|
case "!!float":
|
||||||
|
var f float64
|
||||||
|
f, err = strconv.ParseFloat(node.Value, 64)
|
||||||
|
i = int64(f)
|
||||||
|
default:
|
||||||
|
invalid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || invalid {
|
||||||
|
return 0, &yaml.TypeError{Errors: []string{"invalid numeric timestamp"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user