[fxtime] Add Stopwatch interface

This is used in infra/infra/tilo to measure invocation and test
run times.

IN-597 #comment

Change-Id: I52ef9636085f510a9707acf84808115202d3c5a3
diff --git a/fxtime/time.go b/fxtime/time.go
index 70c06ad..d4c14e4 100644
--- a/fxtime/time.go
+++ b/fxtime/time.go
@@ -23,6 +23,33 @@
 	return fixedClock{moment: moment}
 }
 
+// Stopwatch measures time intervals.
+type Stopwatch interface {
+	Restart() time.Time
+	Elapsed() time.Duration
+}
+
+// NewStopwatch returns a new stopwatch. The watch's initial time is set to clock.Now()
+func NewStopwatch(clock Clock) Stopwatch {
+	sw := &stopwatch{clock: clock}
+	sw.Restart()
+	return sw
+}
+
+type stopwatch struct {
+	clock Clock
+	start time.Time
+}
+
+func (s *stopwatch) Restart() time.Time {
+	s.start = s.clock.Now()
+	return s.start
+}
+
+func (s stopwatch) Elapsed() time.Duration {
+	return s.clock.Now().Sub(s.start)
+}
+
 type clock struct{}
 
 func (c clock) Now() time.Time {
diff --git a/fxtime/time_test.go b/fxtime/time_test.go
new file mode 100644
index 0000000..cbdbfb1
--- /dev/null
+++ b/fxtime/time_test.go
@@ -0,0 +1,74 @@
+package fxtime_test
+
+import (
+	"testing"
+	"time"
+
+	"fuchsia.googlesource.com/infra/infra/fxtime"
+)
+
+func TestFixedClock(t *testing.T) {
+	t.Run("Now should return a fixed time", func(t *testing.T) {
+		now := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
+		clock := fxtime.NewFixedClock(now)
+		expectTime(t, now, clock.Now())
+		expectTime(t, now, clock.Now())
+	})
+}
+
+func TestStopwatch(t *testing.T) {
+	now := time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
+
+	t.Run("Restart should return the current time", func(t *testing.T) {
+		clock := &TestClock{
+			nowTimes: []time.Time{now, now.Add(time.Hour)},
+		}
+		stopwatch := fxtime.NewStopwatch(clock)
+		expectTime(t, clock.nowTimes[1], stopwatch.Restart())
+	})
+
+	t.Run("Elapsed should return the duration since Restart() was last called",
+		func(t *testing.T) {
+			clock := &TestClock{
+				nowTimes: []time.Time{
+					now,
+					now.Add(time.Hour),
+					now.Add(24 * time.Hour),
+					now.Add(72 * time.Hour),
+				},
+			}
+			stopwatch := fxtime.NewStopwatch(clock)
+			expectTime(t, clock.nowTimes[1], stopwatch.Restart())
+			duration := clock.nowTimes[2].Sub(clock.nowTimes[1])
+			expectDuration(t, duration, stopwatch.Elapsed())
+			duration = clock.nowTimes[3].Sub(clock.nowTimes[1])
+			expectDuration(t, duration, stopwatch.Elapsed())
+		})
+}
+
+func expectTime(t *testing.T, expected, actual time.Time) {
+	if expected != actual {
+		t.Fatalf("expected time %v but got %v", expected, actual)
+	}
+}
+
+func expectDuration(t *testing.T, expected, actual time.Duration) {
+	if expected != actual {
+		t.Fatalf("expected duration %v but got %v", expected, actual)
+	}
+}
+
+// TestClock is used to test Stopwatch.
+type TestClock struct {
+	// List of Times to return when Now is called.
+	nowTimes []time.Time
+	// Current index in nowTimes
+	i uint
+}
+
+// Returns the current time. This will panic if called more than len(nowTimes) times.
+func (c *TestClock) Now() time.Time {
+	t := c.nowTimes[c.i]
+	c.i += 1
+	return t
+}