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