diff options
author | 2024-05-27 15:46:15 +0000 | |
---|---|---|
committer | 2024-05-27 17:46:15 +0200 | |
commit | 1e7b32490dfdccddd04f46d4b0416b48d749d51b (patch) | |
tree | 62a11365933a5a11e0800af64cbdf9172e5e6e7a /vendor/github.com/ncruces/go-sqlite3/time.go | |
parent | [chore] Small styling + link issues (#2933) (diff) | |
download | gotosocial-1e7b32490dfdccddd04f46d4b0416b48d749d51b.tar.xz |
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/time.go')
-rw-r--r-- | vendor/github.com/ncruces/go-sqlite3/time.go | 354 |
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 +} |