Skip to content

Commit

Permalink
FloatMS (#17)
Browse files Browse the repository at this point in the history
implements #15
  • Loading branch information
vtopc authored Jan 27, 2024
1 parent 17708a6 commit 15484cf
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 28 deletions.
11 changes: 1 addition & 10 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,10 @@ jobs:
- uses: actions/checkout@v3

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download deps
run: go mod download

Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,18 @@ Same as epoch.Milliseconds, but for strings, e.g.:
{"timestamp":"1136239445999"}
```

## Additional terms of use for users from Russia and Belarus
## FloatMS
Integer part of timestamp represents seconds and fractional - milliseconds since the Epoch(Unix time),
e.g.:
```json
{"timestamp":1136239445.999}
```

## Additional terms of use for users from russia and Belarus

By using the code provided in these repositories you agree with the following:
* Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine).
* Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee.
* russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine).
* russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee.
* [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!)

Glory to Ukraine! 🇺🇦
Expand Down
14 changes: 13 additions & 1 deletion epoch.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package epoch

import "strconv"
import (
"strconv"
"time"
)

const (
msPerS = int64(time.Second / time.Millisecond)
nsPerMs = int64(time.Millisecond)
)

func parseInt64(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
}

func parseFloat64(s string) (float64, error) {
return strconv.ParseFloat(s, 64)
}
7 changes: 7 additions & 0 deletions epoch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package epoch

import (
"time"
)

var tmsTime = time.Unix(1136239445, 999*nsPerMs)
43 changes: 43 additions & 0 deletions float_ms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package epoch

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

// FloatMS - integer part of timestamp represents seconds and fractional - milliseconds since
// the Epoch(Unix time), e.g.
// 1136239445.999
//
// Inherits built-in time.Time type, thus has all it methods, but has custom serializer and
// deserializer(converts float timestamp into built-in time.Time and vice versa).
type FloatMS struct {
time.Time
}

// NewFloatMS - returns FloatMS
func NewFloatMS(t time.Time) FloatMS {
return FloatMS{Time: t}
}

// MarshalJSON - implements JSON marshaling interface
func (s FloatMS) MarshalJSON() ([]byte, error) {
milli := s.Time.UnixMilli()
f := float64(milli) / float64(msPerS)

return json.Marshal(f)
}

func (s *FloatMS) UnmarshalJSON(data []byte) error {
f, err := parseFloat64(string(data))
if err != nil {
return fmt.Errorf("failed to parse epoch.FloatMS: %w", err)
}

i, frac := math.Modf(f)
s.Time = time.Unix(int64(i), int64(frac*1_000)*nsPerMs)

return nil
}
147 changes: 147 additions & 0 deletions float_ms_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package epoch

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

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

type testFloatMSValueStruct struct {
Timestamp FloatMS `json:"timestamp"`
}

type testFloatMSPointerStruct struct {
Timestamp *FloatMS `json:"timestamp"`
}

func TestFloatMS_Marshal(t *testing.T) {
const js = `{"timestamp":1136239445.999}`

t.Run("value", func(t *testing.T) {
tests := map[string]struct {
v testFloatMSValueStruct
want string
wantErr error
}{
"positive": {
v: testFloatMSValueStruct{
Timestamp: FloatMS{Time: tmsTime},
},
want: js,
},
"rounding": {
v: testFloatMSValueStruct{
Timestamp: FloatMS{Time: time.Unix(1136239445, 500999000)},
},
want: `{"timestamp":1136239445.5}`,
},
}

for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
got, err := json.Marshal(tc.v)
require.NoError(t, err)
assert.Equal(t, tc.want, string(got))
})
}
})

t.Run("pointer", func(t *testing.T) {
tests := map[string]struct {
v testFloatMSPointerStruct
want string
wantErr error
}{
"positive": {
v: testFloatMSPointerStruct{
Timestamp: &FloatMS{Time: tmsTime},
},
want: js,
},
"nil": {
v: testFloatMSPointerStruct{
Timestamp: nil,
},
want: `{"timestamp":null}`,
},
}

for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
got, err := json.Marshal(tc.v)
require.NoError(t, err)
assert.Equal(t, tc.want, string(got))
})
}
})
}

func TestFloatMS_Unmarshal(t *testing.T) {
const js = `{"timestamp":1136239445.999}`

t.Run("value", func(t *testing.T) {
tests := map[string]struct {
v string
want FloatMS
wantErr error
}{
"positive": {
v: js,
want: FloatMS{Time: tmsTime},
},
"not_int": {
v: `{"timestamp":"text"}`,
wantErr: errors.New("failed to parse epoch.FloatMS: strconv.ParseFloat: parsing \"\\\"text\\\"\": invalid syntax"),
},
}

for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
var got testFloatMSValueStruct
err := json.Unmarshal([]byte(tc.v), &got)
if tc.wantErr == nil {
require.NoError(t, err)
assert.Equal(t, tc.want, got.Timestamp)

return
}

require.EqualError(t, err, tc.wantErr.Error())
})
}
})

t.Run("pointer", func(t *testing.T) {
tests := map[string]struct {
v string
want *FloatMS
wantErr error
}{
"positive": {
v: js,
want: &FloatMS{Time: tmsTime},
},
"nil": {
v: `{"timestamp":null}`,
want: nil,
},
}

for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
var got testFloatMSPointerStruct
err := json.Unmarshal([]byte(tc.v), &got)
require.NoError(t, err)
assert.Equal(t, tc.want, got.Timestamp)
})
}
})
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module github.com/vtopc/epoch

go 1.17

require github.com/stretchr/testify v1.6.1
require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
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=
5 changes: 0 additions & 5 deletions milliseconds.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ func (m *Milliseconds) UnmarshalJSON(data []byte) error {
return nil
}

const (
msPerS = int64(time.Second / time.Millisecond)
nsPerMs = int64(time.Millisecond)
)

func msToTime(ms int64) time.Time {
s := ms / msPerS
ns := (ms % msPerS) * nsPerMs
Expand Down
2 changes: 0 additions & 2 deletions milliseconds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ type testMillisecondsPointerStruct struct {

const tms = int64(1136239445999)

var tmsTime = time.Unix(1136239445, 999*nsPerMs)

func TestNewMilliseconds(t *testing.T) {
const ns = 123 * nsPerMs
t.Run("seconds", func(t *testing.T) {
Expand Down

0 comments on commit 15484cf

Please sign in to comment.