github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/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/cockroach/pkg/sql/pgwire/pgcode" 32 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 33 "github.com/cockroachdb/cockroach/pkg/util/arith" 34 "github.com/cockroachdb/cockroach/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 // ToTime returns d as a time.Time. Non finite dates return an error. 145 func (d Date) ToTime() (time.Time, error) { 146 if d.days == math.MinInt32 || d.days == math.MaxInt32 { 147 return time.Time{}, pgerror.WithCandidateCode( 148 errors.Newf("%s out of range for timestamp", d), pgcode.DatetimeFieldOverflow) 149 } 150 return timeutil.Unix(d.UnixEpochDays()*secondsPerDay, 0), nil 151 } 152 153 const secondsPerDay = 24 * 60 * 60 154 155 // Format formats d as a string. 156 func (d Date) Format(buf *bytes.Buffer) { 157 switch d.days { 158 case math.MinInt32: 159 buf.WriteString("-infinity") 160 case math.MaxInt32: 161 buf.WriteString("infinity") 162 default: 163 // ToTime only returns an error on infinity, which was already checked above. 164 t, _ := d.ToTime() 165 year, month, day := t.Date() 166 bc := year <= 0 167 if bc { 168 year = -year + 1 169 } 170 fmt.Fprintf(buf, "%04d-%02d-%02d", year, month, day) 171 if bc { 172 buf.WriteString(" BC") 173 } 174 } 175 } 176 177 func (d Date) String() string { 178 var buf bytes.Buffer 179 d.Format(&buf) 180 return buf.String() 181 } 182 183 // IsFinite returns whether d is finite. 184 func (d Date) IsFinite() bool { 185 return d.days != math.MinInt32 && d.days != math.MaxInt32 186 } 187 188 // PGEpochDays returns the number of days since 2001-01-01. This value can 189 // also be MinInt32 or MaxInt32, indicating negative or positive infinity. 190 func (d Date) PGEpochDays() int32 { 191 return d.days 192 } 193 194 // UnixEpochDays returns the number of days since the Unix epoch. Infinite 195 // dates are converted to MinInt64 or MaxInt64. 196 func (d Date) UnixEpochDays() int64 { 197 if d.days == math.MinInt32 { 198 return math.MinInt64 199 } 200 if d.days == math.MaxInt32 { 201 return math.MaxInt64 202 } 203 // Converting to an int64 makes overflow impossible. 204 return int64(d.days) + unixEpochToPGEpoch 205 } 206 207 // UnixEpochDaysWithOrig returns the original on-disk representation if 208 // present, otherwise UnixEpochDays(). 209 func (d Date) UnixEpochDaysWithOrig() int64 { 210 if d.hasOrig { 211 return d.orig 212 } 213 return d.UnixEpochDays() 214 } 215 216 // Compare compares two dates. 217 func (d Date) Compare(other Date) int { 218 if d.days < other.days { 219 return -1 220 } 221 if d.days > other.days { 222 return 1 223 } 224 return 0 225 } 226 227 // AddDays adds days to d with overflow and bounds checking. 228 func (d Date) AddDays(days int64) (Date, error) { 229 if !d.IsFinite() { 230 return d, nil 231 } 232 n, ok := arith.Add32to64WithOverflow(d.days, days) 233 if !ok { 234 return Date{}, pgerror.WithCandidateCode( 235 errors.Newf("%s + %d is out of range", d, errors.Safe(days)), 236 pgcode.DatetimeFieldOverflow) 237 } 238 return MakeDateFromPGEpoch(n) 239 } 240 241 // SubDays subtracts days from d with overflow and bounds checking. 242 func (d Date) SubDays(days int64) (Date, error) { 243 if !d.IsFinite() { 244 return d, nil 245 } 246 n, ok := arith.Sub32to64WithOverflow(d.days, days) 247 if !ok { 248 return Date{}, pgerror.WithCandidateCode( 249 errors.Newf("%s - %d is out of range", d, errors.Safe(days)), 250 pgcode.DatetimeFieldOverflow) 251 } 252 return MakeDateFromPGEpoch(n) 253 }