mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 08:32:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			357 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			357 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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) {
 | 
						|
	if t, ok := v.(time.Time); ok {
 | 
						|
		return t, nil
 | 
						|
	}
 | 
						|
	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
 | 
						|
}
 |