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  }