github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/pkg/fftypes/timeutils.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package fftypes 18 19 import ( 20 "context" 21 "database/sql/driver" 22 "encoding/json" 23 "strconv" 24 "time" 25 26 "github.com/kaleido-io/firefly/internal/i18n" 27 "github.com/kaleido-io/firefly/internal/log" 28 ) 29 30 // FFTime is serialized to JSON on the API in RFC3339 nanosecond UTC time 31 // (noting that JavaScript can parse this format happily into millisecond time with Date.pase()). 32 // It is persisted as a nanosecond resolution timestamp in the database. 33 // It can be parsed from RFC3339, or unix timestamps (second, millisecond or nanosecond resolution) 34 type FFTime time.Time 35 36 // FFDuration is serialized to JSON in the string format of time.Duration 37 // It can be unmarshalled from a number, or a string. 38 // - If it is a string in time.Duration format, that will be used 39 // - If it is a string that can be parsed as an int64, that will be used in Milliseconds 40 // - If it is a number, that will be used in Milliseconds 41 type FFDuration time.Duration 42 43 func Now() *FFTime { 44 t := FFTime(time.Now().UTC()) 45 return &t 46 } 47 48 func ZeroTime() FFTime { 49 return FFTime(time.Time{}.UTC()) 50 } 51 52 func UnixTime(unixTime int64) *FFTime { 53 if unixTime < 1e10 { 54 unixTime *= 1e3 // secs to millis 55 } 56 if unixTime < 1e15 { 57 unixTime *= 1e6 // millis to nanos 58 } 59 t := FFTime(time.Unix(0, unixTime)) 60 return &t 61 } 62 63 func (ft *FFTime) MarshalJSON() ([]byte, error) { 64 if ft == nil || time.Time(*ft).IsZero() { 65 return json.Marshal(nil) 66 } 67 return json.Marshal(ft.String()) 68 } 69 70 func ParseString(str string) (*FFTime, error) { 71 t, err := time.Parse(time.RFC3339Nano, str) 72 if err != nil { 73 var unixTime int64 74 unixTime, err = strconv.ParseInt(str, 10, 64) 75 if err == nil { 76 return UnixTime(unixTime), nil 77 } 78 } 79 if err != nil { 80 zero := ZeroTime() 81 return &zero, i18n.NewError(context.Background(), i18n.MsgTimeParseFail, str) 82 } 83 ft := FFTime(t) 84 return &ft, nil 85 } 86 87 func (ft *FFTime) UnixNano() int64 { 88 if ft == nil { 89 return 0 90 } 91 return time.Time(*ft).UnixNano() 92 } 93 94 func (ft *FFTime) UnmarshalText(b []byte) error { 95 t, err := ParseString(string(b)) 96 if err != nil { 97 return err 98 } 99 *ft = *t 100 return nil 101 } 102 103 // Scan implements sql.Scanner 104 func (ft *FFTime) Scan(src interface{}) error { 105 switch src := src.(type) { 106 case nil: 107 *ft = ZeroTime() 108 return nil 109 110 case string: 111 t, err := ParseString(src) 112 if err != nil { 113 return err 114 } 115 *ft = *t 116 return nil 117 118 case int64: 119 if src == 0 { 120 return nil 121 } 122 t := UnixTime(src) 123 *ft = *t 124 return nil 125 126 default: 127 return i18n.NewError(context.Background(), i18n.MsgScanFailed, src, ft) 128 } 129 130 } 131 132 // Value implements sql.Valuer 133 func (ft FFTime) Value() (driver.Value, error) { 134 if time.Time(ft).IsZero() { 135 return int64(0), nil 136 } 137 return ft.UnixNano(), nil 138 } 139 140 func (ft FFTime) String() string { 141 if time.Time(ft).IsZero() { 142 return "" 143 } 144 return time.Time(ft).UTC().Format(time.RFC3339Nano) 145 } 146 147 // ParseToDuration is a standard handling of any duration string, in config or API options 148 func ParseToDuration(durationString string) time.Duration { 149 if durationString == "" { 150 return time.Duration(0) 151 } 152 ffd, err := ParseDurationString(durationString) 153 if err != nil { 154 log.L(context.Background()).Warn(err) 155 } 156 return time.Duration(ffd) 157 } 158 159 // ParseDurationString is a standard handling of any duration string, in config or API options 160 func ParseDurationString(durationString string) (FFDuration, error) { 161 duration, err := time.ParseDuration(durationString) 162 if err != nil { 163 intVal, err := strconv.ParseInt(durationString, 10, 64) 164 if err != nil { 165 return 0, i18n.NewError(context.Background(), i18n.MsgDurationParseFail, durationString) 166 } 167 // Default is milliseconds for all durations 168 duration = time.Duration(intVal) * time.Millisecond 169 } 170 return FFDuration(duration), nil 171 } 172 173 func (fd *FFDuration) MarshalJSON() ([]byte, error) { 174 return json.Marshal(time.Duration(*fd).String()) 175 } 176 177 func (fd *FFDuration) UnmarshalJSON(b []byte) error { 178 var stringVal string 179 err := json.Unmarshal(b, &stringVal) 180 if err != nil { 181 var intVal int64 182 err = json.Unmarshal(b, &intVal) 183 if err != nil { 184 return err 185 } 186 *fd = FFDuration(intVal) * FFDuration(time.Millisecond) 187 return nil 188 } 189 duration, err := ParseDurationString(stringVal) 190 if err != nil { 191 return err 192 } 193 *fd = duration 194 return nil 195 } 196 197 // Scan implements sql.Scanner 198 func (fd *FFDuration) Scan(src interface{}) error { 199 switch src := src.(type) { 200 case nil: 201 *fd = 0 202 return nil 203 204 case string: 205 duration, err := ParseDurationString(src) 206 if err != nil { 207 return err 208 } 209 *fd = duration 210 return nil 211 212 case int: 213 *fd = FFDuration(time.Duration(src) * time.Millisecond) 214 return nil 215 216 case int64: 217 *fd = FFDuration(time.Duration(src) * time.Millisecond) 218 return nil 219 220 default: 221 return i18n.NewError(context.Background(), i18n.MsgScanFailed, src, fd) 222 } 223 224 } 225 226 // Value implements sql.Valuer 227 func (fd *FFDuration) Value() (driver.Value, error) { 228 return fd.String(), nil 229 } 230 231 func (fd *FFDuration) String() string { 232 if fd == nil { 233 return "" 234 } 235 return time.Duration(*fd).String() 236 }