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  }