Merge pull request #2 from dart-lang/features

Add a zone-scoped clock and features from the old clock package
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b26ad3..de66ff8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,33 @@
 ## 1.0.0
 
-* Initial version.
+This release contains the `Clock` class that was defined in [`quiver`][]. It's
+backwards-compatible with the `quiver` version, and *mostly*
+backwards-compatible with the old version of the `clock` package.
+
+[`quiver`]: https://pub.dartlang.org/packages/quiver
+
+### New Features
+
+* A top-level `clock` field has been added that provides a default `Clock`
+  implementation. It can be controlled by the `withClock()` function. It should
+  generally be used in preference to manual dependency-injection, since it will
+  work with the [`fake_async`][] package.
+
+* A `Clock.stopwatch()` method has been added that creates a `Stopwatch` that
+  uses the clock as its source of time.
+
+[`fake_async`]: https://pub.dartlang.org/packages/fake_async
+
+### Changes Relative to `clock` 0.1
+
+* The top-level `new` getter and `getStopwatch()` methods are deprecated.
+  `clock.new()` and `clock.stopwatch()` should be used instead.
+
+* `Clock.getStopwatch()` is deprecated. `Clock.stopwatch()` should be used instead.
+
+* The `isFinal` argument to `withClock()` is deprecated.
+
+* `new Clock()` now takes an optional positional argument that returns the
+  current time as a `DateTime` instead of its old arguments.
+
+* `Clock.now()` is now a method rather than a getter.
diff --git a/README.md b/README.md
index 905f3b3..68bdba1 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 
 /// Runs [callback] and prints how long it took.
 T runWithTiming<T>(T callback()) {
-  var stopwatch = clock.getStopwatch()..start();
+  var stopwatch = clock.stopwatch()..start();
   var result = callback();
   print("It took ${stopwatch.elapsed}!");
   return result;
diff --git a/lib/clock.dart b/lib/clock.dart
index f3db69b..f7bf0b7 100644
--- a/lib/clock.dart
+++ b/lib/clock.dart
@@ -12,164 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import 'src/utils.dart';
+export 'src/clock.dart';
+export 'src/default.dart';
+
+import 'src/default.dart';
 
 /// Returns current time.
 @Deprecated("Pass around an instance of Clock instead.")
 typedef DateTime TimeFunction();
 
-/// Return current system time.
+/// Returns the current system time.
 @Deprecated("Use new DateTime.now() instead.")
 DateTime systemTime() => new DateTime.now();
 
-/// A provider for the "current time" and points relative to the current time.
-///
-/// This class is designed with testability in mind. The current point in time
-/// (or [now()]) is defined by a function that returns a [DateTime]. By
-/// supplying your own time function or using [new Clock.fixed], you can control
-/// exactly what time a [Clock] returns and base your test expectations on that.
-class Clock {
-  /// The function that's called to determine this clock's notion of the current
-  /// time.
-  final DateTime Function() _time;
+/// Returns the current time as reported by [clock].
+@Deprecated("Use clock.now() instead.")
+DateTime get now => clock.now();
 
-  /// Creates a clock based on the given [currentTime], or on the system clock
-  /// by default.
-  const Clock([DateTime currentTime()]) : _time = currentTime ?? systemTime;
-
-  /// Creates [Clock] that always considers the current time to be [time].
-  Clock.fixed(DateTime time) : _time = (() => time);
-
-  /// Returns current time.
-  DateTime now() => _time();
-
-  /// Returns the point in time [Duration] amount of time ago.
-  DateTime agoBy(Duration duration) => now().subtract(duration);
-
-  /// Returns the point in time [Duration] amount of time from now.
-  DateTime fromNowBy(Duration duration) => now().add(duration);
-
-  /// Returns the point in time that's given amount of time ago.
-  ///
-  /// The amount of time is the sum of the individual parts.
-  DateTime ago(
-          {int days: 0,
-          int hours: 0,
-          int minutes: 0,
-          int seconds: 0,
-          int milliseconds: 0,
-          int microseconds: 0}) =>
-      agoBy(new Duration(
-          days: days,
-          hours: hours,
-          minutes: minutes,
-          seconds: seconds,
-          milliseconds: milliseconds,
-          microseconds: microseconds));
-
-  /// Returns the point in time that's given amount of time from now.
-  ///
-  /// The amount of time is the sum of the individual parts.
-  DateTime fromNow(
-          {int days: 0,
-          int hours: 0,
-          int minutes: 0,
-          int seconds: 0,
-          int milliseconds: 0,
-          int microseconds: 0}) =>
-      fromNowBy(new Duration(
-          days: days,
-          hours: hours,
-          minutes: minutes,
-          seconds: seconds,
-          milliseconds: milliseconds,
-          microseconds: microseconds));
-
-  /// Return the point in time [microseconds] ago.
-  DateTime microsAgo(int microseconds) => ago(microseconds: microseconds);
-
-  /// Return the point in time [microseconds] from now.
-  DateTime microsFromNow(int microseconds) =>
-      fromNow(microseconds: microseconds);
-
-  /// Return the point in time [milliseconds] ago.
-  DateTime millisAgo(int milliseconds) => ago(milliseconds: milliseconds);
-
-  /// Return the point in time [milliseconds] from now.
-  DateTime millisFromNow(int milliseconds) =>
-      fromNow(milliseconds: milliseconds);
-
-  /// Return the point in time [seconds] ago.
-  DateTime secondsAgo(int seconds) => ago(seconds: seconds);
-
-  /// Return the point in time [seconds] from now.
-  DateTime secondsFromNow(int seconds) => fromNow(seconds: seconds);
-
-  /// Return the point in time [minutes] ago.
-  DateTime minutesAgo(int minutes) => ago(minutes: minutes);
-
-  /// Return the point in time [minutes] from now.
-  DateTime minutesFromNow(int minutes) => fromNow(minutes: minutes);
-
-  /// Return the point in time [hours] ago.
-  DateTime hoursAgo(int hours) => ago(hours: hours);
-
-  /// Return the point in time [hours] from now.
-  DateTime hoursFromNow(int hours) => fromNow(hours: hours);
-
-  /// Return the point in time [days] ago.
-  DateTime daysAgo(int days) => ago(days: days);
-
-  /// Return the point in time [days] from now.
-  DateTime daysFromNow(int days) => fromNow(days: days);
-
-  /// Return the point in time [weeks] ago.
-  DateTime weeksAgo(int weeks) => ago(days: 7 * weeks);
-
-  /// Return the point in time [weeks] from now.
-  DateTime weeksFromNow(int weeks) => fromNow(days: 7 * weeks);
-
-  /// Return the point in time [months] ago on the same date.
-  ///
-  /// If the current day of the month isn't valid in the new month, the nearest
-  /// valid day in the new month will be used.
-  DateTime monthsAgo(int months) {
-    var time = now();
-    var month = (time.month - months - 1) % 12 + 1;
-    var year = time.year - (months + 12 - time.month) ~/ 12;
-    var day = clampDayOfMonth(year: year, month: month, day: time.day);
-    return new DateTime(year, month, day, time.hour, time.minute, time.second,
-        time.millisecond);
-  }
-
-  /// Return the point in time [months] from now on the same date.
-  ///
-  /// If the current day of the month isn't valid in the new month, the nearest
-  /// valid day in the new month will be used.
-  DateTime monthsFromNow(int months) {
-    var time = now();
-    var month = (time.month + months - 1) % 12 + 1;
-    var year = time.year + (months + time.month - 1) ~/ 12;
-    var day = clampDayOfMonth(year: year, month: month, day: time.day);
-    return new DateTime(year, month, day, time.hour, time.minute, time.second,
-        time.millisecond);
-  }
-
-  /// Return the point in time [years] ago on the same date.
-  ///
-  /// If the current day of the month isn't valid in the new year, the nearest
-  /// valid day in the original month will be used.
-  DateTime yearsAgo(int years) {
-    var time = now();
-    var year = time.year - years;
-    var day = clampDayOfMonth(year: year, month: time.month, day: time.day);
-    return new DateTime(year, time.month, day, time.hour, time.minute,
-        time.second, time.millisecond);
-  }
-
-  /// Return the point in time [years] from now on the same date.
-  ///
-  /// If the current day of the month isn't valid in the new year, the nearest
-  /// valid day in the original month will be used.
-  DateTime yearsFromNow(int years) => yearsAgo(-years);
-}
+/// Returns a stopwatch that uses the current time as reported by [clock].
+@Deprecated("Use clock.stopwatch() instead.")
+Stopwatch getStopwatch() => clock.stopwatch();
diff --git a/lib/src/clock.dart b/lib/src/clock.dart
new file mode 100644
index 0000000..2984e30
--- /dev/null
+++ b/lib/src/clock.dart
@@ -0,0 +1,181 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'utils.dart';
+
+import '../clock.dart';
+import 'stopwatch.dart';
+
+/// A provider for the "current time" and points relative to the current time.
+///
+/// This class is designed with testability in mind. The current point in time
+/// (or [now()]) is defined by a function that returns a [DateTime]. By
+/// supplying your own time function or using [new Clock.fixed], you can control
+/// exactly what time a [Clock] returns and base your test expectations on that.
+///
+/// Most users should use the top-level [clock] field, which provides access to
+/// a default implementation of [Clock] which can be overridden using
+/// [withClock].
+class Clock {
+  /// The function that's called to determine this clock's notion of the current
+  /// time.
+  final DateTime Function() _time;
+
+  /// Creates a clock based on the given [currentTime], or on the system clock
+  /// by default.
+  const Clock([DateTime currentTime() = systemTime]) : _time = currentTime;
+
+  /// Creates [Clock] that always considers the current time to be [time].
+  Clock.fixed(DateTime time) : _time = (() => time);
+
+  /// Returns current time.
+  DateTime now() => _time();
+
+  /// Returns the point in time [Duration] amount of time ago.
+  DateTime agoBy(Duration duration) => now().subtract(duration);
+
+  /// Returns the point in time [Duration] amount of time from now.
+  DateTime fromNowBy(Duration duration) => now().add(duration);
+
+  /// Returns the point in time that's given amount of time ago.
+  ///
+  /// The amount of time is the sum of the individual parts.
+  DateTime ago(
+          {int days: 0,
+          int hours: 0,
+          int minutes: 0,
+          int seconds: 0,
+          int milliseconds: 0,
+          int microseconds: 0}) =>
+      agoBy(new Duration(
+          days: days,
+          hours: hours,
+          minutes: minutes,
+          seconds: seconds,
+          milliseconds: milliseconds,
+          microseconds: microseconds));
+
+  /// Returns the point in time that's given amount of time from now.
+  ///
+  /// The amount of time is the sum of the individual parts.
+  DateTime fromNow(
+          {int days: 0,
+          int hours: 0,
+          int minutes: 0,
+          int seconds: 0,
+          int milliseconds: 0,
+          int microseconds: 0}) =>
+      fromNowBy(new Duration(
+          days: days,
+          hours: hours,
+          minutes: minutes,
+          seconds: seconds,
+          milliseconds: milliseconds,
+          microseconds: microseconds));
+
+  /// Return the point in time [microseconds] ago.
+  DateTime microsAgo(int microseconds) => ago(microseconds: microseconds);
+
+  /// Return the point in time [microseconds] from now.
+  DateTime microsFromNow(int microseconds) =>
+      fromNow(microseconds: microseconds);
+
+  /// Return the point in time [milliseconds] ago.
+  DateTime millisAgo(int milliseconds) => ago(milliseconds: milliseconds);
+
+  /// Return the point in time [milliseconds] from now.
+  DateTime millisFromNow(int milliseconds) =>
+      fromNow(milliseconds: milliseconds);
+
+  /// Return the point in time [seconds] ago.
+  DateTime secondsAgo(int seconds) => ago(seconds: seconds);
+
+  /// Return the point in time [seconds] from now.
+  DateTime secondsFromNow(int seconds) => fromNow(seconds: seconds);
+
+  /// Return the point in time [minutes] ago.
+  DateTime minutesAgo(int minutes) => ago(minutes: minutes);
+
+  /// Return the point in time [minutes] from now.
+  DateTime minutesFromNow(int minutes) => fromNow(minutes: minutes);
+
+  /// Return the point in time [hours] ago.
+  DateTime hoursAgo(int hours) => ago(hours: hours);
+
+  /// Return the point in time [hours] from now.
+  DateTime hoursFromNow(int hours) => fromNow(hours: hours);
+
+  /// Return the point in time [days] ago.
+  DateTime daysAgo(int days) => ago(days: days);
+
+  /// Return the point in time [days] from now.
+  DateTime daysFromNow(int days) => fromNow(days: days);
+
+  /// Return the point in time [weeks] ago.
+  DateTime weeksAgo(int weeks) => ago(days: 7 * weeks);
+
+  /// Return the point in time [weeks] from now.
+  DateTime weeksFromNow(int weeks) => fromNow(days: 7 * weeks);
+
+  /// Return the point in time [months] ago on the same date.
+  ///
+  /// If the current day of the month isn't valid in the new month, the nearest
+  /// valid day in the new month will be used.
+  DateTime monthsAgo(int months) {
+    var time = now();
+    var month = (time.month - months - 1) % 12 + 1;
+    var year = time.year - (months + 12 - time.month) ~/ 12;
+    var day = clampDayOfMonth(year: year, month: month, day: time.day);
+    return new DateTime(year, month, day, time.hour, time.minute, time.second,
+        time.millisecond);
+  }
+
+  /// Return the point in time [months] from now on the same date.
+  ///
+  /// If the current day of the month isn't valid in the new month, the nearest
+  /// valid day in the new month will be used.
+  DateTime monthsFromNow(int months) {
+    var time = now();
+    var month = (time.month + months - 1) % 12 + 1;
+    var year = time.year + (months + time.month - 1) ~/ 12;
+    var day = clampDayOfMonth(year: year, month: month, day: time.day);
+    return new DateTime(year, month, day, time.hour, time.minute, time.second,
+        time.millisecond);
+  }
+
+  /// Return the point in time [years] ago on the same date.
+  ///
+  /// If the current day of the month isn't valid in the new year, the nearest
+  /// valid day in the original month will be used.
+  DateTime yearsAgo(int years) {
+    var time = now();
+    var year = time.year - years;
+    var day = clampDayOfMonth(year: year, month: time.month, day: time.day);
+    return new DateTime(year, time.month, day, time.hour, time.minute,
+        time.second, time.millisecond);
+  }
+
+  /// Return the point in time [years] from now on the same date.
+  ///
+  /// If the current day of the month isn't valid in the new year, the nearest
+  /// valid day in the original month will be used.
+  DateTime yearsFromNow(int years) => yearsAgo(-years);
+
+  /// Returns a new stopwatch that uses the current time as reported by [this].
+  Stopwatch stopwatch() => new ClockStopwatch(this);
+
+  /// Returns a new stopwatch that uses the current time as reported by [this].
+  @Deprecated("Use stopwatch() instead.")
+  Stopwatch getStopwatch() => stopwatch();
+}
diff --git a/lib/src/default.dart b/lib/src/default.dart
new file mode 100644
index 0000000..b380927
--- /dev/null
+++ b/lib/src/default.dart
@@ -0,0 +1,48 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'clock.dart';
+
+/// The key for the [Zone] value that controls the current implementation of
+/// [clock].
+final _clockKey = new Object();
+
+/// The key for the [Zone] value that controls whether nested zones can override
+/// [clock].
+final _isFinalKey = new Object();
+
+/// The default implementation of [clock] for the current [Zone].
+///
+/// This defaults to the system clock. It can be set within a zone using
+/// [withClock].
+Clock get clock => Zone.current[_clockKey] ?? const Clock();
+
+/// Runs [callback] with the given value for the top-level [clock] field.
+///
+/// This is [Zone]-scoped, so asynchronous callbacks spawned within [callback]
+/// will also use the new value for [clock].
+///
+/// If [isFinal] is `true`, calls to [withClock] within [callback] will throw a
+/// [StateError]. However, this parameter is deprecated and should be avoided.
+T withClock<T>(Clock clock, T callback(), {@deprecated bool isFinal: false}) {
+  if (Zone.current[_isFinalKey] ?? false) {
+    throw new StateError(
+        "Cannot call withClock() within a call to withClock(isFinal: true).");
+  }
+
+  return runZoned(callback,
+      zoneValues: {_clockKey: clock, _isFinalKey: isFinal});
+}
diff --git a/lib/src/stopwatch.dart b/lib/src/stopwatch.dart
new file mode 100644
index 0000000..1cf66a4
--- /dev/null
+++ b/lib/src/stopwatch.dart
@@ -0,0 +1,64 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'clock.dart';
+
+/// The system's timer frequency in Hz.
+///
+/// We can't really know how frequently the clock is updated, and that may not
+/// even make sense for some implementations, so we just pretend we follow the
+/// system's frequency.
+final _frequency = new Stopwatch().frequency;
+
+/// A stopwatch that gets its notion of the current time from a [Clock].
+class ClockStopwatch implements Stopwatch {
+  /// The provider for this stopwatch's notion of the current time.
+  final Clock _clock;
+
+  /// The number of elapsed microseconds that have been recorded from previous
+  /// runs of this stopwatch.
+  ///
+  /// This doesn't include the time between [_start] and the current time.
+  var _elapsed = 0;
+
+  /// The point at which [start] was called most recently, or `null` if this
+  /// isn't active.
+  DateTime _start;
+
+  ClockStopwatch(this._clock);
+
+  int get frequency => _frequency;
+  int get elapsedTicks => (elapsedMicroseconds * frequency) ~/ 1000000;
+  Duration get elapsed => new Duration(microseconds: elapsedMicroseconds);
+  int get elapsedMilliseconds => elapsedMicroseconds ~/ 1000;
+  bool get isRunning => _start != null;
+
+  int get elapsedMicroseconds =>
+      _elapsed +
+      (_start == null ? 0 : _clock.now().difference(_start).inMicroseconds);
+
+  void start() {
+    _start ??= _clock.now();
+  }
+
+  void stop() {
+    _elapsed = elapsedMicroseconds;
+    _start = null;
+  }
+
+  void reset() {
+    _elapsed = 0;
+    if (_start != null) _start = _clock.now();
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 5ebecf2..8a79678 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: clock
-version: 1.0.0-dev
+version: 1.0.0
 description: A fakeable wrapper for dart:core clock APIs
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/clock
@@ -11,4 +11,4 @@
   meta: '>=0.9.0 <2.0.0'
 
 dev_dependencies:
-  test: '^0.12.0'
+  test: '^0.12.28'
diff --git a/test/clock_test.dart b/test/clock_test.dart
index 633cdb9..3d2d1b2 100644
--- a/test/clock_test.dart
+++ b/test/clock_test.dart
@@ -16,20 +16,12 @@
 
 import 'package:test/test.dart';
 
-/// A utility function for tersely constructing a [DateTime] with no time
-/// component.
-DateTime _date(int year, [int month, int day]) =>
-    new DateTime(year, month ?? 1, day ?? 1);
-
-/// Return a clock that always returns a date with the given [year], [month],
-/// and [day].
-Clock _fixed(int year, [int month, int day]) =>
-    new Clock.fixed(_date(year, month, day));
+import 'utils.dart';
 
 main() {
   Clock clock;
   setUp(() {
-    clock = new Clock.fixed(_date(2013));
+    clock = new Clock.fixed(date(2013));
   });
 
   test('should return a non-null value from system clock', () {
@@ -50,24 +42,24 @@
   });
 
   test('should return time provided by a custom function', () {
-    var time = _date(2013);
+    var time = date(2013);
     var fixedClock = new Clock(() => time);
-    expect(fixedClock.now(), _date(2013));
+    expect(fixedClock.now(), date(2013));
 
-    time = _date(2014);
-    expect(fixedClock.now(), _date(2014));
+    time = date(2014);
+    expect(fixedClock.now(), date(2014));
   });
 
   test('should return fixed time', () {
-    expect(new Clock.fixed(_date(2013)).now(), _date(2013));
+    expect(new Clock.fixed(date(2013)).now(), date(2013));
   });
 
   test('should return time Duration ago', () {
-    expect(clock.agoBy(const Duration(days: 366)), _date(2012));
+    expect(clock.agoBy(const Duration(days: 366)), date(2012));
   });
 
   test('should return time Duration from now', () {
-    expect(clock.fromNowBy(const Duration(days: 365)), _date(2014));
+    expect(clock.fromNowBy(const Duration(days: 365)), date(2014));
   });
 
   test('should return time parts ago', () {
@@ -135,86 +127,86 @@
   });
 
   test('should return time days ago', () {
-    expect(clock.daysAgo(10), _date(2012, 12, 22));
+    expect(clock.daysAgo(10), date(2012, 12, 22));
   });
 
   test('should return time days from now', () {
-    expect(clock.daysFromNow(3), _date(2013, 1, 4));
+    expect(clock.daysFromNow(3), date(2013, 1, 4));
   });
 
   test('should return time months ago on the same date', () {
-    expect(clock.monthsAgo(1), _date(2012, 12, 1));
-    expect(clock.monthsAgo(2), _date(2012, 11, 1));
-    expect(clock.monthsAgo(3), _date(2012, 10, 1));
-    expect(clock.monthsAgo(4), _date(2012, 9, 1));
+    expect(clock.monthsAgo(1), date(2012, 12, 1));
+    expect(clock.monthsAgo(2), date(2012, 11, 1));
+    expect(clock.monthsAgo(3), date(2012, 10, 1));
+    expect(clock.monthsAgo(4), date(2012, 9, 1));
   });
 
   test('should return time months from now on the same date', () {
-    expect(clock.monthsFromNow(1), _date(2013, 2, 1));
-    expect(clock.monthsFromNow(2), _date(2013, 3, 1));
-    expect(clock.monthsFromNow(3), _date(2013, 4, 1));
-    expect(clock.monthsFromNow(4), _date(2013, 5, 1));
+    expect(clock.monthsFromNow(1), date(2013, 2, 1));
+    expect(clock.monthsFromNow(2), date(2013, 3, 1));
+    expect(clock.monthsFromNow(3), date(2013, 4, 1));
+    expect(clock.monthsFromNow(4), date(2013, 5, 1));
   });
 
   test('should go from 2013-05-31 to 2012-11-30', () {
-    expect(_fixed(2013, 5, 31).monthsAgo(6), _date(2012, 11, 30));
+    expect(fixed(2013, 5, 31).monthsAgo(6), date(2012, 11, 30));
   });
 
   test('should go from 2013-03-31 to 2013-02-28 (common year)', () {
-    expect(_fixed(2013, 3, 31).monthsAgo(1), _date(2013, 2, 28));
+    expect(fixed(2013, 3, 31).monthsAgo(1), date(2013, 2, 28));
   });
 
   test('should go from 2013-05-31 to 2013-02-28 (common year)', () {
-    expect(_fixed(2013, 5, 31).monthsAgo(3), _date(2013, 2, 28));
+    expect(fixed(2013, 5, 31).monthsAgo(3), date(2013, 2, 28));
   });
 
   test('should go from 2004-03-31 to 2004-02-29 (leap year)', () {
-    expect(_fixed(2004, 3, 31).monthsAgo(1), _date(2004, 2, 29));
+    expect(fixed(2004, 3, 31).monthsAgo(1), date(2004, 2, 29));
   });
 
   test('should go from 2013-03-31 to 2013-06-30', () {
-    expect(_fixed(2013, 3, 31).monthsFromNow(3), _date(2013, 6, 30));
+    expect(fixed(2013, 3, 31).monthsFromNow(3), date(2013, 6, 30));
   });
 
   test('should go from 2003-12-31 to 2004-02-29 (common to leap)', () {
-    expect(_fixed(2003, 12, 31).monthsFromNow(2), _date(2004, 2, 29));
+    expect(fixed(2003, 12, 31).monthsFromNow(2), date(2004, 2, 29));
   });
 
   test('should go from 2004-02-29 to 2003-02-28 by year', () {
-    expect(_fixed(2004, 2, 29).yearsAgo(1), _date(2003, 2, 28));
+    expect(fixed(2004, 2, 29).yearsAgo(1), date(2003, 2, 28));
   });
 
   test('should go from 2004-02-29 to 2003-02-28 by month', () {
-    expect(_fixed(2004, 2, 29).monthsAgo(12), _date(2003, 2, 28));
+    expect(fixed(2004, 2, 29).monthsAgo(12), date(2003, 2, 28));
   });
 
   test('should go from 2004-02-29 to 2005-02-28 by year', () {
-    expect(_fixed(2004, 2, 29).yearsFromNow(1), _date(2005, 2, 28));
+    expect(fixed(2004, 2, 29).yearsFromNow(1), date(2005, 2, 28));
   });
 
   test('should go from 2004-02-29 to 2005-02-28 by month', () {
-    expect(_fixed(2004, 2, 29).monthsFromNow(12), _date(2005, 2, 28));
+    expect(fixed(2004, 2, 29).monthsFromNow(12), date(2005, 2, 28));
   });
 
   test('should return time years ago on the same date', () {
-    expect(clock.yearsAgo(1), _date(2012, 1, 1)); // leap year
-    expect(clock.yearsAgo(2), _date(2011, 1, 1));
-    expect(clock.yearsAgo(3), _date(2010, 1, 1));
-    expect(clock.yearsAgo(4), _date(2009, 1, 1));
-    expect(clock.yearsAgo(5), _date(2008, 1, 1)); // leap year
-    expect(clock.yearsAgo(6), _date(2007, 1, 1));
-    expect(clock.yearsAgo(30), _date(1983, 1, 1));
-    expect(clock.yearsAgo(2013), _date(0, 1, 1));
+    expect(clock.yearsAgo(1), date(2012, 1, 1)); // leap year
+    expect(clock.yearsAgo(2), date(2011, 1, 1));
+    expect(clock.yearsAgo(3), date(2010, 1, 1));
+    expect(clock.yearsAgo(4), date(2009, 1, 1));
+    expect(clock.yearsAgo(5), date(2008, 1, 1)); // leap year
+    expect(clock.yearsAgo(6), date(2007, 1, 1));
+    expect(clock.yearsAgo(30), date(1983, 1, 1));
+    expect(clock.yearsAgo(2013), date(0, 1, 1));
   });
 
   test('should return time years from now on the same date', () {
-    expect(clock.yearsFromNow(1), _date(2014, 1, 1));
-    expect(clock.yearsFromNow(2), _date(2015, 1, 1));
-    expect(clock.yearsFromNow(3), _date(2016, 1, 1));
-    expect(clock.yearsFromNow(4), _date(2017, 1, 1));
-    expect(clock.yearsFromNow(5), _date(2018, 1, 1));
-    expect(clock.yearsFromNow(6), _date(2019, 1, 1));
-    expect(clock.yearsFromNow(30), _date(2043, 1, 1));
-    expect(clock.yearsFromNow(1000), _date(3013, 1, 1));
+    expect(clock.yearsFromNow(1), date(2014, 1, 1));
+    expect(clock.yearsFromNow(2), date(2015, 1, 1));
+    expect(clock.yearsFromNow(3), date(2016, 1, 1));
+    expect(clock.yearsFromNow(4), date(2017, 1, 1));
+    expect(clock.yearsFromNow(5), date(2018, 1, 1));
+    expect(clock.yearsFromNow(6), date(2019, 1, 1));
+    expect(clock.yearsFromNow(30), date(2043, 1, 1));
+    expect(clock.yearsFromNow(1000), date(3013, 1, 1));
   });
 }
diff --git a/test/default_test.dart b/test/default_test.dart
new file mode 100644
index 0000000..edcb7cd
--- /dev/null
+++ b/test/default_test.dart
@@ -0,0 +1,79 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the 'License');
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:clock/clock.dart';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test("the default clock returns the system time", () {
+    expect(new DateTime.now().difference(clock.now()).inMilliseconds.abs(),
+        lessThan(100));
+  });
+
+  group("withClock()", () {
+    group("overrides the clock", () {
+      test("synchronously", () {
+        var time = date(1990, 11, 8);
+        withClock(new Clock(() => time), () {
+          expect(clock.now(), equals(time));
+          time = date(2016, 6, 26);
+          expect(clock.now(), equals(time));
+        });
+      });
+
+      test("asynchronously", () {
+        var time = date(1990, 11, 8);
+        withClock(new Clock.fixed(time), () {
+          expect(new Future(() async {
+            expect(clock.now(), equals(time));
+          }), completes);
+        });
+      });
+
+      test("within another withClock() call", () {
+        var outerTime = date(1990, 11, 8);
+        withClock(new Clock.fixed(outerTime), () {
+          expect(clock.now(), equals(outerTime));
+
+          var innerTime = date(2016, 11, 8);
+          withClock(new Clock.fixed(innerTime), () {
+            expect(clock.now(), equals(innerTime));
+            expect(new Future(() async {
+              expect(clock.now(), equals(innerTime));
+            }), completes);
+          });
+
+          expect(clock.now(), equals(outerTime));
+        });
+      });
+    });
+
+    test("with isFinal: true doesn't allow nested calls", () {
+      var outerTime = date(1990, 11, 8);
+      withClock(new Clock.fixed(outerTime), () {
+        expect(clock.now(), equals(outerTime));
+
+        expect(
+            () => withClock(fixed(2016, 11, 8), neverCalled), throwsStateError);
+
+        expect(clock.now(), equals(outerTime));
+      }, isFinal: true);
+    });
+  });
+}
diff --git a/test/stopwatch_test.dart b/test/stopwatch_test.dart
new file mode 100644
index 0000000..66f01b0
--- /dev/null
+++ b/test/stopwatch_test.dart
@@ -0,0 +1,169 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the 'License');
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:clock/clock.dart';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test("returns the system frequency", () {
+    expect(fixed(1990, 11, 8).stopwatch().frequency,
+        equals(new Stopwatch().frequency));
+  });
+
+  group("before it starts", () {
+    Stopwatch stopwatch;
+    setUp(() {
+      stopwatch = clock.stopwatch();
+    });
+
+    test("is not running", () => expect(stopwatch.isRunning, isFalse));
+
+    test("stop() does nothing", () {
+      stopwatch.stop();
+      expect(stopwatch.isRunning, isFalse);
+      expect(stopwatch.elapsed, equals(Duration.ZERO));
+    });
+
+    group("reports no elapsed", () {
+      test("duration", () => expect(stopwatch.elapsed, equals(Duration.ZERO)));
+      test("ticks", () => expect(stopwatch.elapsedTicks, isZero));
+      test("microseconds", () => expect(stopwatch.elapsedMicroseconds, isZero));
+      test("milliseconds", () => expect(stopwatch.elapsedMilliseconds, isZero));
+    });
+  });
+
+  group("when 12345μs have elapsed", () {
+    DateTime time;
+    Clock clock;
+    Stopwatch stopwatch;
+    setUp(() {
+      time = date(1990, 11, 8);
+      clock = new Clock(() => time);
+      stopwatch = clock.stopwatch()..start();
+      time = clock.microsFromNow(12345);
+    });
+
+    group("and the stopwatch is active", () {
+      test("is running", () {
+        expect(stopwatch.isRunning, isTrue);
+      });
+
+      test("reports more elapsed time", () {
+        time = clock.microsFromNow(54321);
+        expect(stopwatch.elapsedMicroseconds, equals(66666));
+      });
+
+      test("start does nothing", () {
+        stopwatch.start();
+        expect(stopwatch.isRunning, isTrue);
+        expect(stopwatch.elapsedMicroseconds, equals(12345));
+      });
+
+      group("reset()", () {
+        setUp(() {
+          stopwatch.reset();
+        });
+
+        test("sets the elapsed time to zero", () {
+          expect(stopwatch.elapsed, equals(Duration.ZERO));
+        });
+
+        test("reports more elapsed time", () {
+          time = clock.microsFromNow(54321);
+          expect(stopwatch.elapsedMicroseconds, equals(54321));
+        });
+      });
+
+      group("reports elapsed", () {
+        test("duration", () {
+          expect(stopwatch.elapsed, equals(new Duration(microseconds: 12345)));
+        });
+
+        test("ticks", () {
+          expect(stopwatch.elapsedTicks,
+              equals((new Stopwatch().frequency * 12345) ~/ 1000000));
+        });
+
+        test("microseconds", () {
+          expect(stopwatch.elapsedMicroseconds, equals(12345));
+        });
+
+        test("milliseconds", () {
+          expect(stopwatch.elapsedMilliseconds, equals(12));
+        });
+      });
+    });
+
+    group("and the stopwatch is inactive, reports that as", () {
+      setUp(() {
+        stopwatch.stop();
+      });
+
+      test("is not running", () {
+        expect(stopwatch.isRunning, isFalse);
+      });
+
+      test("doesn't report more elapsed time", () {
+        time = clock.microsFromNow(54321);
+        expect(stopwatch.elapsedMicroseconds, equals(12345));
+      });
+
+      test("start starts reporting more elapsed time", () {
+        stopwatch.start();
+        expect(stopwatch.isRunning, isTrue);
+        time = clock.microsFromNow(54321);
+        expect(stopwatch.elapsedMicroseconds, equals(66666));
+      });
+
+      group("reset()", () {
+        setUp(() {
+          stopwatch.reset();
+        });
+
+        test("sets the elapsed time to zero", () {
+          expect(stopwatch.elapsed, equals(Duration.ZERO));
+        });
+
+        test("doesn't report more elapsed time", () {
+          time = clock.microsFromNow(54321);
+          expect(stopwatch.elapsed, equals(Duration.ZERO));
+        });
+      });
+
+      group("reports elapsed", () {
+        test("duration", () {
+          expect(stopwatch.elapsed, equals(new Duration(microseconds: 12345)));
+        });
+
+        test("ticks", () {
+          expect(stopwatch.elapsedTicks,
+              equals((new Stopwatch().frequency * 12345) ~/ 1000000));
+        });
+
+        test("microseconds", () {
+          expect(stopwatch.elapsedMicroseconds, equals(12345));
+        });
+
+        test("milliseconds", () {
+          expect(stopwatch.elapsedMilliseconds, equals(12));
+        });
+      });
+    });
+  });
+}
diff --git a/test/utils.dart b/test/utils.dart
new file mode 100644
index 0000000..17c07f3
--- /dev/null
+++ b/test/utils.dart
@@ -0,0 +1,25 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:clock/clock.dart';
+
+/// A utility function for tersely constructing a [DateTime] with no time
+/// component.
+DateTime date(int year, [int month, int day]) =>
+    new DateTime(year, month ?? 1, day ?? 1);
+
+/// Returns a clock that always returns a date with the given [year], [month],
+/// and [day].
+Clock fixed(int year, [int month, int day]) =>
+    new Clock.fixed(date(year, month, day));