Split up parts in `package:intl` so they can be migrated incrementally to null safety.
PiperOrigin-RevId: 327788719
diff --git a/lib/intl.dart b/lib/intl.dart
index f4ed71d..cb83005 100644
--- a/lib/intl.dart
+++ b/lib/intl.dart
@@ -20,27 +20,17 @@
library intl;
import 'dart:async';
-import 'dart:collection';
-import 'dart:convert';
-import 'dart:math';
-import 'package:clock/clock.dart';
-
-import 'date_symbols.dart';
-import 'number_symbols.dart';
-import 'number_symbols_data.dart';
-import 'src/date_format_internal.dart';
import 'src/global_state.dart' as global_state;
+import 'src/intl/date_format.dart' show DateFormat;
import 'src/intl_helpers.dart' as helpers;
import 'src/plural_rules.dart' as plural_rules;
-part 'src/intl/bidi_formatter.dart';
-part 'src/intl/bidi_utils.dart';
-part 'src/intl/compact_number_format.dart';
-part 'src/intl/date_format.dart';
-part 'src/intl/date_format_field.dart';
-part 'src/intl/date_format_helpers.dart';
-part 'src/intl/number_format.dart';
+export 'src/intl/bidi.dart' show Bidi;
+export 'src/intl/bidi_formatter.dart' show BidiFormatter;
+export 'src/intl/date_format.dart' show DateFormat;
+export 'src/intl/number_format.dart' show NumberFormat, MicroMoney;
+export 'src/intl/text_direction.dart' show TextDirection;
/// The Intl class provides a common entry point for internationalization
/// related tasks. An Intl instance can be created for a particular locale
diff --git a/lib/src/intl/bidi_utils.dart b/lib/src/intl/bidi.dart
similarity index 90%
rename from lib/src/intl/bidi_utils.dart
rename to lib/src/intl/bidi.dart
index 60a268b..ba028c4 100644
--- a/lib/src/intl/bidi_utils.dart
+++ b/lib/src/intl/bidi.dart
@@ -3,50 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
-
-// Suppress naming issues as changing them would be breaking.
-// ignore_for_file: constant_identifier_names
-
/// Bidi stands for Bi-directional text. According to
/// http://en.wikipedia.org/wiki/Bi-directional_text: Bi-directional text is
/// text containing text in both text directionalities, both right-to-left (RTL)
/// and left-to-right (LTR). It generally involves text containing different
/// types of alphabets, but may also refer to boustrophedon, which is changing
/// text directionality in each row.
-///
-/// This file provides some utility classes for determining directionality of
-/// text, switching CSS layout from LTR to RTL, and other normalizing utilities
-/// needed when switching between RTL and LTR formatting.
-///
-/// It defines the TextDirection class which is used to represent directionality
-/// of text,
-/// In most cases, it is preferable to use bidi_formatter.dart, which provides
-/// bidi functionality in the given directional context, instead of using
-/// bidi_utils.dart directly.
-class TextDirection {
- static const LTR = TextDirection._('LTR', 'ltr');
- static const RTL = TextDirection._('RTL', 'rtl');
- // If the directionality of the text cannot be determined and we are not using
- // the context direction (or if the context direction is unknown), then the
- // text falls back on the more common ltr direction.
- static const UNKNOWN = TextDirection._('UNKNOWN', 'ltr');
- /// Textual representation of the directionality constant. One of
- /// 'LTR', 'RTL', or 'UNKNOWN'.
- final String value;
+import '../global_state.dart' as global_state;
+import 'text_direction.dart';
- /// Textual representation of the directionality when used in span tag.
- final String spanText;
-
- const TextDirection._(this.value, this.spanText);
-
- /// Returns true if [otherDirection] is known to be different from this
- /// direction.
- bool isDirectionChange(TextDirection otherDirection) =>
- otherDirection != TextDirection.UNKNOWN && this != otherDirection;
-}
-
+// Suppress naming issues as changing them would be breaking.
+// ignore_for_file: constant_identifier_names
// ignore: avoid_classes_with_only_static_members
/// This provides utility methods for working with bidirectional text. All
/// of the methods are static, and are organized into a class primarily to
@@ -170,7 +138,7 @@
/// Sindhi (sd) and Uyghur (ug). The presence of other subtags of the
/// language code, e.g. regions like EG (Egypt), is ignored.
static bool isRtlLanguage([String languageString]) {
- var language = languageString ?? Intl.getCurrentLocale();
+ var language = languageString ?? global_state.getCurrentLocale();
if (_lastLocaleCheckedForRtl != language) {
_lastLocaleCheckedForRtl = language;
_lastRtlCheck = _rtlLocaleRegex.hasMatch(language);
diff --git a/lib/src/intl/bidi_formatter.dart b/lib/src/intl/bidi_formatter.dart
index 7cccfdd..f84cd14 100644
--- a/lib/src/intl/bidi_formatter.dart
+++ b/lib/src/intl/bidi_formatter.dart
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+import 'dart:convert';
+
+import 'bidi.dart';
+import 'text_direction.dart';
// Suppress naming issues as changing them would be breaking.
// ignore_for_file: non_constant_identifier_names
diff --git a/lib/src/intl/compact_number_format.dart b/lib/src/intl/compact_number_format.dart
index 5ac6442..272ef22 100644
--- a/lib/src/intl/compact_number_format.dart
+++ b/lib/src/intl/compact_number_format.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+part of 'number_format.dart';
// Suppress naming issues as changes would be breaking.
// ignore_for_file: constant_identifier_names
diff --git a/lib/src/intl/constants.dart b/lib/src/intl/constants.dart
new file mode 100644
index 0000000..51ac779
--- /dev/null
+++ b/lib/src/intl/constants.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+final int asciiZeroCodeUnit = '0'.codeUnitAt(0);
diff --git a/lib/src/intl/date_format_helpers.dart b/lib/src/intl/date_builder.dart
similarity index 70%
rename from lib/src/intl/date_format_helpers.dart
rename to lib/src/intl/date_builder.dart
index 3097dc2..7281fb3 100644
--- a/lib/src/intl/date_format_helpers.dart
+++ b/lib/src/intl/date_builder.dart
@@ -3,36 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+import 'package:clock/clock.dart';
-/// Given a month and day number, return the day of the year, all one-based.
-///
-/// For example,
-/// * January 2nd (1, 2) -> 2.
-/// * February 5th (2, 5) -> 36.
-/// * March 1st of a non-leap year (3, 1) -> 60.
-int _dayOfYear(int month, int day, bool leapYear) {
- if (month == 1) return day;
- if (month == 2) return day + 31;
- return ordinalDayFromMarchFirst(month, day) + 59 + (leapYear ? 1 : 0);
-}
-
-/// Return true if this is a leap year. Rely on [DateTime] to do the
-/// underlying calculation, even though it doesn't expose the test to us.
-bool _isLeapYear(DateTime date) {
- var feb29 = DateTime(date.year, 2, 29);
- return feb29.month == 2;
-}
-
-/// Return the day of the year counting March 1st as 1, after which the
-/// number of days per month is constant, so it's easier to calculate.
-/// Formula from http://en.wikipedia.org/wiki/Ordinal_date
-int ordinalDayFromMarchFirst(int month, int day) =>
- ((30.6 * month) - 91.4).floor() + day;
+import 'date_computation.dart' as date_computation;
/// A class for holding onto the data for a date so that it can be built
/// up incrementally.
-class _DateBuilder {
+class DateBuilder {
// Default the date values to the EPOCH so that there's a valid date
// in case the format doesn't set them.
int year = 1970,
@@ -76,14 +53,14 @@
// We do set it, the analyzer just can't tell.
// ignore: prefer_final_fields
- var _dateOnly = false;
+ var dateOnly = false;
/// The function we will call to create a DateTime from its component pieces.
///
/// This is normally only modified in tests that want to introduce errors.
final _DateTimeConstructor _dateTimeConstructor;
- _DateBuilder(this._locale, this._dateTimeConstructor);
+ DateBuilder(this._locale, this._dateTimeConstructor);
// Functions that exist just to be closurized so we can pass them to a general
// method.
@@ -151,13 +128,14 @@
// _correctForErrors, but we may not be able to compensate for a midnight
// that doesn't exist. So tolerate an hour value of zero or one in these
// cases.
- var minimumDate = _dateOnly && date.hour == 1 ? 0 : date.hour;
+ var minimumDate = dateOnly && date.hour == 1 ? 0 : date.hour;
_verify(hour24, minimumDate, date.hour, 'hour', s, date);
if (dayOfYear > 0) {
// We have an ordinal date, compute the corresponding date for the result
// and compare to that.
- var leapYear = _isLeapYear(date);
- var correspondingDay = _dayOfYear(date.month, date.day, leapYear);
+ var leapYear = date_computation.isLeapYear(date);
+ var correspondingDay =
+ date_computation.dayOfYear(date.month, date.day, leapYear);
_verify(
dayOfYear, correspondingDay, correspondingDay, 'dayOfYear', s, date);
} else {
@@ -282,8 +260,9 @@
return result;
}
- var leapYear = _isLeapYear(result);
- var resultDayOfYear = _dayOfYear(result.month, result.day, leapYear);
+ var leapYear = date_computation.isLeapYear(result);
+ var resultDayOfYear =
+ date_computation.dayOfYear(result.month, result.day, leapYear);
// Check for the UTC failure. Are we expecting to produce a local time, but
// the result is UTC. However, the local time might happen to be the same as
@@ -300,7 +279,7 @@
return asDate(retries: retries - 1);
}
- if (_dateOnly && result.hour != 0) {
+ if (dateOnly && result.hour != 0) {
// This could be a flake, try again.
var tryAgain = asDate(retries: retries - 1);
if (tryAgain != result) {
@@ -309,8 +288,9 @@
}
// Trying again didn't work, try to force the offset.
- var expectedDayOfYear =
- dayOfYear == 0 ? _dayOfYear(month, day, leapYear) : dayOfYear;
+ var expectedDayOfYear = dayOfYear == 0
+ ? date_computation.dayOfYear(month, day, leapYear)
+ : dayOfYear;
// If we're _dateOnly, then hours should be zero, but might have been
// offset to e.g. 11:00pm the previous day. Add that time back in. This
@@ -335,7 +315,7 @@
// hour takes us back to 11:00pm the day before. In that case the 1:00am
// answer on the correct date is preferable.
var adjustedDayOfYear =
- _dayOfYear(adjusted.month, adjusted.day, leapYear);
+ date_computation.dayOfYear(adjusted.month, adjusted.day, leapYear);
if (adjustedDayOfYear != expectedDayOfYear) {
return result;
}
@@ -346,96 +326,6 @@
}
}
-/// A simple and not particularly general stream class to make parsing
-/// dates from strings simpler. It is general enough to operate on either
-/// lists or strings.
-// TODO(alanknight): With the improvements to the collection libraries
-// since this was written we might be able to get rid of it entirely
-// in favor of e.g. aString.split('') giving us an iterable of one-character
-// strings, or else make the implementation trivial. And consider renaming,
-// as _Stream is now just confusing with the system Streams.
-class _Stream {
- dynamic contents;
- int index = 0;
-
- _Stream(this.contents);
-
- bool atEnd() => index >= contents.length;
-
- dynamic next() => contents[index++];
-
- /// Return the next [howMany] items, or as many as there are remaining.
- /// Advance the stream by that many positions.
- dynamic read([int howMany = 1]) {
- var result = peek(howMany);
- index += howMany;
- return result;
- }
-
- /// Does the input start with the given string, if we start from the
- /// current position.
- bool startsWith(String pattern) {
- if (contents is String) return contents.startsWith(pattern, index);
- return pattern == peek(pattern.length);
- }
-
- /// Return the next [howMany] items, or as many as there are remaining.
- /// Does not modify the stream position.
- dynamic peek([int howMany = 1]) {
- dynamic result;
- if (contents is String) {
- String stringContents = contents;
- result = stringContents.substring(
- index, min(index + howMany, stringContents.length));
- } else {
- // Assume List
- result = contents.sublist(index, index + howMany);
- }
- return result;
- }
-
- /// Return the remaining contents of the stream
- dynamic rest() => peek(contents.length - index);
-
- /// Find the index of the first element for which [f] returns true.
- /// Advances the stream to that position.
- int findIndex(bool Function(dynamic) f) {
- while (!atEnd()) {
- if (f(next())) return index - 1;
- }
- return null;
- }
-
- /// Find the indexes of all the elements for which [f] returns true.
- /// Leaves the stream positioned at the end.
- List<dynamic> findIndexes(bool Function(dynamic) f) {
- var results = [];
- while (!atEnd()) {
- if (f(next())) results.add(index - 1);
- }
- return results;
- }
-
- /// Assuming that the contents are characters, read as many digits as we
- /// can see and then return the corresponding integer, advancing the receiver.
- ///
- /// For non-ascii digits, the optional arguments are a regular expression
- /// [digitMatcher] to find the next integer, and the codeUnit of the local
- /// zero [zeroDigit].
- int nextInteger({RegExp digitMatcher, int zeroDigit}) {
- var string =
- (digitMatcher ?? DateFormat._asciiDigitMatcher).stringMatch(rest());
- if (string == null || string.isEmpty) return null;
- read(string.length);
- if (zeroDigit != null && zeroDigit != DateFormat._asciiZeroCodeUnit) {
- // Trying to optimize this, as it might get called a lot.
- var oldDigits = string.codeUnits;
- var newDigits = List<int>(string.length);
- for (var i = 0; i < string.length; i++) {
- newDigits[i] = oldDigits[i] - zeroDigit + DateFormat._asciiZeroCodeUnit;
- }
- string = String.fromCharCodes(newDigits);
- }
- return int.parse(string);
- }
-}
+/// Defines a function type for creating DateTime instances.
+typedef _DateTimeConstructor = DateTime Function(int year, int month, int day,
+ int hour24, int minute, int second, int fractionalSecond, bool utc);
diff --git a/lib/src/intl/date_computation.dart b/lib/src/intl/date_computation.dart
new file mode 100644
index 0000000..ed5566f
--- /dev/null
+++ b/lib/src/intl/date_computation.dart
@@ -0,0 +1,24 @@
+/// Given a month and day number, return the day of the year, all one-based.
+///
+/// For example,
+/// * January 2nd (1, 2) -> 2.
+/// * February 5th (2, 5) -> 36.
+/// * March 1st of a non-leap year (3, 1) -> 60.
+int dayOfYear(int month, int day, bool leapYear) {
+ if (month == 1) return day;
+ if (month == 2) return day + 31;
+ return ordinalDayFromMarchFirst(month, day) + 59 + (leapYear ? 1 : 0);
+}
+
+/// Return true if this is a leap year. Rely on [DateTime] to do the
+/// underlying calculation, even though it doesn't expose the test to us.
+bool isLeapYear(DateTime date) {
+ var feb29 = DateTime(date.year, 2, 29);
+ return feb29.month == 2;
+}
+
+/// Return the day of the year counting March 1st as 1, after which the
+/// number of days per month is constant, so it's easier to calculate.
+/// Formula from http://en.wikipedia.org/wiki/Ordinal_date
+int ordinalDayFromMarchFirst(int month, int day) =>
+ ((30.6 * month) - 91.4).floor() + day;
diff --git a/lib/src/intl/date_format.dart b/lib/src/intl/date_format.dart
index 70e23a0..03d868c 100644
--- a/lib/src/intl/date_format.dart
+++ b/lib/src/intl/date_format.dart
@@ -3,7 +3,17 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+import 'package:intl/date_symbols.dart';
+import 'package:intl/intl.dart';
+import 'package:intl/src/date_format_internal.dart';
+
+import 'constants.dart' as constants;
+import 'date_builder.dart';
+import 'date_computation.dart' as date_computation;
+import 'intl_stream.dart';
+import 'regexp.dart' as regexp;
+
+part 'date_format_field.dart';
// Suppress naming issues as changes would breaking.
// ignore_for_file: non_constant_identifier_names, constant_identifier_names
@@ -353,9 +363,9 @@
DateTime _parseLoose(String inputString, bool utc) {
var dateFields =
- _DateBuilder(locale ?? Intl.defaultLocale, dateTimeConstructor);
+ DateBuilder(locale ?? Intl.defaultLocale, dateTimeConstructor);
if (utc) dateFields.utc = true;
- var stream = _Stream(inputString);
+ var stream = IntlStream(inputString);
for (var field in _formatFields) {
field.parseLoose(stream, dateFields);
}
@@ -382,10 +392,10 @@
// 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(locale ?? Intl.defaultLocale, dateTimeConstructor);
+ DateBuilder(locale ?? Intl.defaultLocale, dateTimeConstructor);
if (utc) dateFields.utc = true;
- dateFields._dateOnly = dateOnly;
- var stream = _Stream(inputString);
+ dateFields.dateOnly = dateOnly;
+ var stream = IntlStream(inputString);
for (var field in _formatFields) {
field.parse(stream, dateFields);
}
@@ -777,16 +787,12 @@
return _digitMatcher;
}
- /// Hard-code the most common matcher, which has special RegExp syntax.
- static final RegExp _asciiDigitMatcher = RegExp(r'^\d+');
-
int _localeZeroCodeUnit;
/// For performance, keep the code unit of the zero digit available.
int get localeZeroCodeUnit => _localeZeroCodeUnit == null
? _localeZeroCodeUnit = localeZero.codeUnitAt(0)
: _localeZeroCodeUnit;
- static final int _asciiZeroCodeUnit = '0'.codeUnitAt(0);
String _localeZero;
@@ -797,7 +803,7 @@
// Does this use non-ASCII digits, e.g. Eastern Arabic.
bool get usesNativeDigits =>
- useNativeDigits && _localeZeroCodeUnit != _asciiZeroCodeUnit;
+ useNativeDigits && _localeZeroCodeUnit != constants.asciiZeroCodeUnit;
/// Does this use ASCII digits
bool get usesAsciiDigits => !usesNativeDigits;
@@ -809,7 +815,8 @@
var newDigits = List<int>(numberString.length);
var oldDigits = numberString.codeUnits;
for (var i = 0; i < numberString.length; i++) {
- newDigits[i] = oldDigits[i] + localeZeroCodeUnit - _asciiZeroCodeUnit;
+ newDigits[i] =
+ oldDigits[i] + localeZeroCodeUnit - constants.asciiZeroCodeUnit;
}
return String.fromCharCodes(newDigits);
}
@@ -817,7 +824,7 @@
/// A regular expression that matches for digits in a particular
/// locale, defined by the digit for zero in that locale.
RegExp _initDigitMatcher() {
- if (usesAsciiDigits) return _asciiDigitMatcher;
+ if (usesAsciiDigits) return regexp.asciiDigitMatcher;
var localeDigits = Iterable.generate(10, (i) => i)
.map((i) => localeZeroCodeUnit + i)
.toList();
diff --git a/lib/src/intl/date_format_field.dart b/lib/src/intl/date_format_field.dart
index ad7f087..0bb0159 100644
--- a/lib/src/intl/date_format_field.dart
+++ b/lib/src/intl/date_format_field.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+part of 'date_format.dart';
/// This is a private class internal to DateFormat which is used for formatting
/// particular fields in a template. e.g. if the format is hh:mm:ss then the
@@ -43,15 +43,15 @@
}
/// Abstract method for subclasses to implementing parsing for their format.
- void parse(_Stream input, _DateBuilder dateFields);
+ void parse(IntlStream input, DateBuilder dateFields);
/// Abstract method for subclasses to implementing 'loose' parsing for
/// their format, accepting input case-insensitively, and allowing some
/// delimiters to be skipped.
- void parseLoose(_Stream input, _DateBuilder dateFields);
+ void parseLoose(IntlStream input, DateBuilder dateFields);
/// Parse a literal field. We just look for the exact input.
- void parseLiteral(_Stream input) {
+ void parseLiteral(IntlStream input) {
var found = input.read(width);
if (found != pattern) {
throwFormatException(input);
@@ -66,7 +66,7 @@
/// field's format specification is also trimmed before matching is
/// attempted. Therefore, leading and trailing whitespace is optional, and
/// arbitrary additional whitespace may be added before/after the literal.
- void parseLiteralLoose(_Stream input) {
+ void parseLiteralLoose(IntlStream input) {
_trimWhitespace(input);
var found = input.peek(_trimmedPattern.length);
@@ -77,14 +77,14 @@
_trimWhitespace(input);
}
- void _trimWhitespace(_Stream input) {
+ void _trimWhitespace(IntlStream input) {
while (!input.atEnd() && input.peek().trim().isEmpty) {
input.read();
}
}
/// Throw a format exception with an error message indicating the position.
- void throwFormatException(_Stream stream) {
+ void throwFormatException(IntlStream stream) {
throw FormatException('Trying to read $this from ${stream.contents} '
'at position ${stream.index}');
}
@@ -96,11 +96,11 @@
class _DateFormatLiteralField extends _DateFormatField {
_DateFormatLiteralField(pattern, parent) : super(pattern, parent);
- void parse(_Stream input, _DateBuilder dateFields) {
+ void parse(IntlStream input, DateBuilder dateFields) {
parseLiteral(input);
}
- void parseLoose(_Stream input, _DateBuilder dateFields) =>
+ void parseLoose(IntlStream input, DateBuilder dateFields) =>
parseLiteralLoose(input);
}
@@ -116,11 +116,11 @@
_fullPattern = pattern;
}
- void parse(_Stream input, _DateBuilder dateFields) {
+ void parse(IntlStream input, DateBuilder dateFields) {
parseLiteral(input);
}
- void parseLoose(_Stream input, _DateBuilder dateFields) =>
+ void parseLoose(IntlStream input, DateBuilder dateFields) =>
parseLiteralLoose(input);
static final _twoEscapedQuotes = RegExp(r"''");
@@ -145,7 +145,7 @@
/// Parse from a list of possibilities, but case-insensitively.
/// Assumes that input is lower case.
- int parseEnumeratedString(_Stream input, List<String> possibilities) {
+ int parseEnumeratedString(IntlStream input, List<String> possibilities) {
var lowercasePossibilities =
possibilities.map((x) => x.toLowerCase()).toList();
try {
@@ -216,7 +216,7 @@
/// Parse a day of the week name, case-insensitively.
/// Assumes that input is lower case. Doesn't do anything
- void parseDayOfWeek(_Stream input) {
+ void parseDayOfWeek(IntlStream input) {
// This is IGNORED, but we still have to skip over it the correct amount.
if (width <= 2) {
handleNumericField(input, (x) => x);
@@ -247,14 +247,14 @@
/// Parse the date according to our specification and put the result
/// into the correct place in dateFields.
- void parse(_Stream input, _DateBuilder dateFields) {
+ void parse(IntlStream input, DateBuilder dateFields) {
parseField(input, dateFields);
}
/// Parse the date according to our specification and put the result
/// into the correct place in dateFields. Allow looser parsing, accepting
/// case-insensitive input and skipped delimiters.
- void parseLoose(_Stream input, _DateBuilder dateFields) {
+ void parseLoose(IntlStream input, DateBuilder dateFields) {
_LoosePatternField(pattern, parent).parse(input, dateFields);
}
@@ -271,7 +271,7 @@
/// Parse a field representing part of a date pattern. Note that we do not
/// return a value, but rather build up the result in [builder].
- void parseField(_Stream input, _DateBuilder builder) {
+ void parseField(IntlStream input, DateBuilder builder) {
try {
switch (pattern[0]) {
case 'a':
@@ -415,7 +415,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(int) setter,
+ void handleNumericField(IntlStream input, void Function(int) setter,
[int offset = 0]) {
var result = input.nextInteger(
digitMatcher: parent.digitMatcher,
@@ -433,8 +433,8 @@
/// Then after all parsing is done we construct a date from the
/// arguments. This method handles reading any of string fields from an
/// enumerated set.
- int parseEnumeratedString(_Stream input, List<String> possibilities) {
- var results = _Stream(possibilities)
+ int parseEnumeratedString(IntlStream input, List<String> possibilities) {
+ var results = IntlStream(possibilities)
.findIndexes((each) => input.peek(each.length) == each);
if (results.isEmpty) throwFormatException(input);
results.sort(
@@ -444,7 +444,7 @@
return longestResult;
}
- void parseYear(_Stream input, _DateBuilder builder) {
+ void parseYear(IntlStream input, DateBuilder builder) {
handleNumericField(input, builder.setYear);
builder.setHasAmbiguousCentury(width == 2);
}
@@ -462,7 +462,7 @@
}
}
- void parseMonth(_Stream input, _DateBuilder dateFields) {
+ void parseMonth(IntlStream input, DateBuilder dateFields) {
List<String> possibilities;
switch (width) {
case 5:
@@ -516,7 +516,7 @@
return padTo(width, hours);
}
- void parse1To12Hours(_Stream input, _DateBuilder dateFields) {
+ void parse1To12Hours(IntlStream input, DateBuilder dateFields) {
handleNumericField(input, dateFields.setHour);
if (dateFields.hour == 12) dateFields.hour = 0;
}
@@ -542,7 +542,7 @@
}
}
- void parseStandaloneDay(_Stream input) {
+ void parseStandaloneDay(IntlStream input) {
// This is ignored, but we still have to skip over it the correct amount.
List<String> possibilities;
switch (width) {
@@ -608,8 +608,10 @@
return padTo(width, date.day);
}
- String formatDayOfYear(DateTime date) =>
- padTo(width, _dayOfYear(date.month, date.day, _isLeapYear(date)));
+ String formatDayOfYear(DateTime date) => padTo(
+ width,
+ date_computation.dayOfYear(
+ date.month, date.day, date_computation.isLeapYear(date)));
String formatDayOfWeek(DateTime date) {
// Note that Dart's weekday returns 1 for Monday and 7 for Sunday.
@@ -618,13 +620,13 @@
: symbols.SHORTWEEKDAYS)[(date.weekday) % 7];
}
- void parseDayOfWeek(_Stream input) {
+ void parseDayOfWeek(IntlStream input) {
// This is IGNORED, but we still have to skip over it the correct amount.
var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS;
parseEnumeratedString(input, possibilities);
}
- void parseEra(_Stream input) {
+ void parseEra(IntlStream input) {
var possibilities = width >= 4 ? symbols.ERANAMES : symbols.ERAS;
parseEnumeratedString(input, possibilities);
}
diff --git a/lib/src/intl/intl_stream.dart b/lib/src/intl/intl_stream.dart
new file mode 100644
index 0000000..3149a53
--- /dev/null
+++ b/lib/src/intl/intl_stream.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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.
+// @dart=2.9
+
+import 'dart:math';
+
+import 'constants.dart' as constants;
+import 'regexp.dart' as regexp;
+
+/// A simple and not particularly general stream class to make parsing
+/// dates from strings simpler. It is general enough to operate on either
+/// lists or strings.
+// TODO(alanknight): With the improvements to the collection libraries
+// since this was written we might be able to get rid of it entirely
+// in favor of e.g. aString.split('') giving us an iterable of one-character
+// strings, or else make the implementation trivial.
+class IntlStream {
+ dynamic contents;
+ int index = 0;
+
+ IntlStream(this.contents);
+
+ bool atEnd() => index >= contents.length;
+
+ dynamic next() => contents[index++];
+
+ /// Return the next [howMany] items, or as many as there are remaining.
+ /// Advance the stream by that many positions.
+ dynamic read([int howMany = 1]) {
+ var result = peek(howMany);
+ index += howMany;
+ return result;
+ }
+
+ /// Does the input start with the given string, if we start from the
+ /// current position.
+ bool startsWith(String pattern) {
+ if (contents is String) return contents.startsWith(pattern, index);
+ return pattern == peek(pattern.length);
+ }
+
+ /// Return the next [howMany] items, or as many as there are remaining.
+ /// Does not modify the stream position.
+ dynamic peek([int howMany = 1]) {
+ dynamic result;
+ if (contents is String) {
+ String stringContents = contents;
+ result = stringContents.substring(
+ index, min(index + howMany, stringContents.length));
+ } else {
+ // Assume List
+ result = contents.sublist(index, index + howMany);
+ }
+ return result;
+ }
+
+ /// Return the remaining contents of the stream
+ dynamic rest() => peek(contents.length - index);
+
+ /// Find the index of the first element for which [f] returns true.
+ /// Advances the stream to that position.
+ int findIndex(bool Function(dynamic) f) {
+ while (!atEnd()) {
+ if (f(next())) return index - 1;
+ }
+ return null;
+ }
+
+ /// Find the indexes of all the elements for which [f] returns true.
+ /// Leaves the stream positioned at the end.
+ List<dynamic> findIndexes(bool Function(dynamic) f) {
+ var results = [];
+ while (!atEnd()) {
+ if (f(next())) results.add(index - 1);
+ }
+ return results;
+ }
+
+ /// Assuming that the contents are characters, read as many digits as we
+ /// can see and then return the corresponding integer, advancing the receiver.
+ ///
+ /// For non-ascii digits, the optional arguments are a regular expression
+ /// [digitMatcher] to find the next integer, and the codeUnit of the local
+ /// zero [zeroDigit].
+ int nextInteger({RegExp digitMatcher, int zeroDigit}) {
+ var string = (digitMatcher ?? regexp.asciiDigitMatcher).stringMatch(rest());
+ if (string == null || string.isEmpty) return null;
+ read(string.length);
+ if (zeroDigit != null && zeroDigit != constants.asciiZeroCodeUnit) {
+ // Trying to optimize this, as it might get called a lot.
+ var oldDigits = string.codeUnits;
+ var newDigits = List<int>(string.length);
+ for (var i = 0; i < string.length; i++) {
+ newDigits[i] = oldDigits[i] - zeroDigit + constants.asciiZeroCodeUnit;
+ }
+ string = String.fromCharCodes(newDigits);
+ }
+ return int.parse(string);
+ }
+}
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index 61b4ce5..487e2e3 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -3,7 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
-part of intl;
+import 'dart:collection';
+import 'dart:math';
+
+import 'package:intl/intl.dart';
+import 'package:intl/number_symbols.dart';
+import 'package:intl/number_symbols_data.dart';
+
+import 'intl_stream.dart';
+
+part 'compact_number_format.dart';
// ignore_for_file: constant_identifier_names
@@ -985,7 +994,7 @@
final String text;
/// What we use to iterate over the input text.
- final _Stream input;
+ final IntlStream input;
/// The result of parsing [text] according to [format]. Automatically
/// populated in the constructor.
@@ -1030,7 +1039,7 @@
int get _localeZero => format._localeZero;
/// Create a new [_NumberParser] on which we can call parse().
- _NumberParser(this.format, this.text) : input = _Stream(text) {
+ _NumberParser(this.format, this.text) : input = IntlStream(text) {
scale = format._internalMultiplier;
value = parse();
}
@@ -1183,7 +1192,7 @@
/// Parse the number portion of the input, i.e. not any prefixes or suffixes,
/// and assuming NaN and Infinity are already handled.
- num parseNumber(_Stream input) {
+ num parseNumber(IntlStream input) {
if (gotNegative) {
_normalized.write('-');
}
diff --git a/lib/src/intl/regexp.dart b/lib/src/intl/regexp.dart
new file mode 100644
index 0000000..e6d8e8b
--- /dev/null
+++ b/lib/src/intl/regexp.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+/// Hard-code the most common matcher, which has special RegExp syntax.
+final RegExp asciiDigitMatcher = RegExp(r'^\d+');
diff --git a/lib/src/intl/text_direction.dart b/lib/src/intl/text_direction.dart
new file mode 100644
index 0000000..7bcb7e4
--- /dev/null
+++ b/lib/src/intl/text_direction.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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.
+// @dart=2.9
+
+/// Represents directionality of text.
+///
+/// In most cases, it is preferable to use bidi_formatter.dart, which provides
+/// bidi functionality in the given directional context, instead of using
+/// bidi_utils.dart directly.
+class TextDirection {
+ static const LTR = TextDirection._('LTR', 'ltr');
+ static const RTL = TextDirection._('RTL', 'rtl');
+ // If the directionality of the text cannot be determined and we are not using
+ // the context direction (or if the context direction is unknown), then the
+ // text falls back on the more common ltr direction.
+ static const UNKNOWN = TextDirection._('UNKNOWN', 'ltr');
+
+ /// Textual representation of the directionality constant. One of
+ /// 'LTR', 'RTL', or 'UNKNOWN'.
+ final String value;
+
+ /// Textual representation of the directionality when used in span tag.
+ final String spanText;
+
+ const TextDirection._(this.value, this.spanText);
+
+ /// Returns true if [otherDirection] is known to be different from this
+ /// direction.
+ bool isDirectionChange(TextDirection otherDirection) =>
+ otherDirection != TextDirection.UNKNOWN && this != otherDirection;
+}