Skip to content

Commit bc8476a

Browse files
neyy91aljo242
andauthored
feat: add ValueCodec for time.Time (#25827)
Co-authored-by: Alex | Cosmos Labs <alex@cosmoslabs.io>
1 parent 70e615c commit bc8476a

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
6767
* [#25516](https://github.com/cosmos/cosmos-sdk/pull/25516) Support automatic configuration of OpenTelemetry via [OpenTelemetry declarative configuration](https://pkg.go.dev/go.opentelemetry.io/contrib/otelconf) and add OpenTelemetry instrumentation of `BaseApp`.
6868
* [#25745](https://github.com/cosmos/cosmos-sdk/pull/25745) Add DiskIO telemetry via gopsutil.
6969
* (grpc) [#25648](https://github.com/cosmos/cosmos-sdk/pull/25648) Add `earliest_block_height` and `latest_block_height` fields to `GetSyncingResponse`.
70+
* (collections/codec) [#25614] (https://github.com/cosmos/cosmos-sdk/pull/25827) Add `TimeValue` (`ValueCodec[time.Time]`) to collections/codec.
7071
* (enterprise/poa) [#25838](https://github.com/cosmos/cosmos-sdk/pull/25838) Add the `poa` module under the `enterprise` directory.
7172

7273
### Improvements

collections/codec/time.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package codec
2+
3+
import (
4+
"encoding/binary"
5+
"encoding/json"
6+
"fmt"
7+
"time"
8+
)
9+
10+
// TimeValue returns a ValueCodec for time.Time.
11+
//
12+
// Binary format: 8 bytes (big-endian) of int64 UnixNano.
13+
// JSON format: RFC3339Nano string.
14+
func TimeValue() ValueCodec[time.Time] {
15+
return timeValueCodec{}
16+
}
17+
18+
type timeValueCodec struct{}
19+
20+
func (timeValueCodec) Encode(value time.Time) ([]byte, error) {
21+
value = value.UTC()
22+
n := value.UnixNano()
23+
24+
b := make([]byte, 8)
25+
binary.BigEndian.PutUint64(b, uint64(n))
26+
return b, nil
27+
}
28+
29+
func (timeValueCodec) Decode(b []byte) (time.Time, error) {
30+
if len(b) != 8 {
31+
return time.Time{}, fmt.Errorf("%w: invalid buffer size, wanted: 8", ErrEncoding)
32+
}
33+
34+
n := int64(binary.BigEndian.Uint64(b))
35+
return time.Unix(0, n).UTC(), nil
36+
}
37+
38+
func (timeValueCodec) EncodeJSON(value time.Time) ([]byte, error) {
39+
s := value.UTC().Format(time.RFC3339Nano)
40+
return json.Marshal(s)
41+
}
42+
43+
func (timeValueCodec) DecodeJSON(b []byte) (time.Time, error) {
44+
var s string
45+
if err := json.Unmarshal(b, &s); err != nil {
46+
return time.Time{}, err
47+
}
48+
49+
t, err := time.Parse(time.RFC3339Nano, s)
50+
if err != nil {
51+
return time.Time{}, err
52+
}
53+
54+
return t.UTC(), nil
55+
}
56+
57+
func (timeValueCodec) Stringify(value time.Time) string {
58+
return value.UTC().Format(time.RFC3339Nano)
59+
}
60+
61+
func (timeValueCodec) ValueType() string {
62+
return "time"
63+
}
64+
65+
var _ ValueCodec[time.Time] = timeValueCodec{}

collections/codec/time_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package codec
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestTimeValue_BinaryRoundTrip(t *testing.T) {
11+
cdc := TimeValue()
12+
13+
tt := time.Date(2026, 1, 25, 12, 34, 56, 123456789, time.UTC)
14+
15+
b, err := cdc.Encode(tt)
16+
require.NoError(t, err)
17+
require.Len(t, b, 8)
18+
19+
got, err := cdc.Decode(b)
20+
require.NoError(t, err)
21+
22+
require.True(t, tt.Equal(got))
23+
require.Equal(t, time.UTC, got.Location())
24+
}
25+
26+
func TestTimeValue_BinaryInvalidLen(t *testing.T) {
27+
cdc := TimeValue()
28+
29+
_, err := cdc.Decode([]byte{1, 2, 3})
30+
require.Error(t, err)
31+
}
32+
33+
func TestTimeValue_JSONRoundTrip(t *testing.T) {
34+
cdc := TimeValue()
35+
36+
tt := time.Date(2026, 1, 25, 12, 34, 56, 123456789, time.UTC)
37+
38+
b, err := cdc.EncodeJSON(tt)
39+
require.NoError(t, err)
40+
41+
got, err := cdc.DecodeJSON(b)
42+
require.NoError(t, err)
43+
44+
require.True(t, tt.Equal(got))
45+
require.Equal(t, time.UTC, got.Location())
46+
}
47+
48+
func TestTimeValue_Stringify(t *testing.T) {
49+
cdc := TimeValue()
50+
51+
tt := time.Date(2026, 1, 25, 12, 34, 56, 123456789, time.UTC)
52+
s := cdc.Stringify(tt)
53+
54+
require.Contains(t, s, "2026-01-25T12:34:56")
55+
require.Contains(t, s, "Z")
56+
}

0 commit comments

Comments
 (0)