Merge pull request #691 from dart-lang/merge-clock-package

Merge `package:clock`
diff --git a/.github/ISSUE_TEMPLATE/clock.md b/.github/ISSUE_TEMPLATE/clock.md
new file mode 100644
index 0000000..1ed8f73
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/clock.md
@@ -0,0 +1,5 @@
+---
+name: "package:clock"
+about: "Create a bug or file a feature request against package:clock."
+labels: "package:clock"
+---
\ No newline at end of file
diff --git a/.github/labeler.yml b/.github/labeler.yml
index c3d5de0..a6939c7 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -12,6 +12,10 @@
   - changed-files:
       - any-glob-to-any-file: 'pkgs/cli_config/**'
 
+'package:clock':
+  - changed-files:
+      - any-glob-to-any-file: 'pkgs/clock/**'
+
 'package:coverage':
   - changed-files:
       - any-glob-to-any-file: 'pkgs/coverage/**'
diff --git a/.github/workflows/clock.yaml b/.github/workflows/clock.yaml
new file mode 100644
index 0000000..1ad4ade
--- /dev/null
+++ b/.github/workflows/clock.yaml
@@ -0,0 +1,75 @@
+name: package:clock
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/clock.yml'
+      - 'pkgs/clock/**'
+  pull_request:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/clock.yml'
+      - 'pkgs/clock/**'
+  schedule:
+    - cron: "0 0 * * 0"
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+  run:
+    working-directory: pkgs/clock/
+
+jobs:
+  # Check code formatting and static analysis on a single OS (linux)
+  # against Dart dev.
+  analyze:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [dev]
+    steps:
+      - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938
+      - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: dart analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  # Run tests on a matrix consisting of two dimensions:
+  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+  # 2. release channel: dev
+  test:
+    needs: analyze
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # Add macos-latest and/or windows-latest if relevant for this package.
+        os: [ubuntu-latest]
+        sdk: [3.4, dev]
+    steps:
+      - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938
+      - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Run VM tests
+        run: dart test --platform vm
+        if: always() && steps.install.outcome == 'success'
+      - name: Run Chrome tests
+        run: dart test --platform chrome
+        if: always() && steps.install.outcome == 'success'
diff --git a/README.md b/README.md
index ac4edf2..f797289 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@
 | --- | --- | --- |
 | [boolean_selector](pkgs/boolean_selector/) | A flexible syntax for boolean expressions, based on a simplified version of Dart's expression syntax. | [![pub package](https://img.shields.io/pub/v/boolean_selector.svg)](https://pub.dev/packages/boolean_selector) |
 | [cli_config](pkgs/cli_config/) | A library to take config values from configuration files, CLI arguments, and environment variables. | [![pub package](https://img.shields.io/pub/v/cli_config.svg)](https://pub.dev/packages/cli_config) |
+| [clock](pkgs/clock/) | A fakeable wrapper for dart:core clock APIs. | [![pub package](https://img.shields.io/pub/v/clock.svg)](https://pub.dev/packages/clock) |
 | [coverage](pkgs/coverage/) | Coverage data manipulation and formatting. | [![pub package](https://img.shields.io/pub/v/coverage.svg)](https://pub.dev/packages/coverage) |
 | [extension_discovery](pkgs/extension_discovery/) | A convention and utilities for package extension discovery. | [![pub package](https://img.shields.io/pub/v/extension_discovery.svg)](https://pub.dev/packages/extension_discovery) |
 | [file](pkgs/file/) | A pluggable, mockable file system abstraction for Dart. | [![pub package](https://img.shields.io/pub/v/file.svg)](https://pub.dev/packages/file) |
diff --git a/pkgs/clock/.gitignore b/pkgs/clock/.gitignore
new file mode 100644
index 0000000..63fe85d
--- /dev/null
+++ b/pkgs/clock/.gitignore
@@ -0,0 +1,5 @@
+.packages
+.pub/
+.dart_tool/
+build/
+pubspec.lock
diff --git a/pkgs/clock/AUTHORS b/pkgs/clock/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/clock/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/clock/CHANGELOG.md b/pkgs/clock/CHANGELOG.md
new file mode 100644
index 0000000..f372ada
--- /dev/null
+++ b/pkgs/clock/CHANGELOG.md
@@ -0,0 +1,52 @@
+## 1.1.2
+
+* Require Dart 3.4
+* Move to `dart-lang/tools` monorepo.
+
+## 1.1.1
+
+* Switch to using `package:lints`.
+* Populate the pubspec `repository` field.
+
+## 1.1.0
+
+* Update SDK constraints to `>=2.12.0 <3.0.0`.
+* Update to null safety.
+
+## 1.0.1
+
+* Update to lowercase Dart core library constants.
+
+## 1.0.0
+
+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/pkgs/clock/LICENSE b/pkgs/clock/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/pkgs/clock/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/pkgs/clock/README.md b/pkgs/clock/README.md
new file mode 100644
index 0000000..dccdc07
--- /dev/null
+++ b/pkgs/clock/README.md
@@ -0,0 +1,52 @@
+[![Build Status](https://github.com/dart-lang/tools/actions/workflows/clock.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/clock.yaml)
+[![pub package](https://img.shields.io/pub/v/clock.svg)](https://pub.dev/packages/clock)
+[![package publisher](https://img.shields.io/pub/publisher/clock.svg)](https://pub.dev/packages/clock/publisher)
+
+This package provides a [`Clock`][] class which encapsulates the notion of the
+"current time" and provides easy access to points relative to the current time.
+Different `Clock`s can have a different notion of the current time, and the
+default top-level [`clock`][]'s notion can be swapped out to reliably test
+timing-dependent code.
+
+[`Clock`]: https://pub.dev/documentation/clock/latest/clock/Clock-class.html
+[`clock`]: https://pub.dev/documentation/clock/latest/clock/clock.html
+
+For example, you can use `clock` in your libraries like this:
+
+```dart
+// run_with_timing.dart
+import 'package:clock/clock.dart';
+
+/// Runs [callback] and prints how long it took.
+T runWithTiming<T>(T Function() callback) {
+  var stopwatch = clock.stopwatch()..start();
+  var result = callback();
+  print('It took ${stopwatch.elapsed}!');
+  return result;
+}
+```
+
+...and then test your code using the [`fake_async`][] package, which
+automatically overrides the current clock:
+
+[`fake_async`]: https://pub.dartlang.org/packages/fake_async
+
+```dart
+// run_with_timing_test.dart
+import 'run_with_timing.dart';
+
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('runWithTiming() prints the elapsed time', () {
+    FakeAsync().run((async) {
+      expect(() {
+        runWithTiming(() {
+          async.elapse(Duration(seconds: 10));
+        });
+      }, prints('It took 0:00:10.000000!'));
+    });
+  });
+}
+```
diff --git a/pkgs/clock/analysis_options.yaml b/pkgs/clock/analysis_options.yaml
new file mode 100644
index 0000000..9ee7c2b
--- /dev/null
+++ b/pkgs/clock/analysis_options.yaml
@@ -0,0 +1,14 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+  language:
+    strict-casts: true
+    strict-inference: true
+    strict-raw-types: true
+
+linter:
+  rules:
+    - avoid_private_typedef_functions
+    - avoid_redundant_argument_values
+    - use_super_parameters
diff --git a/pkgs/clock/lib/clock.dart b/pkgs/clock/lib/clock.dart
new file mode 100644
index 0000000..755789e
--- /dev/null
+++ b/pkgs/clock/lib/clock.dart
@@ -0,0 +1,34 @@
+// Copyright 2013 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 'src/default.dart';
+
+export 'src/clock.dart';
+export 'src/default.dart';
+
+/// Returns current time.
+@Deprecated('Pass around an instance of Clock instead.')
+typedef TimeFunction = DateTime Function();
+
+/// Returns the current system time.
+@Deprecated('Use new DateTime.now() instead.')
+DateTime systemTime() => DateTime.now();
+
+/// Returns the current time as reported by [clock].
+@Deprecated('Use clock.now() instead.')
+DateTime get now => clock.now();
+
+/// Returns a stopwatch that uses the current time as reported by [clock].
+@Deprecated('Use clock.stopwatch() instead.')
+Stopwatch getStopwatch() => clock.stopwatch();
diff --git a/pkgs/clock/lib/src/clock.dart b/pkgs/clock/lib/src/clock.dart
new file mode 100644
index 0000000..f6f47de
--- /dev/null
+++ b/pkgs/clock/lib/src/clock.dart
@@ -0,0 +1,182 @@
+// 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';
+import 'stopwatch.dart';
+import 'utils.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 [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.
+  // ignore: deprecated_member_use_from_same_package
+  const Clock([DateTime Function() 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(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(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 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 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 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() => 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/pkgs/clock/lib/src/default.dart b/pkgs/clock/lib/src/default.dart
new file mode 100644
index 0000000..2a46b9f
--- /dev/null
+++ b/pkgs/clock/lib/src/default.dart
@@ -0,0 +1,54 @@
+// 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 = Object();
+
+/// The key for the [Zone] value that controls whether nested zones can override
+/// [clock].
+final _isFinalKey = 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] as Clock? ?? 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].
+///
+// ignore: deprecated_member_use_from_same_package
+/// 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 Function() callback, {
+  @Deprecated('This parameter is deprecated and should be avoided')
+  bool isFinal = false,
+}) {
+  if ((Zone.current[_isFinalKey] ?? false) == true) {
+    throw StateError(
+        'Cannot call withClock() within a call to withClock(isFinal = true).');
+  }
+
+  return runZoned(callback,
+      zoneValues: {_clockKey: clock, _isFinalKey: isFinal});
+}
diff --git a/pkgs/clock/lib/src/stopwatch.dart b/pkgs/clock/lib/src/stopwatch.dart
new file mode 100644
index 0000000..93fe1ab
--- /dev/null
+++ b/pkgs/clock/lib/src/stopwatch.dart
@@ -0,0 +1,73 @@
+// 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 = 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);
+
+  @override
+  int get frequency => _frequency;
+  @override
+  int get elapsedTicks => (elapsedMicroseconds * frequency) ~/ 1000000;
+  @override
+  Duration get elapsed => Duration(microseconds: elapsedMicroseconds);
+  @override
+  int get elapsedMilliseconds => elapsedMicroseconds ~/ 1000;
+  @override
+  bool get isRunning => _start != null;
+
+  @override
+  int get elapsedMicroseconds =>
+      _elapsed +
+      (_start == null ? 0 : _clock.now().difference(_start!).inMicroseconds);
+
+  @override
+  void start() {
+    _start ??= _clock.now();
+  }
+
+  @override
+  void stop() {
+    _elapsed = elapsedMicroseconds;
+    _start = null;
+  }
+
+  @override
+  void reset() {
+    _elapsed = 0;
+    if (_start != null) _start = _clock.now();
+  }
+}
diff --git a/pkgs/clock/lib/src/utils.dart b/pkgs/clock/lib/src/utils.dart
new file mode 100644
index 0000000..4301b09
--- /dev/null
+++ b/pkgs/clock/lib/src/utils.dart
@@ -0,0 +1,59 @@
+// 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.
+
+// This code is copied from quiver. We don't take on an explicit dependency
+// because quiver is very large and the amount of code we use from it is very
+// small.
+
+/// The number of days in each month.
+///
+/// This array uses 1-based month numbers, i.e. January is the 1-st element in
+/// the array, not the 0-th.
+const _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+/// Returns the number of days in the specified month.
+///
+/// This function assumes the use of the Gregorian calendar or the proleptic
+/// Gregorian calendar.
+int daysInMonth(int year, int month) =>
+    (month == DateTime.february && isLeapYear(year)) ? 29 : _daysInMonth[month];
+
+/// Returns true if [year] is a leap year.
+///
+/// This implements the Gregorian calendar leap year rules wherein a year is
+/// considered to be a leap year if it is divisible by 4, excepting years
+/// divisible by 100, but including years divisible by 400.
+///
+/// This function assumes the use of the Gregorian calendar or the proleptic
+/// Gregorian calendar.
+bool isLeapYear(int year) =>
+    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+
+/// Takes a `date` that may be outside the allowed range of dates for a given
+/// [month] in a given [year] and returns the closest date that is within the
+/// allowed range.
+///
+/// For example:
+///
+/// February 31, 2013 => February 28, 2013
+///
+/// When jumping from month to month or from leap year to common year we may
+/// end up in a month that has fewer days than the month we are jumping from.
+/// In that case it is impossible to preserve the exact date. So we "clamp" the
+/// date value to fit within the month. For example, jumping from March 31 one
+/// month back takes us to February 28 (or 29 during a leap year), as February
+/// doesn't have 31-st date.
+int clampDayOfMonth(
+        {required int year, required int month, required int day}) =>
+    day.clamp(1, daysInMonth(year, month));
diff --git a/pkgs/clock/pubspec.yaml b/pkgs/clock/pubspec.yaml
new file mode 100644
index 0000000..2487fe7
--- /dev/null
+++ b/pkgs/clock/pubspec.yaml
@@ -0,0 +1,11 @@
+name: clock
+version: 1.1.2
+description: A fakeable wrapper for dart:core clock APIs.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/clock
+
+environment:
+  sdk: ^3.4.0
+
+dev_dependencies:
+  dart_flutter_team_lints: ^3.0.0
+  test: ^1.16.6
diff --git a/pkgs/clock/test/clock_test.dart b/pkgs/clock/test/clock_test.dart
new file mode 100644
index 0000000..c457153
--- /dev/null
+++ b/pkgs/clock/test/clock_test.dart
@@ -0,0 +1,210 @@
+// Copyright 2013 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';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  late Clock clock;
+  setUp(() {
+    clock = Clock.fixed(date(2013));
+  });
+
+  test('should return a non-null value from system clock', () {
+    expect(const Clock().now(), isNotNull);
+  });
+
+  // This test may be flaky on certain systems. I ran it over 10 million
+  // cycles on my machine without any failures, but that's no guarantee.
+  test('should be close enough to system clock', () {
+    // At 10ms the test doesn't seem to be flaky.
+    var epsilon = 10;
+    expect(DateTime.now().difference(const Clock().now()).inMilliseconds.abs(),
+        lessThan(epsilon));
+    expect(DateTime.now().difference(const Clock().now()).inMilliseconds.abs(),
+        lessThan(epsilon));
+  });
+
+  test('should return time provided by a custom function', () {
+    var time = date(2013);
+    var fixedClock = Clock(() => time);
+    expect(fixedClock.now(), date(2013));
+
+    time = date(2014);
+    expect(fixedClock.now(), date(2014));
+  });
+
+  test('should return fixed time', () {
+    expect(Clock.fixed(date(2013)).now(), date(2013));
+  });
+
+  test('should return time Duration ago', () {
+    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));
+  });
+
+  test('should return time parts ago', () {
+    expect(
+        clock.ago(
+            days: 1,
+            hours: 1,
+            minutes: 1,
+            seconds: 1,
+            milliseconds: 1,
+            microseconds: 1000),
+        DateTime(2012, 12, 30, 22, 58, 58, 998));
+  });
+
+  test('should return time parts from now', () {
+    expect(
+        clock.fromNow(
+            days: 1,
+            hours: 1,
+            minutes: 1,
+            seconds: 1,
+            milliseconds: 1,
+            microseconds: 1000),
+        DateTime(2013, 1, 2, 1, 1, 1, 2));
+  });
+
+  test('should return time micros ago', () {
+    expect(clock.microsAgo(1000), DateTime(2012, 12, 31, 23, 59, 59, 999));
+  });
+
+  test('should return time micros from now', () {
+    expect(clock.microsFromNow(1000), DateTime(2013, 1, 1, 0, 0, 0, 1));
+  });
+
+  test('should return time millis ago', () {
+    expect(clock.millisAgo(1000), DateTime(2012, 12, 31, 23, 59, 59));
+  });
+
+  test('should return time millis from now', () {
+    expect(clock.millisFromNow(3), DateTime(2013, 1, 1, 0, 0, 0, 3));
+  });
+
+  test('should return time seconds ago', () {
+    expect(clock.secondsAgo(10), DateTime(2012, 12, 31, 23, 59, 50));
+  });
+
+  test('should return time seconds from now', () {
+    expect(clock.secondsFromNow(3), DateTime(2013, 1, 1, 0, 0, 3));
+  });
+
+  test('should return time minutes ago', () {
+    expect(clock.minutesAgo(10), DateTime(2012, 12, 31, 23, 50));
+  });
+
+  test('should return time minutes from now', () {
+    expect(clock.minutesFromNow(3), DateTime(2013, 1, 1, 0, 3));
+  });
+
+  test('should return time hours ago', () {
+    expect(clock.hoursAgo(10), DateTime(2012, 12, 31, 14));
+  });
+
+  test('should return time hours from now', () {
+    expect(clock.hoursFromNow(3), DateTime(2013, 1, 1, 3));
+  });
+
+  test('should return time days ago', () {
+    expect(clock.daysAgo(10), date(2012, 12, 22));
+  });
+
+  test('should return time days from now', () {
+    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));
+  });
+
+  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));
+  });
+
+  test('should go from 2013-05-31 to 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));
+  });
+
+  test('should go from 2013-05-31 to 2013-02-28 (common year)', () {
+    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));
+  });
+
+  test('should go from 2013-03-31 to 2013-06-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));
+  });
+
+  test('should go from 2004-02-29 to 2003-02-28 by year', () {
+    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));
+  });
+
+  test('should go from 2004-02-29 to 2005-02-28 by year', () {
+    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));
+  });
+
+  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));
+  });
+
+  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));
+  });
+}
diff --git a/pkgs/clock/test/default_test.dart b/pkgs/clock/test/default_test.dart
new file mode 100644
index 0000000..80acd15
--- /dev/null
+++ b/pkgs/clock/test/default_test.dart
@@ -0,0 +1,84 @@
+// 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';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test('the default clock returns the system time', () {
+    expect(DateTime.now().difference(clock.now()).inMilliseconds.abs(),
+        lessThan(100));
+  });
+
+  group('withClock()', () {
+    group('overrides the clock', () {
+      test('synchronously', () {
+        var time = date(1990, 11, 8);
+        withClock(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(Clock.fixed(time), () {
+          expect(Future(() async {
+            expect(clock.now(), equals(time));
+          }), completes);
+        });
+      });
+
+      test('within another withClock() call', () {
+        var outerTime = date(1990, 11, 8);
+        withClock(Clock.fixed(outerTime), () {
+          expect(clock.now(), equals(outerTime));
+
+          var innerTime = date(2016, 11, 8);
+          withClock(Clock.fixed(innerTime), () {
+            expect(clock.now(), equals(innerTime));
+            expect(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(Clock.fixed(outerTime), () {
+        expect(clock.now(), equals(outerTime));
+
+        expect(() => withClock(fixed(2016, 11, 8), neverCalledVoid),
+            throwsStateError);
+
+        expect(clock.now(), equals(outerTime));
+        // ignore: deprecated_member_use_from_same_package
+      }, isFinal: true);
+    });
+  });
+}
+
+/// A wrapper for [neverCalled] that works around sdk#33015.
+void Function() get neverCalledVoid {
+  var function = neverCalled;
+  return () => function();
+}
diff --git a/pkgs/clock/test/stopwatch_test.dart b/pkgs/clock/test/stopwatch_test.dart
new file mode 100644
index 0000000..e96c0f7
--- /dev/null
+++ b/pkgs/clock/test/stopwatch_test.dart
@@ -0,0 +1,171 @@
+// 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';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test('returns the system frequency', () {
+    expect(fixed(1990, 11, 8).stopwatch().frequency,
+        equals(Stopwatch().frequency));
+  });
+
+  group('before it starts', () {
+    late 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', () {
+    late DateTime time;
+    late Clock clock;
+    late Stopwatch stopwatch;
+    setUp(() {
+      time = date(1990, 11, 8);
+      clock = 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(const Duration(microseconds: 12345)));
+        });
+
+        test('ticks', () {
+          expect(stopwatch.elapsedTicks,
+              equals((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(const Duration(microseconds: 12345)));
+        });
+
+        test('ticks', () {
+          expect(stopwatch.elapsedTicks,
+              equals((Stopwatch().frequency * 12345) ~/ 1000000));
+        });
+
+        test('microseconds', () {
+          expect(stopwatch.elapsedMicroseconds, equals(12345));
+        });
+
+        test('milliseconds', () {
+          expect(stopwatch.elapsedMilliseconds, equals(12));
+        });
+      });
+    });
+  }, onPlatform: {
+    'js': const Skip('Web does not have enough precision'),
+  });
+}
diff --git a/pkgs/clock/test/utils.dart b/pkgs/clock/test/utils.dart
new file mode 100644
index 0000000..ea4b1df
--- /dev/null
+++ b/pkgs/clock/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]) =>
+    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]) =>
+    Clock.fixed(date(year, month, day));