github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/pgdate/field_extract.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package pgdate 12 13 import ( 14 "strconv" 15 "strings" 16 "time" 17 "unicode" 18 "unicode/utf8" 19 20 "github.com/cockroachdb/errors" 21 "github.com/cockroachdb/redact" 22 ) 23 24 // numberChunk associates a value with a leading separator, 25 // such as '-' or '/'. 26 type numberChunk struct { 27 separator rune 28 // The numeric value of the chunk. 29 v int 30 // The magnitude of the value (i.e. how many digits). 31 magnitude int 32 } 33 34 func (n numberChunk) String() string { return redact.StringWithoutMarkers(n) } 35 36 // SafeFormat implements the redact.SafeFormatter interface. 37 func (n numberChunk) SafeFormat(w redact.SafePrinter, _ rune) { 38 if n.separator == utf8.RuneError { 39 w.Print(n.v) 40 return 41 } 42 w.Printf("%c%d", n.separator, n.v) 43 } 44 45 // fieldExtract manages the state of a date/time parsing operation. 46 // This handles cases where a field, such as Julian date, 47 // would conflict with also setting the year. 48 type fieldExtract struct { 49 // The field data is stored in a fixed-size array. 50 data [fieldMaximum + 1]int 51 // Tracks the fields that have been set, to distinguish 0 from unset. 52 has fieldSet 53 // Provides a time for evaluating relative dates as well as a 54 // timezone. Should only be used via the now() and location() accessors. 55 currentTime time.Time 56 57 // location is set to the timezone specified by the timestamp (if any). 58 location *time.Location 59 60 dateStyle DateStyle 61 // The fields that must be present to succeed. 62 required fieldSet 63 // Stores a reference to one of the sentinel values, to be returned 64 // by the makeDateTime() functions 65 sentinel *time.Time 66 // Tracks the sign of the timezone offset. We need to track 67 // this separately from the sign of the tz1 value in case 68 // we're trying to store a (nonsensical) value like -0030. 69 tzSign int 70 // Tracks the fields that we want to extract. 71 wanted fieldSet 72 73 textChunksScratch [fieldMaximum]stringChunk 74 numbersScratch [fieldMaximum]numberChunk 75 76 // This indicates that the value in the year field was only 77 // two digits and should be adjusted to make it recent. 78 tweakYear bool 79 // currentTimeUsed is set if we consulted currentTime (indicating if the 80 // result depends on the context). 81 currentTimeUsed bool 82 // Tracks whether the current timestamp is of db2 format. 83 isDB2 bool 84 } 85 86 func (fe *fieldExtract) now() time.Time { 87 fe.currentTimeUsed = true 88 return fe.currentTime 89 } 90 91 func (fe *fieldExtract) getLocation() *time.Location { 92 if fe.location != nil { 93 return fe.location 94 } 95 fe.currentTimeUsed = true 96 return fe.currentTime.Location() 97 } 98 99 // Extract is the top-level function. It attempts to break the input 100 // string into a collection of date/time fields in order to populate a 101 // fieldExtract. 102 func (fe *fieldExtract) Extract(s string) error { 103 textChunks := fe.textChunksScratch[:fieldMaximum] 104 // Break the string into alphanumeric chunks. 105 count, _ := chunk(s, textChunks) 106 107 if count < 0 { 108 return inputErrorf("too many fields in input") 109 } else if count == 0 { 110 return inputErrorf("empty or blank input") 111 } 112 113 // Create a place to store extracted numeric info. 114 numbers := fe.numbersScratch[:0] 115 116 appendNumber := func(prefix, number string) error { 117 v, err := strconv.Atoi(number) 118 if err != nil { 119 return err 120 } 121 122 // Allow exactly one non-whitespace separator. 123 s := ' ' 124 for _, r := range prefix { 125 switch { 126 case s == ' ': 127 s = r 128 case unicode.IsSpace(r): 129 // Ignore whitespace characters. 130 default: 131 return inputErrorf(`detected multiple separators in "%s""`, prefix) 132 } 133 } 134 135 numbers = append(numbers, numberChunk{separator: s, v: v, magnitude: len(number)}) 136 return nil 137 } 138 139 var leftoverText string 140 141 // First, we'll try to pluck out any keywords that exist in the input. 142 // If a chunk is not a keyword or other special-case pattern, it 143 // must be a numeric value, which we'll pluck out for a second 144 // pass. If we see certain sentinel values, we'll pick them out, 145 // but keep going to ensure that the user hasn't written something 146 // like "epoch infinity". 147 for idx, chunk := range textChunks[:count] { 148 match := strings.ToLower(chunk.Match) 149 150 switch match { 151 case keywordEpoch: 152 if err := fe.matchedSentinel(TimeEpoch, match); err != nil { 153 return err 154 } 155 156 case keywordInfinity: 157 if strings.HasSuffix(chunk.NotMatch, "-") { 158 if err := fe.matchedSentinel(TimeNegativeInfinity, match); err != nil { 159 return err 160 } 161 } else { 162 if err := fe.matchedSentinel(TimeInfinity, match); err != nil { 163 return err 164 } 165 } 166 167 case keywordNow: 168 if err := fe.matchedSentinel(fe.now(), match); err != nil { 169 return err 170 } 171 172 default: 173 // The most probable case is that we have a numeric input. 174 if err := appendNumber(chunk.NotMatch, match); err == nil { 175 continue 176 } 177 178 // Fan out to other keyword-based extracts. 179 if m, ok := keywordSetters[match]; ok { 180 if err := m(fe, match); err != nil { 181 return err 182 } 183 184 // This detects a format like 01-02-Jan. While we could 185 // figure it out if one of those were a four-digit number, 186 // this is consistent with PostgreSQL 10.5 behavior. 187 // We should only ever see a text month in field 0 or 1. 188 if idx == 2 && fe.Wants(fieldYear) && !fe.Wants(fieldMonth) && fe.Wants(fieldDay) { 189 return inputErrorf("no such thing as a YDM or DYM format") 190 } 191 continue 192 } 193 194 // Handle the oddball Z and Zulu suffixes. Try stripping the 195 // suffix and appending the resulting number. 196 if strings.HasSuffix(match, keywordZ) { 197 if err := fieldSetterUTC(fe, ""); err != nil { 198 return err 199 } 200 maybeMatch := match[:len(match)-len(keywordZ)] 201 if err := appendNumber(chunk.NotMatch, maybeMatch); err == nil { 202 continue 203 } 204 205 } else if strings.HasSuffix(match, keywordZulu) { 206 if err := fieldSetterUTC(fe, ""); err != nil { 207 return err 208 } 209 maybeMatch := match[:len(match)-len(keywordZulu)] 210 if err := appendNumber(chunk.NotMatch, maybeMatch); err == nil { 211 continue 212 } 213 } 214 215 // Try to parse Julian dates. 216 if matched, err := fieldSetterJulianDate(fe, match); matched { 217 if err != nil { 218 return err 219 } 220 continue 221 } 222 223 // Save off any leftover text, it might be a timezone name. 224 // We do this concatenation here because Region/City_Name 225 // would get split into two chunks. 226 leftoverText += strings.TrimSpace(chunk.NotMatch) + chunk.Match 227 } 228 } 229 230 // See if our leftover text is a timezone name. 231 if leftoverText != "" { 232 if loc, err := zoneCacheInstance.LoadLocation(leftoverText); err == nil { 233 // Save off the timezone for later resolution to an offset. 234 fe.location = loc 235 236 // Since we're using a named location, we must have a date 237 // in order to compute daylight-savings time. 238 fe.required = fe.required.AddAll(dateRequiredFields) 239 240 // Remove TZ fields from the wanted list, but add a date 241 // in order to resolve the location's DST. Also, if we had a 242 // text month, ensure that it's also not in the wanted field. 243 fe.wanted = fe.wanted.AddAll(dateFields).ClearAll(fe.has.Add(fieldTZHour).Add(fieldTZMinute).Add(fieldTZSecond)) 244 } else { 245 return inputErrorf(`could not parse "%s"`, s) 246 } 247 } 248 249 if fe.sentinel != nil { 250 return nil 251 } 252 253 // In the second pass, we'll use pattern-matching and the knowledge 254 // of which fields have already been set in order to keep picking 255 // out field data. 256 textMonth := !fe.Wants(fieldMonth) 257 for i := range numbers { 258 if fe.wanted == 0 { 259 return inputErrorf("too many input fields") 260 } 261 if err := fe.interpretNumber(numbers, i, textMonth); err != nil { 262 return err 263 } 264 } 265 266 return fe.validate() 267 } 268 269 // Get returns the value of the requested field and whether or not 270 // that field has indeed been set. 271 func (fe *fieldExtract) Get(field field) (int, bool) { 272 return fe.data[field], fe.has.Has(field) 273 } 274 275 // interpretNumber applies pattern-matching rules to figure out which 276 // field the next chunk of input should be applied to. 277 func (fe *fieldExtract) interpretNumber(numbers []numberChunk, idx int, textMonth bool) error { 278 chunk := numbers[idx] 279 var nextSep rune 280 if len(numbers) > idx+1 { 281 nextSep = numbers[idx+1].separator 282 } 283 switch { 284 case chunk.separator == '.': 285 // Example: 04:04:04.913231+00:00, a fractional second. 286 // ^^^^^^ 287 // Example: 1999.123, a year + day-of-year. 288 // ^^^ 289 // Example: 04.04.04.913231+00:00, db2 timestamp 290 switch { 291 case chunk.magnitude == 3 && 292 !fe.Wants(fieldYear) && fe.Wants(fieldMonth) && fe.Wants(fieldDay) && 293 chunk.v >= 1 && chunk.v <= 366: 294 // Example: 1999 001, a year and day-of-year. 295 // ^^^ 296 // Example: 1999 123 297 // ^^^ 298 // BUT NOT: 1999 1 299 return fe.SetDayOfYear(chunk) 300 301 case fe.Wants(fieldMinute) && chunk.v <= 60 && chunk.v >= 0: 302 // db2 timestamp allows for minutes to recorded with periods. 303 // Example 13.12.50 (hh.mm.ss) 304 fe.isDB2 = true 305 return fe.SetChunk(fieldMinute, chunk) 306 case fe.isDB2 && fe.Wants(fieldSecond) && chunk.v <= 60 && chunk.v >= 0: 307 // db2 timestamp allows for seconds to recorded with periods. 308 return fe.SetChunk(fieldSecond, chunk) 309 case !fe.Wants(fieldSecond) && fe.Wants(fieldNanos): 310 // The only other place a period is valid is in a fractional 311 // second. We check to make sure that a second has been set. 312 313 // We need to "right-pad" the parsed integer value to nine 314 // places to wind up with a nanosecond value. Values with 315 // sub-nanosecond precision will be truncated. 316 var mult int 317 switch chunk.magnitude { 318 case 1: 319 mult = 100000000 320 case 2: 321 mult = 10000000 322 case 3: 323 mult = 1000000 324 case 4: 325 mult = 100000 326 case 5: 327 mult = 10000 328 case 6: 329 mult = 1000 330 case 7: 331 mult = 100 332 case 8: 333 mult = 10 334 case 9: 335 mult = 1 336 default: 337 mult = 1 338 for chunk.magnitude > 9 { 339 chunk.magnitude-- 340 chunk.v = chunk.v / 10 341 } 342 } 343 chunk.v *= mult 344 return fe.SetChunk(fieldNanos, chunk) 345 346 default: 347 return fe.decorateError(inputErrorf("cannot interpret field: %s", chunk)) 348 } 349 350 case chunk.magnitude == 3 && 351 !fe.Wants(fieldYear) && fe.Wants(fieldMonth) && fe.Wants(fieldDay) && 352 chunk.v >= 1 && chunk.v <= 366: 353 // Example: 1999 001, a year and day-of-year. 354 // ^^^ 355 // Example: 1999 123 356 // ^^^ 357 // BUT NOT: 1999 1 358 return fe.SetDayOfYear(chunk) 359 360 case fe.Wants(fieldYear) && fe.Wants(fieldMonth) && fe.Wants(fieldDay): 361 // Example: All date formats, we're starting from scratch. 362 switch { 363 // We examine the next separator to decide if this is a 364 // concatenated date or a really long year. If it's a - or / 365 // then this is one part of a date instead of the whole date. 366 case chunk.magnitude >= 6 && chunk.separator != '-' && nextSep != '-' && nextSep != '/': 367 // Example: "YYMMDD" 368 // ^^^^^^ 369 // Example: "YYYYMMDD" 370 // ^^^^^^^^ 371 // We're looking at some kind of concatenated date. We do want 372 // to exclude large-magnitude, negative years from this test. 373 374 // Record whether or not it's a two-digit year. 375 fe.tweakYear = chunk.magnitude == 6 376 if err := fe.Set(fieldDay, chunk.v%100); err != nil { 377 return err 378 } 379 chunk.v /= 100 380 if err := fe.Set(fieldMonth, chunk.v%100); err != nil { 381 return err 382 } 383 chunk.v /= 100 384 return fe.SetChunk(fieldYear, chunk) 385 386 case chunk.magnitude >= 3 || fe.dateStyle.Order == Order_YMD: 387 // Example: "YYYY MM DD" 388 // ^^^^ 389 // Example: "YYY MM DD" 390 // ^^^ 391 // Example: "YY MM DD" 392 // ^^ 393 // A three- or four-digit number must be a year. If we are in a 394 // year-first order, we'll accept the first chunk and possibly 395 // adjust a two-digit value later on. This means that 396 // 99 would get adjusted to 1999, but 0099 would not. 397 if chunk.separator == '-' { 398 chunk.v *= -1 399 } else if chunk.magnitude <= 2 { 400 fe.tweakYear = true 401 } 402 return fe.SetChunk(fieldYear, chunk) 403 case fe.dateStyle.Order == Order_DMY: 404 // Example: "DD MM YY" 405 // ^^ 406 // The first value is ambiguous, so we rely on the order. 407 return fe.SetChunk(fieldDay, chunk) 408 case fe.dateStyle.Order == Order_MDY: 409 // Example: "MM DD YY" 410 // ^^ 411 // The first value is ambiguous, so we rely on the order. 412 return fe.SetChunk(fieldMonth, chunk) 413 } 414 415 case !fe.Wants(fieldYear) && fe.Wants(fieldMonth) && fe.Wants(fieldDay): 416 // Example: "YY MM DD" 417 // ^^ 418 // Example: "YYYY MM DD" 419 // ^^ 420 // We have the year set and are looking for the month and day. 421 return fe.Set(fieldMonth, chunk.v) 422 423 case fe.Wants(fieldYear) && !fe.Wants(fieldMonth) && fe.Wants(fieldDay): 424 // Example: "YYYY Month DD" 425 // ^^^^ 426 // Example: "YYY Month DD" 427 // ^^^ 428 // Example: "MM DD YY"; only in MDY order. 429 // ^^ 430 // Example: "Month DD YY"; only in MDY order 431 // ^^ 432 // Example: "DD Month YY"; only in DMY order 433 // ^^ 434 // WARNING: "YY Month DD"; OK in YMD order. In other orders, we'll 435 // ^^ wind up storing the year in the day. 436 // This is fixed up below. 437 // The month has been set, but we don't yet have a year. If we know 438 // that the month was set in the first phase, we'll look for an 439 // obvious year or defer to the parsing order. 440 if textMonth && (chunk.magnitude >= 3 || fe.dateStyle.Order == Order_YMD) { 441 if chunk.magnitude <= 2 { 442 fe.tweakYear = true 443 } 444 return fe.SetChunk(fieldYear, chunk) 445 } 446 return fe.SetChunk(fieldDay, chunk) 447 448 case !fe.Wants(fieldYear) && !fe.Wants(fieldMonth) && fe.Wants(fieldDay): 449 // Example: "YY MM DD" 450 // ^^ 451 // WARNING: "DD MM YYYY"; may have mis-parsed the day value 452 // ^^ ^^^^ 453 // WARNING: "DD Month YYYY"; may have mis-parsed the day value 454 // ^^ ^^^^ 455 // Example: "YY Month DD" 456 // ^^ 457 // Example: "YYYY MM DD" 458 // ^^ 459 // Example: "YYYY Month DD" 460 // ^^ 461 // We should be looking at just the day component. However, we 462 // need to handle the case where we saw a two-digit year, but 463 // we're now looking at a value that can only be a year. 464 // If this happens, we'll swap the year and day, then set the year. 465 if textMonth && chunk.magnitude >= 3 && fe.tweakYear { 466 fe.tweakYear = false 467 year, _ := fe.Get(fieldYear) 468 if err := fe.Set(fieldDay, year); err != nil { 469 return err 470 } 471 return fe.Reset(fieldYear, chunk.v) 472 } 473 return fe.SetChunk(fieldDay, chunk) 474 475 case fe.Wants(fieldYear) && fe.Wants(fieldMonth) && !fe.Wants(fieldDay): 476 // Example: "DD MM YY" 477 // ^^ 478 // Example: "DD MM YYYY" 479 // ^^ 480 // BUT NOT: "Month DD YYYY"; text month set in first pass 481 return fe.SetChunk(fieldMonth, chunk) 482 483 case fe.Wants(fieldYear) && !fe.Wants(fieldMonth) && !fe.Wants(fieldDay): 484 // Example: "MM DD YY" 485 // ^^ 486 // Example: "MM DD YYYY" 487 // ^^^^ 488 // Handle MDY, DMY formats. 489 if chunk.magnitude <= 2 { 490 fe.tweakYear = true 491 } 492 return fe.SetChunk(fieldYear, chunk) 493 case !fe.Wants(fieldDay) && fe.Wants(fieldHour) && chunk.separator == '-' && nextSep == '.': 494 // Example: "YYYY-MM-DD-HH.MM.SS" 495 // ^^ 496 fe.isDB2 = true 497 return fe.SetChunk(fieldHour, chunk) 498 case fe.Wants(fieldTZHour) && (chunk.separator == '-' || chunk.separator == '+'): 499 // Example: "<Time> +04[:05:06]" 500 // ^^^ 501 // Example: "<Time> +0830" 502 // ^^^^^ 503 // Example: "<Time> +083000" 504 // ^^^^^^^ 505 // Example: "<Date> +08[:05:06]"; weird, but allowable. 506 // ^^^ 507 // Example: "<Date> +0830"; weird, but allowable. 508 // ^^^^^ 509 // Example: "<Date> +083000"; weird, but allowable. 510 // ^^^^^^^ 511 // We're looking at a chunk that begins with a + or -. We 512 // know that it's not a YYY-MM-DD value, since all date 513 // extract is handled in the previous cases. Since these 514 // separators don't occur for the HH fields, it must be 515 // a timezone. 516 517 if chunk.separator == '-' { 518 fe.tzSign = -1 519 } else { 520 fe.tzSign = 1 521 } 522 523 seconds := 0 524 switch chunk.magnitude { 525 case 1, 2: 526 // H or HH 527 return fe.SetChunk(fieldTZHour, chunk) 528 case 6: 529 // HHMMSS 530 seconds = chunk.v % 100 531 chunk.v /= 100 532 fallthrough 533 case 4: 534 // HHMM, but either force seconds to 0 or use value from above. 535 if err := fe.Set(fieldTZSecond, seconds); err != nil { 536 return err 537 } 538 if err := fe.Set(fieldTZMinute, chunk.v%100); err != nil { 539 return err 540 } 541 chunk.v /= 100 542 return fe.SetChunk(fieldTZHour, chunk) 543 544 default: 545 return fe.decorateError(inputErrorf("unexpected number of digits for timezone in: %s", chunk)) 546 } 547 548 case !fe.Wants(fieldTZHour) && fe.Wants(fieldTZMinute): 549 // Example: "<Time> +04:05[:06]" 550 // ^^ 551 // Example: "<Date> +08:05[:06]"; weird, but allowable. 552 // ^^ 553 // BUT NOT: "<Date> +08 HH:MM:SS" 554 // BUT NOT: "<Date> +08 HHMMSS" 555 // If we have the first part of a timezone, we're either going 556 // to see the TZ-minutes field or an HH field next. We can 557 // distinguish because the TZ-minutes field must have a 558 // colon separator. 559 if chunk.separator == ':' { 560 return fe.SetChunk(fieldTZMinute, chunk) 561 } 562 563 // The separator wasn't a colon, so we no longer except to see any 564 // timezone data. Mark the tz as completed and re-interpret the 565 // chunk. This retry only happens for valid inputs when the 566 // timezone is in the middle of a timestamp. 567 fe.has = fe.has.AddAll(tzFields) 568 fe.wanted = fe.wanted.ClearAll(tzFields) 569 return fe.interpretNumber(numbers, idx, textMonth) 570 571 case !fe.Wants(fieldTZHour) && !fe.Wants(fieldTZMinute) && fe.Wants(fieldTZSecond): 572 // Example: "<Time> +04:05:06" 573 // ^^ 574 // Example: "<Date> +08:05:06"; weird, but allowable. 575 // ^^ 576 // BUT NOT: "<Date> +08:30 HH:MM:SS" 577 // BUT NOT: "<Date> +08:30 HHMMSS" 578 // This case is exactly as the one above. 579 if chunk.separator == ':' { 580 return fe.SetChunk(fieldTZSecond, chunk) 581 } 582 583 // See the case above. 584 fe.has = fe.has.Add(fieldTZSecond) 585 fe.wanted = fe.wanted.Clear(fieldTZSecond) 586 return fe.interpretNumber(numbers, idx, textMonth) 587 588 case fe.Wants(fieldHour) && fe.Wants(fieldMinute) && fe.Wants(fieldSecond): 589 // Example: "[Date] HH:MM:SS" 590 // ^^ 591 // Example: "[Date] HHMM" 592 // ^^^^ 593 // Example: "[Date] HHMMSS" 594 // ^^^^^^ 595 // We're no longer looking for date fields at this point, and 596 // we didn't match on a separator for a timezone component. 597 // We must be looking at an hour or packed time field. 598 seconds := 0 599 switch chunk.magnitude { 600 case 1, 2: 601 // H or HH 602 return fe.SetChunk(fieldHour, chunk) 603 case 6: 604 // HHMMSS 605 seconds = chunk.v % 100 606 chunk.v /= 100 607 fallthrough 608 case 4: 609 // HHMM, but either force seconds to 0 or use value from above 610 if err := fe.Set(fieldSecond, seconds); err != nil { 611 return err 612 } 613 if err := fe.Set(fieldMinute, chunk.v%100); err != nil { 614 return err 615 } 616 chunk.v /= 100 617 return fe.SetChunk(fieldHour, chunk) 618 619 default: 620 return fe.decorateError(inputErrorf("unexpected number of digits for time in %v", chunk)) 621 } 622 623 case fe.Wants(fieldMinute): 624 // Example: "HH:MM" 625 // ^^ 626 return fe.SetChunk(fieldMinute, chunk) 627 628 case fe.Wants(fieldSecond): 629 // Example: "HH:MM:SS" 630 // ^^ 631 632 // DB2 expects period separator 633 if !fe.isDB2 { 634 return fe.SetChunk(fieldSecond, chunk) 635 } 636 } 637 return fe.decorateError(inputErrorf("could not parse field: %v", chunk)) 638 } 639 640 // MakeDate returns a time.Time containing only the date components 641 // of the extract. 642 func (fe *fieldExtract) MakeDate() (Date, error) { 643 if fe.sentinel != nil { 644 switch *fe.sentinel { 645 case TimeInfinity: 646 return PosInfDate, nil 647 case TimeNegativeInfinity: 648 return NegInfDate, nil 649 } 650 return MakeDateFromTime(*fe.sentinel) 651 } 652 653 year, _ := fe.Get(fieldYear) 654 month, _ := fe.Get(fieldMonth) 655 day, _ := fe.Get(fieldDay) 656 return MakeDateFromTime(time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)) 657 } 658 659 // MakeTime returns only the time component of the extract. 660 // If the user provided a named timezone, as opposed 661 // to a fixed offset, we will resolve the named zone to an offset 662 // based on the best-available date information. 663 func (fe *fieldExtract) MakeTime() time.Time { 664 if fe.sentinel != nil { 665 return *fe.sentinel 666 } 667 668 ret := fe.MakeTimestamp() 669 hour, min, sec := ret.Clock() 670 _, offset := ret.Zone() 671 return time.Date(0, 1, 1, hour, min, sec, ret.Nanosecond(), time.FixedZone("", offset)) 672 } 673 674 // MakeTimeWithoutTimezone returns only the time component of the extract, 675 // without any timezone information. The returned time always has UTC location. 676 // See ParseTimeWithoutTimezone. 677 func (fe *fieldExtract) MakeTimeWithoutTimezone() time.Time { 678 if fe.sentinel != nil { 679 return stripTimezone(*fe.sentinel) 680 } 681 682 ret := fe.MakeTimestampWithoutTimezone() 683 hour, min, sec := ret.Clock() 684 return time.Date(0, 1, 1, hour, min, sec, ret.Nanosecond(), time.UTC) 685 } 686 687 // stropTimezone converts the given time to a time that looks the same but is in 688 // UTC, e.g. from 689 // 690 // 2020-06-26 01:02:03 +0200 CEST 691 // 692 // to 693 // 694 // 2020-06-27 01:02:03 +0000 UTC. 695 // 696 // Note that the two times don't represent the same time instant. 697 func stripTimezone(t time.Time) time.Time { 698 _, offset := t.Zone() 699 t = t.Add(time.Duration(offset) * time.Second).UTC() 700 return t 701 } 702 703 // MakeTimestamp returns a time.Time containing all extracted information. 704 func (fe *fieldExtract) MakeTimestamp() time.Time { 705 if fe.sentinel != nil { 706 return *fe.sentinel 707 } 708 709 year, _ := fe.Get(fieldYear) 710 month, _ := fe.Get(fieldMonth) 711 day, _ := fe.Get(fieldDay) 712 hour, _ := fe.Get(fieldHour) 713 min, _ := fe.Get(fieldMinute) 714 sec, _ := fe.Get(fieldSecond) 715 nano, _ := fe.Get(fieldNanos) 716 717 return time.Date(year, time.Month(month), day, hour, min, sec, nano, fe.MakeLocation()) 718 } 719 720 // MakeTimestampWithoutTimezone returns a time.Time containing all extracted 721 // information, minus any timezone information (which is stripped). The returned 722 // time always has UTC location. See ParseTimestampWithoutTimezone. 723 func (fe *fieldExtract) MakeTimestampWithoutTimezone() time.Time { 724 if fe.sentinel != nil { 725 return stripTimezone(*fe.sentinel) 726 } 727 728 year, _ := fe.Get(fieldYear) 729 month, _ := fe.Get(fieldMonth) 730 day, _ := fe.Get(fieldDay) 731 hour, _ := fe.Get(fieldHour) 732 min, _ := fe.Get(fieldMinute) 733 sec, _ := fe.Get(fieldSecond) 734 nano, _ := fe.Get(fieldNanos) 735 736 return time.Date(year, time.Month(month), day, hour, min, sec, nano, time.UTC) 737 } 738 739 // MakeLocation returns the timezone information stored in the extract, 740 // or returns the default location. 741 func (fe *fieldExtract) MakeLocation() *time.Location { 742 tzHour, ok := fe.Get(fieldTZHour) 743 if !ok { 744 return fe.getLocation() 745 } 746 tzMin, _ := fe.Get(fieldTZMinute) 747 tzSec, _ := fe.Get(fieldTZSecond) 748 749 tzHour *= fe.tzSign 750 tzMin *= fe.tzSign 751 tzSec *= fe.tzSign 752 753 return zoneCacheInstance.FixedZone(tzHour, tzMin, tzSec) 754 } 755 756 // Certain keywords should result in some kind of sentinel value, 757 // but we want to ensure that we accept only a single sentinel chunk. 758 func (fe *fieldExtract) matchedSentinel(value time.Time, match string) error { 759 if fe.sentinel != nil { 760 return inputErrorf("unexpected input: %s", match) 761 } 762 fe.sentinel = &value 763 return nil 764 } 765 766 // Reset replaces a value of an already-set field. 767 func (fe *fieldExtract) Reset(field field, v int) error { 768 if !fe.has.Has(field) { 769 return errors.AssertionFailedf("field %s is not already set", field.SafePretty()) 770 } 771 fe.data[field] = v 772 return nil 773 } 774 775 // Set updates the value of the given field, returning an error if 776 // the field has already been set. 777 func (fe *fieldExtract) Set(field field, v int) error { 778 if !fe.wanted.Has(field) { 779 return fe.decorateError( 780 inputErrorf("value %v for field %s already present or not wanted", v, field.SafePretty()), 781 ) 782 } 783 fe.data[field] = v 784 fe.has = fe.has.Add(field) 785 fe.wanted = fe.wanted.Clear(field) 786 787 return nil 788 } 789 790 // decorateError adds context to an error object. 791 func (fe *fieldExtract) decorateError(err error) error { 792 return errors.WithDetailf(err, 793 "Wanted: %v\nAlready found in input: %v", &fe.wanted, &fe.has) 794 } 795 796 // SetChunk first validates that the separator in the chunk is appropriate 797 // for the field being set. That is, you'd never see "YYYY:MM:DD"; 798 // colons are only appropriate for hours and minutes. 799 func (fe *fieldExtract) SetChunk(field field, chunk numberChunk) error { 800 // A space rune will be used for the initial chunk. 801 switch field { 802 case fieldYear: 803 switch chunk.separator { 804 case '-', '/', ' ', ',': 805 // DD-MM-YY 806 // DD/MM/YY 807 // DD MM YY 808 // Month DD, YYYY 809 return fe.Set(field, chunk.v) 810 } 811 case fieldMonth, fieldDay: 812 switch chunk.separator { 813 case '-', '/', ' ': 814 // DD-MM-YY 815 // DD/MM/YY 816 // DD MM YY 817 // Month DD, YYYY 818 return fe.Set(field, chunk.v) 819 } 820 case fieldTZHour: 821 switch chunk.separator { 822 case '-', '+': 823 // HH:MM:SS+04 824 // HH:MM:SS-04 825 return fe.Set(field, chunk.v) 826 } 827 case fieldHour: 828 switch chunk.separator { 829 case ' ', 't', '-': 830 // YYYY-MM-DD HH:MM:SS 831 // yyyymmddThhmmss 832 // YYYY-MM-DD-HH.MM.SS 833 return fe.Set(field, chunk.v) 834 } 835 case fieldMinute, fieldSecond: 836 switch chunk.separator { 837 case ':', '.': 838 // HH:MM:SS 839 // HH.MM.SS 840 return fe.Set(field, chunk.v) 841 } 842 case fieldTZMinute, fieldTZSecond: 843 switch chunk.separator { 844 case ':': 845 // HH:MM:SS 846 return fe.Set(field, chunk.v) 847 } 848 case fieldNanos: 849 if chunk.separator == '.' { 850 // HH:MM:SS.NNNNNNNNN 851 return fe.Set(field, chunk.v) 852 } 853 } 854 return fe.decorateError(badFieldPrefixError(field, chunk.separator)) 855 } 856 857 // SetDayOfYear updates the month and day fields to reflect the 858 // given day-of-year. The year must have been previously set. 859 func (fe *fieldExtract) SetDayOfYear(chunk numberChunk) error { 860 if chunk.separator != ' ' && chunk.separator != '.' { 861 return fe.decorateError(badFieldPrefixError(fieldMonth, chunk.separator)) 862 } 863 864 y, ok := fe.Get(fieldYear) 865 if !ok { 866 return errors.AssertionFailedf("year must be set before day of year") 867 } 868 y, m, d := julianDayToDate(DateToJulianDay(y, 1, 1) + chunk.v - 1) 869 if err := fe.Reset(fieldYear, y); err != nil { 870 return err 871 } 872 if err := fe.Set(fieldMonth, m); err != nil { 873 return err 874 } 875 return fe.Set(fieldDay, d) 876 } 877 878 // SafeFormat implements the redact.SafeFormatter interface. 879 func (fe *fieldExtract) SafeFormat(w redact.SafePrinter, _ rune) { 880 w.SafeString("[ ") 881 for f := fieldMinimum; f <= fieldMaximum; f++ { 882 if v, ok := fe.Get(f); ok { 883 w.Printf("%s: %d ", f.SafePretty(), v) 884 } 885 } 886 w.SafeRune(']') 887 } 888 889 func (fe *fieldExtract) String() string { return redact.StringWithoutMarkers(fe) } 890 891 // validate ensures that the data in the extract is reasonable. It also 892 // performs some field fixups, such as converting two-digit years 893 // to actual values and adjusting for AM/PM. 894 func (fe *fieldExtract) validate() error { 895 // If we have any of the required fields, we must have all of the required fields. 896 if fe.has.HasAny(dateRequiredFields) && !fe.has.HasAll(dateRequiredFields) { 897 return fe.decorateError(inputErrorf("missing required date fields")) 898 } 899 900 if (fe.isDB2 && !fe.has.HasAll(db2TimeRequiredFields)) || (fe.has.HasAny(timeRequiredFields) && !fe.has.HasAll(timeRequiredFields)) { 901 return fe.decorateError(inputErrorf("missing required time fields")) 902 } 903 if !fe.has.HasAll(fe.required) { 904 return fe.decorateError(inputErrorf("missing required fields in input")) 905 } 906 907 if year, ok := fe.Get(fieldYear); ok { 908 // Note that here we allow for year to be 0 (which means 1 BC) which is 909 // a deviation from Postgres. The issue is that we support two notations 910 // (numbers or numbers with AD/BC suffix) whereas Postgres supports only 911 // the latter. 912 913 if era, ok := fe.Get(fieldEra); ok { 914 if year <= 0 { 915 return fe.decorateError( 916 inputErrorf("only positive years are permitted in AD/BC notation (%v)", year)) 917 } 918 if era < 0 { 919 // Update for BC dates. 920 if err := fe.Reset(fieldYear, 1-year); err != nil { 921 return err 922 } 923 } 924 } else if fe.tweakYear { 925 if year < 0 { 926 return inputErrorf("negative year (%v) not allowed", year) 927 } 928 if year < 70 { 929 year += 2000 930 } else if year < 100 { 931 year += 1900 932 } 933 if err := fe.Reset(fieldYear, year); err != nil { 934 return err 935 } 936 } 937 938 if month, ok := fe.Get(fieldMonth); ok { 939 if month < 1 || month > 12 { 940 return fe.decorateError(outOfRangeError("month", month)) 941 } 942 943 if day, ok := fe.Get(fieldDay); ok { 944 var maxDay int 945 if isLeap(year) { 946 maxDay = daysInMonth[1][month] 947 } else { 948 maxDay = daysInMonth[0][month] 949 } 950 if day < 1 || day > maxDay { 951 return fe.decorateError(outOfRangeError("day", day)) 952 } 953 } 954 } 955 } 956 957 if hour, ok := fe.Get(fieldHour); ok { 958 hasDate := fe.has.HasAny(dateRequiredFields) 959 960 meridian, _ := fe.Get(fieldMeridian) 961 switch meridian { 962 case fieldValueAM: 963 switch { 964 case hour < 0 || hour > 12: 965 return fe.decorateError(outOfRangeError("hour", hour)) 966 case hour == 12: 967 if err := fe.Reset(fieldHour, 0); err != nil { 968 return err 969 } 970 } 971 972 case fieldValuePM: 973 switch { 974 case hour < 0 || hour > 12: 975 return fe.decorateError(outOfRangeError("hour", hour)) 976 case hour == 12: 977 // 12 PM -> 12 978 default: 979 // 1 PM -> 13 980 if err := fe.Reset(fieldHour, hour+12); err != nil { 981 return err 982 } 983 } 984 985 default: 986 // 24:00:00 is the maximum-allowed value 987 if hour < 0 || (hasDate && hour > 24) || (!hasDate && hour > 23) { 988 return fe.decorateError(outOfRangeError("hour", hour)) 989 } 990 } 991 992 minute, _ := fe.Get(fieldMinute) 993 if minute < 0 || minute > 59 { 994 return fe.decorateError(outOfRangeError("minute", minute)) 995 } 996 997 second, _ := fe.Get(fieldSecond) 998 if second < 0 || (hasDate && second > 60) || (!hasDate && second > 59) { 999 return fe.decorateError(outOfRangeError("second", second)) 1000 } 1001 1002 nanos, _ := fe.Get(fieldNanos) 1003 if nanos < 0 { 1004 return fe.decorateError(outOfRangeError("nanos", nanos)) 1005 } 1006 1007 x := time.Duration(hour)*time.Hour + 1008 time.Duration(minute)*time.Minute + 1009 time.Duration(second)*time.Second + 1010 time.Duration(nanos)*time.Nanosecond 1011 if x > 24*time.Hour { 1012 return fe.decorateError(inputErrorf("time out of range: %d", x)) 1013 } 1014 } 1015 1016 return nil 1017 } 1018 1019 // Wants returns whether or not the field is wanted in the extract. 1020 func (fe *fieldExtract) Wants(field field) bool { 1021 return fe.wanted.Has(field) 1022 }