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  }