github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/pgdate/parsing.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  	"strings"
    15  	"time"
    16  
    17  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode"
    18  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror"
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/timeutil"
    20  	"github.com/cockroachdb/errors"
    21  )
    22  
    23  // Various keywords that appear in timestamps.
    24  const (
    25  	// Alias for UTC.  No idea who actually uses this.
    26  	keywordAllBalls = "allballs"
    27  	keywordAM       = "am"
    28  	keywordEpoch    = "epoch"
    29  	keywordEraAD    = "ad"
    30  	keywordEraBC    = "bc"
    31  	keywordEraBCE   = "bce"
    32  	keywordEraCE    = "ce"
    33  	// Alias for UTC.
    34  	keywordGMT       = "gmt"
    35  	keywordInfinity  = "infinity"
    36  	keywordNow       = "now"
    37  	keywordPM        = "pm"
    38  	keywordToday     = "today"
    39  	keywordTomorrow  = "tomorrow"
    40  	keywordUTC       = "utc"
    41  	keywordYesterday = "yesterday"
    42  	// Alias for UTC.
    43  	keywordZ = "z"
    44  	// Alias for UTC.
    45  	keywordZulu = "zulu"
    46  )
    47  
    48  // Commonly-used collections of fields.
    49  var (
    50  	dateFields         = newFieldSet(fieldYear, fieldMonth, fieldDay, fieldEra)
    51  	dateRequiredFields = newFieldSet(fieldYear, fieldMonth, fieldDay)
    52  
    53  	timeFields = newFieldSet(
    54  		fieldHour, fieldMinute, fieldSecond, fieldNanos, fieldMeridian,
    55  		fieldTZHour, fieldTZMinute, fieldTZSecond)
    56  	timeRequiredFields = newFieldSet(fieldHour, fieldMinute)
    57  
    58  	db2TimeRequiredFields = newFieldSet(fieldHour, fieldMinute, fieldSecond)
    59  
    60  	dateTimeFields = dateFields.AddAll(timeFields)
    61  
    62  	tzFields = newFieldSet(fieldTZHour, fieldTZMinute, fieldTZSecond)
    63  )
    64  
    65  // These are sentinel values for handling special values:
    66  // https://www.postgresql.org/docs/10/static/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE
    67  var (
    68  	TimeEpoch = timeutil.Unix(0, 0)
    69  	// TimeInfinity represents the "highest" possible time.
    70  	// TODO (#41564): this should actually behave as infinity, i.e. any operator
    71  	// leaves this as infinity. This time should always be greater than any other time.
    72  	// We should probably use the next microsecond after this value, i.e. timeutil.Unix(9224318016000, 0).
    73  	// Postgres uses math.MaxInt64 microseconds as the infinity value.
    74  	// See: https://github.com/postgres/postgres/blob/42aa1f0ab321fd43cbfdd875dd9e13940b485900/src/include/datatype/timestamp.h#L107.
    75  	TimeInfinity = timeutil.Unix(9224318016000-1, 999999000)
    76  	// TimeNegativeInfinity represents the "lowest" possible time.
    77  	// TODO (#41564): this should actually behave as -infinity, i.e. any operator
    78  	// leaves this as -infinity. This time should always be less than any other time.
    79  	// We should probably use the next microsecond before this value, i.e. timeutil.Unix(9224318016000-1, 999999000).
    80  	// Postgres uses math.MinInt64 microseconds as the -infinity value.
    81  	// See: https://github.com/postgres/postgres/blob/42aa1f0ab321fd43cbfdd875dd9e13940b485900/src/include/datatype/timestamp.h#L107.
    82  	TimeNegativeInfinity = timeutil.Unix(-210866803200, 0)
    83  )
    84  
    85  type ParseHelper struct {
    86  	fe fieldExtract
    87  }
    88  
    89  // ParseDate converts a string into Date.
    90  //
    91  // Any specified timezone is inconsequential. Examples:
    92  //   - "now": parses to the local date (in the current timezone)
    93  //   - "2020-06-26 01:09:15.511971": parses to '2020-06-26'
    94  //   - "2020-06-26 01:09:15.511971-05": parses to '2020-06-26'
    95  //
    96  // The dependsOnContext return value indicates if we had to consult the given
    97  // `now` value (either for the time or the local timezone).
    98  //
    99  // Memory allocations can be avoided by passing ParseHelper which can be re-used
   100  // across calls for batch parsing purposes, otherwise it can be nil.
   101  func ParseDate(
   102  	now time.Time, dateStyle DateStyle, s string, h *ParseHelper,
   103  ) (_ Date, dependsOnContext bool, _ error) {
   104  	if h == nil {
   105  		h = &ParseHelper{}
   106  	}
   107  	h.fe = fieldExtract{
   108  		currentTime: now,
   109  		dateStyle:   dateStyle,
   110  		required:    dateRequiredFields,
   111  		// We allow time fields to be provided since they occur after
   112  		// the date fields that we're really looking for and for
   113  		// time values like 24:00:00, would push into the next day.
   114  		wanted: dateTimeFields,
   115  	}
   116  
   117  	if err := h.fe.Extract(s); err != nil {
   118  		return Date{}, false, parseError(err, "date", s)
   119  	}
   120  	date, err := h.fe.MakeDate()
   121  	return date, h.fe.currentTimeUsed, err
   122  }
   123  
   124  // ParseTime converts a string into a time value on the epoch day.
   125  //
   126  // The dependsOnContext return value indicates if we had to consult the given
   127  // `now` value (either for the time or the local timezone).
   128  //
   129  // Memory allocations can be avoided by passing ParseHelper which can be re-used
   130  // across calls for batch parsing purposes, otherwise it can be nil.
   131  func ParseTime(
   132  	now time.Time, dateStyle DateStyle, s string, h *ParseHelper,
   133  ) (_ time.Time, dependsOnContext bool, _ error) {
   134  	if h == nil {
   135  		h = &ParseHelper{}
   136  	}
   137  	h.fe = fieldExtract{
   138  		currentTime: now,
   139  		required:    timeRequiredFields,
   140  		wanted:      timeFields,
   141  	}
   142  
   143  	if err := h.fe.Extract(s); err != nil {
   144  		// It's possible that the user has given us a complete
   145  		// timestamp string; let's try again, accepting more fields.
   146  		h.fe = fieldExtract{
   147  			currentTime: now,
   148  			dateStyle:   dateStyle,
   149  			required:    timeRequiredFields,
   150  			wanted:      dateTimeFields,
   151  		}
   152  
   153  		if err := h.fe.Extract(s); err != nil {
   154  			return TimeEpoch, false, parseError(err, "time", s)
   155  		}
   156  	}
   157  	res := h.fe.MakeTime()
   158  	return res, h.fe.currentTimeUsed, nil
   159  }
   160  
   161  // ParseTimeWithoutTimezone converts a string into a time value on the epoch
   162  // day, dropping any timezone information. The returned time always has UTC
   163  // location.
   164  //
   165  // Any specified timezone is inconsequential. Examples:
   166  //   - "now": parses to the local time of day (in the current timezone)
   167  //   - "01:09:15.511971" and "01:09:15.511971-05" parse to the same result
   168  //
   169  // The dependsOnContext return value indicates if we had to consult the given
   170  // `now` value (either for the time or the local timezone).
   171  func ParseTimeWithoutTimezone(
   172  	now time.Time, dateStyle DateStyle, s string,
   173  ) (_ time.Time, dependsOnContext bool, _ error) {
   174  	fe := fieldExtract{
   175  		currentTime: now,
   176  		required:    timeRequiredFields,
   177  		wanted:      timeFields,
   178  	}
   179  
   180  	if err := fe.Extract(s); err != nil {
   181  		// It's possible that the user has given us a complete
   182  		// timestamp string; let's try again, accepting more fields.
   183  		fe = fieldExtract{
   184  			currentTime: now,
   185  			dateStyle:   dateStyle,
   186  			required:    timeRequiredFields,
   187  			wanted:      dateTimeFields,
   188  		}
   189  
   190  		if err := fe.Extract(s); err != nil {
   191  			return TimeEpoch, false, parseError(err, "time", s)
   192  		}
   193  	}
   194  	res := fe.MakeTimeWithoutTimezone()
   195  	return res, fe.currentTimeUsed, nil
   196  }
   197  
   198  // ParseTimestamp converts a string into a timestamp.
   199  //
   200  // The dependsOnContext return value indicates if we had to consult the given
   201  // `now` value (either for the time or the local timezone).
   202  func ParseTimestamp(
   203  	now time.Time, dateStyle DateStyle, s string,
   204  ) (_ time.Time, dependsOnContext bool, _ error) {
   205  	fe := fieldExtract{
   206  		dateStyle:   dateStyle,
   207  		currentTime: now,
   208  		// A timestamp only actually needs a date component; the time
   209  		// would be midnight.
   210  		required: dateRequiredFields,
   211  		wanted:   dateTimeFields,
   212  	}
   213  
   214  	if err := fe.Extract(s); err != nil {
   215  		return TimeEpoch, false, parseError(err, "timestamp", s)
   216  	}
   217  	res := fe.MakeTimestamp()
   218  	return res, fe.currentTimeUsed, nil
   219  }
   220  
   221  // ParseTimestampWithoutTimezone converts a string into a timestamp, stripping
   222  // away any timezone information. Any specified timezone is inconsequential. The
   223  // returned time always has UTC location.
   224  //
   225  // For example, all these inputs return 2020-06-26 01:02:03 +0000 UTC:
   226  //   - '2020-06-26 01:02:03';
   227  //   - '2020-06-26 01:02:03+04';
   228  //   - 'now', if the local time (in the current timezone) is
   229  //     2020-06-26 01:02:03. Note that this does not represent the same time
   230  //     instant, but the one that "reads" the same in UTC.
   231  //
   232  // The dependsOnContext return value indicates if we had to consult the given
   233  // `now` value (either for the time or the local timezone).
   234  func ParseTimestampWithoutTimezone(
   235  	now time.Time, dateStyle DateStyle, s string,
   236  ) (_ time.Time, dependsOnContext bool, _ error) {
   237  	fe := fieldExtract{
   238  		dateStyle:   dateStyle,
   239  		currentTime: now,
   240  		// A timestamp only actually needs a date component; the time
   241  		// would be midnight.
   242  		required: dateRequiredFields,
   243  		wanted:   dateTimeFields,
   244  	}
   245  
   246  	if err := fe.Extract(s); err != nil {
   247  		return TimeEpoch, false, parseError(err, "timestamp", s)
   248  	}
   249  	res := fe.MakeTimestampWithoutTimezone()
   250  	return res, fe.currentTimeUsed, nil
   251  }
   252  
   253  // badFieldPrefixError constructs an error with pg code InvalidDatetimeFormat.
   254  func badFieldPrefixError(field field, prefix rune) error {
   255  	return inputErrorf("unexpected separator '%s' for field %s",
   256  		string(prefix), errors.Safe(field.Pretty()))
   257  }
   258  
   259  // inputErrorf returns an error with pg code InvalidDatetimeFormat.
   260  func inputErrorf(format string, args ...interface{}) error {
   261  	err := errors.Newf(format, args...)
   262  	return pgerror.WithCandidateCode(err, pgcode.InvalidDatetimeFormat)
   263  }
   264  
   265  // outOfRangeError returns an error with pg code DatetimeFieldOverflow.
   266  func outOfRangeError(field string, val int) error {
   267  	err := errors.Newf("field %s value %d is out of range", errors.Safe(field), errors.Safe(val))
   268  	return errors.WithHint(
   269  		pgerror.WithCandidateCode(err, pgcode.DatetimeFieldOverflow),
   270  		`Perhaps you need a different "datestyle" setting.`,
   271  	)
   272  }
   273  
   274  // parseError ensures that any error we return to the client will
   275  // be some kind of error with a pg code.
   276  func parseError(err error, kind string, s string) error {
   277  	return pgerror.WithCandidateCode(
   278  		errors.Wrapf(err, "parsing as type %s", errors.Safe(kind)),
   279  		pgcode.InvalidDatetimeFormat)
   280  }
   281  
   282  // DefaultDateStyle returns the default datestyle for Postgres.
   283  func DefaultDateStyle() DateStyle {
   284  	return DateStyle{
   285  		Order: Order_MDY,
   286  		Style: Style_ISO,
   287  	}
   288  }
   289  
   290  // ParseDateStyle parses a given DateStyle, modifying the existingDateStyle
   291  // as appropriate. This is because specifying just Style or Order will leave
   292  // the other field unchanged.
   293  func ParseDateStyle(s string, existingDateStyle DateStyle) (DateStyle, error) {
   294  	ds := existingDateStyle
   295  	fields := strings.Split(s, ",")
   296  	for _, field := range fields {
   297  		field = strings.ToLower(strings.TrimSpace(field))
   298  		switch field {
   299  		case "iso":
   300  			ds.Style = Style_ISO
   301  		case "german":
   302  			ds.Style = Style_GERMAN
   303  		case "sql":
   304  			ds.Style = Style_SQL
   305  		case "postgres":
   306  			ds.Style = Style_POSTGRES
   307  		case "ymd":
   308  			ds.Order = Order_YMD
   309  		case "mdy":
   310  			ds.Order = Order_MDY
   311  		case "dmy":
   312  			ds.Order = Order_DMY
   313  		default:
   314  			return ds, pgerror.Newf(pgcode.InvalidParameterValue, "unknown DateStyle parameter: %s", field)
   315  		}
   316  	}
   317  	return ds, nil
   318  }