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 }