github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/datacodec/timestamp_test.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 "fmt" 19 "math" 20 "testing" 21 "time" 22 23 "github.com/stretchr/testify/assert" 24 25 "github.com/datastax/go-cassandra-native-protocol/primitive" 26 ) 27 28 var ( 29 timestampNegUTC, _ = time.Parse("2006-01-02 15:04:05 MST", "1951-06-24 23:00:00.999 UTC") // -584499599001 30 timestampNegZoned, _ = time.Parse("2006-01-02 15:04:05 -07:00", "1951-06-24 16:00:00.999 -07:00") // -584499599001 31 timestampPosUTC, _ = time.Parse("2006-01-02 15:04:05 MST", "2021-10-11 23:00:00.999 UTC") // 1633993200999 32 timestampPosZoned, _ = time.Parse("2006-01-02 15:04:05 -07:00", "2021-10-12 00:00:00.999 +01:00") // 1633993200999 33 timestampEpoch = time.Unix(0, 0).UTC() 34 timestampEpochZoned, _ = time.Parse("2006-01-02 15:04:05 -07:00", "1969-12-31 23:00:00 -01:00") 35 timestampOufOfRangeNeg = time.Date(-292275055, time.May, 16, 16, 47, 04, 191_000_000, time.UTC) // -292275055-05-16T16:47:04.191Z 36 timestampOufOfRangePos = time.Date(292278994, time.August, 17, 07, 12, 55, 808_000_000, time.UTC) // +292278994-08-17T07:12:55.808Z 37 paris, _ = time.LoadLocation("Europe/Paris") 38 timestampPosBytes = encodeUint64(0x0000017c71959567) 39 ) 40 41 func TestConvertTimeToEpochMillis(t *testing.T) { 42 tests := []struct { 43 name string 44 input time.Time 45 expected int64 46 err string 47 }{ 48 {"epoch", timestampEpoch, 0, ""}, 49 {"epoch zoned", timestampEpochZoned, 0, ""}, 50 {"negative", timestampNegUTC, -584499599001, ""}, 51 {"negative zoned", timestampNegZoned, -584499599001, ""}, 52 {"positive", timestampPosUTC, 1633993200999, ""}, 53 {"positive zoned", timestampPosZoned, 1633993200999, ""}, 54 {"min", TimestampMin, math.MinInt64, ""}, 55 {"max", TimestampMax, math.MaxInt64, ""}, 56 {"out of range negative", timestampOufOfRangeNeg, 0, "value out of range: -292275055-05-16 16:47:04.191 +0000 UTC"}, 57 {"out of range positive", timestampOufOfRangePos, 0, "value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"}, 58 } 59 for _, tt := range tests { 60 t.Run(tt.name, func(t *testing.T) { 61 actual, err := ConvertTimeToEpochMillis(tt.input) 62 assert.Equal(t, tt.expected, actual) 63 assertErrorMessage(t, tt.err, err) 64 }) 65 } 66 } 67 68 func TestConvertEpochMillisToTime(t *testing.T) { 69 tests := []struct { 70 name string 71 input int64 72 expected time.Time 73 }{ 74 {"epoch", 0, timestampEpoch}, 75 {"negative", -584499599001, timestampNegUTC}, 76 {"positive", 1633993200999, timestampPosUTC}, 77 {"min", math.MinInt64, TimestampMin}, 78 {"max", math.MaxInt64, TimestampMax}, 79 } 80 for _, tt := range tests { 81 t.Run(tt.name, func(t *testing.T) { 82 actual := ConvertEpochMillisToTime(tt.input) 83 assert.True(t, tt.expected.Equal(actual)) 84 }) 85 } 86 } 87 88 func Test_timestampCodec_Encode(t *testing.T) { 89 for _, version := range primitive.SupportedProtocolVersions() { 90 t.Run(version.String(), func(t *testing.T) { 91 tests := []struct { 92 name string 93 source interface{} 94 expected []byte 95 err string 96 }{ 97 {"nil", nil, nil, ""}, 98 {"nil pointer", timeNilPtr(), nil, ""}, 99 {"non nil", timestampPosUTC, timestampPosBytes, ""}, 100 {"conversion failed", timestampOufOfRangePos, nil, fmt.Sprintf("cannot encode time.Time as CQL timestamp with %v: cannot convert from time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC", version)}, 101 } 102 for _, tt := range tests { 103 t.Run(tt.name, func(t *testing.T) { 104 actual, err := Timestamp.Encode(tt.source, version) 105 assert.Equal(t, tt.expected, actual) 106 assertErrorMessage(t, tt.err, err) 107 }) 108 } 109 }) 110 } 111 } 112 113 func Test_timestampCodec_Decode(t *testing.T) { 114 for _, version := range primitive.SupportedProtocolVersions() { 115 t.Run(version.String(), func(t *testing.T) { 116 tests := []struct { 117 name string 118 source []byte 119 dest interface{} 120 expected interface{} 121 wasNull bool 122 err string 123 }{ 124 {"null", nil, new(int64), new(int64), true, ""}, 125 {"non null", timestampPosBytes, new(time.Time), ×tampPosUTC, false, ""}, 126 {"non null interface", timestampPosBytes, new(interface{}), interfacePtr(timestampPosUTC), false, ""}, 127 {"read failed", []byte{1}, new(int64), new(int64), false, fmt.Sprintf("cannot decode CQL timestamp as *int64 with %v: cannot read int64: expected 8 bytes but got: 1", version)}, 128 {"conversion failed", timestampPosBytes, new(float64), new(float64), false, fmt.Sprintf("cannot decode CQL timestamp as *float64 with %v: cannot convert from int64 to *float64: conversion not supported", version)}, 129 } 130 for _, tt := range tests { 131 t.Run(tt.name, func(t *testing.T) { 132 wasNull, err := Timestamp.Decode(tt.source, tt.dest, version) 133 assert.Equal(t, tt.expected, tt.dest) 134 assert.Equal(t, tt.wasNull, wasNull) 135 assertErrorMessage(t, tt.err, err) 136 }) 137 } 138 }) 139 } 140 } 141 142 func Test_convertToInt64Timestamp(t *testing.T) { 143 for _, layout := range []string{TimestampLayoutDefault, "2006-01-02 15:04:05.999 MST"} { 144 t.Run(layout, func(t *testing.T) { 145 for _, location := range []*time.Location{time.UTC, paris} { 146 t.Run(location.String(), func(t *testing.T) { 147 ts := timestampPosUTC.In(location) 148 tests := []struct { 149 name string 150 source interface{} 151 wantVal int64 152 wantWasNil bool 153 wantErr string 154 }{ 155 {"from time", ts, 1633993200999, false, ""}, 156 {"from time out of range", timestampOufOfRangePos, 0, false, "cannot convert from time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"}, 157 {"from *time nil", timeNilPtr(), 0, true, ""}, 158 {"from *time non nil", &ts, 1633993200999, false, ""}, 159 {"from *time out of range", ×tampOufOfRangePos, 0, false, "cannot convert from *time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"}, 160 {"from string", ts.Format(layout), 1633993200999, false, ""}, 161 {"from string malformed", "not a timestamp", 0, false, "cannot convert from string to int64: parsing time \"not a timestamp\" as \"" + layout + "\""}, 162 {"from *string nil", stringNilPtr(), 0, true, ""}, 163 {"from *string non nil", stringPtr(ts.Format(layout)), 1633993200999, false, ""}, 164 {"from *string malformed", stringPtr("not a timestamp"), 0, false, "cannot convert from *string to int64: parsing time \"not a timestamp\" as \"" + layout + "\""}, 165 {"from untyped nil", nil, 0, true, ""}, 166 {"from numeric", 1633993200999, 1633993200999, false, ""}, 167 } 168 for _, tt := range tests { 169 t.Run(tt.name, func(t *testing.T) { 170 gotVal, gotWasNil, gotErr := convertToInt64Timestamp(tt.source, layout, location) 171 assert.Equal(t, tt.wantVal, gotVal) 172 assert.Equal(t, tt.wantWasNil, gotWasNil) 173 assertErrorMessage(t, tt.wantErr, gotErr) 174 }) 175 } 176 }) 177 } 178 }) 179 } 180 } 181 182 func Test_convertFromInt64Timestamp(t *testing.T) { 183 for _, layout := range []string{TimestampLayoutDefault, "2006-01-02 15:04:05 MST"} { 184 t.Run(layout, func(t *testing.T) { 185 for _, location := range []*time.Location{time.UTC, paris} { 186 t.Run(location.String(), func(t *testing.T) { 187 ts := timestampPosUTC.In(location) 188 tests := []struct { 189 name string 190 val int64 191 wasNull bool 192 dest interface{} 193 expected interface{} 194 wantErr string 195 }{ 196 {"to *interface{} nil dest", 1, false, interfaceNilPtr(), interfaceNilPtr(), "cannot convert from int64 to *interface {}: destination is nil"}, 197 {"to *interface{} nil source", 0, true, new(interface{}), new(interface{}), ""}, 198 {"to *interface{} non nil", 1633993200999, false, new(interface{}), interfacePtr(ts), ""}, 199 {"to *time nil dest", 1, false, timeNilPtr(), timeNilPtr(), "cannot convert from int64 to *time.Time: destination is nil"}, 200 {"to *time nil source", 0, true, new(time.Time), new(time.Time), ""}, 201 {"to *time", 1633993200999, false, new(time.Time), &ts, ""}, 202 {"to *string nil dest", 1, false, stringNilPtr(), stringNilPtr(), "cannot convert from int64 to *string: destination is nil"}, 203 {"to *string nil source", 0, true, new(string), new(string), ""}, 204 {"to *string", 1633993200999, false, new(string), stringPtr(ts.Format(layout)), ""}, 205 {"to numeric", 1234, false, new(int32), int32Ptr(1234), ""}, 206 } 207 for _, tt := range tests { 208 t.Run(tt.name, func(t *testing.T) { 209 gotErr := convertFromInt64Timestamp(tt.val, tt.wasNull, tt.dest, layout, location) 210 assert.Equal(t, tt.expected, tt.dest) 211 assertErrorMessage(t, tt.wantErr, gotErr) 212 }) 213 } 214 }) 215 } 216 }) 217 } 218 }