Skip to content
Open
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add `MarshalJSON` and `UnmarshalJSON` method to `atomic.Pointer[T]` type
allowing users to use pointer with json.
- Add `MarshalJSON` and `UnmarshalJSON` methods to `atomic.Time` so it
encodes and decodes the wrapped `time.Time` instead of an empty struct.

## [1.11.0] - 2023-05-02
### Fixed
Expand Down
20 changes: 19 additions & 1 deletion time_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@

package atomic

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

//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go

Expand All @@ -34,3 +37,18 @@ func unpackTime(v interface{}) time.Time {
}
return time.Time{}
}

// MarshalJSON encodes the wrapped time.Time into JSON.
func (x *Time) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}

// UnmarshalJSON decodes a time.Time from JSON.
func (x *Time) UnmarshalJSON(b []byte) error {
var v time.Time
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}
50 changes: 50 additions & 0 deletions time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package atomic

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

Expand Down Expand Up @@ -48,6 +49,55 @@ func TestTimeLocation(t *testing.T) {
assert.Equal(t, ny, atom.Load().Location(), "Location information is wrong")
}

func TestTimeJSON(t *testing.T) {
start := time.Date(2021, 6, 17, 9, 10, 0, 0, time.UTC)
atom := NewTime(start)

t.Run("MarshalJSON", func(t *testing.T) {
marshalled, err := atom.MarshalJSON()
require.NoError(t, err)

// Compare against time.Time's own JSON encoding so we stay aligned with
// the standard library regardless of how it chooses to format times.
wanted, err := json.Marshal(start)
require.NoError(t, err)
assert.Equal(t, string(wanted), string(marshalled))
})

t.Run("UnmarshalJSON", func(t *testing.T) {
raw, err := json.Marshal(start)
require.NoError(t, err)

var got Time
require.NoError(t, got.UnmarshalJSON(raw))
assert.Equal(t, start.UTC(), got.Load().UTC())
})

t.Run("RoundTripInsideStruct", func(t *testing.T) {
// This is the case from #124: atomic.Time was being silently encoded
// as `{}` because it had no MarshalJSON.
type Container struct {
Plain time.Time
Atomic *Time
AtomicNil *Time
AtomicZero *Time
}
c := Container{
Plain: start,
Atomic: atom,
AtomicZero: NewTime(time.Time{}),
}

blob, err := json.Marshal(c)
require.NoError(t, err)

var decoded Container
require.NoError(t, json.Unmarshal(blob, &decoded))
assert.Equal(t, c.Plain.UTC(), decoded.Plain.UTC())
assert.Equal(t, c.Atomic.Load().UTC(), decoded.Atomic.Load().UTC())
})
}

func TestLargeTime(t *testing.T) {
// Check "large/small" time that are beyond int64 ns
// representation (< year 1678 or > year 2262) can be
Expand Down