-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimestamp.go
More file actions
180 lines (162 loc) · 4.69 KB
/
timestamp.go
File metadata and controls
180 lines (162 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package msgpck
import (
"encoding/binary"
"time"
)
// TimestampExtType is the extension type for msgpack timestamps.
const TimestampExtType int8 = -1
// EncodeTimestamp encodes a time.Time as a msgpack timestamp extension.
// Uses the most compact format that can represent the timestamp:
// - Timestamp 32: seconds only (no nanoseconds), fits in uint32
// - Timestamp 64: nanoseconds + seconds fits in 34-bit unsigned
// - Timestamp 96: full range with 64-bit signed seconds
//
// The time is converted to UTC before encoding.
func (e *Encoder) EncodeTimestamp(t time.Time) {
t = t.UTC()
sec := t.Unix()
nsec := uint32(t.Nanosecond())
if nsec == 0 && sec >= 0 && sec <= 0xFFFFFFFF {
// Timestamp 32: fixext 4, type -1, 4 bytes seconds
e.writeByte(formatFixExt4)
e.writeByte(0xff) // -1 as unsigned byte
e.writeUint32(uint32(sec))
} else if sec >= 0 && sec <= 0x3FFFFFFFF {
// Timestamp 64: fixext 8, type -1, 8 bytes (30-bit nsec + 34-bit sec)
// Upper 30 bits: nanoseconds, lower 34 bits: seconds
val := (uint64(nsec) << 34) | uint64(sec)
e.writeByte(formatFixExt8)
e.writeByte(0xff) // -1 as unsigned byte
e.writeUint64(val)
} else {
// Timestamp 96: ext 8, length 12, type -1, 4 bytes nsec + 8 bytes sec
e.writeByte(formatExt8)
e.writeByte(12)
e.writeByte(0xff) // -1 as unsigned byte
e.writeUint32(nsec)
e.writeUint64(uint64(sec))
}
}
// decodeTimestampDataLen parses the format byte and returns the data length.
func (d *Decoder) decodeTimestampDataLen(format byte) (int, error) {
switch format {
case formatFixExt4:
return 4, nil
case formatFixExt8:
return 8, nil
case formatExt8:
n, err := d.readUint8()
if err != nil {
return 0, err
}
if n != 12 {
return 0, ErrTypeMismatch
}
return 12, nil
default:
return 0, ErrTypeMismatch
}
}
// decodeTimestampValue decodes the timestamp value based on data length.
func (d *Decoder) decodeTimestampValue(dataLen int) (time.Time, error) {
switch dataLen {
case 4:
sec, err := d.readUint32()
if err != nil {
return time.Time{}, err
}
return time.Unix(int64(sec), 0).UTC(), nil
case 8:
val, err := d.readUint64()
if err != nil {
return time.Time{}, err
}
return time.Unix(int64(val&0x3FFFFFFFF), int64(val>>34)).UTC(), nil
case 12:
nsec, err := d.readUint32()
if err != nil {
return time.Time{}, err
}
sec, err := d.readInt64()
if err != nil {
return time.Time{}, err
}
return time.Unix(sec, int64(nsec)).UTC(), nil
default:
return time.Time{}, ErrTypeMismatch
}
}
// DecodeTimestamp decodes a msgpack timestamp extension to time.Time.
// Returns the time in UTC.
// Returns ErrTypeMismatch if the value is not a timestamp extension.
func (d *Decoder) DecodeTimestamp() (time.Time, error) {
format, err := d.readByte()
if err != nil {
return time.Time{}, err
}
dataLen, err := d.decodeTimestampDataLen(format)
if err != nil {
return time.Time{}, err
}
extType, err := d.readInt8()
if err != nil {
return time.Time{}, err
}
if extType != TimestampExtType {
return time.Time{}, ErrTypeMismatch
}
return d.decodeTimestampValue(dataLen)
}
// MarshalTimestamp encodes a time.Time to msgpack timestamp bytes.
// Returns a copy of the encoded bytes (safe to retain).
// The time is converted to UTC before encoding.
func MarshalTimestamp(t time.Time) []byte {
e := encoderPool.Get().(*Encoder)
e.Reset()
e.EncodeTimestamp(t)
result := make([]byte, len(e.buf))
copy(result, e.buf)
encoderPool.Put(e)
return result
}
// UnmarshalTimestamp decodes msgpack timestamp bytes to time.Time.
// Returns the time in UTC.
// If the input has no timezone info, it is treated as UTC.
func UnmarshalTimestamp(data []byte) (time.Time, error) {
d := decoderPool.Get().(*Decoder)
d.Reset(data)
t, err := d.DecodeTimestamp()
decoderPool.Put(d)
return t, err
}
// IsTimestamp checks if an Ext value is a timestamp.
func IsTimestamp(ext Ext) bool {
return ext.Type == TimestampExtType
}
// ExtToTimestamp converts an Ext value to time.Time.
// Returns ErrTypeMismatch if the extension type is not a timestamp.
// Returns the time in UTC.
func ExtToTimestamp(ext Ext) (time.Time, error) {
if ext.Type != TimestampExtType {
return time.Time{}, ErrTypeMismatch
}
switch len(ext.Data) {
case 4:
// Timestamp 32
sec := binary.BigEndian.Uint32(ext.Data)
return time.Unix(int64(sec), 0).UTC(), nil
case 8:
// Timestamp 64
val := binary.BigEndian.Uint64(ext.Data)
nsec := int64(val >> 34)
sec := int64(val & 0x3FFFFFFFF)
return time.Unix(sec, nsec).UTC(), nil
case 12:
// Timestamp 96
nsec := binary.BigEndian.Uint32(ext.Data[:4])
sec := int64(binary.BigEndian.Uint64(ext.Data[4:]))
return time.Unix(sec, int64(nsec)).UTC(), nil
default:
return time.Time{}, ErrTypeMismatch
}
}