github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/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  	"math"
    15  	"time"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    19  	"github.com/cockroachdb/cockroach/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  	dateTimeFields = dateFields.AddAll(timeFields)
    59  
    60  	tzFields = newFieldSet(fieldTZHour, fieldTZMinute, fieldTZSecond)
    61  )
    62  
    63  // These are sentinel values for handling special values:
    64  // https://www.postgresql.org/docs/10/static/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE
    65  var (
    66  	TimeEpoch            = timeutil.Unix(0, 0)
    67  	TimeInfinity         = timeutil.Unix(math.MaxInt64, math.MaxInt64)
    68  	TimeNegativeInfinity = timeutil.Unix(math.MinInt64, math.MinInt64)
    69  )
    70  
    71  //go:generate stringer -type=ParseMode
    72  
    73  // ParseMode controls the resolution of ambiguous date formats such as
    74  // `01/02/03`.
    75  type ParseMode uint
    76  
    77  // These are the various parsing modes that determine in which order
    78  // we should look for years, months, and date.
    79  // ParseModeYMD is the default value.
    80  const (
    81  	ParseModeYMD ParseMode = iota
    82  	ParseModeDMY
    83  	ParseModeMDY
    84  )
    85  
    86  // ParseDate converts a string into Date.
    87  func ParseDate(now time.Time, mode ParseMode, s string) (Date, error) {
    88  	fe := fieldExtract{
    89  		now:      now,
    90  		mode:     mode,
    91  		required: dateRequiredFields,
    92  		// We allow time fields to be provided since they occur after
    93  		// the date fields that we're really looking for and for
    94  		// time values like 24:00:00, would push into the next day.
    95  		wanted: dateTimeFields,
    96  	}
    97  
    98  	if err := fe.Extract(s); err != nil {
    99  		return Date{}, parseError(err, "date", s)
   100  	}
   101  	return fe.MakeDate()
   102  }
   103  
   104  // ParseTime converts a string into a time value on the epoch day.
   105  func ParseTime(now time.Time, mode ParseMode, s string) (time.Time, error) {
   106  	fe := fieldExtract{
   107  		now:      now,
   108  		required: timeRequiredFields,
   109  		wanted:   timeFields,
   110  	}
   111  
   112  	if err := fe.Extract(s); err != nil {
   113  		// It's possible that the user has given us a complete
   114  		// timestamp string; let's try again, accepting more fields.
   115  		fe = fieldExtract{
   116  			now:      now,
   117  			mode:     mode,
   118  			required: timeRequiredFields,
   119  			wanted:   dateTimeFields,
   120  		}
   121  
   122  		if err := fe.Extract(s); err != nil {
   123  			return TimeEpoch, parseError(err, "time", s)
   124  		}
   125  	}
   126  	return fe.MakeTime(), nil
   127  }
   128  
   129  // ParseTimestamp converts a string into a timestamp.
   130  func ParseTimestamp(now time.Time, mode ParseMode, s string) (time.Time, error) {
   131  	fe := fieldExtract{
   132  		mode: mode,
   133  		now:  now,
   134  		// A timestamp only actually needs a date component; the time
   135  		// would be midnight.
   136  		required: dateRequiredFields,
   137  		wanted:   dateTimeFields,
   138  	}
   139  
   140  	if err := fe.Extract(s); err != nil {
   141  		return TimeEpoch, parseError(err, "timestamp", s)
   142  	}
   143  	return fe.MakeTimestamp(), nil
   144  }
   145  
   146  // badFieldPrefixError constructs an error with pg code InvalidDatetimeFormat.
   147  func badFieldPrefixError(field field, prefix rune) error {
   148  	return inputErrorf("unexpected separator '%s' for field %s",
   149  		string(prefix), errors.Safe(field.Pretty()))
   150  }
   151  
   152  // inputErrorf returns an error with pg code InvalidDatetimeFormat.
   153  func inputErrorf(format string, args ...interface{}) error {
   154  	err := errors.Newf(format, args...)
   155  	return pgerror.WithCandidateCode(err, pgcode.InvalidDatetimeFormat)
   156  }
   157  
   158  // outOfRangeError returns an error with pg code DatetimeFieldOverflow.
   159  func outOfRangeError(field string, val int) error {
   160  	err := errors.Newf("field %s value %d is out of range", errors.Safe(field), errors.Safe(val))
   161  	return pgerror.WithCandidateCode(err, pgcode.DatetimeFieldOverflow)
   162  }
   163  
   164  // parseError ensures that any error we return to the client will
   165  // be some kind of error with a pg code.
   166  func parseError(err error, kind string, s string) error {
   167  	return pgerror.WithCandidateCode(
   168  		errors.Wrapf(err, "parsing as type %s", errors.Safe(kind)),
   169  		pgcode.InvalidDatetimeFormat)
   170  }