Merge pull request #290 from jamesderlin/copybara-sync

Copybara sync
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0658d4..56e3dae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,18 @@
+## 0.17.0
+ * **Breaking Change** [#123][]: Fix parsing of two-digit years to match the
+   documented behavior. Previously a two-digit year would be parsed to a value
+   in the range [0, 99]. Now it is parsed relative to the current date,
+   returning a value between 80 years in the past and 20 years in the future.
+ * Use package:clock to get the current date/time.
+ * Fix some more analysis complaints.
+ * Update documentation to indicate that time zone specifiers are not yet
+   implemented [#264][].
+
 ## 0.16.2
  * Fix bug with dates in January being treated as ordinal. e.g. 2020-01-32 would
    be accepted as valid and the day treated as day-of-year.
+ * Compact currency formats will avoid displaying unecessary trailing zeros
+   in compact formats for currencies which specify decimal places.
 
 ## 0.16.1
  * Add an analysis_options.yaml and fix or suppress all the complaints from it.
@@ -351,3 +363,6 @@
 * Handle two different messages with the same text.
 
 * Allow complex string literals in arguments (e.g. multi-line)
+
+[#123]: https://github.com/dart-lang/intl/issues/123
+[#264]: https://github.com/dart-lang/intl/issues/264
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 76203fd..81ede1d 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,9 +1,13 @@
 analyzer:
+  language:
+    strict-raw-types: true
+
   exclude:
      - test/number_format_compact_icu_test.dart # TODO(240): Update for FFI changes
 
 errors:
     dead_code: error
+    missing_required_param: error
     override_on_non_overriding_method: error
     unused_element: error
     unused_import: error
@@ -64,7 +68,6 @@
     - prefer_is_empty
     - prefer_is_not_empty
     - prefer_null_aware_operators
-    - prefer_single_quotes
     - prefer_typing_uninitialized_variables
     - recursive_getters
     - slash_for_doc_comments
diff --git a/lib/intl.dart b/lib/intl.dart
index 102e2dc..98f3899 100644
--- a/lib/intl.dart
+++ b/lib/intl.dart
@@ -23,6 +23,8 @@
 import 'dart:convert';
 import 'dart:math';
 
+import 'package:clock/clock.dart';
+
 import 'date_symbols.dart';
 import 'number_symbols.dart';
 import 'number_symbols_data.dart';
@@ -51,7 +53,7 @@
 ///          args: [date],
 ///          desc: 'Indicate the current date',
 ///          examples: const {'date' : 'June 8, 2012'});
-///      print(today(new DateTime.now().toString());
+///      print(today(DateTime.now().toString());
 ///
 ///      howManyPeople(numberOfPeople, place) => Intl.plural(numberOfPeople,
 ///            zero: 'I see no one at all in $place.',
@@ -75,7 +77,7 @@
 ///
 /// To temporarily use a locale other than the default, use the `withLocale`
 /// function.
-///       var todayString = new DateFormat('pt_BR').format(new DateTime.now());
+///       var todayString = DateFormat('pt_BR').format(DateTime.now());
 ///       print(withLocale('pt_BR', () => today(todayString));
 ///
 /// See `tests/message_format_test.dart` for more examples.
@@ -566,7 +568,7 @@
   ///
   /// For example
   ///
-  ///       Intl.withLocale('fr', () => new NumberFormat.format(123456));
+  ///       Intl.withLocale('fr', () => NumberFormat.format(123456));
   ///
   /// or
   ///
@@ -575,7 +577,7 @@
   ///           name: 'hello',
   ///           args: [name],
   ///           desc: 'Say Hello');
-  ///       Intl.withLocale('zh', new Timer(new Duration(milliseconds:10),
+  ///       Intl.withLocale('zh', Timer(Duration(milliseconds:10),
   ///           () => print(hello('World')));
   static dynamic withLocale<T>(String locale, T Function() function) {
     // TODO(alanknight): Make this return T. This requires work because T might
diff --git a/lib/message_format.dart b/lib/message_format.dart
index 86d4d7b..44b68e9 100644
--- a/lib/message_format.dart
+++ b/lib/message_format.dart
@@ -604,10 +604,7 @@
       argumentName = match.group(1);
       return '';
     });
-    // The lint complaints about "Omit type annotations for local variables"
-    // But if I make this `var` then it assumes that the value is a
-    // always a string, but it is not.
-    Map<String, Object> result = {'argumentName': argumentName};
+    var result = <String, Object>{'argumentName': argumentName};
 
     var parts = _extractParts(pattern);
     // Looking for (key block)+ sequence. One of the keys has to be "other".
diff --git a/lib/src/intl/compact_number_format.dart b/lib/src/intl/compact_number_format.dart
index d696685..162d739 100644
--- a/lib/src/intl/compact_number_format.dart
+++ b/lib/src/intl/compact_number_format.dart
@@ -57,8 +57,8 @@
 ///       4: '00K'
 /// which matches
 ///
-///      new _CompactStyle(pattern: '00K', normalizedExponent: 4, divisor: 1000,
-///      expectedDigits: 1, prefix: '', suffix: 'K');
+///       _CompactStyle(pattern: '00K', normalizedExponent: 4, divisor: 1000,
+///           expectedDigits: 1, prefix: '', suffix: 'K');
 ///
 /// where expectedDigits is the number of zeros.
 class _CompactStyle extends _CompactStyleBase {
@@ -244,8 +244,8 @@
 
   String format(number) {
     _style = _styleFor(number);
-    var divisor = _style.printsAsIs ? 1 : _style.divisor;
-    var numberToFormat = _divide(number, divisor);
+    final divisor = _style.printsAsIs ? 1 : _style.divisor;
+    final numberToFormat = _divide(number, divisor);
     var formatted = super.format(numberToFormat);
     var prefix = _style.prefix;
     var suffix = _style.suffix;
@@ -259,7 +259,7 @@
       prefix = prefix.replaceFirst('\u00a4', currencySymbol);
       suffix = suffix.replaceFirst('\u00a4', currencySymbol);
     }
-    var withExtras = '$prefix$formatted$suffix';
+    final withExtras = '$prefix$formatted$suffix';
     _style = null;
     return withExtras;
   }
@@ -267,7 +267,7 @@
   /// How many digits after the decimal place should we display, given that
   /// there are [remainingSignificantDigits] left to show.
   int _fractionDigitsAfter(int remainingSignificantDigits) {
-    var newFractionDigits =
+    final newFractionDigits =
         super._fractionDigitsAfter(remainingSignificantDigits);
     // For non-currencies, or for currencies if the numbers are large enough to
     // compact, always use the number of significant digits and ignore
@@ -284,6 +284,19 @@
     }
   }
 
+  /// Defines minimumFractionDigits based on current style being formatted.
+  @override
+  int get minimumFractionDigits {
+    if (!_isForCurrency ||
+        !significantDigitsInUse ||
+        _style == null ||
+        _style.isFallback) {
+      return super.minimumFractionDigits;
+    } else {
+      return 0;
+    }
+  }
+
   /// Divide numbers that may not have a division operator (e.g. Int64).
   ///
   /// Only used for powers of 10, so we require an integer denominator.
diff --git a/lib/src/intl/date_format.dart b/lib/src/intl/date_format.dart
index a6b0e24..282d3ef 100644
--- a/lib/src/intl/date_format.dart
+++ b/lib/src/intl/date_format.dart
@@ -23,7 +23,7 @@
 /// initialization. e.g.
 ///
 /// ```dart
-/// print(new DateFormat.yMMMd().format(new DateTime.now()));
+/// print(DateFormat.yMMMd().format(DateTime.now()));
 /// ```
 ///
 /// But for other locales, the formatting data for the locale must be
@@ -108,24 +108,26 @@
 ///      HOUR                         j
 ///      HOUR_MINUTE                  jm
 ///      HOUR_MINUTE_SECOND           jms
-///      HOUR_MINUTE_GENERIC_TZ       jmv
-///      HOUR_MINUTE_TZ               jmz
-///      HOUR_GENERIC_TZ              jv
-///      HOUR_TZ                      jz
+///      HOUR_MINUTE_GENERIC_TZ       jmv   (not yet implemented)
+///      HOUR_MINUTE_TZ               jmz   (not yet implemented)
+///      HOUR_GENERIC_TZ              jv    (not yet implemented)
+///      HOUR_TZ                      jz    (not yet implemented)
 ///      MINUTE                       m
 ///      MINUTE_SECOND                ms
 ///      SECOND                       s
+//
+// TODO(https://github.com/dart-lang/intl/issues/74): Update table above.
 ///
 /// Examples Using the US Locale:
 ///
 ///      Pattern                           Result
 ///      ----------------                  -------
-///      new DateFormat.yMd()             -> 7/10/1996
-///      new DateFormat('yMd')            -> 7/10/1996
-///      new DateFormat.yMMMMd('en_US')   -> July 10, 1996
-///      new DateFormat.jm()              -> 5:08 PM
-///      new DateFormat.yMd().add_jm()    -> 7/10/1996 5:08 PM
-///      new DateFormat.Hm()              -> 17:08 // force 24 hour time
+///      DateFormat.yMd()                 -> 7/10/1996
+///      DateFormat('yMd')                -> 7/10/1996
+///      DateFormat.yMMMMd('en_US')       -> July 10, 1996
+///      DateFormat.jm()                  -> 5:08 PM
+///      DateFormat.yMd().add_jm()        -> 7/10/1996 5:08 PM
+///      DateFormat.Hm()                  -> 17:08 // force 24 hour time
 ///
 /// Explicit Pattern Syntax: Formats can also be specified with a pattern
 /// string.  This can be used for formats that don't have a skeleton available,
@@ -156,13 +158,20 @@
 ///     a        am/pm marker           (Text)             PM
 ///     k        hour in day (1~24)     (Number)           24
 ///     K        hour in am/pm (0~11)   (Number)           0
-///     z        time zone              (Text)             Pacific Standard Time
-///     Z        time zone (RFC 822)    (Number)           -0800
-///     v        time zone (generic)    (Text)             Pacific Time
 ///     Q        quarter                (Text)             Q3
 ///     '        escape for text        (Delimiter)        'Date='
 ///     ''       single quote           (Literal)          'o''clock'
 ///
+//  TODO(https://github.com/dart-lang/intl/issues/74): Merge tables.
+//
+/// The following characters are reserved and currently are unimplemented:
+///
+///     Symbol   Meaning                Presentation       Example
+///     ------   -------                ------------       -------
+///     z        time zone              (Text)             Pacific Standard Time
+///     Z        time zone (RFC 822)    (Number)           -0800
+///     v        time zone (generic)    (Text)             Pacific Time
+///
 /// The count of pattern letters determine the format.
 ///
 /// **Text**:
@@ -190,23 +199,29 @@
 ///
 ///     Format Pattern                    Result
 ///     --------------                    -------
-///     'yyyy.MM.dd G 'at' HH:mm:ss vvvv' 1996.07.10 AD at 15:08:56 Pacific Time
 ///     'EEE, MMM d, ''yy'                Wed, Jul 10, '96
 ///     'h:mm a'                          12:08 PM
-///     'hh 'o''clock' a, zzzz'           12 o'clock PM, Pacific Daylight Time
-///     'K:mm a, vvv'                     0:00 PM, PT
 ///     'yyyyy.MMMMM.dd GGG hh:mm aaa'    01996.July.10 AD 12:08 PM
+//
+// TODO(https://github.com/dart-lang/intl/issues/74): Merge tables.
+//
+//      NOT YET IMPLEMENTED
+//      -------------------
+//      'yyyy.MM.dd G 'at' HH:mm:ss vvvv' 1996.07.10 AD at 15:08:56 Pacific Time
+//      'hh 'o''clock' a, zzzz'           12 o'clock PM, Pacific Daylight Time
+//      'K:mm a, vvv'                     0:00 PM, PT
 ///
 /// When parsing a date string using the abbreviated year pattern ('yy'),
 /// DateFormat must interpret the abbreviated year relative to some
 /// century. It does this by adjusting dates to be within 80 years before and 20
 /// years after the time the parse function is called. For example, using a
-/// pattern of 'MM/dd/yy' and a DateParse instance created on Jan 1, 1997,
+/// pattern of 'MM/dd/yy' and a DateFormat instance created on Jan 1, 1997,
 /// the string '01/11/12' would be interpreted as Jan 11, 2012 while the string
 /// '05/04/64' would be interpreted as May 4, 1964. During parsing, only
 /// strings consisting of exactly two digits will be parsed into the default
 /// century. Any other numeric string, such as a one digit string, a three or
-/// more digit string will be interpreted as its face value.
+/// more digit string will be interpreted as its face value. Tests that parse
+/// two-digit years can control the current date with package:clock.
 ///
 /// If the year pattern does not have exactly two 'y' characters, the year is
 /// interpreted literally, regardless of the number of digits. So using the
@@ -224,13 +239,13 @@
   /// For example, in an en_US locale, specifying the skeleton
   ///
   /// ```dart
-  /// new DateFormat.yMEd();
+  /// DateFormat.yMEd();
   /// ```
   ///
   /// or the explicit
   ///
   /// ```dart
-  /// new DateFormat('EEE, M/d/y');
+  /// DateFormat('EEE, M/d/y');
   /// ```
   ///
   /// would produce the same result, a date of the form 'Wed, 6/27/2012'.
@@ -319,14 +334,14 @@
   ///
   /// For example, this will accept
   ///
-  ///       new DateFormat.yMMMd('en_US').parseLoose('SEp   3 2014');
-  ///       new DateFormat.yMd('en_US').parseLoose('09    03/2014');
-  ///       new DateFormat.yMd('en_US').parseLoose('09 / 03 / 2014');
+  ///       DateFormat.yMMMd('en_US').parseLoose('SEp   3 2014');
+  ///       DateFormat.yMd('en_US').parseLoose('09    03/2014');
+  ///       DateFormat.yMd('en_US').parseLoose('09 / 03 / 2014');
   ///
   /// It will NOT accept
   ///
-  ///      // 'Sept' is not a valid month name.
-  ///      new DateFormat.yMMMd('en_US').parseLoose('Sept 3, 2014');
+  ///       // 'Sept' is not a valid month name.
+  ///       DateFormat.yMMMd('en_US').parseLoose('Sept 3, 2014');
   DateTime parseLoose(String inputString, [bool utc = false]) {
     try {
       return _parse(inputString, utc: utc, strict: true);
@@ -356,7 +371,7 @@
   ///
   /// If [inputString] does not match our format, throws a [FormatException].
   /// This will reject dates whose values are not strictly valid, even if the
-  /// DateTime constructor will accept them. It will also rejct strings with
+  /// DateTime constructor will accept them. It will also reject strings with
   /// additional characters (including whitespace) after a valid date. For
   /// looser parsing, use [parse].
   DateTime parseStrict(String inputString, [bool utc = false]) =>
@@ -418,20 +433,20 @@
   /// So,
   ///
   /// ```dart
-  /// new DateFormat.yMd('en_US')
+  /// DateFormat.yMd('en_US')
   /// ```
   ///
   /// is equivalent to
   ///
   /// ```dart
-  /// new DateFormat('yMd', 'en_US')
+  /// DateFormat('yMd', 'en_US')
   /// ```
   ///
   /// To create a compound format you can use these constructors in combination
   /// with the 'add_*' methods below. e.g.
   ///
   /// ```dart
-  /// new DateFormat.yMd().add_Hms();
+  /// DateFormat.yMd().add_Hms();
   /// ```
   ///
   /// If the optional [locale] is omitted, the format will be created using the
@@ -470,9 +485,17 @@
   DateFormat.j([locale]) : this('j', locale);
   DateFormat.jm([locale]) : this('jm', locale);
   DateFormat.jms([locale]) : this('jms', locale);
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat.jmv([locale]) : this('jmv', locale);
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat.jmz([locale]) : this('jmz', locale);
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat.jv([locale]) : this('jv', locale);
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat.jz([locale]) : this('jz', locale);
   DateFormat.m([locale]) : this('m', locale);
   DateFormat.ms([locale]) : this('ms', locale);
@@ -483,7 +506,7 @@
   /// useful for creating compound formats. For example
   ///
   /// ```dart
-  /// new DateFormat.yMd().add_Hms();
+  /// DateFormat.yMd().add_Hms();
   /// ```
   ///
   /// would create a date format that prints both the date and the time.
@@ -521,9 +544,17 @@
   DateFormat add_j() => addPattern('j');
   DateFormat add_jm() => addPattern('jm');
   DateFormat add_jms() => addPattern('jms');
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat add_jmv() => addPattern('jmv');
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat add_jmz() => addPattern('jmz');
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat add_jv() => addPattern('jv');
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   DateFormat add_jz() => addPattern('jz');
   DateFormat add_m() => addPattern('m');
   DateFormat add_ms() => addPattern('ms');
@@ -565,9 +596,17 @@
   static const String HOUR = 'j';
   static const String HOUR_MINUTE = 'jm';
   static const String HOUR_MINUTE_SECOND = 'jms';
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   static const String HOUR_MINUTE_GENERIC_TZ = 'jmv';
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   static const String HOUR_MINUTE_TZ = 'jmz';
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   static const String HOUR_GENERIC_TZ = 'jv';
+  /// NOT YET IMPLEMENTED.
+  // TODO(https://github.com/dart-lang/intl/issues/74)
   static const String HOUR_TZ = 'jz';
   static const String MINUTE = 'm';
   static const String MINUTE_SECOND = 'ms';
diff --git a/lib/src/intl/date_format_field.dart b/lib/src/intl/date_format_field.dart
index e15e685..360998c 100644
--- a/lib/src/intl/date_format_field.dart
+++ b/lib/src/intl/date_format_field.dart
@@ -324,7 +324,7 @@
         case 'v':
           break; // time zone id
         case 'y':
-          handleNumericField(input, builder.setYear);
+          parseYear(input, builder);
           break;
         case 'z':
           break; // time zone
@@ -414,7 +414,7 @@
   ///
   /// This method handles reading any of the numeric fields. The [offset]
   /// argument allows us to compensate for zero-based versus one-based values.
-  void handleNumericField(_Stream input, void Function(num) setter,
+  void handleNumericField(_Stream input, void Function(int) setter,
       [int offset = 0]) {
     var result = input.nextInteger(
         digitMatcher: parent.digitMatcher,
@@ -443,6 +443,11 @@
     return longestResult;
   }
 
+  void parseYear(_Stream input, _DateBuilder builder) {
+    handleNumericField(input, builder.setYear);
+    builder.setHasAmbiguousCentury(width == 2);
+  }
+
   String formatMonth(DateTime date) {
     switch (width) {
       case 5:
@@ -456,7 +461,7 @@
     }
   }
 
-  void parseMonth(input, dateFields) {
+  void parseMonth(_Stream input, _DateBuilder dateFields) {
     List<String> possibilities;
     switch (width) {
       case 5:
diff --git a/lib/src/intl/date_format_helpers.dart b/lib/src/intl/date_format_helpers.dart
index c0b7f13..be42b84 100644
--- a/lib/src/intl/date_format_helpers.dart
+++ b/lib/src/intl/date_format_helpers.dart
@@ -45,6 +45,11 @@
   bool pm = false;
   bool utc = false;
 
+  /// Whether the century portion of [year] is ambiguous.
+  ///
+  /// Ignored if `year < 0` or `year >= 100`.
+  bool _hasAmbiguousCentury = false;
+
   /// The locale, kept for logging purposes when there's an error.
   final String _locale;
 
@@ -81,19 +86,25 @@
 
   // Functions that exist just to be closurized so we can pass them to a general
   // method.
-  void setYear(x) {
+  void setYear(int x) {
     year = x;
   }
 
-  void setMonth(x) {
+  /// Sets whether [year] should be treated as ambiguous because it lacks a
+  /// century.
+  void setHasAmbiguousCentury(bool isAmbiguous) {
+    _hasAmbiguousCentury = isAmbiguous;
+  }
+
+  void setMonth(int x) {
     month = x;
   }
 
-  void setDay(x) {
+  void setDay(int x) {
     day = x;
   }
 
-  void setDayOfYear(x) {
+  void setDayOfYear(int x) {
     dayOfYear = x;
   }
 
@@ -101,19 +112,19 @@
   /// the day of the month.
   int get dayOrDayOfYear => dayOfYear == 0 ? day : dayOfYear;
 
-  void setHour(x) {
+  void setHour(int x) {
     hour = x;
   }
 
-  void setMinute(x) {
+  void setMinute(int x) {
     minute = x;
   }
 
-  void setSecond(x) {
+  void setSecond(int x) {
     second = x;
   }
 
-  void setFractionalSecond(x) {
+  void setFractionalSecond(int x) {
     fractionalSecond = x;
   }
 
@@ -171,6 +182,22 @@
     }
   }
 
+  /// Offsets a [DateTime] by a specified number of years.
+  ///
+  /// All other fields of the [DateTime] normally will remain unaffected.  An
+  /// exception is if the resulting [DateTime] otherwise would represent an
+  /// invalid date (e.g. February 29 of a non-leap year).
+  DateTime _offsetYear(DateTime dateTime, int offsetYears) =>
+      _dateTimeConstructor(
+          dateTime.year + offsetYears,
+          dateTime.month,
+          dateTime.day,
+          dateTime.hour,
+          dateTime.minute,
+          dateTime.second,
+          dateTime.millisecond,
+          dateTime.isUtc);
+
   /// Return a date built using our values. If no date portion is set,
   /// use the 'Epoch' of January 1, 1970.
   DateTime asDate({int retries = 3}) {
@@ -178,12 +205,47 @@
     // can crash the VM, e.g. large month values.
     if (_date != null) return _date;
 
-    if (utc) {
-      _date = _dateTimeConstructor(year, month, dayOrDayOfYear, hour24, minute,
-          second, fractionalSecond, utc);
-    } else {
-      var preliminaryResult = _dateTimeConstructor(year, month, dayOrDayOfYear,
+    DateTime preliminaryResult;
+    final hasCentury = !_hasAmbiguousCentury || year < 0 || year >= 100;
+    if (hasCentury) {
+      preliminaryResult = _dateTimeConstructor(year, month, dayOrDayOfYear,
           hour24, minute, second, fractionalSecond, utc);
+    } else {
+      var now = clock.now();
+      if (utc) {
+        now = now.toUtc();
+      }
+
+      const lookBehindYears = 80;
+      var lowerDate = _offsetYear(now, -lookBehindYears);
+      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);
+
+      // 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
+      // in the past (mod 100).  We'll treat the lower-bound date as the
+      // exclusive bound because:
+      // * It's farther away from the present, and we're less likely to care
+      //   about it.
+      // * By the time this function exits, time will have advanced to favor
+      //   the upper-bound date.
+      //
+      // We don't actually need to check both bounds.
+      if (preliminaryResult.compareTo(upperDate) <= 0) {
+        // Within range.
+        assert(preliminaryResult.compareTo(lowerDate) > 0);
+      } else {
+        preliminaryResult = _dateTimeConstructor(lowerCentury + year, month,
+            dayOrDayOfYear, hour24, minute, second, fractionalSecond, utc);
+      }
+    }
+
+    if (utc && hasCentury) {
+      _date = preliminaryResult;
+    } else {
       _date = _correctForErrors(preliminaryResult, retries);
     }
     return _date;
@@ -336,7 +398,7 @@
 
   /// Find the index of the first element for which [f] returns true.
   /// Advances the stream to that position.
-  int findIndex(Function f) {
+  int findIndex(bool Function(dynamic) f) {
     while (!atEnd()) {
       if (f(next())) return index - 1;
     }
@@ -345,7 +407,7 @@
 
   /// Find the indexes of all the elements for which [f] returns true.
   /// Leaves the stream positioned at the end.
-  List<dynamic> findIndexes(Function f) {
+  List<dynamic> findIndexes(bool Function(dynamic) f) {
     var results = [];
     while (!atEnd()) {
       if (f(next())) results.add(index - 1);
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index 25d4454..000da63 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -30,7 +30,7 @@
 ///
 /// For example,
 ///
-///       var f = new NumberFormat("###.0#", "en_US");
+///       var f = NumberFormat("###.0#", "en_US");
 ///       print(f.format(12.345));
 ///           ==> 12.34
 ///
@@ -41,8 +41,8 @@
 /// There are also standard patterns available via the special constructors.
 /// e.g.
 ///
-///       var percent = new NumberFormat.percentPattern("ar"); var
-///       eurosInUSFormat = new NumberFormat.currency(locale: "en_US",
+///       var percent = NumberFormat.percentPattern("ar"); var
+///       eurosInUSFormat = NumberFormat.currency(locale: "en_US",
 ///           symbol: "€");
 ///
 /// There are several such constructors available, though some of them are
@@ -142,12 +142,12 @@
   /// otherwise we use the value from the pattern for the locale.
   ///
   /// So, for example,
-  ///      new NumberFormat.currency(name: 'USD', decimalDigits: 7)
+  ///       NumberFormat.currency(name: 'USD', decimalDigits: 7)
   /// will format with 7 decimal digits, because that's what we asked for. But
-  ///       new NumberFormat.currency(locale: 'en_US', name: 'JPY')
+  ///       NumberFormat.currency(locale: 'en_US', name: 'JPY')
   /// will format with zero, because that's the default for JPY, and the
   /// currency's default takes priority over the locale's default.
-  ///       new NumberFormat.currency(locale: 'en_US')
+  ///       NumberFormat.currency(locale: 'en_US')
   /// will format with two, which is the default for that locale.
   ///
   int get decimalDigits => _decimalDigits;
@@ -203,8 +203,8 @@
   ///
   /// If provided,
   /// use [currencyNameOrSymbol] in place of the default currency name. e.g.
-  ///        var eurosInCurrentLocale = new NumberFormat
-  ///            .currencyPattern(Intl.defaultLocale, "€");
+  ///       var eurosInCurrentLocale = NumberFormat
+  ///           .currencyPattern(Intl.defaultLocale, "€");
   @Deprecated('Use NumberFormat.currency')
   factory NumberFormat.currencyPattern(
       [String locale, String currencyNameOrSymbol]) {
@@ -226,28 +226,28 @@
   /// Otherwise we will use the default currency name for the current locale. If
   /// no [symbol] is specified, we will use the currency name in the formatted
   /// result. e.g.
-  ///      var f = new NumberFormat.currency(locale: 'en_US', name: 'EUR')
+  ///       var f = NumberFormat.currency(locale: 'en_US', name: 'EUR')
   /// will format currency like "EUR1.23". If we did not specify the name, it
   /// would format like "USD1.23".
   ///
   /// If [symbol] is used, then that symbol will be used in formatting instead
   /// of the name. e.g.
-  ///      var eurosInCurrentLocale = new NumberFormat.currency(symbol: "€");
+  ///       var eurosInCurrentLocale = NumberFormat.currency(symbol: "€");
   /// will format like "€1.23". Otherwise it will use the currency name.
   /// If this is not explicitly specified in the constructor, then for
   /// currencies we use the default value for the currency if the name is given,
-  ///  otherwise we use the value from the pattern for the locale.
+  /// otherwise we use the value from the pattern for the locale.
   ///
   /// If [decimalDigits] is specified, numbers will format with that many digits
   /// after the decimal place. If it's not, they will use the default for the
   /// currency in [name], and the default currency for [locale] if the currency
   /// name is not specified. e.g.
-  ///       new NumberFormat.currency(name: 'USD', decimalDigits: 7)
+  ///       NumberFormat.currency(name: 'USD', decimalDigits: 7)
   /// will format with 7 decimal digits, because that's what we asked for. But
-  ///       new NumberFormat.currency(locale: 'en_US', name: 'JPY')
+  ///       NumberFormat.currency(locale: 'en_US', name: 'JPY')
   /// will format with zero, because that's the default for JPY, and the
   /// currency's default takes priority over the locale's default.
-  ///       new NumberFormat.currency(locale: 'en_US')
+  ///       NumberFormat.currency(locale: 'en_US')
   /// will format with two, which is the default for that locale.
   ///
   /// The [customPattern] parameter can be used to specify a particular
@@ -282,12 +282,12 @@
   /// after the decimal place. If it's not, they will use the default for the
   /// currency in [name], and the default currency for [locale] if the currency
   /// name is not specified. e.g.
-  ///       new NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7)
+  ///       NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7)
   /// will format with 7 decimal digits, because that's what we asked for. But
-  ///       new NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY')
+  ///       NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY')
   /// will format with zero, because that's the default for JPY, and the
   /// currency's default takes priority over the locale's default.
-  ///       new NumberFormat.simpleCurrency(locale: 'en_US')
+  ///       NumberFormat.simpleCurrency(locale: 'en_US')
   /// will format with two, which is the default for that locale.
   factory NumberFormat.simpleCurrency(
       {String locale, String name, int decimalDigits}) {
diff --git a/lib/src/intl_helpers.dart b/lib/src/intl_helpers.dart
index a807d97..356d00c 100644
--- a/lib/src/intl_helpers.dart
+++ b/lib/src/intl_helpers.dart
@@ -68,7 +68,12 @@
 
   List<String> get keys => _throwException() as List<String>;
 
-  bool containsKey(String key) => _isFallback(key) ? true : _throwException();
+  bool containsKey(String key) {
+    if (!_isFallback(key)) {
+      _throwException();
+    }
+    return true;
+  }
 
   F _throwException() {
     throw LocaleDataException('Locale data has not been initialized'
diff --git a/lib/src/lazy_locale_data.dart b/lib/src/lazy_locale_data.dart
index 0469ef6..0620033 100644
--- a/lib/src/lazy_locale_data.dart
+++ b/lib/src/lazy_locale_data.dart
@@ -41,7 +41,7 @@
   /// [keys] lists the set of remotely available locale names so we know which
   /// things can be fetched without having to check remotely.
   LazyLocaleData(this._reader, this._creationFunction, this.availableLocales) {
-    map = Map();
+    map = {};
     availableLocaleSet = Set.from(availableLocales);
   }
 
@@ -58,7 +58,7 @@
   /// [localeName] then throw an exception with a different message.
   dynamic operator [](String localeName) {
     if (containsKey(localeName)) {
-      var data = map[localeName];
+      dynamic data = map[localeName];
       if (data == null) {
         throw LocaleDataException('Locale $localeName has not been initialized.'
             ' Call initializeDateFormatting($localeName, <data url>) first');
@@ -72,7 +72,7 @@
 
   /// Throw an exception indicating that the locale has no data available,
   /// either locally or remotely.
-  void unsupportedLocale(localeName) {
+  void unsupportedLocale(String localeName) {
     throw LocaleDataException('Locale $localeName has no data available');
   }
 
diff --git a/lib/src/plural_rules.dart b/lib/src/plural_rules.dart
index 1a3beb5..656bf62 100644
--- a/lib/src/plural_rules.dart
+++ b/lib/src/plural_rules.dart
@@ -75,7 +75,7 @@
 
   _v = precision ?? math.min(_decimals(n, precision), defaultDigits);
 
-  int base = math.pow(10, _v);
+  var base = math.pow(10, _v) as int;
   _f = (n * base).floor() % base;
 }
 
@@ -444,7 +444,7 @@
 }
 
 /// Selected Plural rules by locale.
-final Map pluralRules = {
+final pluralRules = {
   'af': _es_rule,
   'am': _hi_rule,
   'ar': _ar_rule,
diff --git a/pubspec.yaml b/pubspec.yaml
index 064d030..d989d61 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: intl
-version: 0.16.2-dev
+version: 0.17.0-dev
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/intl
 description: >-
@@ -11,6 +11,7 @@
   sdk: '>=2.5.0 <3.0.0'
 
 dependencies:
+  clock: ^1.0.1
   path: '>=0.9.0 <2.0.0'
 dev_dependencies:
   fixnum: '>=0.9.0 <0.11.0'
diff --git a/test/compact_number_test_data.dart b/test/compact_number_test_data.dart
old mode 100755
new mode 100644
diff --git a/test/compact_number_test_data_33.dart b/test/compact_number_test_data_33.dart
old mode 100755
new mode 100644
diff --git a/test/date_time_format_test_core.dart b/test/date_time_format_test_core.dart
index 83982c0..3c7a539 100644
--- a/test/date_time_format_test_core.dart
+++ b/test/date_time_format_test_core.dart
@@ -8,6 +8,7 @@
 
 library date_time_format_tests;
 
+import 'package:clock/clock.dart';
 import 'package:intl/intl.dart';
 import 'package:test/test.dart';
 import 'date_time_format_test_data.dart';
@@ -209,6 +210,28 @@
         orderedEquals(['hh', ':', 'mm', ':', 'ss']));
   });
 
+  test('Two-digit years', () {
+    withClock(Clock.fixed(DateTime(2000, 1, 1)), () {
+      var dateFormat = DateFormat('yy');
+      expect(dateFormat.parse('99'), DateTime(1999));
+      expect(dateFormat.parse('00'), DateTime(2000));
+      expect(dateFormat.parse('19'), DateTime(2019));
+      expect(dateFormat.parse('20'), DateTime(2020));
+      expect(dateFormat.parse('21'), DateTime(1921));
+
+      expect(dateFormat.parse('2000'), DateTime(2000));
+
+      dateFormat = DateFormat('MM-dd-yy');
+      expect(dateFormat.parse('12-31-19'), DateTime(2019, 12, 31));
+      expect(dateFormat.parse('1-1-20'), DateTime(2020, 1, 1));
+      expect(dateFormat.parse('1-2-20'), DateTime(1920, 1, 2));
+
+      expect(DateFormat('y').parse('99'), DateTime(99));
+      expect(DateFormat('yyy').parse('99'), DateTime(99));
+      expect(DateFormat('yyyy').parse('99'), DateTime(99));
+    });
+  });
+
   test('Test ALL the supported formats on representative locales', () {
     var aDate = DateTime(2012, 1, 27, 20, 58, 59, 0);
     testLocale('en_US', english, aDate);
diff --git a/test/date_time_strict_test.dart b/test/date_time_strict_test.dart
index daed8c3..730ceeb 100644
--- a/test/date_time_strict_test.dart
+++ b/test/date_time_strict_test.dart
@@ -67,7 +67,7 @@
 
   test('Invalid times 24 hour', () {
     var format = DateFormat.Hms();
-    check(s) => expect(() => format.parseStrict(s), throwsFormatException);
+    void check(s) => expect(() => format.parseStrict(s), throwsFormatException);
     check('-1:15:00');
     expect(format.parseStrict('0:15:00'), DateTime(1970, 1, 1, 0, 15));
     check('24:00:00');
diff --git a/test/locale_test_data.dart b/test/locale_test_data.dart
old mode 100755
new mode 100644
diff --git a/test/more_compact_number_test_data.dart b/test/more_compact_number_test_data.dart
index 0c3bf8b..b443c0d 100644
--- a/test/more_compact_number_test_data.dart
+++ b/test/more_compact_number_test_data.dart
@@ -13,12 +13,12 @@
 
   num number;
   String expected;
-  int maximumIntegerDigits = null;
-  int minimumIntegerDigits = null;
-  int maximumFractionDigits = null;
-  int minimumFractionDigits = null;
-  int minimumExponentDigits = null;
-  int significantDigits = null;
+  int maximumIntegerDigits;
+  int minimumIntegerDigits;
+  int maximumFractionDigits;
+  int minimumFractionDigits;
+  int minimumExponentDigits;
+  int significantDigits;
 
   String toString() => "CompactRoundingTestCase for $number, "
       "maxIntDig: $maximumIntegerDigits, "
diff --git a/test/number_format_compact_test.dart b/test/number_format_compact_test.dart
index c89aa6a..a1e44bb 100644
--- a/test/number_format_compact_test.dart
+++ b/test/number_format_compact_test.dart
@@ -67,6 +67,7 @@
   testCurrency('en_US', 12, r'$12.00', r'$10');
   testCurrency('en_US', 12.3, r'$12.30', r'$10');
   testCurrency('en_US', 123, r'$123', r'$100');
+  testCurrency('en_US', 1000, r'$1K', r'$1K');
   testCurrency('en_US', 1234, r'$1.23K', r'$1K');
   testCurrency('en_US', 12345, r'$12.3K', r'$10K');
   testCurrency('en_US', 123456, r'$123K', r'$100K');
@@ -313,19 +314,30 @@
 }
 
 void validateFancy(more_testdata.CompactRoundingTestCase t) {
-  var shortFormat = new NumberFormat.compact(locale: 'en');
-  if (t.maximumIntegerDigits != null)
+  var shortFormat = NumberFormat.compact(locale: 'en');
+  if (t.maximumIntegerDigits != null) {
     shortFormat.maximumIntegerDigits = t.maximumIntegerDigits;
-  if (t.minimumIntegerDigits != null)
+  }
+
+  if (t.minimumIntegerDigits != null) {
     shortFormat.minimumIntegerDigits = t.minimumIntegerDigits;
-  if (t.maximumFractionDigits != null)
+  }
+
+  if (t.maximumFractionDigits != null) {
     shortFormat.maximumFractionDigits = t.maximumFractionDigits;
-  if (t.minimumFractionDigits != null)
+  }
+
+  if (t.minimumFractionDigits != null) {
     shortFormat.minimumFractionDigits = t.minimumFractionDigits;
-  if (t.minimumExponentDigits != null)
+  }
+
+  if (t.minimumExponentDigits != null) {
     shortFormat.minimumExponentDigits = t.minimumExponentDigits;
-  if (t.significantDigits != null)
+  }
+
+  if (t.significantDigits != null) {
     shortFormat.significantDigits = t.significantDigits;
+  }
 
   test(t.toString(), () {
     expect(shortFormat.format(t.number), t.expected);
diff --git a/test/number_format_compact_web_test.dart b/test/number_format_compact_web_test.dart
index 46d608f..a6c60e1 100644
--- a/test/number_format_compact_web_test.dart
+++ b/test/number_format_compact_web_test.dart
@@ -8,7 +8,7 @@
 /// We use @Tags rather than @TestOn to be able to specify something that can be
 /// ignored when using a build system that can't read dart_test.yaml. This
 /// depends on https://github.com/tc39/proposal-unified-intl-numberformat.
-@Tags(const ['unifiedNumberFormat'])
+@Tags(['unifiedNumberFormat'])
 
 import 'package:intl/intl.dart' as intl;
 import 'package:js/js_util.dart' as js;
@@ -17,7 +17,7 @@
 import 'compact_number_test_data.dart' as testdata35;
 import 'more_compact_number_test_data.dart' as more_testdata;
 
-main() {
+void main() {
   testdata35.compactNumberTestData.forEach(validate);
   more_testdata.cldr35CompactNumTests.forEach(validateMore);
 
@@ -62,14 +62,12 @@
 }
 
 String ecmaFormatNumber(String locale, num number,
-    {String style: null,
-    String currency: null,
-    String notation: null,
-    String compactDisplay: null}) {
+    {String style, String currency, String notation, String compactDisplay}) {
   var options = js.newObject();
   if (notation != null) js.setProperty(options, 'notation', notation);
-  if (compactDisplay != null)
+  if (compactDisplay != null) {
     js.setProperty(options, 'compactDisplay', compactDisplay);
+  }
   if (style != null) js.setProperty(options, 'style', style);
   if (currency != null) js.setProperty(options, 'currency', currency);
   return js.callMethod(number, 'toLocaleString', [locale, options]);
@@ -131,16 +129,26 @@
 void validateMore(more_testdata.CompactRoundingTestCase t) {
   var options = js.newObject();
   js.setProperty(options, 'notation', 'compact');
-  if (t.maximumIntegerDigits != null)
+  if (t.maximumIntegerDigits != null) {
     js.setProperty(options, 'maximumIntegerDigits', t.maximumIntegerDigits);
-  if (t.minimumIntegerDigits != null)
+  }
+
+  if (t.minimumIntegerDigits != null) {
     js.setProperty(options, 'minimumIntegerDigits', t.minimumIntegerDigits);
-  if (t.maximumFractionDigits != null)
+  }
+
+  if (t.maximumFractionDigits != null) {
     js.setProperty(options, 'maximumFractionDigits', t.maximumFractionDigits);
-  if (t.minimumFractionDigits != null)
+  }
+
+  if (t.minimumFractionDigits != null) {
     js.setProperty(options, 'minimumFractionDigits', t.minimumFractionDigits);
-  if (t.minimumExponentDigits != null)
+  }
+
+  if (t.minimumExponentDigits != null) {
     js.setProperty(options, 'minimumExponentDigits', t.minimumExponentDigits);
+  }
+
   if (t.significantDigits != null) {
     js.setProperty(options, 'minimumSignificantDigits', t.significantDigits);
     js.setProperty(options, 'maximumSignificantDigits', t.significantDigits);
diff --git a/test/number_format_test_core.dart b/test/number_format_test_core.dart
index b2961cd..372b555 100644
--- a/test/number_format_test_core.dart
+++ b/test/number_format_test_core.dart
@@ -130,7 +130,7 @@
       '9,876,543,210',
     ];
     for (var i = 0; i < expected.length; i++) {
-      var f = new NumberFormat.decimalPattern();
+      var f = NumberFormat.decimalPattern();
       f.maximumIntegerDigits = i;
       expect(f.format(9876543210), expected[i],
           reason: 'maximumIntegerDigits: $i');
@@ -148,7 +148,7 @@
       '1.000000',
     ];
     for (var i = 0; i < 6; i++) {
-      var f = new NumberFormat.decimalPattern();
+      var f = NumberFormat.decimalPattern();
       f.minimumFractionDigits = i;
       if (i > f.maximumFractionDigits) f.maximumFractionDigits = i;
       expect(f.format(1), expected[i],
@@ -173,7 +173,7 @@
       '9.123456789',
     ];
     for (var i = 0; i < expected.length; i++) {
-      var f = new NumberFormat.decimalPattern();
+      var f = NumberFormat.decimalPattern();
       f.maximumFractionDigits = i;
       expect(f.format(9.123456789), expected[i],
           reason: 'maximumFractionDigits: $i');
@@ -198,7 +198,7 @@
       '3.21E003',
     ];
     for (var i = 0; i < expected.length; i++) {
-      var f = new NumberFormat("#.###E0");
+      var f = NumberFormat("#.###E0");
       f.minimumExponentDigits = i;
       expect(f.format(3210), expected[i], reason: 'minimumExponentDigits: $i');
     }
@@ -222,7 +222,7 @@
       '9,876,543.21012',
     ];
     for (var i = 0; i < expected.length; i++) {
-      var f = new NumberFormat.decimalPattern();
+      var f = NumberFormat.decimalPattern();
       f.significantDigits = i;
       expect(f.format(9876543.21012), expected[i],
           reason: 'significantDigits: $i');