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  }