Browse code

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 <ahmetalpbalkan@gmail.com>

Ahmet Alp Balkan authored on 2015/05/14 01:27:00
Showing 3 changed files
... ...
@@ -13,6 +13,41 @@ import (
13 13
 	"github.com/go-check/check"
14 14
 )
15 15
 
16
+func (s *DockerSuite) TestEventsTimestampFormats(c *check.C) {
17
+	image := "busybox"
18
+
19
+	// Start stopwatch, generate an event
20
+	time.Sleep(time.Second) // so that we don't grab events from previous test occured in the same second
21
+	start := daemonTime(c)
22
+	time.Sleep(time.Second) // remote API precision is only a second, wait a while before creating an event
23
+	dockerCmd(c, "tag", image, "timestamptest:1")
24
+	dockerCmd(c, "rmi", "timestamptest:1")
25
+	time.Sleep(time.Second) // so that until > since
26
+	end := daemonTime(c)
27
+
28
+	// List of available time formats to --since
29
+	unixTs := func(t time.Time) string { return fmt.Sprintf("%v", t.Unix()) }
30
+	rfc3339 := func(t time.Time) string { return t.Format(time.RFC3339) }
31
+
32
+	// --since=$start must contain only the 'untag' event
33
+	for _, f := range []func(time.Time) string{unixTs, rfc3339} {
34
+		since, until := f(start), f(end)
35
+		cmd := exec.Command(dockerBinary, "events", "--since="+since, "--until="+until)
36
+		out, _, err := runCommandWithOutput(cmd)
37
+		if err != nil {
38
+			c.Fatalf("docker events cmd failed: %v\nout=%s", err, out)
39
+		}
40
+		events := strings.Split(strings.TrimSpace(out), "\n")
41
+		if len(events) != 1 {
42
+			c.Fatalf("unexpected events, was expecting only 1 (since=%s, until=%s) out=%s", since, until, out)
43
+		}
44
+		if !strings.Contains(out, "untag") {
45
+			c.Fatalf("expected 'untag' event not found (since=%s, until=%s) out=%s", since, until, out)
46
+		}
47
+	}
48
+
49
+}
50
+
16 51
 func (s *DockerSuite) TestEventsUntag(c *check.C) {
17 52
 	image := "busybox"
18 53
 	dockerCmd(c, "tag", image, "utest:tag1")
... ...
@@ -2,14 +2,21 @@ package timeutils
2 2
 
3 3
 import (
4 4
 	"strconv"
5
+	"strings"
5 6
 	"time"
6 7
 )
7 8
 
8 9
 // GetTimestamp tries to parse given string as RFC3339 time
9
-// or Unix timestamp, if successful returns a Unix timestamp
10
-// as string otherwise returns value back.
10
+// or Unix timestamp (with seconds precision), if successful
11
+//returns a Unix timestamp as string otherwise returns value back.
11 12
 func GetTimestamp(value string) string {
12
-	format := RFC3339NanoFixed
13
+	var format string
14
+	if strings.Contains(value, ".") {
15
+		format = time.RFC3339Nano
16
+	} else {
17
+		format = time.RFC3339
18
+	}
19
+
13 20
 	loc := time.FixedZone(time.Now().Zone())
14 21
 	if len(value) < len(format) {
15 22
 		format = format[:len(value)]
16 23
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package timeutils
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestGetTimestamp(t *testing.T) {
7
+	cases := []struct{ in, expected string }{
8
+		{"0", "-62167305600"}, // 0 gets parsed year 0
9
+
10
+		// Partial RFC3339 strings get parsed with second precision
11
+		{"2006-01-02T15:04:05.999999999+07:00", "1136189045"},
12
+		{"2006-01-02T15:04:05.999999999Z", "1136214245"},
13
+		{"2006-01-02T15:04:05.999999999", "1136214245"},
14
+		{"2006-01-02T15:04:05", "1136214245"},
15
+		{"2006-01-02T15:04", "1136214240"},
16
+		{"2006-01-02T15", "1136214000"},
17
+		{"2006-01-02T", "1136160000"},
18
+		{"2006-01-02", "1136160000"},
19
+		{"2006", "1136073600"},
20
+		{"2015-05-13T20:39:09Z", "1431549549"},
21
+
22
+		// unix timestamps returned as is
23
+		{"1136073600", "1136073600"},
24
+
25
+		// String fallback
26
+		{"invalid", "invalid"},
27
+	}
28
+
29
+	for _, c := range cases {
30
+		o := GetTimestamp(c.in)
31
+		if o != c.expected {
32
+			t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o)
33
+		}
34
+	}
35
+}