Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeutil package #186

Merged
merged 1 commit into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.75.9
1.76.0
4 changes: 2 additions & 2 deletions examples/service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.20
replace github.com/Vonage/gosrvlib => ../..

require (
github.com/Vonage/gosrvlib v1.75.9
github.com/Vonage/gosrvlib v1.76.0
github.com/golang/mock v1.6.0
github.com/jstemmer/go-junit-report v1.0.0
github.com/prometheus/client_golang v1.16.0
Expand Down Expand Up @@ -86,7 +86,7 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions examples/service/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ require (
go.etcd.io/etcd/client/v3 v3.5.9 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -916,8 +916,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
Expand Down
49 changes: 49 additions & 0 deletions pkg/timeutil/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package timeutil

import (
"encoding/json"
"fmt"
"time"
)

// Duration is an alias for the standard time.Duration.
type Duration time.Duration

// String returns a string representing the duration in the form "72h3m0.5s".
// It is a wrapper for time.Duration.String().
func (d Duration) String() string {
return time.Duration(d).String()
}

// MarshalJSON returns d as the JSON encoding of d.
// It encodes the time.Duration in human readable format (e.g.: 20s, 1h, ...).
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String()) //nolint:wrapcheck
}

// UnmarshalJSON sets *d to a copy of data.
// It converts human readable time duration format (e.g.: 20s, 1h, ...) in standard time.Duration.
func (d *Duration) UnmarshalJSON(data []byte) error {
var v any

if err := json.Unmarshal(data, &v); err != nil {
return err //nolint:wrapcheck
}

switch value := v.(type) {
case float64:
*d = Duration(value)
return nil
case string:
aux, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("unable to parse the time duration %s :%w", value, err)
}

*d = Duration(aux)

return nil
default:
return fmt.Errorf("invalid time duration type: %v", value)
}
}
241 changes: 241 additions & 0 deletions pkg/timeutil/duration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package timeutil

import (
"encoding/json"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestDuration_MarshalJSON(t *testing.T) {
t.Parallel()

tests := []struct {
name string
dur Duration
want []byte
}{
{
name: "seconds",
dur: Duration(13 * time.Second),
want: []byte(`"13s"`),
},
{
name: "minutes",
dur: Duration(17 * time.Minute),
want: []byte(`"17m0s"`),
},
{
name: "hours",
dur: Duration(7*time.Hour + 11*time.Minute + 13*time.Second),
want: []byte(`"7h11m13s"`),
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := tt.dur.MarshalJSON()
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}

func Test_json_Marshal(t *testing.T) {
t.Parallel()

type testData struct {
Time Duration
}

tests := []struct {
name string
data testData
want []byte
}{
{
name: "seconds",
data: testData{Time: Duration(13 * time.Second)},
want: []byte(`{"Time":"13s"}`),
},
{
name: "minutes",
data: testData{Time: Duration(17 * time.Minute)},
want: []byte(`{"Time":"17m0s"}`),
},
{
name: "hours",
data: testData{Time: Duration(7*time.Hour + 11*time.Minute + 13*time.Second)},
want: []byte(`{"Time":"7h11m13s"}`),
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := json.Marshal(tt.data)
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}

func TestDuration_UnmarshalJSON(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data []byte
want Duration
wantErr bool
}{
{
name: "seconds",
data: []byte(`"13s"`),
want: Duration(13 * time.Second),
},
{
name: "minutes",
data: []byte(`"17m0s"`),
want: Duration(17 * time.Minute),
},
{
name: "hours",
data: []byte(`"73h0m0s"`),
want: Duration(73 * time.Hour),
},
{
name: "number",
data: []byte(`123456789`),
want: Duration(123456789),
},
{
name: "zero number",
data: []byte(`0`),
want: Duration(0),
},
{
name: "negative number",
data: []byte(`-17`),
want: Duration(-17),
},
{
name: "empty",
data: []byte(``),
wantErr: true,
},
{
name: "empty string",
data: []byte(`""`),
wantErr: true,
},
{
name: "invalid string",
data: []byte(`"-"`),
wantErr: true,
},
{
name: "invalid type",
data: []byte(`{"a":"b"}`),
wantErr: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var dur Duration

err := dur.UnmarshalJSON(tt.data)
require.Equal(t, tt.wantErr, err != nil, "error = %v, wantErr %v", err, tt.wantErr)
require.Equal(t, int64(tt.want), int64(dur))
})
}
}

func Test_json_Unmarshal(t *testing.T) {
t.Parallel()

type testData struct {
Time Duration
}

tests := []struct {
name string
data []byte
want Duration
wantErr bool
}{
{
name: "seconds",
data: []byte(`{"Time":"13s"}`),
want: Duration(13 * time.Second),
},
{
name: "minutes",
data: []byte(`{"Time":"17m0s"}`),
want: Duration(17 * time.Minute),
},
{
name: "hours",
data: []byte(`{"Time":"7h11m13s"}`),
want: Duration(7*time.Hour + 11*time.Minute + 13*time.Second),
},
{
name: "number",
data: []byte(`{"Time":123456789}`),
want: Duration(123456789),
},
{
name: "zero number",
data: []byte(`{"Time":0}`),
want: Duration(0),
},
{
name: "negative number",
data: []byte(`{"Time":-17}`),
want: Duration(-17),
},
{
name: "null",
data: []byte(`{"Time":null}`),
wantErr: true,
},
{
name: "empty string",
data: []byte(`{"Time":""}`),
wantErr: true,
},
{
name: "invalid string",
data: []byte(`{"Time":"-"}`),
wantErr: true,
},
{
name: "invalid type",
data: []byte(`{"Time":{"a":"b"}}`),
wantErr: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var d testData

err := json.Unmarshal(tt.data, &d)
require.Equal(t, tt.wantErr, err != nil, "error = %v, wantErr %v", err, tt.wantErr)
require.Equal(t, int64(tt.want), int64(d.Time))
})
}
}
50 changes: 50 additions & 0 deletions pkg/timeutil/example_duration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package timeutil_test

import (
"encoding/json"
"fmt"
"log"
"time"

"github.com/Vonage/gosrvlib/pkg/timeutil"
)

func ExampleDuration_MarshalJSON() {
type testData struct {
Time timeutil.Duration
}

data := testData{
Time: timeutil.Duration(7*time.Hour + 11*time.Minute + 13*time.Second),
}

enc, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}

fmt.Println(string(enc))

// Output:
// {"Time":"7h11m13s"}
}

func ExampleDuration_UnmarshalJSON() {
type testData struct {
Time timeutil.Duration
}

enc := []byte(`{"Time":"7h11m13s"}`)

var data testData

err := json.Unmarshal(enc, &data)
if err != nil {
log.Fatal(err)
}

fmt.Println(data.Time.String())

// Output:
// 7h11m13s
}
2 changes: 2 additions & 0 deletions pkg/timeutil/timeutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package timeutil adds utility functions to the standard time library.
package timeutil