Skip to content

Commit

Permalink
Adding Read/Write Time functions
Browse files Browse the repository at this point in the history
  • Loading branch information
pkedy committed Oct 19, 2022
1 parent 211b0cd commit 58c677e
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 3 deletions.
139 changes: 137 additions & 2 deletions decoder.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package msgpack

import (
"encoding/binary"
"math"
"strconv"
"time"
)

type Decoder struct {
Expand Down Expand Up @@ -345,6 +347,80 @@ func (d *Decoder) ReadNillableFloat64() (*float64, error) {
return &val, err
}

func (d *Decoder) ReadTime() (time.Time, error) {
prefix, err := d.reader.PeekUint8()
if err != nil {
return time.Time{}, err
}

if isString(prefix) {
str, err := d.ReadString()
if err != nil {
return time.Time{}, err
}
return time.Parse(time.RFC3339Nano, str)
}

d.reader.Discard(1)
extID, extLen, err := d.extHeader(prefix)
if err != nil {
return time.Time{}, err
}

// NodeJS seems to use extID 13.
if extID != -1 && extID != 13 {
return time.Time{}, ReadError{"msgpack: invalid time ext id=" + strconv.FormatUint(uint64(extID), 10)}
}

tm, err := d.decodeTime(extLen)
if err != nil {
return tm, err
}

if tm.IsZero() {
// Zero time does not have timezone information.
return tm.UTC(), nil
}

return tm, nil
}

func (d *Decoder) ReadNillableTime() (*time.Time, error) {
isNil, err := d.IsNextNil()
if isNil || err != nil {
return nil, err
}
val, err := d.ReadTime()
if err != nil {
return nil, err
}
return &val, err
}

func (d *Decoder) decodeTime(extLen uint32) (time.Time, error) {
b, err := d.reader.GetBytes(extLen)
if err != nil {
return time.Time{}, err
}

switch len(b) {
case 4:
sec := binary.BigEndian.Uint32(b)
return time.Unix(int64(sec), 0), nil
case 8:
sec := binary.BigEndian.Uint64(b)
nsec := int64(sec >> 34)
sec &= 0x00000003ffffffff
return time.Unix(int64(sec), nsec), nil
case 12:
nsec := binary.BigEndian.Uint32(b)
sec := binary.BigEndian.Uint64(b[4:])
return time.Unix(int64(sec), int64(nsec)), nil
default:
return time.Time{}, ReadError{"msgpack: invalid time ext len=" + strconv.FormatUint(uint64(extLen), 10)}
}
}

func (d *Decoder) ReadString() (string, error) {
strLen, err := d.readStringLength()
return d.readString(strLen, err)
Expand Down Expand Up @@ -378,13 +454,14 @@ func (d *Decoder) readStringLength() (uint32, error) {
case FormatString8:
v, err := d.reader.GetUint8()
return uint32(v), err
case FormatString16:
case FormatString16, FormatArray16:
v, err := d.reader.GetUint16()
return uint32(v), err
case FormatString32:
case FormatString32, FormatArray32:
v, err := d.reader.GetUint32()
return v, err
}

return 0, ReadError{"bad prefix for string length"}
}

Expand Down Expand Up @@ -744,6 +821,54 @@ func (d *Decoder) readMap(m map[any]any, length uint32) error {
return nil
}

func (d *Decoder) extHeader(c byte) (int8, uint32, error) {
extLen, err := d.parseExtLen(c)
if err != nil {
return 0, 0, err
}

extID, err := d.readCode()
if err != nil {
return 0, 0, err
}

return int8(extID), extLen, nil
}

func (d *Decoder) readCode() (byte, error) {
c, err := d.reader.GetUint8()
if err != nil {
return 0, err
}
return c, nil
}

func (d *Decoder) parseExtLen(c byte) (uint32, error) {
switch c {
case FormatFixExt1:
return 1, nil
case FormatFixExt2:
return 2, nil
case FormatFixExt4:
return 4, nil
case FormatFixExt8:
return 8, nil
case FormatFixExt16:
return 16, nil
case FormatExt8:
n, err := d.ReadUint8()
return uint32(n), err
case FormatExt16:
n, err := d.ReadUint16()
return uint32(n), err
case FormatExt32:
n, err := d.ReadUint32()
return n, err
default:
return 0, ReadError{"msgpack: invalid code=" + strconv.FormatUint(uint64(c), 16) + " decoding ext len"}
}
}

func (d *Decoder) Err() error {
return d.reader.Err()
}
Expand Down Expand Up @@ -797,6 +922,16 @@ func isFixedString(u byte) bool {
return (u & 0xe0) == FormatFixString
}

func isString(u byte) bool {
return isFixedString(u) ||
u == FormatString8 ||
u == FormatString16 ||
u == FormatString32 ||
isFixedArray(u) ||
u == FormatArray16 ||
u == FormatArray32
}

type ReadError struct {
message string
}
Expand Down
87 changes: 87 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package msgpack

import (
"encoding/binary"
"math"
"time"
)

type Encoder struct {
Expand Down Expand Up @@ -215,6 +217,44 @@ func (e *Encoder) WriteNillableString(value *string) {
}
}

func (e *Encoder) WriteTime(tm time.Time) {
var timeBuf [12]byte
b := e.encodeTime(tm, timeBuf[:])
e.encodeExtLen(len(b))
e.reader.SetInt8(-1)
e.reader.SetBytes(b)
}

func (e *Encoder) WriteNillableTime(value *time.Time) {
if value == nil {
e.WriteNil()
} else {
e.WriteTime(*value)
}
}

func (e *Encoder) encodeTime(tm time.Time, timeBuf []byte) []byte {
secs := uint64(tm.Unix())
if secs>>34 == 0 {
data := uint64(tm.Nanosecond())<<34 | secs

if data&0xffffffff00000000 == 0 {
b := timeBuf[:4]
binary.BigEndian.PutUint32(b, uint32(data))
return b
}

b := timeBuf[:8]
binary.BigEndian.PutUint64(b, data)
return b
}

b := timeBuf[:12]
binary.BigEndian.PutUint32(b, uint32(tm.Nanosecond()))
binary.BigEndian.PutUint64(b[4:], secs)
return b
}

func (e *Encoder) writeBinLength(length uint32) {
if length <= math.MaxUint8 {
e.reader.SetUint8(FormatBin8)
Expand Down Expand Up @@ -477,6 +517,53 @@ func (e *Encoder) WriteAny(value any) {
}
}

func (e *Encoder) encodeExtLen(l int) error {
switch l {
case 1:
return e.reader.SetUint8(FormatFixExt1)
case 2:
return e.reader.SetUint8(FormatFixExt2)
case 4:
return e.reader.SetUint8(FormatFixExt4)
case 8:
return e.reader.SetUint8(FormatFixExt8)
case 16:
return e.reader.SetUint8(FormatFixExt16)
}
if l <= math.MaxUint8 {
return e.write1(FormatExt8, uint8(l))
}
if l <= math.MaxUint16 {
return e.write2(FormatExt16, uint16(l))
}
return e.write4(FormatExt32, uint32(l))
}

func (e *Encoder) write1(code byte, n uint8) error {
var buf [2]byte
buf[0] = code
buf[1] = n
return e.reader.SetBytes(buf[:])
}

func (e *Encoder) write2(code byte, n uint16) error {
var buf [3]byte
buf[0] = code
buf[1] = byte(n >> 8)
buf[2] = byte(n)
return e.reader.SetBytes(buf[:])
}

func (e *Encoder) write4(code byte, n uint32) error {
var buf [5]byte
buf[0] = code
buf[1] = byte(n >> 24)
buf[2] = byte(n >> 16)
buf[3] = byte(n >> 8)
buf[4] = byte(n)
return e.reader.SetBytes(buf[:])
}

func (e *Encoder) Err() error {
return e.reader.Err()
}
8 changes: 8 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package msgpack

import (
"time"
)

// Reader is the interface for reading data from the MessagePack format.
type Reader interface {
IsNextNil() (bool, error)
Expand Down Expand Up @@ -27,6 +31,8 @@ type Reader interface {
ReadNillableFloat64() (*float64, error)
ReadString() (string, error)
ReadNillableString() (*string, error)
ReadTime() (time.Time, error)
ReadNillableTime() (*time.Time, error)
ReadByteArray() ([]byte, error)
ReadNillableByteArray() ([]byte, error)
ReadArraySize() (uint32, error)
Expand Down Expand Up @@ -63,6 +69,8 @@ type Writer interface {
WriteNillableFloat64(value *float64)
WriteString(value string)
WriteNillableString(value *string)
WriteTime(value time.Time)
WriteNillableTime(value *time.Time)
WriteByteArray(value []byte)
WriteNillableByteArray(value []byte)
WriteArraySize(length uint32)
Expand Down
Loading

0 comments on commit 58c677e

Please sign in to comment.