github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/pgdate/pgdate.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 contains parsing functions and types for dates and times 12 // in a manner that is compatible with PostgreSQL. 13 // 14 // The implementation here is inspired by the following 15 // https://github.com/postgres/postgres/blob/REL_10_5/src/backend/utils/adt/datetime.c 16 // 17 // The general approach is to take each input string and break it into 18 // contiguous runs of alphanumeric characters. Then, we attempt to 19 // interpret the input in order to populate a collection of date/time 20 // fields. We track which fields have been set in order to be able 21 // to apply various parsing heuristics to map the input chunks into 22 // date/time fields. 23 package pgdate 24 25 import ( 26 "bytes" 27 "fmt" 28 "math" 29 "time" 30 31 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode" 32 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror" 33 "github.com/cockroachdb/cockroachdb-parser/pkg/util/arith" 34 "github.com/cockroachdb/cockroachdb-parser/pkg/util/timeutil" 35 "github.com/cockroachdb/errors" 36 ) 37 38 const ( 39 unixEpochToPGEpoch = 10957 // Number of days from 1970-01-01 to 2000-01-01. 40 lowDays = -2451545 // 4714-11-24 BC 41 highDays = 2145031948 // 5874897-12-31 42 ) 43 44 var ( 45 // LowDate is the lowest non-infinite Date. 46 LowDate = Date{days: lowDays} 47 // HighDate is the highest non-infinite Date. 48 HighDate = Date{days: highDays} 49 // PosInfDate is the positive infinity Date. 50 PosInfDate = Date{days: math.MaxInt32} 51 // NegInfDate is the negative infinity date. 52 NegInfDate = Date{days: math.MinInt32} 53 ) 54 55 // Date is a postgres-compatible date implementation. It stores the 56 // number of days from the postgres epoch (2000-01-01). Its properties 57 // are unexported so that it cannot be misused by external packages. This 58 // package takes care to prevent silent problems like overflow that can 59 // occur when adding days or converting to and from a time.Time. 60 type Date struct { 61 days int32 62 63 // orig is the original on-disk number of days. It is only set when 64 // creating a Date from an on-disk encoding and will be used instead 65 // of days when encoding for on-disk. This is required so that we 66 // can roundtrip to the same on-disk value, which is necessary for 67 // deleting old index entries. This value should never be copied 68 // to another Date, but always cleared and forgotten on any mutation. 69 hasOrig bool 70 orig int64 71 } 72 73 // MakeCompatibleDateFromDisk creates a Date from the number of days 74 // since the Unix epoch. If it is out of range of LowDate and HighDate, 75 // positive or negative infinity dates are returned. This function should 76 // be used only by the on-disk decoder to maintain backward 77 // compatibility. It retains the origin days argument which is returned 78 // by UnixEpochDaysWithOrig. 79 func MakeCompatibleDateFromDisk(unixDays int64) Date { 80 date := Date{ 81 hasOrig: true, 82 orig: unixDays, 83 } 84 if pgDays, ok := arith.SubWithOverflow(unixDays, unixEpochToPGEpoch); !ok { 85 // We overflowed converting from Unix to PG days. 86 date.days = math.MinInt32 87 } else if pgDays > highDays { 88 date.days = math.MaxInt32 89 } else if pgDays < lowDays { 90 date.days = math.MinInt32 91 } else { 92 date.days = int32(pgDays) 93 } 94 return date 95 } 96 97 // MakeDateFromTime creates a Date from the specified time. The 98 // timezone-relative date is used. 99 func MakeDateFromTime(t time.Time) (Date, error) { 100 sec := t.Unix() 101 _, offset := t.Zone() 102 sec += int64(offset) 103 104 days := sec / secondsPerDay 105 if sec < 0 && sec%secondsPerDay != 0 { 106 // If days are negative AND not divisible by secondsPerDay, 107 // we need to round down. 108 // e.g. for 1969-12-30 01:00, the division will round to -1 109 // but we want -2. 110 days-- 111 } 112 d, err := MakeDateFromUnixEpoch(days) 113 return d, err 114 } 115 116 var errDateOutOfRange = pgerror.WithCandidateCode(errors.New("date is out of range"), pgcode.DatetimeFieldOverflow) 117 118 // MakeDateFromUnixEpoch creates a Date from the number of days since the 119 // Unix epoch. 120 func MakeDateFromUnixEpoch(days int64) (Date, error) { 121 days, ok := arith.SubWithOverflow(days, unixEpochToPGEpoch) 122 if !ok || days <= math.MinInt32 || days >= math.MaxInt32 { 123 return Date{}, errors.WithStack(errDateOutOfRange) 124 } 125 return MakeDateFromPGEpoch(int32(days)) 126 } 127 128 // MakeDateFromPGEpoch creates a Date from the number of days since 129 // 2000-01-01. MaxInt32 or MinInt32 represent the positive and negative 130 // infinity dates. 131 func MakeDateFromPGEpoch(days int32) (Date, error) { 132 if days == math.MaxInt32 { 133 return PosInfDate, nil 134 } 135 if days == math.MinInt32 { 136 return NegInfDate, nil 137 } 138 if days < lowDays || days > highDays { 139 return Date{}, errors.WithStack(errDateOutOfRange) 140 } 141 return Date{days: days}, nil 142 } 143 144 // MakeDateFromPGEpochClampFinite creates a Date from the number of days since 145 // 2000-01-01, clamping to LowDate or HighDate if outside those bounds. 146 func MakeDateFromPGEpochClampFinite(days int32) Date { 147 if days < lowDays { 148 return LowDate 149 } else if days > highDays { 150 return HighDate 151 } 152 return Date{days: days} 153 } 154 155 // ToTime returns d as a time.Time. Non finite dates return an error. 156 func (d Date) ToTime() (time.Time, error) { 157 if d.days == math.MinInt32 || d.days == math.MaxInt32 { 158 return time.Time{}, pgerror.WithCandidateCode( 159 errors.Newf("%s out of range for timestamp", d), pgcode.DatetimeFieldOverflow) 160 } 161 return timeutil.Unix(d.UnixEpochDays()*secondsPerDay, 0), nil 162 } 163 164 const secondsPerDay = 24 * 60 * 60 165 166 // Format formats d as a string. 167 func (d Date) Format(buf *bytes.Buffer) { 168 switch d.days { 169 case math.MinInt32: 170 buf.WriteString("-infinity") 171 case math.MaxInt32: 172 buf.WriteString("infinity") 173 default: 174 // ToTime only returns an error on infinity, which was already checked above. 175 t, _ := d.ToTime() 176 year, month, day := t.Date() 177 bc := year <= 0 178 if bc { 179 // For the ISO 8601 standard, the conversion from a negative year to BC changes the year value (ex. -2013 == 2014 BC). 180 // https://en.wikipedia.org/wiki/ISO_8601#Years 181 year = -year + 1 182 } 183 fmt.Fprintf(buf, "%04d-%02d-%02d", year, month, day) 184 if bc { 185 buf.WriteString(" BC") 186 } 187 } 188 } 189 190 func (d Date) String() string { 191 var buf bytes.Buffer 192 d.Format(&buf) 193 return buf.String() 194 } 195 196 // IsFinite returns whether d is finite. 197 func (d Date) IsFinite() bool { 198 return d.days != math.MinInt32 && d.days != math.MaxInt32 199 } 200 201 // PGEpochDays returns the number of days since 2001-01-01. This value can 202 // also be MinInt32 or MaxInt32, indicating negative or positive infinity. 203 func (d Date) PGEpochDays() int32 { 204 return d.days 205 } 206 207 // UnixEpochDays returns the number of days since the Unix epoch. Infinite 208 // dates are converted to MinInt64 or MaxInt64. 209 func (d Date) UnixEpochDays() int64 { 210 if d.days == math.MinInt32 { 211 return math.MinInt64 212 } 213 if d.days == math.MaxInt32 { 214 return math.MaxInt64 215 } 216 // Converting to an int64 makes overflow impossible. 217 return int64(d.days) + unixEpochToPGEpoch 218 } 219 220 // UnixEpochDaysWithOrig returns the original on-disk representation if 221 // present, otherwise UnixEpochDays(). 222 func (d Date) UnixEpochDaysWithOrig() int64 { 223 if d.hasOrig { 224 return d.orig 225 } 226 return d.UnixEpochDays() 227 } 228 229 // Compare compares two dates. 230 func (d Date) Compare(other Date) int { 231 if d.days < other.days { 232 return -1 233 } 234 if d.days > other.days { 235 return 1 236 } 237 return 0 238 } 239 240 // AddDays adds days to d with overflow and bounds checking. 241 func (d Date) AddDays(days int64) (Date, error) { 242 if !d.IsFinite() { 243 return d, nil 244 } 245 n, ok := arith.Add32to64WithOverflow(d.days, days) 246 if !ok { 247 return Date{}, pgerror.WithCandidateCode( 248 errors.Newf("%s + %d is out of range", d, errors.Safe(days)), 249 pgcode.DatetimeFieldOverflow) 250 } 251 return MakeDateFromPGEpoch(n) 252 } 253 254 // SubDays subtracts days from d with overflow and bounds checking. 255 func (d Date) SubDays(days int64) (Date, error) { 256 if !d.IsFinite() { 257 return d, nil 258 } 259 n, ok := arith.Sub32to64WithOverflow(d.days, days) 260 if !ok { 261 return Date{}, pgerror.WithCandidateCode( 262 errors.Newf("%s - %d is out of range", d, errors.Safe(days)), 263 pgcode.DatetimeFieldOverflow) 264 } 265 return MakeDateFromPGEpoch(n) 266 } 267 268 // SQLString formats the Style into a SQL string. 269 func (s Style) SQLString() string { 270 switch s { 271 case Style_POSTGRES: 272 return "Postgres" 273 case Style_GERMAN: 274 return "German" 275 } 276 return s.String() 277 } 278 279 // SQLString formats DateStyle into a SQL format. 280 func (ds DateStyle) SQLString() string { 281 return fmt.Sprintf("%s, %s", ds.Style.SQLString(), ds.Order.String()) 282 } 283 284 // AllowedDateStyles returns the list of allowed date styles. 285 func AllowedDateStyles() []string { 286 var allowed []string 287 for _, order := range []Order{Order_MDY, Order_DMY, Order_YMD} { 288 allowed = append(allowed, fmt.Sprintf("%s", DateStyle{Style: Style_ISO, Order: order})) 289 } 290 return allowed 291 }