github.com/philpearl/plenc@v0.0.15/plenccodec/time.go (about)

     1  package plenccodec
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  	"unsafe"
     7  
     8  	"github.com/philpearl/plenc/plenccore"
     9  )
    10  
    11  // ptime is a representation of time in UTC. It is used to encode time.Time
    12  type ptime struct {
    13  	Seconds     int64 `plenc:"1"`
    14  	Nanoseconds int32 `plenc:"2"`
    15  }
    16  
    17  // Set sets the time from a time.Time
    18  func (e *ptime) Set(t time.Time) {
    19  	e.Seconds = t.Unix()
    20  	n := t.Nanosecond()
    21  	e.Nanoseconds = int32(n)
    22  }
    23  
    24  func (e *ptime) Standard() time.Time {
    25  	return time.Unix(e.Seconds, int64(e.Nanoseconds)).UTC()
    26  }
    27  
    28  // TimeCodec is a codec for Time
    29  // Note that this is not compatible with google.protobuf.Timestamp. The structure
    30  // is the same, but they don't use zigzag encoding for the fields. They still do
    31  // allow negative values though
    32  type TimeCodec struct{}
    33  
    34  // size returns the number of bytes needed to encode a Time
    35  func (tc TimeCodec) size(ptr unsafe.Pointer) (size int) {
    36  	t := *(*time.Time)(ptr)
    37  	var e ptime
    38  	e.Set(t)
    39  	return IntCodec[int64]{}.Size(unsafe.Pointer(&e.Seconds), varInt1Tag) +
    40  		IntCodec[int32]{}.Size(unsafe.Pointer(&e.Nanoseconds), varInt2Tag)
    41  }
    42  
    43  var (
    44  	varInt1Tag = plenccore.AppendTag(nil, plenccore.WTVarInt, 1)
    45  	varInt2Tag = plenccore.AppendTag(nil, plenccore.WTVarInt, 2)
    46  )
    47  
    48  // append encodes a Time
    49  func (tc TimeCodec) append(data []byte, ptr unsafe.Pointer) []byte {
    50  	t := *(*time.Time)(ptr)
    51  	var e ptime
    52  	e.Set(t)
    53  
    54  	data = IntCodec[int64]{}.Append(data, unsafe.Pointer(&e.Seconds), varInt1Tag)
    55  	data = IntCodec[int32]{}.Append(data, unsafe.Pointer(&e.Nanoseconds), varInt2Tag)
    56  	return data
    57  }
    58  
    59  // Read decodes a Time
    60  func (tc TimeCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
    61  	l := len(data)
    62  	if l == 0 {
    63  		*(*time.Time)(ptr) = time.Time{}
    64  		return 0, nil
    65  	}
    66  
    67  	var e ptime
    68  	var offset int
    69  	for offset < l {
    70  		wt, index, n := plenccore.ReadTag(data[offset:])
    71  		offset += n
    72  
    73  		switch index {
    74  		case 1:
    75  			n, err := IntCodec[int64]{}.Read(data[offset:], unsafe.Pointer(&e.Seconds), wt)
    76  			if err != nil {
    77  				return 0, fmt.Errorf("failed reading seconds field of time. %w", err)
    78  			}
    79  			offset += n
    80  
    81  		case 2:
    82  			n, err := IntCodec[int32]{}.Read(data[offset:], unsafe.Pointer(&e.Nanoseconds), wt)
    83  			if err != nil {
    84  				return 0, fmt.Errorf("failed reading nanoseconds field of time. %w", err)
    85  			}
    86  			offset += n
    87  
    88  		default:
    89  			// Field corresponding to index does not exist
    90  			n, err := plenccore.Skip(data[offset:], wt)
    91  			if err != nil {
    92  				return 0, fmt.Errorf("failed to skip field %d of time. %w", index, err)
    93  			}
    94  			offset += n
    95  		}
    96  	}
    97  
    98  	*(*time.Time)(ptr) = e.Standard()
    99  
   100  	return offset, nil
   101  }
   102  
   103  func (TimeCodec) New() unsafe.Pointer {
   104  	return unsafe.Pointer(&time.Time{})
   105  }
   106  
   107  func (TimeCodec) Omit(ptr unsafe.Pointer) bool {
   108  	return (*time.Time)(ptr).IsZero()
   109  }
   110  
   111  func (tc TimeCodec) WireType() plenccore.WireType {
   112  	return plenccore.WTLength
   113  }
   114  
   115  func (tc TimeCodec) Descriptor() Descriptor {
   116  	return Descriptor{Type: FieldTypeTime, LogicalType: LogicalTypeTimestamp}
   117  }
   118  
   119  func (c TimeCodec) Size(ptr unsafe.Pointer, tag []byte) int {
   120  	l := c.size(ptr)
   121  	if len(tag) != 0 {
   122  		l += len(tag) + plenccore.SizeVarUint(uint64(l))
   123  	}
   124  	return l
   125  }
   126  
   127  func (c TimeCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   128  	if len(tag) != 0 {
   129  		data = append(data, tag...)
   130  		data = plenccore.AppendVarUint(data, uint64(c.size(ptr)))
   131  	}
   132  
   133  	return c.append(data, ptr)
   134  }
   135  
   136  type TimeCompatCodec struct {
   137  	TimeCodec
   138  }
   139  
   140  // size returns the number of bytes needed to encode a Time
   141  func (tc TimeCompatCodec) size(ptr unsafe.Pointer) (size int) {
   142  	t := *(*time.Time)(ptr)
   143  	var e ptime
   144  	e.Set(t)
   145  	return UintCodec[uint64]{}.Size(unsafe.Pointer(&e.Seconds), varInt1Tag) +
   146  		+UintCodec[uint32]{}.Size(unsafe.Pointer(&e.Nanoseconds), varInt2Tag)
   147  }
   148  
   149  // append encodes a Time
   150  func (tc TimeCompatCodec) append(data []byte, ptr unsafe.Pointer) []byte {
   151  	t := *(*time.Time)(ptr)
   152  	var e ptime
   153  	e.Set(t)
   154  
   155  	data = UintCodec[uint64]{}.Append(data, unsafe.Pointer(&e.Seconds), varInt1Tag)
   156  	data = UintCodec[uint32]{}.Append(data, unsafe.Pointer(&e.Nanoseconds), varInt2Tag)
   157  	return data
   158  }
   159  
   160  func (c TimeCompatCodec) Size(ptr unsafe.Pointer, tag []byte) int {
   161  	l := c.size(ptr)
   162  	if len(tag) != 0 {
   163  		l += len(tag) + plenccore.SizeVarUint(uint64(l))
   164  	}
   165  	return l
   166  }
   167  
   168  func (c TimeCompatCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   169  	if len(tag) != 0 {
   170  		data = append(data, tag...)
   171  		data = plenccore.AppendVarUint(data, uint64(c.size(ptr)))
   172  	}
   173  
   174  	return c.append(data, ptr)
   175  }
   176  
   177  // Read decodes a Time
   178  func (tc TimeCompatCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
   179  	l := len(data)
   180  	if l == 0 {
   181  		*(*time.Time)(ptr) = time.Time{}
   182  		return 0, nil
   183  	}
   184  
   185  	var e ptime
   186  	var offset int
   187  	for offset < l {
   188  		wt, index, n := plenccore.ReadTag(data[offset:])
   189  		offset += n
   190  
   191  		switch index {
   192  		case 1:
   193  			n, err := UintCodec[uint64]{}.Read(data[offset:], unsafe.Pointer(&e.Seconds), wt)
   194  			if err != nil {
   195  				return 0, fmt.Errorf("failed reading seconds field of time. %w", err)
   196  			}
   197  			offset += n
   198  
   199  		case 2:
   200  			n, err := UintCodec[uint32]{}.Read(data[offset:], unsafe.Pointer(&e.Nanoseconds), wt)
   201  			if err != nil {
   202  				return 0, fmt.Errorf("failed reading nanoseconds field of time. %w", err)
   203  			}
   204  			offset += n
   205  
   206  		default:
   207  			// Field corresponding to index does not exist
   208  			n, err := plenccore.Skip(data[offset:], wt)
   209  			if err != nil {
   210  				return 0, fmt.Errorf("failed to skip field %d of time. %w", index, err)
   211  			}
   212  			offset += n
   213  		}
   214  	}
   215  
   216  	*(*time.Time)(ptr) = e.Standard()
   217  
   218  	return offset, nil
   219  }
   220  
   221  // BQTimestampStruct encodes a time.Time as a flat (not zigzag) int64 of
   222  // microseconds since the Epoch. This is how the BigQuery write API expects a
   223  // timestamp to be encoded
   224  type BQTimestampCodec struct {
   225  	FlatIntCodec[uint64]
   226  }
   227  
   228  func (BQTimestampCodec) New() unsafe.Pointer {
   229  	return unsafe.Pointer(&time.Time{})
   230  }
   231  
   232  func (BQTimestampCodec) Omit(ptr unsafe.Pointer) bool {
   233  	return (*time.Time)(ptr).IsZero()
   234  }
   235  
   236  func (c BQTimestampCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
   237  	var ts int64
   238  	n, err = c.FlatIntCodec.Read(data, unsafe.Pointer(&ts), wt)
   239  	if err != nil {
   240  		return n, err
   241  	}
   242  	*(*time.Time)(ptr) = time.UnixMicro(ts).UTC()
   243  	return n, nil
   244  }
   245  
   246  func (c BQTimestampCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   247  	ts := (*time.Time)(ptr).UnixMicro()
   248  	return c.FlatIntCodec.Append(data, unsafe.Pointer(&ts), tag)
   249  }
   250  
   251  func (c BQTimestampCodec) Descriptor() Descriptor {
   252  	return Descriptor{Type: FieldTypeFlatInt, LogicalType: LogicalTypeTimestamp}
   253  }