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