summaryrefslogtreecommitdiff
path: root/vendor/github.com/ncruces/go-sqlite3/time.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/time.go')
-rw-r--r--vendor/github.com/ncruces/go-sqlite3/time.go354
1 files changed, 354 insertions, 0 deletions
diff --git a/vendor/github.com/ncruces/go-sqlite3/time.go b/vendor/github.com/ncruces/go-sqlite3/time.go
new file mode 100644
index 000000000..0164a307b
--- /dev/null
+++ b/vendor/github.com/ncruces/go-sqlite3/time.go
@@ -0,0 +1,354 @@
+package sqlite3
+
+import (
+ "math"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/ncruces/go-sqlite3/internal/util"
+ "github.com/ncruces/julianday"
+)
+
+// TimeFormat specifies how to encode/decode time values.
+//
+// See the documentation for the [TimeFormatDefault] constant
+// for formats recognized by SQLite.
+//
+// https://sqlite.org/lang_datefunc.html
+type TimeFormat string
+
+// TimeFormats recognized by SQLite to encode/decode time values.
+//
+// https://sqlite.org/lang_datefunc.html#time_values
+const (
+ TimeFormatDefault TimeFormat = "" // time.RFC3339Nano
+
+ // Text formats
+ TimeFormat1 TimeFormat = "2006-01-02"
+ TimeFormat2 TimeFormat = "2006-01-02 15:04"
+ TimeFormat3 TimeFormat = "2006-01-02 15:04:05"
+ TimeFormat4 TimeFormat = "2006-01-02 15:04:05.000"
+ TimeFormat5 TimeFormat = "2006-01-02T15:04"
+ TimeFormat6 TimeFormat = "2006-01-02T15:04:05"
+ TimeFormat7 TimeFormat = "2006-01-02T15:04:05.000"
+ TimeFormat8 TimeFormat = "15:04"
+ TimeFormat9 TimeFormat = "15:04:05"
+ TimeFormat10 TimeFormat = "15:04:05.000"
+
+ TimeFormat2TZ = TimeFormat2 + "Z07:00"
+ TimeFormat3TZ = TimeFormat3 + "Z07:00"
+ TimeFormat4TZ = TimeFormat4 + "Z07:00"
+ TimeFormat5TZ = TimeFormat5 + "Z07:00"
+ TimeFormat6TZ = TimeFormat6 + "Z07:00"
+ TimeFormat7TZ = TimeFormat7 + "Z07:00"
+ TimeFormat8TZ = TimeFormat8 + "Z07:00"
+ TimeFormat9TZ = TimeFormat9 + "Z07:00"
+ TimeFormat10TZ = TimeFormat10 + "Z07:00"
+
+ // Numeric formats
+ TimeFormatJulianDay TimeFormat = "julianday"
+ TimeFormatUnix TimeFormat = "unixepoch"
+ TimeFormatUnixFrac TimeFormat = "unixepoch_frac"
+ TimeFormatUnixMilli TimeFormat = "unixepoch_milli" // not an SQLite format
+ TimeFormatUnixMicro TimeFormat = "unixepoch_micro" // not an SQLite format
+ TimeFormatUnixNano TimeFormat = "unixepoch_nano" // not an SQLite format
+
+ // Auto
+ TimeFormatAuto TimeFormat = "auto"
+)
+
+// Encode encodes a time value using this format.
+//
+// [TimeFormatDefault] and [TimeFormatAuto] encode using [time.RFC3339Nano],
+// with nanosecond accuracy, and preserving any timezone offset.
+//
+// This is the format used by the [database/sql] driver:
+// [database/sql.Row.Scan] will decode as [time.Time]
+// values encoded with [time.RFC3339Nano].
+//
+// Time values encoded with [time.RFC3339Nano] cannot be sorted as strings
+// to produce a time-ordered sequence.
+//
+// Assuming that the time zones of the time values are the same (e.g., all in UTC),
+// and expressed using the same string (e.g., all "Z" or all "+00:00"),
+// use the TIME [collating sequence] to produce a time-ordered sequence.
+//
+// Otherwise, use [TimeFormat7] for time-ordered encoding.
+//
+// Formats [TimeFormat1] through [TimeFormat10]
+// convert time values to UTC before encoding.
+//
+// Returns a string for the text formats,
+// a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac],
+// or an int64 for the other numeric formats.
+//
+// https://sqlite.org/lang_datefunc.html
+//
+// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
+func (f TimeFormat) Encode(t time.Time) any {
+ switch f {
+ // Numeric formats
+ case TimeFormatJulianDay:
+ return julianday.Float(t)
+ case TimeFormatUnix:
+ return t.Unix()
+ case TimeFormatUnixFrac:
+ return float64(t.Unix()) + float64(t.Nanosecond())*1e-9
+ case TimeFormatUnixMilli:
+ return t.UnixMilli()
+ case TimeFormatUnixMicro:
+ return t.UnixMicro()
+ case TimeFormatUnixNano:
+ return t.UnixNano()
+ // Special formats.
+ case TimeFormatDefault, TimeFormatAuto:
+ f = time.RFC3339Nano
+ // SQLite assumes UTC if unspecified.
+ case
+ TimeFormat1, TimeFormat2,
+ TimeFormat3, TimeFormat4,
+ TimeFormat5, TimeFormat6,
+ TimeFormat7, TimeFormat8,
+ TimeFormat9, TimeFormat10:
+ t = t.UTC()
+ }
+ return t.Format(string(f))
+}
+
+// Decode decodes a time value using this format.
+//
+// The time value can be a string, an int64, or a float64.
+//
+// Formats [TimeFormat8] through [TimeFormat10]
+// (and [TimeFormat8TZ] through [TimeFormat10TZ])
+// assume a date of 2000-01-01.
+//
+// The timezone indicator and fractional seconds are always optional
+// for formats [TimeFormat2] through [TimeFormat10]
+// (and [TimeFormat2TZ] through [TimeFormat10TZ]).
+//
+// [TimeFormatAuto] implements (and extends) the SQLite auto modifier.
+// Julian day numbers are safe to use for historical dates,
+// from 4712BC through 9999AD.
+// Unix timestamps (expressed in seconds, milliseconds, microseconds, or nanoseconds)
+// are safe to use for current events, from at least 1980 through at least 2260.
+// Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers,
+// or have the wrong time unit.
+//
+// https://sqlite.org/lang_datefunc.html
+func (f TimeFormat) Decode(v any) (time.Time, error) {
+ switch f {
+ // Numeric formats.
+ case TimeFormatJulianDay:
+ switch v := v.(type) {
+ case string:
+ return julianday.Parse(v)
+ case float64:
+ return julianday.FloatTime(v), nil
+ case int64:
+ return julianday.Time(v, 0), nil
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ case TimeFormatUnix, TimeFormatUnixFrac:
+ if s, ok := v.(string); ok {
+ f, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return time.Time{}, err
+ }
+ v = f
+ }
+ switch v := v.(type) {
+ case float64:
+ sec, frac := math.Modf(v)
+ nsec := math.Floor(frac * 1e9)
+ return time.Unix(int64(sec), int64(nsec)).UTC(), nil
+ case int64:
+ return time.Unix(v, 0).UTC(), nil
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ case TimeFormatUnixMilli:
+ if s, ok := v.(string); ok {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return time.Time{}, err
+ }
+ v = i
+ }
+ switch v := v.(type) {
+ case float64:
+ return time.UnixMilli(int64(math.Floor(v))).UTC(), nil
+ case int64:
+ return time.UnixMilli(v).UTC(), nil
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ case TimeFormatUnixMicro:
+ if s, ok := v.(string); ok {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return time.Time{}, err
+ }
+ v = i
+ }
+ switch v := v.(type) {
+ case float64:
+ return time.UnixMicro(int64(math.Floor(v))).UTC(), nil
+ case int64:
+ return time.UnixMicro(v).UTC(), nil
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ case TimeFormatUnixNano:
+ if s, ok := v.(string); ok {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return time.Time{}, util.TimeErr
+ }
+ v = i
+ }
+ switch v := v.(type) {
+ case float64:
+ return time.Unix(0, int64(math.Floor(v))).UTC(), nil
+ case int64:
+ return time.Unix(0, v).UTC(), nil
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ // Special formats.
+ case TimeFormatAuto:
+ switch s := v.(type) {
+ case string:
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err == nil {
+ v = i
+ break
+ }
+ f, err := strconv.ParseFloat(s, 64)
+ if err == nil {
+ v = f
+ break
+ }
+
+ dates := []TimeFormat{
+ TimeFormat9, TimeFormat8,
+ TimeFormat6, TimeFormat5,
+ TimeFormat3, TimeFormat2, TimeFormat1,
+ }
+ for _, f := range dates {
+ t, err := f.Decode(s)
+ if err == nil {
+ return t, nil
+ }
+ }
+ }
+ switch v := v.(type) {
+ case float64:
+ if 0 <= v && v < 5373484.5 {
+ return TimeFormatJulianDay.Decode(v)
+ }
+ if v < 253402300800 {
+ return TimeFormatUnixFrac.Decode(v)
+ }
+ if v < 253402300800_000 {
+ return TimeFormatUnixMilli.Decode(v)
+ }
+ if v < 253402300800_000000 {
+ return TimeFormatUnixMicro.Decode(v)
+ }
+ return TimeFormatUnixNano.Decode(v)
+ case int64:
+ if 0 <= v && v < 5373485 {
+ return TimeFormatJulianDay.Decode(v)
+ }
+ if v < 253402300800 {
+ return TimeFormatUnixFrac.Decode(v)
+ }
+ if v < 253402300800_000 {
+ return TimeFormatUnixMilli.Decode(v)
+ }
+ if v < 253402300800_000000 {
+ return TimeFormatUnixMicro.Decode(v)
+ }
+ return TimeFormatUnixNano.Decode(v)
+ default:
+ return time.Time{}, util.TimeErr
+ }
+
+ case
+ TimeFormat2, TimeFormat2TZ,
+ TimeFormat3, TimeFormat3TZ,
+ TimeFormat4, TimeFormat4TZ,
+ TimeFormat5, TimeFormat5TZ,
+ TimeFormat6, TimeFormat6TZ,
+ TimeFormat7, TimeFormat7TZ:
+ s, ok := v.(string)
+ if !ok {
+ return time.Time{}, util.TimeErr
+ }
+ return f.parseRelaxed(s)
+
+ case
+ TimeFormat8, TimeFormat8TZ,
+ TimeFormat9, TimeFormat9TZ,
+ TimeFormat10, TimeFormat10TZ:
+ s, ok := v.(string)
+ if !ok {
+ return time.Time{}, util.TimeErr
+ }
+ t, err := f.parseRelaxed(s)
+ if err != nil {
+ return time.Time{}, err
+ }
+ return t.AddDate(2000, 0, 0), nil
+
+ default:
+ s, ok := v.(string)
+ if !ok {
+ return time.Time{}, util.TimeErr
+ }
+ if f == "" {
+ f = time.RFC3339Nano
+ }
+ return time.Parse(string(f), s)
+ }
+}
+
+func (f TimeFormat) parseRelaxed(s string) (time.Time, error) {
+ fs := string(f)
+ fs = strings.TrimSuffix(fs, "Z07:00")
+ fs = strings.TrimSuffix(fs, ".000")
+ t, err := time.Parse(fs+"Z07:00", s)
+ if err != nil {
+ return time.Parse(fs, s)
+ }
+ return t, nil
+}
+
+// Scanner returns a [database/sql.Scanner] that can be used as an argument to
+// [database/sql.Row.Scan] and similar methods to
+// decode a time value into dest using this format.
+func (f TimeFormat) Scanner(dest *time.Time) interface{ Scan(any) error } {
+ return timeScanner{dest, f}
+}
+
+type timeScanner struct {
+ *time.Time
+ TimeFormat
+}
+
+func (s timeScanner) Scan(src any) error {
+ var ok bool
+ var err error
+ if *s.Time, ok = src.(time.Time); !ok {
+ *s.Time, err = s.Decode(src)
+ }
+ return err
+}