From 0585b88aeef198ac89a0e8685f49d6b433a5fcc6 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 15 Dec 2015 14:49:41 -0500 Subject: [PATCH] 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 --- jsonlog/jsonlog_marshalling.go | 8 +- .../json.go => jsonlog/time_marshalling.go | 8 +- .../time_marshalling_test.go | 18 +-- jsonmessage/jsonmessage.go | 6 +- jsonmessage/jsonmessage_test.go | 14 +- timeutils/utils.go | 124 ------------------ timeutils/utils_test.go | 93 ------------- 7 files changed, 25 insertions(+), 246 deletions(-) rename timeutils/json.go => jsonlog/time_marshalling.go (74%) rename timeutils/json_test.go => jsonlog/time_marshalling_test.go (63%) delete mode 100644 timeutils/utils.go delete mode 100644 timeutils/utils_test.go diff --git a/jsonlog/jsonlog_marshalling.go b/jsonlog/jsonlog_marshalling.go index 6dcb69d..31b047e 100644 --- a/jsonlog/jsonlog_marshalling.go +++ b/jsonlog/jsonlog_marshalling.go @@ -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 } diff --git a/timeutils/json.go b/jsonlog/time_marshalling.go similarity index 74% rename from timeutils/json.go rename to jsonlog/time_marshalling.go index ea19daa..2117338 100644 --- a/timeutils/json.go +++ b/jsonlog/time_marshalling.go @@ -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. diff --git a/timeutils/json_test.go b/jsonlog/time_marshalling_test.go similarity index 63% rename from timeutils/json_test.go rename to jsonlog/time_marshalling_test.go index 1ff3331..02d0302 100644 --- a/timeutils/json_test.go +++ b/jsonlog/time_marshalling_test.go @@ -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) } diff --git a/jsonmessage/jsonmessage.go b/jsonmessage/jsonmessage.go index c234ff6..419a47d 100644 --- a/jsonmessage/jsonmessage.go +++ b/jsonmessage/jsonmessage.go @@ -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) diff --git a/jsonmessage/jsonmessage_test.go b/jsonmessage/jsonmessage_test.go index 7f46a8f..b81b656 100644 --- a/jsonmessage/jsonmessage_test.go +++ b/jsonmessage/jsonmessage_test.go @@ -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{ diff --git a/timeutils/utils.go b/timeutils/utils.go deleted file mode 100644 index 077d091..0000000 --- a/timeutils/utils.go +++ /dev/null @@ -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 -} diff --git a/timeutils/utils_test.go b/timeutils/utils_test.go deleted file mode 100644 index 1c44349..0000000 --- a/timeutils/utils_test.go +++ /dev/null @@ -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() - } - } -}