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

FloatMS #17

Merged
merged 5 commits into from
Jan 27, 2024
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
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}

Check warning on line 22 in float_ms.go

View check run for this annotation

Codecov / codecov/patch

float_ms.go#L22

Added line #L22 was not covered by tests
}

// 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
Loading