Skip to content

Commit

Permalink
Add timeutil package (#186)
Browse files Browse the repository at this point in the history
Co-authored-by: nicolaasuni-vonage <[email protected]>
  • Loading branch information
AntonioMA and nicolaasuni-vonage authored Aug 1, 2023
1 parent 4189fd4 commit 6d3f399
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 8 deletions.
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

0 comments on commit 6d3f399

Please sign in to comment.