From 999f464feb5b78e8ecc97625e70f3b1796d0a758 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 13 May 2015 16:27:00 +0000 Subject: [PATCH] Parse input timestamps with standard RFC3339 Fix for #13175. This change allows user-input timestamps (e.g. to `docker events --since/--until` or `docker logs --since` to be parsed using standard RFC3339Nano layout in Go instead of the layout that parses all timestamps into fixed-length strings (currently buggy). User inputs need not to be complying to the internal format (`RFC3339NanoFixed`) anyway. Added test case for `events --since/--until` with all possible timestamp input formats. Signed-off-by: Ahmet Alp Balkan --- integration-cli/docker_cli_events_test.go | 35 ++++++++++++++++++++++ pkg/timeutils/utils.go | 13 ++++++-- pkg/timeutils/utils_test.go | 36 +++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 pkg/timeutils/utils_test.go diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 80cc0c69d5..8bd007d41a 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -13,6 +13,41 @@ import ( "github.com/go-check/check" ) +func (s *DockerSuite) TestEventsTimestampFormats(c *check.C) { + image := "busybox" + + // Start stopwatch, generate an event + time.Sleep(time.Second) // so that we don't grab events from previous test occured in the same second + start := daemonTime(c) + time.Sleep(time.Second) // remote API precision is only a second, wait a while before creating an event + dockerCmd(c, "tag", image, "timestamptest:1") + dockerCmd(c, "rmi", "timestamptest:1") + time.Sleep(time.Second) // so that until > since + end := daemonTime(c) + + // List of available time formats to --since + unixTs := func(t time.Time) string { return fmt.Sprintf("%v", t.Unix()) } + rfc3339 := func(t time.Time) string { return t.Format(time.RFC3339) } + + // --since=$start must contain only the 'untag' event + for _, f := range []func(time.Time) string{unixTs, rfc3339} { + since, until := f(start), f(end) + cmd := exec.Command(dockerBinary, "events", "--since="+since, "--until="+until) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("docker events cmd failed: %v\nout=%s", err, out) + } + events := strings.Split(strings.TrimSpace(out), "\n") + if len(events) != 1 { + c.Fatalf("unexpected events, was expecting only 1 (since=%s, until=%s) out=%s", since, until, out) + } + if !strings.Contains(out, "untag") { + c.Fatalf("expected 'untag' event not found (since=%s, until=%s) out=%s", since, until, out) + } + } + +} + func (s *DockerSuite) TestEventsUntag(c *check.C) { image := "busybox" dockerCmd(c, "tag", image, "utest:tag1") diff --git a/pkg/timeutils/utils.go b/pkg/timeutils/utils.go index c0c38facb4..6af16a1d7f 100644 --- a/pkg/timeutils/utils.go +++ b/pkg/timeutils/utils.go @@ -2,14 +2,21 @@ package timeutils import ( "strconv" + "strings" "time" ) // GetTimestamp tries to parse given string as RFC3339 time -// or Unix timestamp, if successful returns a Unix timestamp -// as string otherwise returns value back. +// or Unix timestamp (with seconds precision), if successful +//returns a Unix timestamp as string otherwise returns value back. func GetTimestamp(value string) string { - format := RFC3339NanoFixed + var format string + if strings.Contains(value, ".") { + format = time.RFC3339Nano + } else { + format = time.RFC3339 + } + loc := time.FixedZone(time.Now().Zone()) if len(value) < len(format) { format = format[:len(value)] diff --git a/pkg/timeutils/utils_test.go b/pkg/timeutils/utils_test.go new file mode 100644 index 0000000000..1d724fb2ac --- /dev/null +++ b/pkg/timeutils/utils_test.go @@ -0,0 +1,36 @@ +package timeutils + +import ( + "testing" +) + +func TestGetTimestamp(t *testing.T) { + cases := []struct{ in, expected string }{ + {"0", "-62167305600"}, // 0 gets parsed year 0 + + // Partial RFC3339 strings get parsed with second precision + {"2006-01-02T15:04:05.999999999+07:00", "1136189045"}, + {"2006-01-02T15:04:05.999999999Z", "1136214245"}, + {"2006-01-02T15:04:05.999999999", "1136214245"}, + {"2006-01-02T15:04:05", "1136214245"}, + {"2006-01-02T15:04", "1136214240"}, + {"2006-01-02T15", "1136214000"}, + {"2006-01-02T", "1136160000"}, + {"2006-01-02", "1136160000"}, + {"2006", "1136073600"}, + {"2015-05-13T20:39:09Z", "1431549549"}, + + // unix timestamps returned as is + {"1136073600", "1136073600"}, + + // String fallback + {"invalid", "invalid"}, + } + + for _, c := range cases { + o := GetTimestamp(c.in) + if o != c.expected { + t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o) + } + } +}