| // Copyright (c) 2012, 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. |
| |
| import 'package:intl/date_symbols.dart'; |
| import 'package:intl/src/date_format_internal.dart'; |
| import 'package:intl/src/intl_helpers.dart' as helpers; |
| |
| 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 |
| |
| // TODO(efortuna): Customized pattern system -- suggested by i18n needs |
| // feedback on appropriateness. |
| /// DateFormat is for formatting and parsing dates in a locale-sensitive |
| /// manner. |
| /// |
| /// It allows the user to choose from a set of standard date time formats as |
| /// well as specify a customized pattern under certain locales. Date elements |
| /// that vary across locales include month name, week name, field order, etc. |
| /// We also allow the user to use any customized pattern to parse or format |
| /// date-time strings under certain locales. Date elements that vary across |
| /// locales include month name, weekname, field, order, etc. |
| /// |
| /// Formatting dates in the default 'en_US' format does not require any |
| /// initialization. e.g. |
| /// |
| /// ```dart |
| /// print(DateFormat.yMMMd().format(DateTime.now())); |
| /// ``` |
| /// |
| /// But for other locales, the formatting data for the locale must be |
| /// obtained. This can currently be done in one of three ways, determined by |
| /// which library you import. In all cases, the 'initializeDateFormatting' |
| /// method must be called and will return a future that is complete once the |
| /// locale data is available. The result of the future isn't important, but the |
| /// data for that locale is available to the date formatting and parsing once it |
| /// completes. |
| /// |
| /// The easiest option is that the data may be available locally, imported in a |
| /// library that contains data for all the locales. |
| /// |
| /// ```dart |
| /// import 'package:intl/date_symbol_data_local.dart'; |
| /// initializeDateFormatting('fr_FR', null).then((_) => runMyCode()); |
| /// ``` |
| /// |
| /// If we are running outside of a browser, we may want to read the data |
| /// from files in the file system. |
| /// |
| /// ```dart |
| /// import 'package:intl/date_symbol_data_file.dart'; |
| /// initializeDateFormatting('de_DE', null).then((_) => runMyCode()); |
| /// ``` |
| /// |
| /// If we are running in a browser, we may want to read the data from the |
| /// server using the XmlHttpRequest mechanism. |
| /// |
| /// ```dart |
| /// import 'package:intl/date_symbol_data_http_request.dart'; |
| /// initializeDateFormatting('pt_BR', null).then((_) => runMyCode()); |
| /// ``` |
| /// |
| /// Once we have the locale data, we need to specify the particular format. |
| /// This library uses the ICU/JDK date/time pattern specification both for |
| /// complete format specifications and also the abbreviated 'skeleton' form |
| /// which can also adapt to different locales and is preferred where available. |
| /// |
| /// Skeletons: These can be specified either as the ICU constant name or as the |
| /// skeleton to which it resolves. The supported set of skeletons is as follows. |
| /// For each skeleton there is a named constructor that can be used to create |
| /// it. It's also possible to pass the skeleton as a string, but the |
| /// constructor is preferred. |
| /// |
| /// ICU Name Skeleton |
| /// -------- -------- |
| /// DAY d |
| /// ABBR_WEEKDAY E |
| /// WEEKDAY EEEE |
| /// ABBR_STANDALONE_MONTH LLL |
| /// STANDALONE_MONTH LLLL |
| /// NUM_MONTH M |
| /// NUM_MONTH_DAY Md |
| /// NUM_MONTH_WEEKDAY_DAY MEd |
| /// ABBR_MONTH MMM |
| /// ABBR_MONTH_DAY MMMd |
| /// ABBR_MONTH_WEEKDAY_DAY MMMEd |
| /// MONTH MMMM |
| /// MONTH_DAY MMMMd |
| /// MONTH_WEEKDAY_DAY MMMMEEEEd |
| /// ABBR_QUARTER QQQ |
| /// QUARTER QQQQ |
| /// YEAR y |
| /// YEAR_NUM_MONTH yM |
| /// YEAR_NUM_MONTH_DAY yMd |
| /// YEAR_NUM_MONTH_WEEKDAY_DAY yMEd |
| /// YEAR_ABBR_MONTH yMMM |
| /// YEAR_ABBR_MONTH_DAY yMMMd |
| /// YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd |
| /// YEAR_MONTH yMMMM |
| /// YEAR_MONTH_DAY yMMMMd |
| /// YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd |
| /// YEAR_ABBR_QUARTER yQQQ |
| /// YEAR_QUARTER yQQQQ |
| /// HOUR24 H |
| /// HOUR24_MINUTE Hm |
| /// HOUR24_MINUTE_SECOND Hms |
| /// HOUR j |
| /// HOUR_MINUTE jm |
| /// HOUR_MINUTE_SECOND jms |
| /// 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 |
| /// ---------------- ------- |
| /// 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, |
| /// but these will not adapt to different locales. For example, in an explicit |
| /// pattern the letters 'H' and 'h' are available for 24 hour and 12 hour time |
| /// formats respectively. But there isn't a way in an explicit pattern to get |
| /// the behaviour of the 'j' skeleton, which prints 24 hour or 12 hour time |
| /// according to the conventions of the locale, and also includes am/pm markers |
| /// where appropriate. So it is preferable to use the skeletons. |
| /// |
| /// The following characters are available in explicit patterns: |
| /// |
| /// Symbol Meaning Presentation Example |
| /// ------ ------- ------------ ------- |
| /// G era designator (Text) AD |
| /// y year (Number) 1996 |
| /// M month in year (Text & Number) July & 07 |
| /// L standalone month (Text & Number) July & 07 |
| /// d day in month (Number) 10 |
| /// c standalone day (Number) 10 |
| /// h hour in am/pm (1~12) (Number) 12 |
| /// H hour in day (0~23) (Number) 0 |
| /// m minute in hour (Number) 30 |
| /// s second in minute (Number) 55 |
| /// S fractional second (Number) 978 |
| /// E day of week (Text) Tuesday |
| /// D day in year (Number) 189 |
| /// a am/pm marker (Text) PM |
| /// k hour in day (1~24) (Number) 24 |
| /// K hour in am/pm (0~11) (Number) 0 |
| /// 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**: |
| /// * 5 pattern letters--use narrow form for standalone. Otherwise not used. |
| /// * 4 or more pattern letters--use full form, |
| /// * 3 pattern letters--use short or abbreviated form if one exists |
| /// * less than 3--use numeric form if one exists |
| /// |
| /// **Number**: the minimum number of digits. Shorter numbers are zero-padded to |
| /// this amount (e.g. if 'm' produces '6', 'mm' produces '06'). Year is handled |
| /// specially; that is, if the count of 'y' is 2, the Year will be truncated to |
| /// 2 digits. (e.g., if 'yyyy' produces '1997', 'yy' produces '97'.) Unlike |
| /// other fields, fractional seconds are padded on the right with zero. |
| /// |
| /// **(Text & Number)**: 3 or over, use text, otherwise use number. |
| /// |
| /// Any characters not in the pattern will be treated as quoted text. For |
| /// instance, characters like ':', '.', ' ', '#' and '@' will appear in the |
| /// resulting text even though they are not enclosed in single quotes. In our |
| /// current pattern usage, not all letters have meanings. But those unused |
| /// letters are strongly discouraged to be used as quoted text without quotes, |
| /// because we may use other letters as pattern characters in the future. |
| /// |
| /// Examples Using the US Locale: |
| /// |
| /// Format Pattern Result |
| /// -------------- ------- |
| /// 'EEE, MMM d, ''yy' Wed, Jul 10, '96 |
| /// 'h:mm a' 12:08 PM |
| /// '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 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. 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 |
| /// pattern 'MM/dd/yyyy', '01/11/12' parses to Jan 11, 12 A.D. |
| |
| class DateFormat { |
| /// Creates a new DateFormat, using the format specified by [newPattern]. |
| /// |
| /// For forms that match one of our predefined skeletons, we look up the |
| /// corresponding pattern in [locale] (or in the default locale if none is |
| /// specified) and use the resulting full format string. This is the preferred |
| /// usage, but if [newPattern] does not match one of the skeletons, then it is |
| /// used as a format directly, but will not be adapted to suit the locale. |
| /// |
| /// For example, in an en_US locale, specifying the skeleton |
| /// |
| /// ```dart |
| /// DateFormat.yMEd(); |
| /// ``` |
| /// |
| /// or the explicit |
| /// |
| /// ```dart |
| /// DateFormat('EEE, M/d/y'); |
| /// ``` |
| /// |
| /// would produce the same result, a date of the form 'Wed, 6/27/2012'. |
| /// |
| /// The first version would produce a different format string if used in |
| /// another locale, but the second format would always be the same. |
| /// |
| /// If [locale] does not exist in our set of supported locales then an |
| /// [ArgumentError] is thrown. |
| DateFormat([String? newPattern, String? locale]) |
| : _locale = helpers.verifiedLocale(locale, localeExists, null)! { |
| // TODO(alanknight): It should be possible to specify multiple skeletons eg |
| // date, time, timezone all separately. Adding many or named parameters to |
| // the constructor seems awkward, especially with the possibility of |
| // confusion with the locale. A 'fluent' interface with cascading on an |
| // instance might work better? A list of patterns is also possible. |
| addPattern(newPattern); |
| } |
| |
| /// Allows specifying a different way of creating a DateTime instance for |
| /// testing. |
| /// |
| /// There can be rare and erratic errors in DateTime creation in both |
| /// JavaScript and the Dart VM, and this allows us to test ways of |
| /// compensating for them. |
| _DateTimeConstructor dateTimeConstructor = (int year, int month, int day, |
| int hour24, int minute, int second, int fractionalSecond, bool utc) { |
| if (utc) { |
| return DateTime.utc( |
| year, month, day, hour24, minute, second, fractionalSecond); |
| } else { |
| return DateTime( |
| year, month, day, hour24, minute, second, fractionalSecond); |
| } |
| }; |
| |
| /// Return a string representing [date] formatted according to our locale |
| /// and internal format. |
| String format(DateTime date) { |
| // TODO(efortuna): read optional TimeZone argument (or similar)? |
| var result = StringBuffer(); |
| for (var field in _formatFields) { |
| result.write(field.format(date)); |
| } |
| return result.toString(); |
| } |
| |
| /// NOT YET IMPLEMENTED. |
| /// |
| /// Returns a date string indicating how long ago (3 hours, 2 minutes) |
| /// something has happened or how long in the future something will happen |
| /// given a [reference] DateTime relative to the current time. |
| String formatDuration(DateTime reference) => ''; |
| |
| /// NOT YET IMPLEMENTED. |
| /// |
| /// Formats a string indicating how long ago (negative [duration]) or how far |
| /// in the future (positive [duration]) some time is with respect to a |
| /// reference [date]. |
| String formatDurationFrom(Duration duration, DateTime date) => ''; |
| |
| /// Given user input, attempt to parse the [inputString] into the anticipated |
| /// format, treating it as being in the local timezone. |
| /// |
| /// If [inputString] does not match our format, throws a [FormatException]. |
| /// This will accept dates whose values are not strictly valid, or strings |
| /// with additional characters (including whitespace) after a valid date. For |
| /// stricter parsing, use [parseStrict]. |
| DateTime parse(String inputString, [bool utc = false]) => |
| _parse(inputString, utc: utc, strict: false); |
| |
| /// Given user input, attempt to parse the [inputString] 'loosely' into the |
| /// anticipated format, accepting some variations from the strict format. |
| /// |
| /// If [inputString] is accepted by [parseStrict], just return the result. If |
| /// not, attempt to parse it, but accepting either upper or lower case, |
| /// allowing delimiters to be missing and replaced or supplemented with |
| /// whitespace, and allowing arbitrary amounts of whitespace wherever |
| /// whitespace is permitted. Note that this does not allow trailing |
| /// characters, the way [parse] does. It also does not allow alternative |
| /// names for months or weekdays other than those the format knows about. The |
| /// restrictions are quite arbitrary and it's not known how well they'll work |
| /// for locales that aren't English-like. |
| /// |
| /// If [inputString] does not parse, this throws a [FormatException]. |
| /// |
| /// For example, this will accept |
| /// |
| /// 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. |
| /// DateFormat.yMMMd('en_US').parseLoose('Sept 3, 2014'); |
| DateTime parseLoose(String inputString, [bool utc = false]) { |
| try { |
| return _parse(inputString, utc: utc, strict: true); |
| } on FormatException { |
| return _parseLoose(inputString.toLowerCase(), utc); |
| } |
| } |
| |
| DateTime _parseLoose(String inputString, bool utc) { |
| var dateFields = DateBuilder(locale, dateTimeConstructor); |
| if (utc) dateFields.utc = true; |
| var stream = IntlStream(inputString); |
| for (var field in _formatFields) { |
| field.parseLoose(stream, dateFields); |
| } |
| if (!stream.atEnd()) { |
| throw FormatException( |
| 'Characters remaining after date parsing in $inputString'); |
| } |
| dateFields.verify(inputString); |
| return dateFields.asDate(); |
| } |
| |
| /// Given user input, attempt to parse the [inputString] into the anticipated |
| /// format, treating it as being in the local timezone. |
| /// |
| /// 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 reject strings with |
| /// additional characters (including whitespace) after a valid date. For |
| /// looser parsing, use [parse]. |
| DateTime parseStrict(String inputString, [bool utc = false]) => |
| _parse(inputString, utc: utc, strict: true); |
| |
| 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(locale, dateTimeConstructor); |
| if (utc) dateFields.utc = true; |
| dateFields.dateOnly = dateOnly; |
| var stream = IntlStream(inputString); |
| for (var field in _formatFields) { |
| field.parse(stream, dateFields); |
| } |
| if (strict && !stream.atEnd()) { |
| throw FormatException( |
| 'Characters remaining after date parsing in $inputString'); |
| } |
| if (strict) dateFields.verify(inputString); |
| return dateFields.asDate(); |
| } |
| |
| /// Does our format only only date fields, and no time fields. |
| /// |
| /// For example, 'yyyy-MM-dd' would be true, but 'dd hh:mm' would be false. |
| bool get dateOnly => _dateOnly ??= _checkDateOnly; |
| bool? _dateOnly; |
| bool get _checkDateOnly => _formatFields.every((each) => each.forDate); |
| |
| /// Given user input, attempt to parse the [inputString] into the anticipated |
| /// format, treating it as being in UTC. |
| /// |
| /// The canonical Dart style name |
| /// is [parseUtc], but [parseUTC] is retained |
| /// for backward-compatibility. |
| DateTime parseUTC(String inputString) => parse(inputString, true); |
| |
| /// Given user input, attempt to parse the [inputString] into the anticipated |
| /// format, treating it as being in UTC. |
| /// |
| /// The canonical Dart style name |
| /// is [parseUtc], but [parseUTC] is retained |
| /// for backward-compatibility. |
| DateTime parseUtc(String inputString) => parse(inputString, true); |
| |
| /// Return the locale code in which we operate, e.g. 'en_US' or 'pt'. |
| String get locale => _locale; |
| |
| /// Returns a list of all locales for which we have date formatting |
| /// information. |
| static List<String> allLocalesWithSymbols() => |
| List<String>.from(dateTimeSymbols.keys); |
| |
| /// The named constructors for this class are all conveniences for creating |
| /// instances using one of the known 'skeleton' formats, and having code |
| /// completion support for discovering those formats. |
| /// So, |
| /// |
| /// ```dart |
| /// DateFormat.yMd('en_US') |
| /// ``` |
| /// |
| /// is equivalent to |
| /// |
| /// ```dart |
| /// DateFormat('yMd', 'en_US') |
| /// ``` |
| /// |
| /// To create a compound format you can use these constructors in combination |
| /// with the 'add_*' methods below. e.g. |
| /// |
| /// ```dart |
| /// DateFormat.yMd().add_Hms(); |
| /// ``` |
| /// |
| /// If the optional [locale] is omitted, the format will be created using the |
| /// default locale in [Intl.systemLocale]. |
| DateFormat.d([locale]) : this('d', locale); |
| DateFormat.E([locale]) : this('E', locale); |
| DateFormat.EEEE([locale]) : this('EEEE', locale); |
| DateFormat.LLL([locale]) : this('LLL', locale); |
| DateFormat.LLLL([locale]) : this('LLLL', locale); |
| DateFormat.M([locale]) : this('M', locale); |
| DateFormat.Md([locale]) : this('Md', locale); |
| DateFormat.MEd([locale]) : this('MEd', locale); |
| DateFormat.MMM([locale]) : this('MMM', locale); |
| DateFormat.MMMd([locale]) : this('MMMd', locale); |
| DateFormat.MMMEd([locale]) : this('MMMEd', locale); |
| DateFormat.MMMM([locale]) : this('MMMM', locale); |
| DateFormat.MMMMd([locale]) : this('MMMMd', locale); |
| DateFormat.MMMMEEEEd([locale]) : this('MMMMEEEEd', locale); |
| DateFormat.QQQ([locale]) : this('QQQ', locale); |
| DateFormat.QQQQ([locale]) : this('QQQQ', locale); |
| DateFormat.y([locale]) : this('y', locale); |
| DateFormat.yM([locale]) : this('yM', locale); |
| DateFormat.yMd([locale]) : this('yMd', locale); |
| DateFormat.yMEd([locale]) : this('yMEd', locale); |
| DateFormat.yMMM([locale]) : this('yMMM', locale); |
| DateFormat.yMMMd([locale]) : this('yMMMd', locale); |
| DateFormat.yMMMEd([locale]) : this('yMMMEd', locale); |
| DateFormat.yMMMM([locale]) : this('yMMMM', locale); |
| DateFormat.yMMMMd([locale]) : this('yMMMMd', locale); |
| DateFormat.yMMMMEEEEd([locale]) : this('yMMMMEEEEd', locale); |
| DateFormat.yQQQ([locale]) : this('yQQQ', locale); |
| DateFormat.yQQQQ([locale]) : this('yQQQQ', locale); |
| DateFormat.H([locale]) : this('H', locale); |
| DateFormat.Hm([locale]) : this('Hm', locale); |
| DateFormat.Hms([locale]) : this('Hms', locale); |
| 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); |
| DateFormat.s([locale]) : this('s', locale); |
| |
| /// The 'add_*' methods append a particular skeleton to the format, or set |
| /// it as the only format if none was previously set. These are primarily |
| /// useful for creating compound formats. For example |
| /// |
| /// ```dart |
| /// DateFormat.yMd().add_Hms(); |
| /// ``` |
| /// |
| /// would create a date format that prints both the date and the time. |
| DateFormat add_d() => addPattern('d'); |
| DateFormat add_E() => addPattern('E'); |
| DateFormat add_EEEE() => addPattern('EEEE'); |
| DateFormat add_LLL() => addPattern('LLL'); |
| DateFormat add_LLLL() => addPattern('LLLL'); |
| DateFormat add_M() => addPattern('M'); |
| DateFormat add_Md() => addPattern('Md'); |
| DateFormat add_MEd() => addPattern('MEd'); |
| DateFormat add_MMM() => addPattern('MMM'); |
| DateFormat add_MMMd() => addPattern('MMMd'); |
| DateFormat add_MMMEd() => addPattern('MMMEd'); |
| DateFormat add_MMMM() => addPattern('MMMM'); |
| DateFormat add_MMMMd() => addPattern('MMMMd'); |
| DateFormat add_MMMMEEEEd() => addPattern('MMMMEEEEd'); |
| DateFormat add_QQQ() => addPattern('QQQ'); |
| DateFormat add_QQQQ() => addPattern('QQQQ'); |
| DateFormat add_y() => addPattern('y'); |
| DateFormat add_yM() => addPattern('yM'); |
| DateFormat add_yMd() => addPattern('yMd'); |
| DateFormat add_yMEd() => addPattern('yMEd'); |
| DateFormat add_yMMM() => addPattern('yMMM'); |
| DateFormat add_yMMMd() => addPattern('yMMMd'); |
| DateFormat add_yMMMEd() => addPattern('yMMMEd'); |
| DateFormat add_yMMMM() => addPattern('yMMMM'); |
| DateFormat add_yMMMMd() => addPattern('yMMMMd'); |
| DateFormat add_yMMMMEEEEd() => addPattern('yMMMMEEEEd'); |
| DateFormat add_yQQQ() => addPattern('yQQQ'); |
| DateFormat add_yQQQQ() => addPattern('yQQQQ'); |
| DateFormat add_H() => addPattern('H'); |
| DateFormat add_Hm() => addPattern('Hm'); |
| DateFormat add_Hms() => addPattern('Hms'); |
| 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'); |
| DateFormat add_s() => addPattern('s'); |
| |
| /// For each of the skeleton formats we also allow the use of the |
| /// corresponding ICU constant names. |
| static const String ABBR_MONTH = 'MMM'; |
| static const String DAY = 'd'; |
| static const String ABBR_WEEKDAY = 'E'; |
| static const String WEEKDAY = 'EEEE'; |
| static const String ABBR_STANDALONE_MONTH = 'LLL'; |
| static const String STANDALONE_MONTH = 'LLLL'; |
| static const String NUM_MONTH = 'M'; |
| static const String NUM_MONTH_DAY = 'Md'; |
| static const String NUM_MONTH_WEEKDAY_DAY = 'MEd'; |
| static const String ABBR_MONTH_DAY = 'MMMd'; |
| static const String ABBR_MONTH_WEEKDAY_DAY = 'MMMEd'; |
| static const String MONTH = 'MMMM'; |
| static const String MONTH_DAY = 'MMMMd'; |
| static const String MONTH_WEEKDAY_DAY = 'MMMMEEEEd'; |
| static const String ABBR_QUARTER = 'QQQ'; |
| static const String QUARTER = 'QQQQ'; |
| static const String YEAR = 'y'; |
| static const String YEAR_NUM_MONTH = 'yM'; |
| static const String YEAR_NUM_MONTH_DAY = 'yMd'; |
| static const String YEAR_NUM_MONTH_WEEKDAY_DAY = 'yMEd'; |
| static const String YEAR_ABBR_MONTH = 'yMMM'; |
| static const String YEAR_ABBR_MONTH_DAY = 'yMMMd'; |
| static const String YEAR_ABBR_MONTH_WEEKDAY_DAY = 'yMMMEd'; |
| static const String YEAR_MONTH = 'yMMMM'; |
| static const String YEAR_MONTH_DAY = 'yMMMMd'; |
| static const String YEAR_MONTH_WEEKDAY_DAY = 'yMMMMEEEEd'; |
| static const String YEAR_ABBR_QUARTER = 'yQQQ'; |
| static const String YEAR_QUARTER = 'yQQQQ'; |
| static const String HOUR24 = 'H'; |
| static const String HOUR24_MINUTE = 'Hm'; |
| static const String HOUR24_MINUTE_SECOND = 'Hms'; |
| 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'; |
| static const String SECOND = 's'; |
| |
| /// The locale in which we operate, e.g. 'en_US', or 'pt'. |
| String _locale; |
| |
| /// The full template string. This may have been specified directly, or |
| /// it may have been derived from a skeleton and the locale information |
| /// on how to interpret that skeleton. |
| String? _pattern; |
| |
| /// We parse the format string into individual [_DateFormatField] objects |
| /// that are used to do the actual formatting and parsing. Do not use |
| /// this variable directly, use the getter [_formatFields]. |
| List<_DateFormatField>? _formatFieldsPrivate; |
| |
| /// Getter for [_formatFieldsPrivate] that lazily initializes it. |
| List<_DateFormatField> get _formatFields { |
| if (_formatFieldsPrivate == null) { |
| if (_pattern == null) _useDefaultPattern(); |
| _formatFieldsPrivate = parsePattern(_pattern!); |
| } |
| return _formatFieldsPrivate!; |
| } |
| |
| /// We are being asked to do formatting without having set any pattern. |
| /// Use a default. |
| void _useDefaultPattern() { |
| add_yMMMMd(); |
| add_jms(); |
| } |
| |
| /// A series of regular expressions used to parse a format string into its |
| /// component fields. |
| static final List<RegExp> _matchers = [ |
| // Quoted String - anything between single quotes, with escaping |
| // of single quotes by doubling them. |
| // e.g. in the pattern 'hh 'o''clock'' will match 'o''clock' |
| RegExp('^\'(?:[^\']|\'\')*\''), |
| // Fields - any sequence of 1 or more of the same field characters. |
| // e.g. in 'hh:mm:ss' will match hh, mm, and ss. But in 'hms' would |
| // match each letter individually. |
| RegExp('^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|D+|m+|s+|v+|z+|Z+)'), |
| // Everything else - A sequence that is not quotes or field characters. |
| // e.g. in 'hh:mm:ss' will match the colons. |
| RegExp('^[^\'GyMkSEahKHcLQdDmsvzZ]+') |
| ]; |
| |
| /// Set our pattern, appending it to any existing patterns. Also adds a single |
| /// space to separate the two. |
| void _appendPattern(String inputPattern, [String separator = ' ']) { |
| _pattern = |
| _pattern == null ? inputPattern : '$_pattern$separator$inputPattern'; |
| } |
| |
| /// Add [inputPattern] to this instance as a pattern. |
| /// |
| /// If there was a previous pattern, then this appends to it, separating the |
| /// two by [separator]. [inputPattern] is first looked up in our list of |
| /// known skeletons. If it's found there, then use the corresponding pattern |
| /// for this locale. If it's not, then treat [inputPattern] as an explicit |
| /// pattern. |
| DateFormat addPattern(String? inputPattern, [String separator = ' ']) { |
| // TODO(alanknight): This is an expensive operation. Caching recently used |
| // formats, or possibly introducing an entire 'locale' object that would |
| // cache patterns for that locale could be a good optimization. |
| // If we have already parsed the format fields, reset them. |
| _formatFieldsPrivate = null; |
| if (inputPattern == null) return this; |
| if (!_availableSkeletons.containsKey(inputPattern)) { |
| _appendPattern(inputPattern, separator); |
| } else { |
| _appendPattern(_availableSkeletons[inputPattern], separator); |
| } |
| return this; |
| } |
| |
| /// Return the pattern that we use to format dates. |
| String? get pattern => _pattern; |
| |
| /// Return the skeletons for our current locale. |
| Map<dynamic, dynamic> get _availableSkeletons => dateTimePatterns[locale]; |
| |
| /// Return the [DateSymbols] information for the locale. |
| /// |
| /// This can be useful to find lists like the names of weekdays or months in a |
| /// locale, but the structure of this data may change, and it's generally |
| /// better to go through the [format] and [parse] APIs. |
| /// |
| /// If the locale isn't present, or is uninitialized, throws. |
| DateSymbols get dateSymbols { |
| if (_locale != lastDateSymbolLocale) { |
| lastDateSymbolLocale = _locale; |
| cachedDateSymbols = dateTimeSymbols[_locale]; |
| } |
| return cachedDateSymbols!; |
| } |
| |
| static final Map<String, bool> _useNativeDigitsByDefault = {}; |
| |
| /// Should a new DateFormat for [locale] have useNativeDigits true. |
| /// |
| /// For example, for locale 'ar' when this setting is true, DateFormat will |
| /// format using Eastern Arabic digits, e.g. '\u0660, \u0661, \u0662'. If it |
| /// is false, a new DateFormat will format using ASCII digits. |
| static bool shouldUseNativeDigitsByDefaultFor(String locale) { |
| return _useNativeDigitsByDefault[locale] ?? true; |
| } |
| |
| /// Indicate if a new DateFormat for [locale] should have useNativeDigits |
| /// true. |
| /// |
| /// For example, for locale 'ar' when this setting is true, DateFormat will |
| /// format using Eastern Arabic digits, e.g. '\u0660, \u0661, \u0662'. If it |
| /// is false, a new DateFormat will format using ASCII digits. |
| /// |
| /// If not indicated, the default value is true, so native digits will be |
| /// used. |
| static void useNativeDigitsByDefaultFor(String locale, bool value) { |
| _useNativeDigitsByDefault[locale] = value; |
| } |
| |
| bool? _useNativeDigits; |
| |
| /// Should we use native digits for printing DateTime, or ASCII. |
| /// |
| /// The default for this can be set using [useNativeDigitsByDefaultFor]. |
| bool get useNativeDigits => _useNativeDigits == null |
| ? _useNativeDigits = shouldUseNativeDigitsByDefaultFor(locale) |
| : _useNativeDigits!; |
| |
| /// Should we use native digits for printing DateTime, or ASCII. |
| set useNativeDigits(bool native) { |
| _useNativeDigits = native; |
| // Invalidate any cached information that would depend on this setting. |
| _digitMatcher = null; |
| _localeZeroCodeUnit = null; |
| _localeZero = null; |
| } |
| |
| /// Caches digit matchers that we have already calculated for particular |
| /// digits. |
| /// |
| /// Keys are the zero digits, and the values are matchers for digits in that |
| /// locale. |
| static final Map<String, RegExp> _digitMatchers = {}; |
| |
| RegExp? _digitMatcher; |
| |
| /// A regular expression which matches against digits for this locale. |
| RegExp get digitMatcher { |
| if (_digitMatcher != null) return _digitMatcher!; |
| _digitMatcher = _digitMatchers.putIfAbsent(localeZero, _initDigitMatcher); |
| return _digitMatcher!; |
| } |
| |
| int? _localeZeroCodeUnit; |
| |
| /// For performance, keep the code unit of the zero digit available. |
| int get localeZeroCodeUnit => _localeZeroCodeUnit == null |
| ? _localeZeroCodeUnit = localeZero.codeUnitAt(0) |
| : _localeZeroCodeUnit!; |
| |
| String? _localeZero; |
| |
| /// For performance, keep the zero digit available. |
| String get localeZero => _localeZero == null |
| ? _localeZero = useNativeDigits ? dateSymbols.ZERODIGIT ?? '0' : '0' |
| : _localeZero!; |
| |
| // Does this use non-ASCII digits, e.g. Eastern Arabic. |
| bool get usesNativeDigits => |
| useNativeDigits && _localeZeroCodeUnit != constants.asciiZeroCodeUnit; |
| |
| /// Does this use ASCII digits |
| bool get usesAsciiDigits => !usesNativeDigits; |
| |
| /// Given a numeric string in ASCII digits, return a copy updated for our |
| /// locale digits. |
| String _localizeDigits(String numberString) { |
| if (usesAsciiDigits) return numberString; |
| var newDigits = List<int>.filled(numberString.length, 0); |
| var oldDigits = numberString.codeUnits; |
| for (var i = 0; i < numberString.length; i++) { |
| newDigits[i] = |
| oldDigits[i] + localeZeroCodeUnit - constants.asciiZeroCodeUnit; |
| } |
| return String.fromCharCodes(newDigits); |
| } |
| |
| /// 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 regexp.asciiDigitMatcher; |
| var localeDigits = Iterable.generate(10, (i) => i) |
| .map((i) => localeZeroCodeUnit + i) |
| .toList(); |
| var localeDigitsString = String.fromCharCodes(localeDigits); |
| return RegExp('^[$localeDigitsString]+'); |
| } |
| |
| /// Return true if the locale exists, or if it is null. The null case |
| /// is interpreted to mean that we use the default locale. |
| static bool localeExists(localeName) { |
| if (localeName == null) return false; |
| return dateTimeSymbols.containsKey(localeName); |
| } |
| |
| static List<_DateFormatField Function(String, DateFormat)> |
| get _fieldConstructors => [ |
| (pattern, parent) => _DateFormatQuotedField(pattern, parent), |
| (pattern, parent) => _DateFormatPatternField(pattern, parent), |
| (pattern, parent) => _DateFormatLiteralField(pattern, parent) |
| ]; |
| |
| /// Parse the template pattern and return a list of field objects. |
| List<_DateFormatField> parsePattern(String pattern) { |
| return _parsePatternHelper(pattern).reversed.toList(); |
| } |
| |
| /// Recursive helper for parsing the template pattern. |
| List<_DateFormatField> _parsePatternHelper(String pattern) { |
| if (pattern.isEmpty) return []; |
| |
| var matched = _match(pattern); |
| if (matched == null) return []; |
| |
| var parsed = |
| _parsePatternHelper(pattern.substring(matched.fullPattern().length)); |
| parsed.add(matched); |
| return parsed; |
| } |
| |
| /// Find elements in a string that are patterns for specific fields. |
| _DateFormatField? _match(String pattern) { |
| for (var i = 0; i < _matchers.length; i++) { |
| var regex = _matchers[i]; |
| var match = regex.firstMatch(pattern); |
| if (match != null) { |
| return _fieldConstructors[i](match.group(0)!, this); |
| } |
| } |
| return null; |
| } |
| } |
| |
| /// 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); |