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 }