Move timeutils functions to the only places where they are used.
- Move time json marshaling to the jsonlog package: this is a docker internal hack that we should not promote as a library. - Move Timestamp encoding/decoding functions to the API types: This is only used there. It could be a standalone library but I don't this it's worth having a separated repo for this. It could introduce more complexity than it solves. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
202e73c7ad
commit
0585b88aee
7 changed files with 25 additions and 246 deletions
|
@ -13,8 +13,6 @@
|
|||
// "bytes"
|
||||
//-
|
||||
// "unicode/utf8"
|
||||
//+
|
||||
//+ "github.com/docker/docker/pkg/timeutils"
|
||||
// )
|
||||
//
|
||||
// func (mj *JSONLog) MarshalJSON() ([]byte, error) {
|
||||
|
@ -43,7 +41,7 @@
|
|||
// }
|
||||
// buf.WriteString(`"time":`)
|
||||
//- obj, err = mj.Created.MarshalJSON()
|
||||
//+ timestamp, err = timeutils.FastMarshalJSON(mj.Created)
|
||||
//+ timestamp, err = FastTimeMarshalJSON(mj.Created)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
@ -69,8 +67,6 @@ package jsonlog
|
|||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
)
|
||||
|
||||
// MarshalJSON marshals the JSONLog.
|
||||
|
@ -111,7 +107,7 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error {
|
|||
buf.WriteString(`,`)
|
||||
}
|
||||
buf.WriteString(`"time":`)
|
||||
timestamp, err = timeutils.FastMarshalJSON(mj.Created)
|
||||
timestamp, err = FastTimeMarshalJSON(mj.Created)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Package timeutils provides helper functions to parse and print time (time.Time).
|
||||
package timeutils
|
||||
// Package jsonlog provides helper functions to parse and print time (time.Time) as JSON.
|
||||
package jsonlog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -15,9 +15,9 @@ const (
|
|||
JSONFormat = `"` + time.RFC3339Nano + `"`
|
||||
)
|
||||
|
||||
// FastMarshalJSON avoids one of the extra allocations that
|
||||
// FastTimeMarshalJSON avoids one of the extra allocations that
|
||||
// time.MarshalJSON is making.
|
||||
func FastMarshalJSON(t time.Time) (string, error) {
|
||||
func FastTimeMarshalJSON(t time.Time) (string, error) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
// RFC 3339 is clear that years are 4 digits exactly.
|
||||
// See golang.org/issue/4556#c15 for more discussion.
|
|
@ -1,4 +1,4 @@
|
|||
package timeutils
|
||||
package jsonlog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -6,23 +6,23 @@ import (
|
|||
)
|
||||
|
||||
// Testing to ensure 'year' fields is between 0 and 9999
|
||||
func TestFastMarshalJSONWithInvalidDate(t *testing.T) {
|
||||
func TestFastTimeMarshalJSONWithInvalidDate(t *testing.T) {
|
||||
aTime := time.Date(-1, 1, 1, 0, 0, 0, 0, time.Local)
|
||||
json, err := FastMarshalJSON(aTime)
|
||||
json, err := FastTimeMarshalJSON(aTime)
|
||||
if err == nil {
|
||||
t.Fatalf("FastMarshalJSON should throw an error, but was '%v'", json)
|
||||
t.Fatalf("FastTimeMarshalJSON should throw an error, but was '%v'", json)
|
||||
}
|
||||
anotherTime := time.Date(10000, 1, 1, 0, 0, 0, 0, time.Local)
|
||||
json, err = FastMarshalJSON(anotherTime)
|
||||
json, err = FastTimeMarshalJSON(anotherTime)
|
||||
if err == nil {
|
||||
t.Fatalf("FastMarshalJSON should throw an error, but was '%v'", json)
|
||||
t.Fatalf("FastTimeMarshalJSON should throw an error, but was '%v'", json)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFastMarshalJSON(t *testing.T) {
|
||||
func TestFastTimeMarshalJSON(t *testing.T) {
|
||||
aTime := time.Date(2015, 5, 29, 11, 1, 2, 3, time.UTC)
|
||||
json, err := FastMarshalJSON(aTime)
|
||||
json, err := FastTimeMarshalJSON(aTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func TestFastMarshalJSON(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
aTime = time.Date(2015, 5, 29, 11, 1, 2, 3, location)
|
||||
json, err = FastMarshalJSON(aTime)
|
||||
json, err = FastTimeMarshalJSON(aTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
|
@ -123,9 +123,9 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
|
|||
return nil
|
||||
}
|
||||
if jm.TimeNano != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(timeutils.RFC3339NanoFixed))
|
||||
fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed))
|
||||
} else if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(timeutils.RFC3339NanoFixed))
|
||||
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed))
|
||||
}
|
||||
if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%s: ", jm.ID)
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
|
@ -71,8 +71,8 @@ func TestJSONMessageDisplay(t *testing.T) {
|
|||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// General, with nano precision time
|
||||
JSONMessage{
|
||||
|
@ -81,8 +81,8 @@ func TestJSONMessageDisplay(t *testing.T) {
|
|||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// General, with both times Nano is preferred
|
||||
JSONMessage{
|
||||
|
@ -92,8 +92,8 @@ func TestJSONMessageDisplay(t *testing.T) {
|
|||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// Stream over status
|
||||
JSONMessage{
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
package timeutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These are additional predefined layouts for use in Time.Format and Time.Parse
|
||||
// with --since and --until parameters for `docker logs` and `docker events`
|
||||
const (
|
||||
rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone
|
||||
rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone
|
||||
dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00
|
||||
dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00
|
||||
)
|
||||
|
||||
// GetTimestamp tries to parse given string as golang duration,
|
||||
// then RFC3339 time and finally as a Unix timestamp. If
|
||||
// any of these were successful, it returns a Unix timestamp
|
||||
// as string otherwise returns the given value back.
|
||||
// In case of duration input, the returned timestamp is computed
|
||||
// as the given reference time minus the amount of the duration.
|
||||
func GetTimestamp(value string, reference time.Time) (string, error) {
|
||||
if d, err := time.ParseDuration(value); value != "0" && err == nil {
|
||||
return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil
|
||||
}
|
||||
|
||||
var format string
|
||||
var parseInLocation bool
|
||||
|
||||
// if the string has a Z or a + or three dashes use parse otherwise use parseinlocation
|
||||
parseInLocation = !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3)
|
||||
|
||||
if strings.Contains(value, ".") {
|
||||
if parseInLocation {
|
||||
format = rFC3339NanoLocal
|
||||
} else {
|
||||
format = time.RFC3339Nano
|
||||
}
|
||||
} else if strings.Contains(value, "T") {
|
||||
// we want the number of colons in the T portion of the timestamp
|
||||
tcolons := strings.Count(value, ":")
|
||||
// if parseInLocation is off and we have a +/- zone offset (not Z) then
|
||||
// there will be an extra colon in the input for the tz offset subtract that
|
||||
// colon from the tcolons count
|
||||
if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 {
|
||||
tcolons--
|
||||
}
|
||||
if parseInLocation {
|
||||
switch tcolons {
|
||||
case 0:
|
||||
format = "2006-01-02T15"
|
||||
case 1:
|
||||
format = "2006-01-02T15:04"
|
||||
default:
|
||||
format = rFC3339Local
|
||||
}
|
||||
} else {
|
||||
switch tcolons {
|
||||
case 0:
|
||||
format = "2006-01-02T15Z07:00"
|
||||
case 1:
|
||||
format = "2006-01-02T15:04Z07:00"
|
||||
default:
|
||||
format = time.RFC3339
|
||||
}
|
||||
}
|
||||
} else if parseInLocation {
|
||||
format = dateLocal
|
||||
} else {
|
||||
format = dateWithZone
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
var err error
|
||||
|
||||
if parseInLocation {
|
||||
t, err = time.ParseInLocation(format, value, time.FixedZone(time.Now().Zone()))
|
||||
} else {
|
||||
t, err = time.Parse(format, value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// if there is a `-` then its an RFC3339 like timestamp otherwise assume unixtimestamp
|
||||
if strings.Contains(value, "-") {
|
||||
return "", err // was probably an RFC3339 like timestamp but the parser failed with an error
|
||||
}
|
||||
return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
|
||||
}
|
||||
|
||||
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the
|
||||
// format "%d.%09d", time.Unix(), int64(time.Nanosecond()))
|
||||
// if the incoming nanosecond portion is longer or shorter than 9 digits it is
|
||||
// converted to nanoseconds. The expectation is that the seconds and
|
||||
// seconds will be used to create a time variable. For example:
|
||||
// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0)
|
||||
// if err == nil since := time.Unix(seconds, nanoseconds)
|
||||
// returns seconds as def(aultSeconds) if value == ""
|
||||
func ParseTimestamps(value string, def int64) (int64, int64, error) {
|
||||
if value == "" {
|
||||
return def, 0, nil
|
||||
}
|
||||
sa := strings.SplitN(value, ".", 2)
|
||||
s, err := strconv.ParseInt(sa[0], 10, 64)
|
||||
if err != nil {
|
||||
return s, 0, err
|
||||
}
|
||||
if len(sa) != 2 {
|
||||
return s, 0, nil
|
||||
}
|
||||
n, err := strconv.ParseInt(sa[1], 10, 64)
|
||||
if err != nil {
|
||||
return s, n, err
|
||||
}
|
||||
// should already be in nanoseconds but just in case convert n to nanoseonds
|
||||
n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1]))))
|
||||
return s, n, nil
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package timeutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetTimestamp(t *testing.T) {
|
||||
now := time.Now()
|
||||
cases := []struct {
|
||||
in, expected string
|
||||
expectedErr bool
|
||||
}{
|
||||
// Partial RFC3339 strings get parsed with second precision
|
||||
{"2006-01-02T15:04:05.999999999+07:00", "1136189045.999999999", false},
|
||||
{"2006-01-02T15:04:05.999999999Z", "1136214245.999999999", false},
|
||||
{"2006-01-02T15:04:05.999999999", "1136214245.999999999", false},
|
||||
{"2006-01-02T15:04:05Z", "1136214245.000000000", false},
|
||||
{"2006-01-02T15:04:05", "1136214245.000000000", false},
|
||||
{"2006-01-02T15:04:0Z", "", true},
|
||||
{"2006-01-02T15:04:0", "", true},
|
||||
{"2006-01-02T15:04Z", "1136214240.000000000", false},
|
||||
{"2006-01-02T15:04+00:00", "1136214240.000000000", false},
|
||||
{"2006-01-02T15:04-00:00", "1136214240.000000000", false},
|
||||
{"2006-01-02T15:04", "1136214240.000000000", false},
|
||||
{"2006-01-02T15:0Z", "", true},
|
||||
{"2006-01-02T15:0", "", true},
|
||||
{"2006-01-02T15Z", "1136214000.000000000", false},
|
||||
{"2006-01-02T15+00:00", "1136214000.000000000", false},
|
||||
{"2006-01-02T15-00:00", "1136214000.000000000", false},
|
||||
{"2006-01-02T15", "1136214000.000000000", false},
|
||||
{"2006-01-02T1Z", "1136163600.000000000", false},
|
||||
{"2006-01-02T1", "1136163600.000000000", false},
|
||||
{"2006-01-02TZ", "", true},
|
||||
{"2006-01-02T", "", true},
|
||||
{"2006-01-02+00:00", "1136160000.000000000", false},
|
||||
{"2006-01-02-00:00", "1136160000.000000000", false},
|
||||
{"2006-01-02-00:01", "1136160060.000000000", false},
|
||||
{"2006-01-02Z", "1136160000.000000000", false},
|
||||
{"2006-01-02", "1136160000.000000000", false},
|
||||
{"2015-05-13T20:39:09Z", "1431549549.000000000", false},
|
||||
|
||||
// unix timestamps returned as is
|
||||
{"1136073600", "1136073600", false},
|
||||
{"1136073600.000000001", "1136073600.000000001", false},
|
||||
// Durations
|
||||
{"1m", fmt.Sprintf("%d", now.Add(-1*time.Minute).Unix()), false},
|
||||
{"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
|
||||
{"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
|
||||
|
||||
// String fallback
|
||||
{"invalid", "invalid", false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
o, err := GetTimestamp(c.in, now)
|
||||
if o != c.expected ||
|
||||
(err == nil && c.expectedErr) ||
|
||||
(err != nil && !c.expectedErr) {
|
||||
t.Errorf("wrong value for '%s'. expected:'%s' got:'%s' with error: `%s`", c.in, c.expected, o, err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTimestamps(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
def, expectedS, expectedN int64
|
||||
expectedErr bool
|
||||
}{
|
||||
// unix timestamps
|
||||
{"1136073600", 0, 1136073600, 0, false},
|
||||
{"1136073600.000000001", 0, 1136073600, 1, false},
|
||||
{"1136073600.0000000010", 0, 1136073600, 1, false},
|
||||
{"1136073600.00000001", 0, 1136073600, 10, false},
|
||||
{"foo.bar", 0, 0, 0, true},
|
||||
{"1136073600.bar", 0, 1136073600, 0, true},
|
||||
{"", -1, -1, 0, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
s, n, err := ParseTimestamps(c.in, c.def)
|
||||
if s != c.expectedS ||
|
||||
n != c.expectedN ||
|
||||
(err == nil && c.expectedErr) ||
|
||||
(err != nil && !c.expectedErr) {
|
||||
t.Errorf("wrong values for input `%s` with default `%d` expected:'%d'seconds and `%d`nanosecond got:'%d'seconds and `%d`nanoseconds with error: `%s`", c.in, c.def, c.expectedS, c.expectedN, s, n, err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue