github.com/segmentio/encoding@v0.4.0/iso8601/parse_test.go (about) 1 package iso8601 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 ) 8 9 func TestParse(t *testing.T) { 10 for _, input := range []string{ 11 // Fast path (20 bytes) 12 "1987-12-16T23:45:12Z", 13 "2006-01-02T15:04:05Z", 14 "2000-02-29T23:59:59Z", // leap year 15 "2020-02-29T23:59:59Z", // leap year 16 "0000-01-01T00:00:00Z", 17 "9999-12-31T23:59:59Z", 18 19 // Fast path (24 bytes) 20 "1987-12-16T23:45:12.123Z", 21 "2006-01-02T15:04:05.123Z", 22 "2000-02-29T23:59:59.123Z", 23 "2020-02-29T23:59:59.123Z", 24 "0000-01-01T00:00:00.000Z", 25 "9999-12-31T23:59:59.999Z", 26 27 // Fast path (30 bytes) 28 "1987-12-16T23:45:12.123456789Z", 29 "2006-01-02T15:04:05.123456789Z", 30 "2000-02-29T23:59:59.123456789Z", 31 "2020-02-29T23:59:59.123456789Z", 32 "0000-01-01T00:00:00.000000000Z", 33 "9999-12-31T23:59:59.999999999Z", 34 35 // Slow path 36 "2006-01-02T15:04:05.1Z", 37 "2006-01-02T15:04:05.12Z", 38 "2006-01-02T15:04:05.1234Z", 39 "2006-01-02T15:04:05.12345Z", 40 "2006-01-02T15:04:05.123456Z", 41 "2006-01-02T15:04:05.1234567Z", 42 "2006-01-02T15:04:05.12345678Z", 43 "2021-10-16T07:55:07+10:00", 44 "2021-10-16T07:55:07.1+10:00", 45 "2021-10-16T07:55:07.12+10:00", 46 "2021-10-16T07:55:07.123+10:00", 47 "2021-10-16T07:55:07.1234+10:00", 48 "2021-10-16T07:55:07.12345+10:00", 49 "2021-10-16T07:55:07.123456+10:00", 50 "2021-10-16T07:55:07.1234567+10:00", 51 "2021-10-16T07:55:07.12345678+10:00", 52 "2021-10-16T07:55:07.123456789+10:00", 53 "2021-10-16T07:55:07-10:00", 54 "2021-10-16T07:55:07.1-10:00", 55 "2021-10-16T07:55:07.12-10:00", 56 "2021-10-16T07:55:07.123-10:00", 57 "2021-10-16T07:55:07.1234-10:00", 58 "2021-10-16T07:55:07.12345-10:00", 59 "2021-10-16T07:55:07.123456-10:00", 60 "2021-10-16T07:55:07.1234567-10:00", 61 "2021-10-16T07:55:07.12345678-10:00", 62 "2021-10-16T07:55:07.123456789-10:00", 63 } { 64 t.Run(input, func(t *testing.T) { 65 expect, err := time.Parse(time.RFC3339Nano, input) 66 if err != nil { 67 t.Fatal(err) 68 } 69 actual, err := Parse(input) 70 if err != nil { 71 t.Error(err) 72 } else if !actual.Equal(expect) { 73 t.Errorf("unexpected time: %v vs expected %v", actual, expect) 74 } else if actual.Location().String() != expect.Location().String() { 75 t.Errorf("unexpected timezone: %v vs expected %v", actual.Location().String(), expect.Location().String()) 76 } 77 }) 78 } 79 80 // Check ~4M YYYY-MM-DD dates in 20 byte form. 81 for year := 0; year <= 9999; year++ { 82 for month := 0; month <= 13; month++ { 83 for day := 0; day <= 32; day++ { 84 input := fmt.Sprintf("%04d-%02d-%02dT12:34:56Z", year, month, day) 85 expect, expectErr := time.Parse(time.RFC3339Nano, input) 86 actual, actualErr := Parse(input) 87 if (expectErr != nil) != (actualErr != nil) { 88 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 89 } else if !actual.Equal(expect) { 90 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 91 } 92 } 93 } 94 } 95 96 // Check ~4M YYYY-MM-DD dates in 24 byte form. 97 for year := 0; year <= 9999; year++ { 98 for month := 0; month <= 13; month++ { 99 for day := 0; day <= 32; day++ { 100 input := fmt.Sprintf("%04d-%02d-%02dT12:34:56.789Z", year, month, day) 101 expect, expectErr := time.Parse(time.RFC3339Nano, input) 102 actual, actualErr := Parse(input) 103 if (expectErr != nil) != (actualErr != nil) { 104 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 105 } else if !actual.Equal(expect) { 106 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 107 } 108 } 109 } 110 } 111 112 // Check ~4M YYYY-MM-DD dates in 30 byte form. 113 for year := 0; year <= 9999; year++ { 114 for month := 0; month <= 13; month++ { 115 for day := 0; day <= 32; day++ { 116 input := fmt.Sprintf("%04d-%02d-%02dT12:34:56.123456789Z", year, month, day) 117 expect, expectErr := time.Parse(time.RFC3339Nano, input) 118 actual, actualErr := Parse(input) 119 if (expectErr != nil) != (actualErr != nil) { 120 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 121 } else if !actual.Equal(expect) { 122 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 123 } 124 } 125 } 126 } 127 128 // Check all ~1M HH:MM:SS times in 20 byte form. 129 for hour := 0; hour < 100; hour++ { 130 for minute := 0; minute < 100; minute++ { 131 for second := 0; second < 100; second++ { 132 input := fmt.Sprintf("2000-01-01T%02d:%02d:%02dZ", hour, minute, second) 133 expect, expectErr := time.Parse(time.RFC3339Nano, input) 134 actual, actualErr := Parse(input) 135 if (expectErr != nil) != (actualErr != nil) { 136 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 137 } else if !actual.Equal(expect) { 138 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 139 } 140 } 141 } 142 } 143 144 // Check ~1M HH:MM:SS.MMM times in 24 byte form. 145 for hour := 0; hour < 100; hour++ { 146 for minute := 0; minute < 100; minute++ { 147 for second := 0; second < 100; second++ { 148 input := fmt.Sprintf("2000-01-01T%02d:%02d:%02d.123Z", hour, minute, second) 149 expect, expectErr := time.Parse(time.RFC3339Nano, input) 150 actual, actualErr := Parse(input) 151 if (expectErr != nil) != (actualErr != nil) { 152 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 153 } else if !actual.Equal(expect) { 154 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 155 } 156 } 157 } 158 } 159 160 // Check ~1M HH:MM:SS.MMM times in 30 byte form. 161 for hour := 0; hour < 100; hour++ { 162 for minute := 0; minute < 100; minute++ { 163 for second := 0; second < 100; second++ { 164 input := fmt.Sprintf("2000-01-01T%02d:%02d:%02d.123456789Z", hour, minute, second) 165 expect, expectErr := time.Parse(time.RFC3339Nano, input) 166 actual, actualErr := Parse(input) 167 if (expectErr != nil) != (actualErr != nil) { 168 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 169 } else if !actual.Equal(expect) { 170 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 171 } 172 } 173 } 174 } 175 176 // Check milliseconds. 177 for millis := 1; millis < 1000; millis <<= 1 { 178 input := fmt.Sprintf("2000-01-01T00:00:00.%03dZ", millis) 179 expect, expectErr := time.Parse(time.RFC3339Nano, input) 180 actual, actualErr := Parse(input) 181 if (expectErr != nil) != (actualErr != nil) { 182 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 183 } else if !actual.Equal(expect) { 184 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 185 } 186 } 187 188 // Check nanoseconds. 189 for nanos := 1; nanos < 1e9; nanos <<= 1 { 190 input := fmt.Sprintf("2000-01-01T00:00:00.%09dZ", nanos) 191 expect, expectErr := time.Parse(time.RFC3339Nano, input) 192 actual, actualErr := Parse(input) 193 if (expectErr != nil) != (actualErr != nil) { 194 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 195 } else if !actual.Equal(expect) { 196 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 197 } 198 } 199 200 // Check with trailing zeroes omitted. 201 for n := 1; n < 1e9; n <<= 1 { 202 input := fmt.Sprintf("2000-01-01T00:00:00.%dZ", n) 203 expect, expectErr := time.Parse(time.RFC3339Nano, input) 204 actual, actualErr := Parse(input) 205 if (expectErr != nil) != (actualErr != nil) { 206 t.Errorf("unexpected error for %v: %v vs. %v expected", input, actualErr, expectErr) 207 } else if !actual.Equal(expect) { 208 t.Errorf("unexpected time for %v: %v vs. %v expected", input, actual, expect) 209 } 210 } 211 } 212 213 func TestParseInvalid(t *testing.T) { 214 for _, input := range []string{ 215 // 20 bytes 216 "XXXXXXXXXXXXXXXXXXXX", 217 "00000000000000000000", 218 "1900-02-29T00:00:00Z", // 28 days in month (not a leap year) 219 "2021-02-29T00:00:00Z", // 28 days in month (not a leap year) 220 "2021-02-30T00:00:00Z", // 28 days in month 221 "2021-02-31T00:00:00Z", // 28 days in month 222 "2021-04-31T00:00:00Z", // 30 days in month 223 "2021-06-31T00:00:00Z", // 30 days in month 224 "2021-09-31T00:00:00Z", // 30 days in month 225 "2021-11-31T00:00:00Z", // 30 days in month 226 "XXXX-13-01T00:00:00Z", // invalid year 227 "2000-13-01T00:00:00Z", // invalid month (1) 228 "2000-00-01T00:00:00Z", // invalid month (2) 229 "2000-XX-01T00:00:00Z", // invalid month (3) 230 "2000-12-32T00:00:00Z", // invalid day (1) 231 "2000-12-00T00:00:00Z", // invalid day (2) 232 "2000-12-XXT00:00:00Z", // invalid day (3) 233 "2000-12-31T24:00:00Z", // invalid hour (1) 234 "2000-12-31TXX:00:00Z", // invalid hour (2) 235 "2000-12-31T23:60:00Z", // invalid minute (1) 236 "2000-12-31T23:XX:00Z", // invalid minute (2) 237 "2000-12-31T23:59:60Z", // invalid second (1) 238 "2000-12-31T23:59:XXZ", // invalid second (2) 239 "1999-01-01 23:45:00Z", // missing T separator 240 "1999 01-01T23:45:00Z", // missing date separator (1) 241 "1999-01 01T23:45:00Z", // missing date separator (2) 242 "1999-01-01T23 45:00Z", // missing time separator (1) 243 "1999-01-01T23:45 00Z", // missing time separator (2) 244 "1999-01-01T23:45:00 ", // missing timezone 245 "1999-01-01t23:45:00Z", // lowercase T 246 "1999-01-01T23:45:00z", // lowercase Z 247 "X999-01-01T23:45:00Z", // X in various positions 248 "1X99-01-01T23:45:00Z", 249 "19X9-01-01T23:45:00Z", 250 "199X-01-01T23:45:00Z", 251 "1999X01-01T23:45:00Z", 252 "1999-X1-01T23:45:00Z", 253 "1999-0X-01T23:45:00Z", 254 "1999-01X01T23:45:00Z", 255 "1999-01-X1T23:45:00Z", 256 "1999-01-0XT23:45:00Z", 257 "1999-01-01X23:45:00Z", 258 "1999-01-01TX3:45:00Z", 259 "1999-01-01T2X:45:00Z", 260 "1999-01-01T23X45:00Z", 261 "1999-01-01T23:X5:00Z", 262 "1999-01-01T23:4X:00Z", 263 "1999-01-01T23:45X00Z", 264 "1999-01-01T23:45:X0Z", 265 "1999-01-01T23:45:0XZ", 266 "1999-01-01T23:45:00X", 267 268 // 24 bytes 269 "XXXXXXXXXXXXXXXXXXXXXXXX", 270 "000000000000000000000000", 271 "1900-02-29T00:00:00.123Z", // 28 days in month (not a leap year) 272 "2021-02-29T00:00:00.123Z", // 28 days in month (not a leap year) 273 "2021-02-30T00:00:00.123Z", // 28 days in month 274 "2021-02-31T00:00:00.123Z", // 28 days in month 275 "2021-04-31T00:00:00.123Z", // 30 days in month 276 "2021-06-31T00:00:00.123Z", // 30 days in month 277 "2021-09-31T00:00:00.123Z", // 30 days in month 278 "2021-11-31T00:00:00.123Z", // 30 days in month 279 "XXXX-13-01T00:00:00.123Z", // invalid year 280 "2000-13-01T00:00:00.123Z", // invalid month (1) 281 "2000-00-01T00:00:00.123Z", // invalid month (2) 282 "2000-XX-01T00:00:00.123Z", // invalid month (3) 283 "2000-12-32T00:00:00.123Z", // invalid day (1) 284 "2000-12-00T00:00:00.123Z", // invalid day (2) 285 "2000-12-XXT00:00:00.123Z", // invalid day (3) 286 "2000-12-31T24:00:00.123Z", // invalid hour (1) 287 "2000-12-31TXX:00:00.123Z", // invalid hour (2) 288 "2000-12-31T23:60:00.123Z", // invalid minute (1) 289 "2000-12-31T23:XX:00.123Z", // invalid minute (2) 290 "2000-12-31T23:59:60.123Z", // invalid second (1) 291 "2000-12-31T23:59:XX.123Z", // invalid second (2) 292 "2000-12-31T23:59:59.XXXZ", // invalid millis 293 "1999-01-01 23:45:00.123Z", // missing T separator 294 "1999 01-01T23:45:00.123Z", // missing date separator (1) 295 "1999-01 01T23:45:00.123Z", // missing date separator (2) 296 "1999-01-01T23 45:00.123Z", // missing time separator (1) 297 "1999-01-01T23:45 00.123Z", // missing time separator (2) 298 "1999-01-01T23:45:00 123Z", // missing time separator (3) 299 "1999-01-01T23:45:00.123 ", // missing timezone 300 "1999-01-01t23:45:00.123Z", // lowercase T 301 "1999-01-01T23:45:00.123z", // lowercase Z 302 "X999-01-01T23:45:00.123Z", // X in various positions 303 "1X99-01-01T23:45:00.123Z", 304 "19X9-01-01T23:45:00.123Z", 305 "199X-01-01T23:45:00.123Z", 306 "1999X01-01T23:45:00.123Z", 307 "1999-X1-01T23:45:00.123Z", 308 "1999-0X-01T23:45:00.123Z", 309 "1999-01X01T23:45:00.123Z", 310 "1999-01-X1T23:45:00.123Z", 311 "1999-01-0XT23:45:00.123Z", 312 "1999-01-01X23:45:00.123Z", 313 "1999-01-01TX3:45:00.123Z", 314 "1999-01-01T2X:45:00.123Z", 315 "1999-01-01T23X45:00.123Z", 316 "1999-01-01T23:X5:00.123Z", 317 "1999-01-01T23:4X:00.123Z", 318 "1999-01-01T23:45X00.123Z", 319 "1999-01-01T23:45:X0.123Z", 320 "1999-01-01T23:45:0X.123Z", 321 "1999-01-01T23:45:00X123Z", 322 "1999-01-01T23:45:00.X23Z", 323 "1999-01-01T23:45:00.1X3Z", 324 "1999-01-01T23:45:00.12XZ", 325 "1999-01-01T23:45:00.123X", 326 327 // 30 bytes 328 "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 329 "000000000000000000000000000000", 330 "1900-02-29T00:00:00.123456789Z", // 28 days in month (not a leap year) 331 "2021-02-29T00:00:00.123456789Z", // 28 days in month (not a leap year) 332 "2021-02-30T00:00:00.123456789Z", // 28 days in month 333 "2021-02-31T00:00:00.123456789Z", // 28 days in month 334 "2021-04-31T00:00:00.123456789Z", // 30 days in month 335 "2021-06-31T00:00:00.123456789Z", // 30 days in month 336 "2021-09-31T00:00:00.123456789Z", // 30 days in month 337 "2021-11-31T00:00:00.123456789Z", // 30 days in month 338 "XXXX-13-01T00:00:00.123456789Z", // invalid year 339 "2000-13-01T00:00:00.123456789Z", // invalid month (1) 340 "2000-00-01T00:00:00.123456789Z", // invalid month (2) 341 "2000-XX-01T00:00:00.123456789Z", // invalid month (3) 342 "2000-12-32T00:00:00.123456789Z", // invalid day (1) 343 "2000-12-00T00:00:00.123456789Z", // invalid day (2) 344 "2000-12-XXT00:00:00.123456789Z", // invalid day (3) 345 "2000-12-31T24:00:00.123456789Z", // invalid hour (1) 346 "2000-12-31TXX:00:00.123456789Z", // invalid hour (2) 347 "2000-12-31T23:60:00.123456789Z", // invalid minute (1) 348 "2000-12-31T23:XX:00.123456789Z", // invalid minute (2) 349 "2000-12-31T23:59:60.123456789Z", // invalid second (1) 350 "2000-12-31T23:59:XX.123456789Z", // invalid second (2) 351 "2000-12-31T23:59:59.XXXXXXXXXZ", // invalid nanos 352 "1999-01-01 23:45:00.123456789Z", // missing T separator 353 "1999 01-01T23:45:00.123456789Z", // missing date separator (1) 354 "1999-01 01T23:45:00.123456789Z", // missing date separator (2) 355 "1999-01-01T23 45:00.123456789Z", // missing time separator (1) 356 "1999-01-01T23:45 00.123456789Z", // missing time separator (2) 357 "1999-01-01T23:45:00 123456789Z", // missing time separator (3) 358 "1999-01-01T23:45:00.123456789 ", // missing timezone 359 "1999-01-01t23:45:00.123456789Z", // lowercase T 360 "1999-01-01T23:45:00.123456789z", // lowercase Z 361 "X999-01-01T23:45:00.123456789Z", // X in various positions 362 "1X99-01-01T23:45:00.123456789Z", 363 "19X9-01-01T23:45:00.123456789Z", 364 "199X-01-01T23:45:00.123456789Z", 365 "1999X01-01T23:45:00.123456789Z", 366 "1999-X1-01T23:45:00.123456789Z", 367 "1999-0X-01T23:45:00.123456789Z", 368 "1999-01X01T23:45:00.123456789Z", 369 "1999-01-X1T23:45:00.123456789Z", 370 "1999-01-0XT23:45:00.123456789Z", 371 "1999-01-01X23:45:00.123456789Z", 372 "1999-01-01TX3:45:00.123456789Z", 373 "1999-01-01T2X:45:00.123456789Z", 374 "1999-01-01T23X45:00.123456789Z", 375 "1999-01-01T23:X5:00.123456789Z", 376 "1999-01-01T23:4X:00.123456789Z", 377 "1999-01-01T23:45X00.123456789Z", 378 "1999-01-01T23:45:X0.123456789Z", 379 "1999-01-01T23:45:0X.123456789Z", 380 "1999-01-01T23:45:00X123456789Z", 381 "1999-01-01T23:45:00.X23456789Z", 382 "1999-01-01T23:45:00.1X3456789Z", 383 "1999-01-01T23:45:00.12X456789Z", 384 "1999-01-01T23:45:00.123X56789Z", 385 "1999-01-01T23:45:00.1234X6789Z", 386 "1999-01-01T23:45:00.12345X789Z", 387 "1999-01-01T23:45:00.123456X89Z", 388 "1999-01-01T23:45:00.1234567X9Z", 389 "1999-01-01T23:45:00.12345678XZ", 390 "1999-01-01T23:45:00.123456789X", 391 392 "2000-01-01T00:00:00.Z", // missing number after decimal point 393 } { 394 t.Run(input, func(t *testing.T) { 395 ts, err := time.Parse(time.RFC3339Nano, input) 396 if err == nil { 397 t.Fatalf("expected time.Parse('%s') error, got %v", input, ts) 398 } 399 ts, actualErr := Parse(input) 400 if (err != nil) != (actualErr != nil) { 401 t.Fatalf("expected Parse('%s') error %v, got %v", input, err, actualErr) 402 } 403 }) 404 } 405 } 406 407 func BenchmarkParse(b *testing.B) { 408 for i := 0; i < b.N; i++ { 409 Parse("2006-01-02T15:04:05Z") 410 } 411 } 412 413 func BenchmarkParseMilliseconds(b *testing.B) { 414 for i := 0; i < b.N; i++ { 415 Parse("2006-01-02T15:04:05.123Z") 416 } 417 } 418 419 func BenchmarkParseMicroseconds(b *testing.B) { 420 for i := 0; i < b.N; i++ { 421 Parse("2006-01-02T15:04:05.123456Z") 422 } 423 } 424 425 func BenchmarkParseNanoseconds(b *testing.B) { 426 for i := 0; i < b.N; i++ { 427 Parse("2006-01-02T15:04:05.123456789Z") 428 } 429 } 430 431 func BenchmarkParseInvalid(b *testing.B) { 432 for i := 0; i < b.N; i++ { 433 Parse("2006-01-02T15:04:05.XZ") 434 } 435 }