Repository: dart-lang/i18n

Issue:
FormatException on specific date and time (very strange)
**Describe the bug**
Getting the following exception on this specific date range only i.e. **23-03-12 02:00:00** to **23-03-12 02:59:59** with the following message

_FormatException: Error parsing 23-03-12 02:00:00, invalid hour value: 2 in en_US with time zone offset -4:00:00.000000. Expected value between 3 and 3. Date parsed as 0023-03-12 03:00:00.000.._

**To Reproduce**
The timezone should be either UTC -4, -5, -6, -7 and running a local test as following will reproduce it. Please note that this works fine for all the time windows except **02:00:00 till 02:59:59** 
```
void main(List<String> arguments) {
    String date = "23-03-12 02:00:00";
    DateTime? dateTime = DateFormat('y-MM-dd HH:mm:ss').parseStrict(date).toLocal();
}
```

**Expected**
The library should work as expected on the above mentioned time range. 




Discussion: This can be reproduced with
```
DateFormat('yy').parseStrict("23");
```
as the `verify` method checks if the year is valid, where `23` is not in the valid range of `2023` to `2023`.

This should be fixed by adapting the validation code to check for the `_hasAmbiguousCentury` variable.
The issue I am facing is due to the Daylight saving adjustment in the mentioned timezones, which occurs on 12 March every year, hence the verify method fails because there is no consideration of Daylight saving.
I am not sure I understand - the problem you described in the first post is reproducible without any specific reference to a time, just using the year. Could you elaborate?
The issue I am facing is specific to the time (particularly hour) and not the year. If you try to parse this date **23-03-12 02:00:00** with this format _y-MM-dd HH:mm:ss_ then the time returned is DayLight saving adjusted, i.e. **23-03-12 03:00:00** which then goes through the series of verify checks as I am using the `DateFormat.parseStrict` method. It fails to verify the Hour field and throws the FormatException with the following Stack trace;

```
DateBuilder._verify (date_builder.dart:159)
DateBuilder.verify (date_builder.dart:131)
DateFormat._parse (date_format.dart:400)
DateFormat.parseStrict (date_format.dart:384)
Data.getDate (data.dart:875)
```

Hope the above stack trace helps.
The format `y-MM-dd HH:mm:ss` would expect the date to be `2023-03-12 02:00:00`, which then works. Does this solve your problem?
 Try it on the 26th of March at 2:00am.

This failure depends on the underlying OS/browser's daylight savings time
transition code. This is different between North America/Europe/elsewhere.

But also, it's not that there's no accommodation of daylight savings time.
It's failing precisely because it does take it into account. There are
DateTimes that are not valid because they don't exist. If you try to create
one, the underlying DateTime code will adjust it to be something different.
It fails validation rather than silently changing the value.

On Mon, Mar 27, 2023 at 8:58 AM Moritz ***@***.***> wrote:

> The format y-MM-dd HH:mm:ss would expect the date to be 2023-03-12
> 02:00:00, which then works. Does this solve your problem?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/dart-lang/i18n/issues/483>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AA2QU6DMFBTBCUICUZG7S53W6GFGDANCNFSM6AAAAAAWAYVR3U>
> .
> You are receiving this because you are subscribed to this thread.Message
> ID: ***@***.***>
>

Facing the same error in Eastern Timezone. Any fix?
The error is that this `DateTime` does not exist, so there is no fix for this. This issue is open for fixing https://github.com/dart-lang/i18n/issues/483#issuecomment-1513231997.

Fix patch:
diff --git a/pkgs/intl/CHANGELOG.md b/pkgs/intl/CHANGELOG.md
index 6c1ee15f..5be32540 100644
--- a/pkgs/intl/CHANGELOG.md
+++ b/pkgs/intl/CHANGELOG.md
@@ -6,8 +6,10 @@
  * Update to CLDR v43.
  * Switch QAR currency name to Riyal.
  * Add license headers to some files.
