Add more information to the error when a DateTime fails verification.

Tracking down some erratic errors where dates seem to be off by an integer number of hours, which might be a time zone offset.

PiperOrigin-RevId: 273811213
diff --git a/lib/src/intl/date_format.dart b/lib/src/intl/date_format.dart
index 5625802..2046035 100644
--- a/lib/src/intl/date_format.dart
+++ b/lib/src/intl/date_format.dart
@@ -319,7 +319,7 @@
   }
 
   DateTime _parseLoose(String inputString, bool utc) {
-    var dateFields = _DateBuilder();
+    var dateFields = _DateBuilder(locale ?? Intl.defaultLocale);
     if (utc) dateFields.utc = true;
     var stream = _Stream(inputString);
     for (var field in _formatFields) {
@@ -347,7 +347,7 @@
   DateTime _parse(String inputString, {bool utc = false, bool strict = false}) {
     // TODO(alanknight): The Closure code refers to special parsing of numeric
     // values with no delimiters, which we currently don't do. Should we?
-    var dateFields = _DateBuilder();
+    var dateFields = _DateBuilder(locale ?? Intl.defaultLocale);
     if (utc) dateFields.utc = true;
     dateFields._dateOnly = dateOnly;
     var stream = _Stream(inputString);
diff --git a/lib/src/intl/date_format_helpers.dart b/lib/src/intl/date_format_helpers.dart
index 8193a47..acfe003 100644
--- a/lib/src/intl/date_format_helpers.dart
+++ b/lib/src/intl/date_format_helpers.dart
@@ -44,6 +44,18 @@
   bool pm = false;
   bool utc = false;
 
+  /// The locale, kept for logging purposes when there's an error.
+  final String _locale;
+
+  /// The date result produced from [asDate].
+  ///
+  /// Kept as a field to cache the result and to reduce the possibility of error
+  /// after we've verified.
+  DateTime _date;
+
+  /// The number of times we've retried, for error reporting.
+  int _retried = 0;
+
   /// Is this constructing a pure date.
   ///
   /// This is important because some locales change times at midnight,
@@ -59,6 +71,8 @@
   // ignore: prefer_final_fields
   var _dateOnly = false;
 
+  _DateBuilder(this._locale);
+
   // Functions that exist just to be closurized so we can pass them to a general
   // method.
   void setYear(x) {
@@ -130,9 +144,15 @@
       [DateTime parsed]) {
     if (value < min || value > max) {
       var parsedDescription = parsed == null ? '' : ' Date parsed as $parsed.';
-      throw FormatException(
-          'Error parsing $originalInput, invalid $desc value: $value.'
-          ' Expected value between $min and $max.$parsedDescription');
+      var errorDescription =
+          'Error parsing $originalInput, invalid $desc value: $value'
+          ' in $_locale'
+          ' with time zone offset ${parsed?.timeZoneOffset ?? 'unknown'}.'
+          ' Expected value between $min and $max.$parsedDescription.';
+      if (_retried > 0) {
+        errorDescription += ' Failed after $_retried retries.';
+      }
+      throw FormatException(errorDescription);
     }
   }
 
@@ -141,15 +161,17 @@
   DateTime asDate({int retries = 3}) {
     // TODO(alanknight): Validate the date, especially for things which
     // can crash the VM, e.g. large month values.
+    if (_date != null) return _date;
 
     if (utc) {
-      return DateTime.utc(
+      _date = DateTime.utc(
           year, month, day, hour24, minute, second, fractionalSecond);
     } else {
       var preliminaryResult =
           DateTime(year, month, day, hour24, minute, second, fractionalSecond);
-      return _correctForErrors(preliminaryResult, retries);
+      _date = _correctForErrors(preliminaryResult, retries);
     }
+    return _date;
   }
 
   /// Given a local DateTime, check for errors and try to compensate for them if
@@ -193,6 +215,7 @@
             !DateTime.now().isUtc)) {
       // This may be a UTC failure. Retry and if the result doesn't look
       // like it's in the UTC time zone, use that instead.
+      _retried++;
       return asDate(retries: retries - 1);
     }
     if (_dateOnly && day != correspondingDay) {