add mtime package
This commit is contained in:
parent
5f8eb46c51
commit
1276c31fe9
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# mediocre-go-lib
|
||||
|
||||
This is a collection of packages which I use across many of my personal
|
||||
projects. All packages intended to be used start with an `m`, packages not
|
||||
starting with `m` are for internal use within this set of packages.
|
42
mtime/dur.go
Normal file
42
mtime/dur.go
Normal file
@ -0,0 +1,42 @@
|
||||
package mtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration wraps time.Duration to implement marshaling and unmarshaling methods
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
// MarshalText implements the text.Marshaler interface
|
||||
func (d Duration) MarshalText() ([]byte, error) {
|
||||
return []byte(d.Duration.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the text.Unmarshaler interface
|
||||
func (d *Duration) UnmarshalText(b []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(b))
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface, marshaling the Duration
|
||||
// as a json string via Duration's String method
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface, unmarshaling the
|
||||
// Duration as a JSON string and using the time.ParseDuration function on that
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Duration, err = time.ParseDuration(s)
|
||||
return err
|
||||
}
|
30
mtime/dur_test.go
Normal file
30
mtime/dur_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package mtime
|
||||
|
||||
import (
|
||||
. "testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDuration(t *T) {
|
||||
{
|
||||
b, err := Duration{5 * time.Second}.MarshalText()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("5s"), b)
|
||||
|
||||
var d Duration
|
||||
assert.NoError(t, d.UnmarshalText(b))
|
||||
assert.Equal(t, 5*time.Second, d.Duration)
|
||||
}
|
||||
|
||||
{
|
||||
b, err := Duration{5 * time.Second}.MarshalJSON()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(`"5s"`), b)
|
||||
|
||||
var d Duration
|
||||
assert.NoError(t, d.UnmarshalJSON(b))
|
||||
assert.Equal(t, 5*time.Second, d.Duration)
|
||||
}
|
||||
}
|
5
mtime/mtime.go
Normal file
5
mtime/mtime.go
Normal file
@ -0,0 +1,5 @@
|
||||
// Package mtime provides a wrapper around time.Time which marshals and unmarshals
|
||||
// it as a floating point unix timestamp.
|
||||
|
||||
// Package mtime extends the standard time package with extra functionality
|
||||
package mtime
|
118
mtime/ts.go
Normal file
118
mtime/ts.go
Normal file
@ -0,0 +1,118 @@
|
||||
package mtime
|
||||
|
||||
// Code based off the timeutil package in github.com/levenlabs/golib
|
||||
// Changes performed:
|
||||
// - Renamed Timestamp to TS for brevity
|
||||
// - Added NewTS function
|
||||
// - Moved Float64 method
|
||||
// - Moved initialization methods to top
|
||||
// - Made MarshalJSON use String method
|
||||
// - TSNow -> NowTS, make it use NewTS
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var unixZero = time.Unix(0, 0)
|
||||
|
||||
func timeToFloat(t time.Time) float64 {
|
||||
// If time.Time is the empty value, UnixNano will return the farthest back
|
||||
// timestamp a float can represent, which is some large negative value. We
|
||||
// compromise and call it zero
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return float64(t.UnixNano()) / 1e9
|
||||
}
|
||||
|
||||
// TS is a wrapper around time.Time which adds methods to marshal and
|
||||
// unmarshal the value as a unix timestamp instead of a formatted string
|
||||
type TS struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// NewTS returns a new TS instance wrapping the given time.Time, which will
|
||||
// possibly be truncated a certain amount to account for floating point
|
||||
// precision.
|
||||
func NewTS(t time.Time) TS {
|
||||
return TSFromFloat64(timeToFloat(t))
|
||||
}
|
||||
|
||||
// NowTS is a wrapper around time.Now which returns a TS.
|
||||
func NowTS() TS {
|
||||
return NewTS(time.Now())
|
||||
}
|
||||
|
||||
// TSFromInt64 returns a TS equal to the given int64, assuming it too is a unix
|
||||
// timestamp
|
||||
func TSFromInt64(ts int64) TS {
|
||||
return TS{time.Unix(ts, 0)}
|
||||
}
|
||||
|
||||
// TSFromFloat64 returns a TS equal to the given float64, assuming it too is a
|
||||
// unix timestamp. The float64 is interpreted as number of seconds, with
|
||||
// everything after the decimal indicating milliseconds, microseconds, and
|
||||
// nanoseconds
|
||||
func TSFromFloat64(ts float64) TS {
|
||||
secs := int64(ts)
|
||||
nsecs := int64((ts - float64(secs)) * 1e9)
|
||||
return TS{time.Unix(secs, nsecs)}
|
||||
}
|
||||
|
||||
// TSFromString attempts to parse the string as a float64, and then passes that
|
||||
// into TSFromFloat64, returning the result
|
||||
func TSFromString(ts string) (TS, error) {
|
||||
f, err := strconv.ParseFloat(ts, 64)
|
||||
if err != nil {
|
||||
return TS{}, err
|
||||
}
|
||||
return TSFromFloat64(f), nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the TS, in the form of a floating
|
||||
// point form of the time as a unix timestamp
|
||||
func (t TS) String() string {
|
||||
ts := timeToFloat(t.Time)
|
||||
return strconv.FormatFloat(ts, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// Float64 returns the float representation of the timestamp in seconds.
|
||||
func (t TS) Float64() float64 {
|
||||
return timeToFloat(t.Time)
|
||||
}
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// MarshalJSON returns the JSON representation of the TS as an integer. It
|
||||
// never returns an error
|
||||
func (t TS) MarshalJSON() ([]byte, error) {
|
||||
if t.IsZero() {
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes a JSON integer and converts it into a TS, or
|
||||
// returns an error if this can't be done
|
||||
func (t *TS) UnmarshalJSON(b []byte) error {
|
||||
// since 0 is a valid timestamp we can't use that to mean "unset", so we
|
||||
// take null to mean unset instead
|
||||
if bytes.Equal(b, jsonNull) {
|
||||
t.Time = time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
*t, err = TSFromString(string(b))
|
||||
return err
|
||||
}
|
||||
|
||||
// IsUnixZero returns true if the timestamp is equal to the unix zero timestamp,
|
||||
// representing 1/1/1970. This is different than checking if the timestamp is
|
||||
// the empty value (which should be done with IsZero)
|
||||
func (t TS) IsUnixZero() bool {
|
||||
return t.Equal(unixZero)
|
||||
}
|
118
mtime/ts_test.go
Normal file
118
mtime/ts_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
package mtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
. "testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTS(t *T) {
|
||||
ts := NowTS()
|
||||
|
||||
tsJ, err := json.Marshal(&ts)
|
||||
require.Nil(t, err)
|
||||
|
||||
// tsJ should basically be an integer
|
||||
tsF, err := strconv.ParseFloat(string(tsJ), 64)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, tsF > 0)
|
||||
|
||||
ts2 := TSFromFloat64(tsF)
|
||||
assert.Equal(t, ts, ts2)
|
||||
|
||||
var ts3 TS
|
||||
err = json.Unmarshal(tsJ, &ts3)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, ts, ts3)
|
||||
}
|
||||
|
||||
// Make sure that we can take in a non-float from json
|
||||
func TestTSMarshalInt(t *T) {
|
||||
now := time.Now()
|
||||
tsJ := []byte(strconv.FormatInt(now.Unix(), 10))
|
||||
var ts TS
|
||||
err := json.Unmarshal(tsJ, &ts)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, ts.Float64(), float64(now.Unix()))
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
T TS `json:"timestamp" bson:"t"`
|
||||
}
|
||||
|
||||
func TestTSJSON(t *T) {
|
||||
now := NowTS()
|
||||
in := Foo{now}
|
||||
b, err := json.Marshal(in)
|
||||
require.Nil(t, err)
|
||||
assert.NotEmpty(t, b)
|
||||
|
||||
var out Foo
|
||||
err = json.Unmarshal(b, &out)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, in, out)
|
||||
}
|
||||
|
||||
func TestTSJSONNull(t *T) {
|
||||
{
|
||||
var foo Foo
|
||||
timestampNull := []byte(`{"timestamp":null}`)
|
||||
fooJSON, err := json.Marshal(foo)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, timestampNull, fooJSON)
|
||||
|
||||
require.Nil(t, json.Unmarshal(timestampNull, &foo))
|
||||
assert.True(t, foo.T.IsZero())
|
||||
assert.False(t, foo.T.IsUnixZero())
|
||||
}
|
||||
|
||||
{
|
||||
var foo Foo
|
||||
foo.T = TS{Time: unixZero}
|
||||
timestampZero := []byte(`{"timestamp":0}`)
|
||||
fooJSON, err := json.Marshal(foo)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, timestampZero, fooJSON)
|
||||
|
||||
require.Nil(t, json.Unmarshal(timestampZero, &foo))
|
||||
assert.False(t, foo.T.IsZero())
|
||||
assert.True(t, foo.T.IsUnixZero())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTSZero(t *T) {
|
||||
var ts TS
|
||||
assert.True(t, ts.IsZero())
|
||||
assert.False(t, ts.IsUnixZero())
|
||||
tsf := timeToFloat(ts.Time)
|
||||
assert.Zero(t, tsf)
|
||||
|
||||
ts = TSFromFloat64(0)
|
||||
assert.False(t, ts.IsZero())
|
||||
assert.True(t, ts.IsUnixZero())
|
||||
tsf = timeToFloat(ts.Time)
|
||||
assert.Zero(t, tsf)
|
||||
}
|
||||
|
||||
func TestTSBSON(t *T) {
|
||||
// BSON only supports up to millisecond precision, but even if we keep that
|
||||
// many it kinda gets messed up due to rounding errors. So we just give it
|
||||
// one with second precision
|
||||
now := TSFromInt64(time.Now().Unix())
|
||||
|
||||
in := Foo{now}
|
||||
b, err := bson.Marshal(in)
|
||||
require.Nil(t, err)
|
||||
assert.NotEmpty(t, b)
|
||||
|
||||
var out Foo
|
||||
err = bson.Unmarshal(b, &out)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, in, out)
|
||||
}
|
Loading…
Reference in New Issue
Block a user