+ * Update CVE currency symbol.
  * Add `EEEEE` skeleton for `DateFormat`, closing [#176](https://github.com/dart-lang/i18n/issues/176).
  * Switch to `3.0.0` SDK.
+ * Fix issue [#483](https://github.com/dart-lang/i18n/issues/483) about date parsing with a `yy` skeleton.
 
 ## 0.18.1
  * Update ruble sign and update corresponding test.
diff --git a/pkgs/intl/lib/src/intl/date_builder.dart b/pkgs/intl/lib/src/intl/date_builder.dart
index c5f5b106..ca1add92 100644
--- a/pkgs/intl/lib/src/intl/date_builder.dart
+++ b/pkgs/intl/lib/src/intl/date_builder.dart
@@ -27,6 +27,8 @@ class DateBuilder {
   /// Ignored if `year < 0` or `year >= 100`.
   bool _hasAmbiguousCentury = false;
 
+  bool get _hasCentury => !_hasAmbiguousCentury || year < 0 || year >= 100;
+
   /// The locale, kept for logging purposes when there's an error.
   final String _locale;
 
@@ -68,9 +70,8 @@ class DateBuilder {
 
   /// Sets whether [year] should be treated as ambiguous because it lacks a
   /// century.
-  void setHasAmbiguousCentury(bool isAmbiguous) {
-    _hasAmbiguousCentury = isAmbiguous;
-  }
+  set hasAmbiguousCentury(bool isAmbiguous) =>
+      _hasAmbiguousCentury = isAmbiguous;
 
   void setMonth(int x) {
     month = x;
@@ -140,7 +141,7 @@ class DateBuilder {
       // We have the day of the month, compare directly.
       _verify(day, date.day, date.day, 'day', s, date);
     }
-    _verify(year, date.year, date.year, 'year', s, date);
+    _verify(_estimatedYear, date.year, date.year, 'year', s, date);
   }
 
   void _verify(int value, int min, int max, String desc, String originalInput,
@@ -181,12 +182,38 @@ class DateBuilder {
     // TODO(alanknight): Validate the date, especially for things which
     // can crash the VM, e.g. large month values.
     if (_date != null) return _date!;
+    DateTime preliminaryResult = _dateTimeConstructor(
+      _estimatedYear,
+      month,
+      dayOrDayOfYear,
+      hour24,
+      minute,
+      second,
+      fractionalSecond,
+      utc,
+    );
+    if (utc && _hasCentury) {
+      _date = preliminaryResult;
+    } else {
+      _date = _correctForErrors(preliminaryResult, retries);
+    }
+    return _date!;
+  }
 
-    DateTime preliminaryResult;
-    final hasCentury = !_hasAmbiguousCentury || year < 0 || year >= 100;
-    if (hasCentury) {
-      preliminaryResult = _dateTimeConstructor(year, month, dayOrDayOfYear,
-          hour24, minute, second, fractionalSecond, utc);
+  int get _estimatedYear {
+    DateTime preliminaryResult(int year) => _dateTimeConstructor(
+          year,
+          month,
+          dayOrDayOfYear,
+          hour24,
+          minute,
+          second,
+          fractionalSecond,
+          utc,
+        );
+    int estimatedYear;
+    if (_hasCentury) {
+      estimatedYear = year;
     } else {
       var now = clock.now();
       if (utc) {
@@ -198,8 +225,7 @@ class DateBuilder {
       var upperDate = _offsetYear(now, 100 - lookBehindYears);
       var lowerCentury = (lowerDate.year ~/ 100) * 100;
       var upperCentury = (upperDate.year ~/ 100) * 100;
-      preliminaryResult = _dateTimeConstructor(upperCentury + year, month,
-          dayOrDayOfYear, hour24, minute, second, fractionalSecond, utc);
+      estimatedYear = upperCentury + year;
 
       // Our interval must be half-open since there otherwise could be ambiguity
       // for a date that is exactly 20 years in the future or exactly 80 years
@@ -211,21 +237,14 @@ class DateBuilder {
       //   the upper-bound date.
       //
       // We don't actually need to check both bounds.
-      if (preliminaryResult.compareTo(upperDate) <= 0) {
+      if (preliminaryResult(upperCentury + year).compareTo(upperDate) <= 0) {
         // Within range.
-        assert(preliminaryResult.compareTo(lowerDate) > 0);
+        assert(preliminaryResult(upperCentury + year).compareTo(lowerDate) > 0);
       } else {
-        preliminaryResult = _dateTimeConstructor(lowerCentury + year, month,
-            dayOrDayOfYear, hour24, minute, second, fractionalSecond, utc);
+        estimatedYear = lowerCentury + year;
       }
     }
-
-    if (utc && hasCentury) {
-      _date = preliminaryResult;
-    } else {
-      _date = _correctForErrors(preliminaryResult, retries);
-    }
-    return _date!;
+    return estimatedYear;
   }
 
   /// Given a local DateTime, check for errors and try to compensate for them if
diff --git a/pkgs/intl/lib/src/intl/date_format_field.dart b/pkgs/intl/lib/src/intl/date_format_field.dart
index e1b89460..732c279a 100644
--- a/pkgs/intl/lib/src/intl/date_format_field.dart
+++ b/pkgs/intl/lib/src/intl/date_format_field.dart
@@ -487,7 +487,7 @@ class _DateFormatPatternField extends _DateFormatField {
 
   void parseYear(StringStack input, DateBuilder builder) {
     handleNumericField(input, builder.setYear);
-    builder.setHasAmbiguousCentury(width == 2);
+    builder.hasAmbiguousCentury = width == 2;
   }
 
   String formatMonth(DateTime date) {


Test patch:
diff --git a/pkgs/intl/test/date_time_strict_test.dart b/pkgs/intl/test/date_time_strict_test.dart
index 92b86b71..7d8f1d51 100644
--- a/pkgs/intl/test/date_time_strict_test.dart
+++ b/pkgs/intl/test/date_time_strict_test.dart
@@ -162,4 +162,9 @@ void main() {
       expect(format.tryParseStrict('14:0:59'), DateTime(1970, 1, 1, 14, 0, 59));
     });
   });
+
+  test('Year in yy format', () {
+    expect(DateFormat('yy').parseStrict('23'), DateTime(2023));
+    expect(DateFormat('yyyy').parseStrict('2023'), DateTime(2023));
+  });
 }