Fix bug in `Duration.toString`.
On the web, some inputs to `Duration` would make -0.0
occur in positions where the code assumed an integer
with a reasonable `toString`.
Adds `+ 0` and using `0 - x` instead of `-x` to
avoid ever ending up with a `-0.0`.
Fixes #51584
CoreLibraryReviewExempt: Bugfix, no API changes.
Change-Id: Idecfaf049f2ae5677792387ce24e95add99596cb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291280
Commit-Queue: Nate Bosch <nbosch@google.com>
Auto-Submit: Lasse Nielsen <lrn@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
diff --git a/sdk/lib/_internal/vm_shared/lib/date_patch.dart b/sdk/lib/_internal/vm_shared/lib/date_patch.dart
index 4aaf62a..8daa59b 100644
--- a/sdk/lib/_internal/vm_shared/lib/date_patch.dart
+++ b/sdk/lib/_internal/vm_shared/lib/date_patch.dart
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import "dart:_internal" show patch, unsafeCast;
+import "dart:_internal" show checkNotNullable, patch, unsafeCast;
// VM implementation of DateTime.
@patch
@@ -47,12 +47,11 @@
@patch
DateTime._internal(int year, int month, int day, int hour, int minute,
int second, int millisecond, int microsecond, bool isUtc)
- : this.isUtc = isUtc,
+ : this.isUtc = checkNotNullable(isUtc, "isUtc"),
this._value = _brokenDownDateToValue(year, month, day, hour, minute,
second, millisecond, microsecond, isUtc) ??
-1 {
if (_value == -1) throw new ArgumentError();
- if (isUtc == null) throw new ArgumentError();
}
static int _validateMilliseconds(int millisecondsSinceEpoch) =>
diff --git a/sdk/lib/core/duration.dart b/sdk/lib/core/duration.dart
index bd73e56..3f1542b 100644
--- a/sdk/lib/core/duration.dart
+++ b/sdk/lib/core/duration.dart
@@ -161,8 +161,9 @@
/// underflow and subtract from the next larger unit.
///
/// If the total number of microseconds cannot be represented
- /// as an integer value, the number of microseconds might be truncated
- /// and it might lose precision.
+ /// as an integer value, the number of microseconds might overflow
+ /// and be truncated to a smaller number of bits,
+ /// or it might lose precision.
///
/// All arguments are 0 by default.
/// ```dart
@@ -184,9 +185,10 @@
microsecondsPerHour * hours +
microsecondsPerDay * days);
- // Fast path internal direct constructor to avoids the optional arguments and
- // [_microseconds] recomputation.
- const Duration._microseconds(this._duration);
+ // Fast path internal direct constructor to avoids the optional arguments
+ // and [_microseconds] recomputation.
+ // The `+ 0` prevents -0.0 on the web, if the incoming duration happens to be -0.0.
+ const Duration._microseconds(int duration) : _duration = duration + 0;
/// Adds this Duration and [other] and
/// returns the sum as a new Duration object.
@@ -212,7 +214,7 @@
/// Divides this Duration by the given [quotient] and returns the truncated
/// result as a new Duration object.
///
- /// Throws an [IntegerDivisionByZeroException] if [quotient] is `0`.
+ /// The [quotient] must not be `0`.
Duration operator ~/(int quotient) {
// By doing the check here instead of relying on "~/" below we get the
// exception even with dart2js.
@@ -331,12 +333,19 @@
/// ```
String toString() {
var microseconds = inMicroseconds;
- var sign = (microseconds < 0) ? "-" : "";
+ var sign = "";
+ var negative = microseconds < 0;
var hours = microseconds ~/ microsecondsPerHour;
microseconds = microseconds.remainder(microsecondsPerHour);
- if (microseconds < 0) microseconds = -microseconds;
+ // Correcting for being negative after first division, instead of before,
+ // to avoid negating min-int, -(2^31-1), of a native int64.
+ if (negative) {
+ hours = 0 - hours; // Not using `-hours` to avoid creating -0.0 on web.
+ microseconds = 0 - microseconds;
+ sign = "-";
+ }
var minutes = microseconds ~/ microsecondsPerMinute;
microseconds = microseconds.remainder(microsecondsPerMinute);
@@ -348,10 +357,17 @@
var secondsPadding = seconds < 10 ? "0" : "";
- var paddedMicroseconds = microseconds.toString().padLeft(6, "0");
- return "$sign${hours.abs()}:"
+ var microsecondsText = microseconds.toString();
+
+ // Padding up to six digits for microseconds.
+ const zeroPadding = ["00000", "0000", "000", "00", "0", ""];
+ assert(microsecondsText.length >= 1 && microsecondsText.length <= 6);
+ var microsecondsPadding = zeroPadding[microsecondsText.length - 1];
+
+ return "$sign$hours:"
"$minutesPadding$minutes:"
- "$secondsPadding$seconds.$paddedMicroseconds";
+ "$secondsPadding$seconds."
+ "$microsecondsPadding$microsecondsText";
}
/// Whether this [Duration] is negative.
diff --git a/tests/corelib/duration_test.dart b/tests/corelib/duration_test.dart
index e60efb8..3d7327b 100644
--- a/tests/corelib/duration_test.dart
+++ b/tests/corelib/duration_test.dart
@@ -315,4 +315,19 @@
Expect.isFalse(d.toString().startsWith("--"));
d = const Duration(minutes: -0); // is -0.0 on web.
Expect.equals("0:00:00.000000", d.toString());
+
+ // Regression test for https://github.com/dart-lang/sdk/issues/51584
+ // (Didn't fix it all in 48841.)
+ d = const Duration(hours: -1);
+ Expect.equals("-1:00:00.000000", d.toString());
+
+ // Adding -0.0's gives -0.0 again.
+ d = const Duration(
+ days: -0,
+ hours: -0,
+ minutes: -0,
+ seconds: -0,
+ milliseconds: -0,
+ microseconds: -0);
+ Expect.equals("0:00:00.000000", d.toString());
}
diff --git a/tests/corelib_2/duration_test.dart b/tests/corelib_2/duration_test.dart
index 33ebf1f..9cab8e6 100644
--- a/tests/corelib_2/duration_test.dart
+++ b/tests/corelib_2/duration_test.dart
@@ -317,4 +317,19 @@
Expect.isFalse(d.toString().startsWith("--"));
d = const Duration(minutes: -0); // is -0.0 on web.
Expect.equals("0:00:00.000000", d.toString());
+
+ // Regression test for https://github.com/dart-lang/sdk/issues/51584
+ // (Didn't fix it all in 48841.)
+ d = const Duration(hours: -1);
+ Expect.equals("-1:00:00.000000", d.toString());
+
+ // Adding -0.0's gives -0.0 again.
+ d = const Duration(
+ days: -0,
+ hours: -0,
+ minutes: -0,
+ seconds: -0,
+ milliseconds: -0,
+ microseconds: -0);
+ Expect.equals("0:00:00.000000", d.toString());
}