github.com/mitranim/gg@v0.1.17/time_micro.go (about)

     1  package gg
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/json"
     6  	r "reflect"
     7  	"strconv"
     8  	"time"
     9  )
    10  
    11  // Calls `time.Now` and converts to `TimeMicro`, truncating precision.
    12  func TimeMicroNow() TimeMicro { return TimeMicro(time.Now().UnixMicro()) }
    13  
    14  // Shortcut for parsing text into `TimeMicro`. Panics on error.
    15  func TimeMicroParse[A Text](src A) TimeMicro {
    16  	var out TimeMicro
    17  	Try(out.Parse(ToString(src)))
    18  	return out
    19  }
    20  
    21  /*
    22  Represents a Unix timestamp in microseconds. In text and JSON, this type
    23  supports parsing numeric timestamps and RFC3339 timestamps, but always encodes
    24  as a number. In SQL, this type is represented in the RFC3339 format. This type
    25  is "zero-optional" or "zero-nullable". The zero value is considered empty in
    26  text and null in JSON/SQL. Conversion to `time.Time` doesn't specify a
    27  timezone, which means it uses `time.Local` by default. If you prefer UTC,
    28  enforce it across the app by updating `time.Local`.
    29  
    30  Caution: corresponding DB columns MUST be restricted to microsecond precision.
    31  Without this restriction, encoding and decoding is not reversible. After losing
    32  precision to an encoding-decoding roundtrip, you might be unable to find a
    33  corresponding value in a database, if timestamp precision is higher than a
    34  microsecond.
    35  
    36  Also see `TimeMilli`, which uses milliseconds.
    37  */
    38  type TimeMicro int64
    39  
    40  // Implement `Nullable`. True if zero.
    41  func (self TimeMicro) IsNull() bool { return self == 0 }
    42  
    43  // Implement `Clearer`, zeroing the receiver.
    44  func (self *TimeMicro) Clear() {
    45  	if self != nil {
    46  		*self = 0
    47  	}
    48  }
    49  
    50  /*
    51  Convert to `time.Time` by calling `time.UnixMicro`. The resulting timestamp has
    52  the timezone `time.Local`. To enforce UTC, modify `time.Local` at app startup,
    53  or call `.In(time.UTC)`.
    54  */
    55  func (self TimeMicro) Time() time.Time { return time.UnixMicro(int64(self)) }
    56  
    57  /*
    58  Implement `AnyGetter` for compatibility with some 3rd party libraries. If zero,
    59  returns `nil`, otherwise creates `time.Time` by calling `TimeMicro.Time`.
    60  */
    61  func (self TimeMicro) Get() any {
    62  	if self.IsNull() {
    63  		return nil
    64  	}
    65  	return self.Time()
    66  }
    67  
    68  // Sets the receiver to the given input.
    69  func (self *TimeMicro) SetInt64(val int64) { *self = TimeMicro(val) }
    70  
    71  // Sets the receiver to the result of `time.Time.UnixMicro`.
    72  func (self *TimeMicro) SetTime(val time.Time) { self.SetInt64(val.UnixMicro()) }
    73  
    74  /*
    75  Implement `Parser`. The input must be either an integer in base 10, representing
    76  a Unix millisecond timestamp, or an RFC3339 timestamp. RFC3339 is the default
    77  time encoding/decoding format in Go and some other languages.
    78  */
    79  func (self *TimeMicro) Parse(src string) error {
    80  	if len(src) <= 0 {
    81  		self.Clear()
    82  		return nil
    83  	}
    84  
    85  	if isIntString(src) {
    86  		num, err := strconv.ParseInt(src, 10, 64)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		self.SetInt64(num)
    91  		return nil
    92  	}
    93  
    94  	inst, err := time.Parse(time.RFC3339, src)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	self.SetTime(inst)
    99  	return nil
   100  }
   101  
   102  /*
   103  Implement `fmt.Stringer`. If zero, returns an empty string. Otherwise returns
   104  the base 10 representation of the underlying number.
   105  */
   106  func (self TimeMicro) String() string {
   107  	if self.IsNull() {
   108  		return ``
   109  	}
   110  	return strconv.FormatInt(int64(self), 10)
   111  }
   112  
   113  // Implement `AppenderTo`, using the same representation as `.String`.
   114  func (self TimeMicro) AppendTo(buf []byte) []byte {
   115  	if self.IsNull() {
   116  		return buf
   117  	}
   118  	return strconv.AppendInt(buf, int64(self), 10)
   119  }
   120  
   121  /*
   122  Implement `encoding.TextMarhaler`. If zero, returns nil. Otherwise returns the
   123  same representation as `.String`.
   124  */
   125  func (self TimeMicro) MarshalText() ([]byte, error) {
   126  	if self.IsNull() {
   127  		return nil, nil
   128  	}
   129  	return ToBytes(self.String()), nil
   130  }
   131  
   132  // Implement `encoding.TextUnmarshaler`, using the same algorithm as `.Parse`.
   133  func (self *TimeMicro) UnmarshalText(src []byte) error {
   134  	return self.Parse(ToString(src))
   135  }
   136  
   137  /*
   138  Implement `json.Marshaler`. If zero, returns bytes representing `null`.
   139  Otherwise encodes as a JSON number.
   140  */
   141  func (self TimeMicro) MarshalJSON() ([]byte, error) {
   142  	if self.IsNull() {
   143  		return ToBytes(`null`), nil
   144  	}
   145  	return json.Marshal(int64(self))
   146  }
   147  
   148  /*
   149  Implement `json.Unmarshaler`. If the input is empty or represents JSON `null`,
   150  zeroes the receiver. If the input is a JSON number, parses it in accordance
   151  with `.Parse`. Otherwise uses the default `json.Unmarshal` behavior for
   152  `*time.Time` and stores the resulting timestamp in milliseconds.
   153  */
   154  func (self *TimeMicro) UnmarshalJSON(src []byte) error {
   155  	if IsJsonEmpty(src) {
   156  		self.Clear()
   157  		return nil
   158  	}
   159  
   160  	if isIntString(ToString(src)) {
   161  		num, err := strconv.ParseInt(ToString(src), 10, 64)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		self.SetInt64(num)
   166  		return nil
   167  	}
   168  
   169  	var inst time.Time
   170  	err := json.Unmarshal(src, &inst)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	self.SetTime(inst)
   175  	return nil
   176  }
   177  
   178  // Implement `driver.Valuer`, using `.Get`.
   179  func (self TimeMicro) Value() (driver.Value, error) {
   180  	return self.Get(), nil
   181  }
   182  
   183  /*
   184  Implement `sql.Scanner`, converting an arbitrary input to `TimeMicro` and
   185  modifying the receiver. Acceptable inputs:
   186  
   187  	* `nil`         -> use `.Clear`
   188  	* integer       -> assign, assuming milliseconds
   189  	* text          -> use `.Parse`
   190  	* `time.Time`   -> use `.SetTime`
   191  	* `*time.Time`  -> use `.Clear` or `.SetTime`
   192  	* `AnyGetter`   -> scan underlying value
   193  */
   194  func (self *TimeMicro) Scan(src any) error {
   195  	str, ok := AnyToText[string](src)
   196  	if ok {
   197  		return self.Parse(str)
   198  	}
   199  
   200  	switch src := src.(type) {
   201  	case nil:
   202  		self.Clear()
   203  		return nil
   204  
   205  	case time.Time:
   206  		self.SetTime(src)
   207  		return nil
   208  
   209  	case *time.Time:
   210  		if src == nil {
   211  			self.Clear()
   212  		} else {
   213  			self.SetTime(*src)
   214  		}
   215  		return nil
   216  
   217  	case int64:
   218  		self.SetInt64(src)
   219  		return nil
   220  
   221  	case TimeMicro:
   222  		*self = src
   223  		return nil
   224  
   225  	default:
   226  		val := r.ValueOf(src)
   227  		if val.CanInt() {
   228  			self.SetInt64(val.Int())
   229  			return nil
   230  		}
   231  		return ErrConv(src, Type[TimeMicro]())
   232  	}
   233  }