github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/datacodec/time.go (about) 1 // Copyright 2021 DataStax 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package datacodec 16 17 import ( 18 "time" 19 20 "github.com/datastax/go-cassandra-native-protocol/datatype" 21 "github.com/datastax/go-cassandra-native-protocol/primitive" 22 ) 23 24 const ( 25 TimeLayoutDefault = "15:04:05.999999999" 26 27 // TimeMaxDuration is the maximum duration that can be stored in a CQL time value. Any int64 value that is lesser 28 // than zero or grater than this value will be rejected. 29 TimeMaxDuration = 24*time.Hour - 1 30 ) 31 32 // ConvertTimeToNanosOfDay is a function that converts from a time.Time into nanos since the beginning of the day. 33 // The given time is normalized to UTC before the computation. 34 func ConvertTimeToNanosOfDay(t time.Time) int64 { 35 t = t.UTC() 36 return int64(t.Nanosecond()) + 37 int64(t.Second())*int64(time.Second) + 38 int64(t.Minute())*int64(time.Minute) + 39 int64(t.Hour())*int64(time.Hour) 40 } 41 42 // ConvertDurationToNanosOfDay is a function that converts from a time.Duration into nanos since the beginning of the 43 // day. An error is returned if the given time value is outside the valid range for CQL time values: from 0 to 44 // TimeMaxDuration inclusive. 45 func ConvertDurationToNanosOfDay(d time.Duration) (int64, error) { 46 if d < 0 || d > TimeMaxDuration { 47 return 0, errValueOutOfRange(d) 48 } else { 49 return d.Nanoseconds(), nil 50 } 51 } 52 53 // ConvertNanosOfDayToTime is a function that converts from nanos since the beginning of the day into a time.Time in UTC. 54 // The returned time will have its date part set to 0001-01-01 and its time zone will be UTC. An error is returned if 55 // the given time value is outside the valid range for CQL time values: from 0 to TimeMaxDuration inclusive. 56 func ConvertNanosOfDayToTime(nanos int64) (time.Time, error) { 57 if d := time.Duration(nanos); d < 0 || d > TimeMaxDuration { 58 return time.Time{}, errValueOutOfRange(d) 59 } else { 60 return time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC).Add(d), nil 61 } 62 } 63 64 // ConvertNanosOfDayToDuration is a function that converts from nanos since the beginning of the day into a 65 // time.Duration. An error is returned if the given time value is outside the valid range for CQL time values: from 0 to 66 // TimeMaxDuration inclusive. 67 func ConvertNanosOfDayToDuration(nanos int64) (time.Duration, error) { 68 if d := time.Duration(nanos); d < 0 || d > TimeMaxDuration { 69 return 0, errValueOutOfRange(d) 70 } else { 71 return d, nil 72 } 73 } 74 75 // Time is a codec for the CQL time type with default layout. Its preferred Go type is time.Duration, but it 76 // can encode from and decode to time.Time, string and to most numeric types as well. 77 // When encoding from and decoding to time.Duration, the duration must be >= 0 and <= TimeMaxDuration, otherwise an 78 // error is returned. 79 // When encoding from and decoding to time.Time, only the clock part is considered, the date part is ignored. Also note 80 // that all time.Time values are normalized to UTC before encoding and after decoding. 81 // When encoding from and decoding to numeric types, the numeric value represents the number of nanoseconds since the 82 // beginning of the day. 83 // Note that a better representation for the CQL date type can be found in the civil package 84 // from cloud.google.com, see https://pkg.go.dev/cloud.google.com/go/civil. 85 var Time = NewTime(TimeLayoutDefault) 86 87 // NewTime creates a new codec for CQL time type, with the given layout. The Layout is used only when 88 // encoding from or decoding to string; it is ignored otherwise. See NewTime for important notes on accepted types. 89 func NewTime(layout string) Codec { 90 return &timeCodec{layout: layout} 91 } 92 93 type timeCodec struct { 94 layout string 95 } 96 97 func (c *timeCodec) DataType() datatype.DataType { 98 return datatype.Time 99 } 100 101 func (c *timeCodec) Encode(source interface{}, version primitive.ProtocolVersion) (dest []byte, err error) { 102 if !version.SupportsDataType(c.DataType().Code()) { 103 err = errDataTypeNotSupported(c.DataType(), version) 104 } else { 105 var val int64 106 var wasNil bool 107 if val, wasNil, err = convertToInt64Time(source, c.layout); err == nil && !wasNil { 108 dest = writeInt64(val) 109 } 110 } 111 if err != nil { 112 err = errCannotEncode(source, c.DataType(), version, err) 113 } 114 return 115 } 116 117 func (c *timeCodec) Decode(source []byte, dest interface{}, version primitive.ProtocolVersion) (wasNull bool, err error) { 118 if !version.SupportsDataType(c.DataType().Code()) { 119 wasNull = len(source) == 0 120 err = errDataTypeNotSupported(c.DataType(), version) 121 } else { 122 var val int64 123 if val, wasNull, err = readInt64(source); err == nil { 124 err = convertFromInt64Time(val, wasNull, dest, c.layout) 125 } 126 } 127 if err != nil { 128 err = errCannotDecode(dest, c.DataType(), version, err) 129 } 130 return 131 } 132 133 func convertToInt64Time(source interface{}, layout string) (val int64, wasNil bool, err error) { 134 switch s := source.(type) { 135 case time.Duration: 136 val, err = ConvertDurationToNanosOfDay(s) 137 case *time.Duration: 138 if wasNil = s == nil; !wasNil { 139 val, err = ConvertDurationToNanosOfDay(*s) 140 } 141 case time.Time: 142 val = ConvertTimeToNanosOfDay(s) 143 case *time.Time: 144 if wasNil = s == nil; !wasNil { 145 val = ConvertTimeToNanosOfDay(*s) 146 } 147 case string: 148 val, err = stringToNanosOfDay(s, layout) 149 case *string: 150 if wasNil = s == nil; !wasNil { 151 val, err = stringToNanosOfDay(*s, layout) 152 } 153 case nil: 154 wasNil = true 155 default: 156 return convertToInt64(source) 157 } 158 if err != nil { 159 err = errSourceConversionFailed(source, val, err) 160 } 161 return 162 } 163 164 func convertFromInt64Time(val int64, wasNull bool, dest interface{}, layout string) (err error) { 165 switch d := dest.(type) { 166 case *interface{}: 167 if d == nil { 168 err = ErrNilDestination 169 } else if wasNull { 170 *d = nil 171 } else { 172 *d, err = ConvertNanosOfDayToDuration(val) 173 } 174 case *time.Duration: 175 if d == nil { 176 err = ErrNilDestination 177 } else if wasNull { 178 *d = 0 179 } else { 180 *d, err = ConvertNanosOfDayToDuration(val) 181 } 182 case *time.Time: 183 if d == nil { 184 err = ErrNilDestination 185 } else if wasNull { 186 *d = time.Time{} 187 } else { 188 *d, err = ConvertNanosOfDayToTime(val) 189 } 190 case *string: 191 if d == nil { 192 err = ErrNilDestination 193 } else if wasNull { 194 *d = "" 195 } else { 196 var t time.Time 197 if t, err = ConvertNanosOfDayToTime(val); err == nil { 198 *d = t.Format(layout) 199 } 200 } 201 default: 202 return convertFromInt64(val, wasNull, dest) 203 } 204 if err != nil { 205 err = errDestinationConversionFailed(val, dest, err) 206 } 207 return 208 }