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 }