| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of intl; | |
| 6 | |
| 7 // TODO(efortuna): Customized pattern system -- suggested by i18n needs | |
| 8 // feedback on appropriateness. | |
| 9 /** | |
| 10 * DateFormat is for formatting and parsing dates in a locale-sensitive | |
| 11 * manner. | |
| 12 * | |
| 13 * It allows the user to choose from a set of standard date time formats as well | |
| 14 * as specify a customized pattern under certain locales. Date elements that | |
| 15 * vary across locales include month name, week name, field order, etc. | |
| 16 * We also allow the user to use any customized pattern to parse or format | |
| 17 * date-time strings under certain locales. Date elements that vary across | |
| 18 * locales include month name, weekname, field, order, etc. | |
| 19 * | |
| 20 * Formatting dates in the default "en_US" format does not require any | |
| 21 * initialization. e.g. | |
| 22 * print(new DateFormat.yMMMd().format(new Date.now())); | |
| 23 * | |
| 24 * But for other locales, the formatting data for the locale must be | |
| 25 * obtained. This can currently be done | |
| 26 * in one of three ways, determined by which library you import. In all cases, | |
| 27 * the "initializeDateFormatting" method must be called and will return a future | |
| 28 * that is complete once the locale data is available. The result of the future | |
| 29 * isn't important, but the data for that locale is available to the date | |
| 30 * formatting and parsing once it completes. | |
| 31 * | |
| 32 * The easiest option is that the data may be available locally, imported in a | |
| 33 * library that contains data for all the locales. | |
| 34 * import 'package:intl/date_symbol_data_local.dart'; | |
| 35 * initializeDateFormatting("fr_FR", null).then((_) => runMyCode()); | |
| 36 * | |
| 37 * If we are running outside of a browser, we may want to read the data | |
| 38 * from files in the file system. | |
| 39 * import 'package:intl/date_symbol_data_file.dart'; | |
| 40 * initializeDateFormatting("de_DE", null).then((_) => runMyCode()); | |
| 41 * | |
| 42 * If we are running in a browser, we may want to read the data from the | |
| 43 * server using the XmlHttpRequest mechanism. | |
| 44 * import 'package:intl/date_symbol_data_http_request.dart'; | |
| 45 * initializeDateFormatting("pt_BR", null).then((_) => runMyCode()); | |
| 46 * | |
| 47 * The code in example/basic/basic_example.dart shows a full example of | |
| 48 * using this mechanism. | |
| 49 * | |
| 50 * Once we have the locale data, we need to specify the particular format. | |
| 51 * This library uses the ICU/JDK date/time pattern specification both for | |
| 52 * complete format specifications and also the abbreviated "skeleton" form | |
| 53 * which can also adapt to different locales and is preferred where available. | |
| 54 * | |
| 55 * Skeletons: These can be specified either as the ICU constant name or as the | |
| 56 * skeleton to which it resolves. The supported set of skeletons is as follows. | |
| 57 * For each skeleton there is a named constructor that can be used to create it. | |
| 58 * It's also possible to pass the skeleton as a string, but the constructor | |
| 59 * is preferred. | |
| 60 * | |
| 61 * ICU Name Skeleton | |
| 62 * -------- -------- | |
| 63 * DAY d | |
| 64 * ABBR_WEEKDAY E | |
| 65 * WEEKDAY EEEE | |
| 66 * ABBR_STANDALONE_MONTH LLL | |
| 67 * STANDALONE_MONTH LLLL | |
| 68 * NUM_MONTH M | |
| 69 * NUM_MONTH_DAY Md | |
| 70 * NUM_MONTH_WEEKDAY_DAY MEd | |
| 71 * ABBR_MONTH MMM | |
| 72 * ABBR_MONTH_DAY MMMd | |
| 73 * ABBR_MONTH_WEEKDAY_DAY MMMEd | |
| 74 * MONTH MMMM | |
| 75 * MONTH_DAY MMMMd | |
| 76 * MONTH_WEEKDAY_DAY MMMMEEEEd | |
| 77 * ABBR_QUARTER QQQ | |
| 78 * QUARTER QQQQ | |
| 79 * YEAR y | |
| 80 * YEAR_NUM_MONTH yM | |
| 81 * YEAR_NUM_MONTH_DAY yMd | |
| 82 * YEAR_NUM_MONTH_WEEKDAY_DAY yMEd | |
| 83 * YEAR_ABBR_MONTH yMMM | |
| 84 * YEAR_ABBR_MONTH_DAY yMMMd | |
| 85 * YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd | |
| 86 * YEAR_MONTH yMMMM | |
| 87 * YEAR_MONTH_DAY yMMMMd | |
| 88 * YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd | |
| 89 * YEAR_ABBR_QUARTER yQQQ | |
| 90 * YEAR_QUARTER yQQQQ | |
| 91 * HOUR24 H | |
| 92 * HOUR24_MINUTE Hm | |
| 93 * HOUR24_MINUTE_SECOND Hms | |
| 94 * HOUR j | |
| 95 * HOUR_MINUTE jm | |
| 96 * HOUR_MINUTE_SECOND jms | |
| 97 * HOUR_MINUTE_GENERIC_TZ jmv | |
| 98 * HOUR_MINUTE_TZ jmz | |
| 99 * HOUR_GENERIC_TZ jv | |
| 100 * HOUR_TZ jz | |
| 101 * MINUTE m | |
| 102 * MINUTE_SECOND ms | |
| 103 * SECOND s | |
| 104 * | |
| 105 * Examples Using the US Locale: | |
| 106 * | |
| 107 * Pattern Result | |
| 108 * ---------------- ------- | |
| 109 * new DateFormat.yMd() -> 7/10/1996 | |
| 110 * new DateFormat("yMd") -> 7/10/1996 | |
| 111 * new DateFormat.yMMMMd("en_US") -> July 10, 1996 | |
| 112 * new DateFormat.jm() -> 5:08 PM | |
| 113 * new DateFormat.yMd().add_jm() -> 7/10/1996 5:08 PM | |
| 114 * new DateFormat.Hm() -> 17:08 // force 24 hour time | |
| 115 * | |
| 116 * Explicit Pattern Syntax: Formats can also be specified with a pattern string. | |
| 117 * This can be used for formats that don't have a skeleton available, but these | |
| 118 * will not adapt to different locales. For example, in an explicit pattern the | |
| 119 * letters "H" and "h" are available for 24 hour and 12 hour time formats | |
| 120 * respectively. But there isn't a way in an explicit pattern to get the | |
| 121 * behaviour of the "j" skeleton, which prints 24 hour or 12 hour time according | |
| 122 * to the conventions of the locale, and also includes am/pm markers where | |
| 123 * appropriate. So it is preferable to use the skeletons. | |
| 124 * | |
| 125 * The following characters are available in explicit patterns: | |
| 126 * | |
| 127 * Symbol Meaning Presentation Example | |
| 128 * ------ ------- ------------ ------- | |
| 129 * G era designator (Text) AD | |
| 130 * y year (Number) 1996 | |
| 131 * M month in year (Text & Number) July & 07 | |
| 132 * L standalone month (Text & Number) July & 07 | |
| 133 * d day in month (Number) 10 | |
| 134 * c standalone day (Number) 10 | |
| 135 * h hour in am/pm (1~12) (Number) 12 | |
| 136 * H hour in day (0~23) (Number) 0 | |
| 137 * m minute in hour (Number) 30 | |
| 138 * s second in minute (Number) 55 | |
| 139 * S fractional second (Number) 978 | |
| 140 * E day of week (Text) Tuesday | |
| 141 * D day in year (Number) 189 | |
| 142 * a am/pm marker (Text) PM | |
| 143 * k hour in day (1~24) (Number) 24 | |
| 144 * K hour in am/pm (0~11) (Number) 0 | |
| 145 * z time zone (Text) Pacific Standard Time | |
| 146 * Z time zone (RFC 822) (Number) -0800 | |
| 147 * v time zone (generic) (Text) Pacific Time | |
| 148 * Q quarter (Text) Q3 | |
| 149 * ' escape for text (Delimiter) 'Date=' | |
| 150 * '' single quote (Literal) 'o''clock' | |
| 151 * | |
| 152 * The count of pattern letters determine the format. | |
| 153 * | |
| 154 * **Text**: | |
| 155 * * 5 pattern letters--use narrow form for standalone. Otherwise does not apply | |
| 156 * * 4 or more pattern letters--use full form, | |
| 157 * * 3 pattern letters--use short or abbreviated form if one exists | |
| 158 * * less than 3--use numeric form if one exists | |
| 159 * | |
| 160 * **Number**: the minimum number of digits. Shorter numbers are zero-padded to | |
| 161 * this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled | |
| 162 * specially; that is, if the count of 'y' is 2, the Year will be truncated to | |
| 163 * 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other | |
| 164 * fields, fractional seconds are padded on the right with zero. | |
| 165 * | |
| 166 * **(Text & Number)**: 3 or over, use text, otherwise use number. | |
| 167 * | |
| 168 * Any characters not in the pattern will be treated as quoted text. For | |
| 169 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the | |
| 170 * resulting text even though they are not enclosed in single quotes. In our | |
| 171 * current pattern usage, not all letters have meanings. But those unused | |
| 172 * letters are strongly discouraged to be used as quoted text without quotes, | |
| 173 * because we may use other letters as pattern characters in the future. | |
| 174 * | |
| 175 * Examples Using the US Locale: | |
| 176 * | |
| 177 * Format Pattern Result | |
| 178 * -------------- ------- | |
| 179 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" 1996.07.10 AD at 15:08:56 Pacific Time | |
| 180 * "EEE, MMM d, ''yy" Wed, July 10, '96 | |
| 181 * "h:mm a" 12:08 PM | |
| 182 * "hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time | |
| 183 * "K:mm a, vvv" 0:00 PM, PT | |
| 184 * "yyyyy.MMMMM.dd GGG hh:mm aaa" 01996.July.10 AD 12:08 PM | |
| 185 * | |
| 186 * When parsing a date string using the abbreviated year pattern ("yy"), | |
| 187 * DateFormat must interpret the abbreviated year relative to some | |
| 188 * century. It does this by adjusting dates to be within 80 years before and 20 | |
| 189 * years after the time the parse function is called. For example, using a | |
| 190 * pattern of "MM/dd/yy" and a DateParse instance created on Jan 1, 1997, | |
| 191 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string | |
| 192 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only | |
| 193 * strings consisting of exactly two digits, as defined by {@link | |
| 194 * java.lang.Character#isDigit(char)}, will be parsed into the default | |
| 195 * century. Any other numeric string, such as a one digit string, a three or | |
| 196 * more digit string will be interpreted as its face value. | |
| 197 * | |
| 198 * If the year pattern does not have exactly two 'y' characters, the year is | |
| 199 * interpreted literally, regardless of the number of digits. So using the | |
| 200 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. | |
| 201 */ | |
| 202 | |
| 203 class DateFormat { | |
| 204 | |
| 205 /** | |
| 206 * Creates a new DateFormat, using the format specified by [newPattern]. For | |
| 207 * forms that match one of our predefined skeletons, we look up the | |
| 208 * corresponding pattern in [locale] (or in the default locale if none is | |
| 209 * specified) and use the resulting full format string. This is the | |
| 210 * preferred usage, but if [newPattern] does not match one of the skeletons, | |
| 211 * then it is used as a format directly, but will not be adapted to suit | |
| 212 * the locale. | |
| 213 * | |
| 214 * For example, in an en_US locale, specifying the skeleton | |
| 215 * new DateFormat.yMEd(); | |
| 216 * or the explicit | |
| 217 * new DateFormat('EEE, M/d/y'); | |
| 218 * would produce the same result, a date of the form | |
| 219 * Wed, 6/27/2012 | |
| 220 * The first version would produce a different format string if used in | |
| 221 * another locale, but the second format would always be the same. | |
| 222 * | |
| 223 * If [locale] does not exist in our set of supported locales then an | |
| 224 * [ArgumentError] is thrown. | |
| 225 */ | |
| 226 DateFormat([String newPattern, String locale]) { | |
| 227 // TODO(alanknight): It should be possible to specify multiple skeletons eg | |
| 228 // date, time, timezone all separately. Adding many or named parameters to | |
| 229 // the constructor seems awkward, especially with the possibility of | |
| 230 // confusion with the locale. A "fluent" interface with cascading on an | |
| 231 // instance might work better? A list of patterns is also possible. | |
| 232 _locale = Intl.verifiedLocale(locale, localeExists); | |
| 233 addPattern(newPattern); | |
| 234 } | |
| 235 | |
| 236 /** | |
| 237 * Return a string representing [date] formatted according to our locale | |
| 238 * and internal format. | |
| 239 */ | |
| 240 String format(DateTime date) { | |
| 241 // TODO(efortuna): read optional TimeZone argument (or similar)? | |
| 242 var result = new StringBuffer(); | |
| 243 _formatFields.forEach((field) => result.write(field.format(date))); | |
| 244 return result.toString(); | |
| 245 } | |
| 246 | |
| 247 /** | |
| 248 * NOT YET IMPLEMENTED. | |
| 249 * | |
| 250 * Returns a date string indicating how long ago (3 hours, 2 minutes) | |
| 251 * something has happened or how long in the future something will happen | |
| 252 * given a [reference] DateTime relative to the current time. | |
| 253 */ | |
| 254 String formatDuration(DateTime reference) => ''; | |
| 255 | |
| 256 /** | |
| 257 * NOT YET IMPLEMENTED. | |
| 258 * | |
| 259 * Formats a string indicating how long ago (negative [duration]) or how far | |
| 260 * in the future (positive [duration]) some time is with respect to a | |
| 261 * reference [date]. | |
| 262 */ | |
| 263 String formatDurationFrom(Duration duration, DateTime date) => ''; | |
| 264 | |
| 265 /** | |
| 266 * Given user input, attempt to parse the [inputString] into the anticipated | |
| 267 * format, treating it as being in the local timezone. If [inputString] does | |
| 268 * not match our format, throws a [FormatException]. This will accept dates | |
| 269 * whose values are not strictly valid, or strings with additional characters | |
| 270 * (including whitespace) after a valid date. For stricter parsing, use | |
| 271 * [parseStrict]. | |
| 272 */ | |
| 273 DateTime parse(String inputString, [utc = false]) => | |
| 274 _parse(inputString, utc: utc, strict: false); | |
| 275 | |
| 276 /** | |
| 277 * Given user input, attempt to parse the [inputString] "loosely" into the | |
| 278 * anticipated format, accepting some variations from the strict format. | |
| 279 * | |
| 280 * If [inputString] | |
| 281 * is accepted by [parseStrict], just return the result. If not, attempt to | |
| 282 * parse it, but accepting either upper or | |
| 283 * lower case, allowing delimiters to be missing and replaced or | |
| 284 * supplemented with whitespace, | |
| 285 * and allowing arbitrary amounts of whitespace wherever whitespace is | |
| 286 * permitted. Note that this does not allow trailing characters, the way | |
| 287 * [parse] does. It also does not allow leading whitespace on delimiters, | |
| 288 * and does not allow alternative names for months or weekdays other than | |
| 289 * those the format knows about. The restrictions are quite arbitrary and | |
| 290 * it's not known how well they'll work for locales that aren't English-like. | |
| 291 * | |
| 292 * If [inputString] does not parse, this throws a | |
| 293 * [FormatException]. | |
| 294 * | |
| 295 * For example, this will accept | |
| 296 * | |
| 297 * new DateFormat.yMMMd("en_US").parseLoose("SEp 3 2014"); | |
| 298 * new DateFormat.yMd("en_US").parseLoose("09 03/2014"); | |
| 299 * | |
| 300 * It will NOT accept | |
| 301 * | |
| 302 * // "Sept" is not a valid month name. | |
| 303 * new DateFormat.yMMMd("en_US").parseLoose("Sept 3, 2014"); | |
| 304 * // Delimiters can't have leading whitespace. | |
| 305 * new DateFormat.yMd("en_US").parseLoose("09 / 03 / 2014"); | |
| 306 */ | |
| 307 DateTime parseLoose(String inputString, [utc = false]) { | |
| 308 try { | |
| 309 return _parse(inputString, utc: utc, strict: true); | |
| 310 } on FormatException { | |
| 311 return _parseLoose(inputString.toLowerCase(), utc); | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 _parseLoose(String inputString, bool utc) { | |
| 316 var dateFields = new _DateBuilder(); | |
| 317 if (utc) dateFields.utc = true; | |
| 318 var stream = new _Stream(inputString); | |
| 319 _formatFields.forEach((f) => f.parseLoose(stream, dateFields)); | |
| 320 if (!stream.atEnd()) { | |
| 321 throw new FormatException( | |
| 322 "Characters remaining after date parsing in $inputString"); | |
| 323 } | |
| 324 dateFields.verify(inputString); | |
| 325 return dateFields.asDate(); | |
| 326 } | |
| 327 | |
| 328 /** | |
| 329 * Given user input, attempt to parse the [inputString] into the anticipated | |
| 330 * format, treating it as being in the local timezone. If [inputString] does | |
| 331 * not match our format, throws a [FormatException]. This will reject dates | |
| 332 * whose values are not strictly valid, even if the | |
| 333 * DateTime constructor will accept them. It will also rejct strings with | |
| 334 * additional characters (including whitespace) after a valid date. For | |
| 335 * looser parsing, use [parse]. | |
| 336 */ | |
| 337 DateTime parseStrict(String inputString, [utc = false]) => | |
| 338 _parse(inputString, utc: utc, strict: true); | |
| 339 | |
| 340 DateTime _parse(String inputString, {utc: false, strict: false}) { | |
| 341 // TODO(alanknight): The Closure code refers to special parsing of numeric | |
| 342 // values with no delimiters, which we currently don't do. Should we? | |
| 343 var dateFields = new _DateBuilder(); | |
| 344 if (utc) dateFields.utc = true; | |
| 345 var stream = new _Stream(inputString); | |
| 346 _formatFields.forEach((f) => f.parse(stream, dateFields)); | |
| 347 if (strict && !stream.atEnd()) { | |
| 348 throw new FormatException( | |
| 349 "Characters remaining after date parsing in $inputString"); | |
| 350 } | |
| 351 if (strict) dateFields.verify(inputString); | |
| 352 return dateFields.asDate(); | |
| 353 } | |
| 354 | |
| 355 /** | |
| 356 * Given user input, attempt to parse the [inputString] into the anticipated | |
| 357 * format, treating it as being in UTC. | |
| 358 * | |
| 359 * The canonical Dart style name | |
| 360 * is [parseUtc], but [parseUTC] is retained | |
| 361 * for backward-compatibility. | |
| 362 */ | |
| 363 DateTime parseUTC(String inputString) => parse(inputString, true); | |
| 364 | |
| 365 /** | |
| 366 * Given user input, attempt to parse the [inputString] into the anticipated | |
| 367 * format, treating it as being in UTC. | |
| 368 * | |
| 369 * The canonical Dart style name | |
| 370 * is [parseUtc], but [parseUTC] is retained | |
| 371 * for backward-compatibility. | |
| 372 */ | |
| 373 DateTime parseUtc(String inputString) => parse(inputString, true); | |
| 374 | |
| 375 /** | |
| 376 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. | |
| 377 */ | |
| 378 String get locale => _locale; | |
| 379 | |
| 380 /** | |
| 381 * Returns a list of all locales for which we have date formatting | |
| 382 * information. | |
| 383 */ | |
| 384 static List<String> allLocalesWithSymbols() => dateTimeSymbols.keys.toList(); | |
| 385 | |
| 386 /** | |
| 387 * The named constructors for this class are all conveniences for creating | |
| 388 * instances using one of the known "skeleton" formats, and having code | |
| 389 * completion support for discovering those formats. | |
| 390 * So, | |
| 391 * new DateFormat.yMd("en_US") | |
| 392 * is equivalent to | |
| 393 * new DateFormat("yMd", "en_US") | |
| 394 * To create a compound format you can use these constructors in combination | |
| 395 * with the add_ methods below. e.g. | |
| 396 * new DateFormat.yMd().add_Hms(); | |
| 397 * If the optional [locale] is omitted, the format will be created using the | |
| 398 * default locale in [Intl.systemLocale]. | |
| 399 */ | |
| 400 DateFormat.d([locale]) : this("d", locale); | |
| 401 DateFormat.E([locale]) : this("E", locale); | |
| 402 DateFormat.EEEE([locale]) : this("EEEE", locale); | |
| 403 DateFormat.LLL([locale]) : this("LLL", locale); | |
| 404 DateFormat.LLLL([locale]) : this("LLLL", locale); | |
| 405 DateFormat.M([locale]) : this("M", locale); | |
| 406 DateFormat.Md([locale]) : this("Md", locale); | |
| 407 DateFormat.MEd([locale]) : this("MEd", locale); | |
| 408 DateFormat.MMM([locale]) : this("MMM", locale); | |
| 409 DateFormat.MMMd([locale]) : this("MMMd", locale); | |
| 410 DateFormat.MMMEd([locale]) : this("MMMEd", locale); | |
| 411 DateFormat.MMMM([locale]) : this("MMMM", locale); | |
| 412 DateFormat.MMMMd([locale]) : this("MMMMd", locale); | |
| 413 DateFormat.MMMMEEEEd([locale]) : this("MMMMEEEEd", locale); | |
| 414 DateFormat.QQQ([locale]) : this("QQQ", locale); | |
| 415 DateFormat.QQQQ([locale]) : this("QQQQ", locale); | |
| 416 DateFormat.y([locale]) : this("y", locale); | |
| 417 DateFormat.yM([locale]) : this("yM", locale); | |
| 418 DateFormat.yMd([locale]) : this("yMd", locale); | |
| 419 DateFormat.yMEd([locale]) : this("yMEd", locale); | |
| 420 DateFormat.yMMM([locale]) : this("yMMM", locale); | |
| 421 DateFormat.yMMMd([locale]) : this("yMMMd", locale); | |
| 422 DateFormat.yMMMEd([locale]) : this("yMMMEd", locale); | |
| 423 DateFormat.yMMMM([locale]) : this("yMMMM", locale); | |
| 424 DateFormat.yMMMMd([locale]) : this("yMMMMd", locale); | |
| 425 DateFormat.yMMMMEEEEd([locale]) : this("yMMMMEEEEd", locale); | |
| 426 DateFormat.yQQQ([locale]) : this("yQQQ", locale); | |
| 427 DateFormat.yQQQQ([locale]) : this("yQQQQ", locale); | |
| 428 DateFormat.H([locale]) : this("H", locale); | |
| 429 DateFormat.Hm([locale]) : this("Hm", locale); | |
| 430 DateFormat.Hms([locale]) : this("Hms", locale); | |
| 431 DateFormat.j([locale]) : this("j", locale); | |
| 432 DateFormat.jm([locale]) : this("jm", locale); | |
| 433 DateFormat.jms([locale]) : this("jms", locale); | |
| 434 DateFormat.jmv([locale]) : this("jmv", locale); | |
| 435 DateFormat.jmz([locale]) : this("jmz", locale); | |
| 436 DateFormat.jv([locale]) : this("jv", locale); | |
| 437 DateFormat.jz([locale]) : this("jz", locale); | |
| 438 DateFormat.m([locale]) : this("m", locale); | |
| 439 DateFormat.ms([locale]) : this("ms", locale); | |
| 440 DateFormat.s([locale]) : this("s", locale); | |
| 441 | |
| 442 /** | |
| 443 * The "add_*" methods append a particular skeleton to the format, or set | |
| 444 * it as the only format if none was previously set. These are primarily | |
| 445 * useful for creating compound formats. For example | |
| 446 * new DateFormat.yMd().add_Hms(); | |
| 447 * would create a date format that prints both the date and the time. | |
| 448 */ | |
| 449 DateFormat add_d() => addPattern("d"); | |
| 450 DateFormat add_E() => addPattern("E"); | |
| 451 DateFormat add_EEEE() => addPattern("EEEE"); | |
| 452 DateFormat add_LLL() => addPattern("LLL"); | |
| 453 DateFormat add_LLLL() => addPattern("LLLL"); | |
| 454 DateFormat add_M() => addPattern("M"); | |
| 455 DateFormat add_Md() => addPattern("Md"); | |
| 456 DateFormat add_MEd() => addPattern("MEd"); | |
| 457 DateFormat add_MMM() => addPattern("MMM"); | |
| 458 DateFormat add_MMMd() => addPattern("MMMd"); | |
| 459 DateFormat add_MMMEd() => addPattern("MMMEd"); | |
| 460 DateFormat add_MMMM() => addPattern("MMMM"); | |
| 461 DateFormat add_MMMMd() => addPattern("MMMMd"); | |
| 462 DateFormat add_MMMMEEEEd() => addPattern("MMMMEEEEd"); | |
| 463 DateFormat add_QQQ() => addPattern("QQQ"); | |
| 464 DateFormat add_QQQQ() => addPattern("QQQQ"); | |
| 465 DateFormat add_y() => addPattern("y"); | |
| 466 DateFormat add_yM() => addPattern("yM"); | |
| 467 DateFormat add_yMd() => addPattern("yMd"); | |
| 468 DateFormat add_yMEd() => addPattern("yMEd"); | |
| 469 DateFormat add_yMMM() => addPattern("yMMM"); | |
| 470 DateFormat add_yMMMd() => addPattern("yMMMd"); | |
| 471 DateFormat add_yMMMEd() => addPattern("yMMMEd"); | |
| 472 DateFormat add_yMMMM() => addPattern("yMMMM"); | |
| 473 DateFormat add_yMMMMd() => addPattern("yMMMMd"); | |
| 474 DateFormat add_yMMMMEEEEd() => addPattern("yMMMMEEEEd"); | |
| 475 DateFormat add_yQQQ() => addPattern("yQQQ"); | |
| 476 DateFormat add_yQQQQ() => addPattern("yQQQQ"); | |
| 477 DateFormat add_H() => addPattern("H"); | |
| 478 DateFormat add_Hm() => addPattern("Hm"); | |
| 479 DateFormat add_Hms() => addPattern("Hms"); | |
| 480 DateFormat add_j() => addPattern("j"); | |
| 481 DateFormat add_jm() => addPattern("jm"); | |
| 482 DateFormat add_jms() => addPattern("jms"); | |
| 483 DateFormat add_jmv() => addPattern("jmv"); | |
| 484 DateFormat add_jmz() => addPattern("jmz"); | |
| 485 DateFormat add_jv() => addPattern("jv"); | |
| 486 DateFormat add_jz() => addPattern("jz"); | |
| 487 DateFormat add_m() => addPattern("m"); | |
| 488 DateFormat add_ms() => addPattern("ms"); | |
| 489 DateFormat add_s() => addPattern("s"); | |
| 490 | |
| 491 /** | |
| 492 * For each of the skeleton formats we also allow the use of the corresponding | |
| 493 * ICU constant names. | |
| 494 */ | |
| 495 static const String ABBR_MONTH = 'MMM'; | |
| 496 static const String DAY = 'd'; | |
| 497 static const String ABBR_WEEKDAY = 'E'; | |
| 498 static const String WEEKDAY = 'EEEE'; | |
| 499 static const String ABBR_STANDALONE_MONTH = 'LLL'; | |
| 500 static const String STANDALONE_MONTH = 'LLLL'; | |
| 501 static const String NUM_MONTH = 'M'; | |
| 502 static const String NUM_MONTH_DAY = 'Md'; | |
| 503 static const String NUM_MONTH_WEEKDAY_DAY = 'MEd'; | |
| 504 static const String ABBR_MONTH_DAY = 'MMMd'; | |
| 505 static const String ABBR_MONTH_WEEKDAY_DAY = 'MMMEd'; | |
| 506 static const String MONTH = 'MMMM'; | |
| 507 static const String MONTH_DAY = 'MMMMd'; | |
| 508 static const String MONTH_WEEKDAY_DAY = 'MMMMEEEEd'; | |
| 509 static const String ABBR_QUARTER = 'QQQ'; | |
| 510 static const String QUARTER = 'QQQQ'; | |
| 511 static const String YEAR = 'y'; | |
| 512 static const String YEAR_NUM_MONTH = 'yM'; | |
| 513 static const String YEAR_NUM_MONTH_DAY = 'yMd'; | |
| 514 static const String YEAR_NUM_MONTH_WEEKDAY_DAY = 'yMEd'; | |
| 515 static const String YEAR_ABBR_MONTH = 'yMMM'; | |
| 516 static const String YEAR_ABBR_MONTH_DAY = 'yMMMd'; | |
| 517 static const String YEAR_ABBR_MONTH_WEEKDAY_DAY = 'yMMMEd'; | |
| 518 static const String YEAR_MONTH = 'yMMMM'; | |
| 519 static const String YEAR_MONTH_DAY = 'yMMMMd'; | |
| 520 static const String YEAR_MONTH_WEEKDAY_DAY = 'yMMMMEEEEd'; | |
| 521 static const String YEAR_ABBR_QUARTER = 'yQQQ'; | |
| 522 static const String YEAR_QUARTER = 'yQQQQ'; | |
| 523 static const String HOUR24 = 'H'; | |
| 524 static const String HOUR24_MINUTE = 'Hm'; | |
| 525 static const String HOUR24_MINUTE_SECOND = 'Hms'; | |
| 526 static const String HOUR = 'j'; | |
| 527 static const String HOUR_MINUTE = 'jm'; | |
| 528 static const String HOUR_MINUTE_SECOND = 'jms'; | |
| 529 static const String HOUR_MINUTE_GENERIC_TZ = 'jmv'; | |
| 530 static const String HOUR_MINUTE_TZ = 'jmz'; | |
| 531 static const String HOUR_GENERIC_TZ = 'jv'; | |
| 532 static const String HOUR_TZ = 'jz'; | |
| 533 static const String MINUTE = 'm'; | |
| 534 static const String MINUTE_SECOND = 'ms'; | |
| 535 static const String SECOND = 's'; | |
| 536 | |
| 537 /** The locale in which we operate, e.g. 'en_US', or 'pt'. */ | |
| 538 String _locale; | |
| 539 | |
| 540 /** | |
| 541 * The full template string. This may have been specified directly, or | |
| 542 * it may have been derived from a skeleton and the locale information | |
| 543 * on how to interpret that skeleton. | |
| 544 */ | |
| 545 String _pattern; | |
| 546 | |
| 547 /** | |
| 548 * We parse the format string into individual [_DateFormatField] objects | |
| 549 * that are used to do the actual formatting and parsing. Do not use | |
| 550 * this variable directly, use the getter [_formatFields]. | |
| 551 */ | |
| 552 List<_DateFormatField> _formatFieldsPrivate; | |
| 553 | |
| 554 /** | |
| 555 * Getter for [_formatFieldsPrivate] that lazily initializes it. | |
| 556 */ | |
| 557 get _formatFields { | |
| 558 if (_formatFieldsPrivate == null) { | |
| 559 if (_pattern == null) _useDefaultPattern(); | |
| 560 _formatFieldsPrivate = parsePattern(_pattern); | |
| 561 } | |
| 562 return _formatFieldsPrivate; | |
| 563 } | |
| 564 | |
| 565 /** | |
| 566 * We are being asked to do formatting without having set any pattern. | |
| 567 * Use a default. | |
| 568 */ | |
| 569 _useDefaultPattern() { | |
| 570 add_yMMMMd(); | |
| 571 add_jms(); | |
| 572 } | |
| 573 | |
| 574 /** | |
| 575 * A series of regular expressions used to parse a format string into its | |
| 576 * component fields. | |
| 577 */ | |
| 578 static List<RegExp> _matchers = [ | |
| 579 // Quoted String - anything between single quotes, with escaping | |
| 580 // of single quotes by doubling them. | |
| 581 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock' | |
| 582 new RegExp("^\'(?:[^\']|\'\')*\'"), | |
| 583 // Fields - any sequence of 1 or more of the same field characters. | |
| 584 // e.g. in "hh:mm:ss" will match hh, mm, and ss. But in "hms" would | |
| 585 // match each letter individually. | |
| 586 new RegExp( | |
| 587 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|D+|m+|s+|v+|z+|Z+)"), | |
| 588 // Everything else - A sequence that is not quotes or field characters. | |
| 589 // e.g. in "hh:mm:ss" will match the colons. | |
| 590 new RegExp("^[^\'GyMkSEahKHcLQdDmsvzZ]+") | |
| 591 ]; | |
| 592 | |
| 593 /** | |
| 594 * Set our pattern, appending it to any existing patterns. Also adds a single | |
| 595 * space to separate the two. | |
| 596 */ | |
| 597 _appendPattern(String inputPattern, [String separator = ' ']) { | |
| 598 _pattern = | |
| 599 _pattern == null ? inputPattern : "$_pattern$separator$inputPattern"; | |
| 600 } | |
| 601 | |
| 602 /** | |
| 603 * Add [inputPattern] to this instance as a pattern. If there was a previous | |
| 604 * pattern, then this appends to it, separating the two by [separator]. | |
| 605 * [inputPattern] is first looked up in our list of known skeletons. | |
| 606 * If it's found there, then use the corresponding pattern for this locale. | |
| 607 * If it's not, then treat [inputPattern] as an explicit pattern. | |
| 608 */ | |
| 609 DateFormat addPattern(String inputPattern, [String separator = ' ']) { | |
| 610 // TODO(alanknight): This is an expensive operation. Caching recently used | |
| 611 // formats, or possibly introducing an entire "locale" object that would | |
| 612 // cache patterns for that locale could be a good optimization. | |
| 613 // If we have already parsed the format fields, reset them. | |
| 614 _formatFieldsPrivate = null; | |
| 615 if (inputPattern == null) return this; | |
| 616 if (!_availableSkeletons.containsKey(inputPattern)) { | |
| 617 _appendPattern(inputPattern, separator); | |
| 618 } else { | |
| 619 _appendPattern(_availableSkeletons[inputPattern], separator); | |
| 620 } | |
| 621 return this; | |
| 622 } | |
| 623 | |
| 624 /** Return the pattern that we use to format dates.*/ | |
| 625 get pattern => _pattern; | |
| 626 | |
| 627 /** Return the skeletons for our current locale. */ | |
| 628 Map get _availableSkeletons => dateTimePatterns[locale]; | |
| 629 | |
| 630 /** | |
| 631 * Return the [DateSymbol] information for the locale. This can be useful | |
| 632 * to find lists like the names of weekdays or months in a locale, but | |
| 633 * the structure of this data may change, and it's generally better to go | |
| 634 * through the [format] and [parse] APIs. If the locale isn't present, or | |
| 635 * is uninitialized, returns null; | |
| 636 */ | |
| 637 DateSymbols get dateSymbols => dateTimeSymbols[_locale]; | |
| 638 | |
| 639 /** | |
| 640 * Set the locale. If the locale can't be found, we also look up | |
| 641 * based on alternative versions, e.g. if we have no 'en_CA' we will | |
| 642 * look for 'en' as a fallback. It will also translate en-ca into en_CA. | |
| 643 * Null is also considered a valid value for [newLocale], indicating | |
| 644 * to use the default. | |
| 645 */ | |
| 646 _setLocale(String newLocale) { | |
| 647 _locale = Intl.verifiedLocale(newLocale, localeExists); | |
| 648 } | |
| 649 | |
| 650 /** | |
| 651 * Return true if the locale exists, or if it is null. The null case | |
| 652 * is interpreted to mean that we use the default locale. | |
| 653 */ | |
| 654 static bool localeExists(localeName) { | |
| 655 if (localeName == null) return false; | |
| 656 return dateTimeSymbols.containsKey(localeName); | |
| 657 } | |
| 658 | |
| 659 static List get _fieldConstructors => [ | |
| 660 (pattern, parent) => new _DateFormatQuotedField(pattern, parent), | |
| 661 (pattern, parent) => new _DateFormatPatternField(pattern, parent), | |
| 662 (pattern, parent) => new _DateFormatLiteralField(pattern, parent) | |
| 663 ]; | |
| 664 | |
| 665 /** Parse the template pattern and return a list of field objects.*/ | |
| 666 List parsePattern(String pattern) { | |
| 667 if (pattern == null) return null; | |
| 668 return _parsePatternHelper(pattern).reversed.toList(); | |
| 669 } | |
| 670 | |
| 671 /** Recursive helper for parsing the template pattern. */ | |
| 672 List _parsePatternHelper(String pattern) { | |
| 673 if (pattern.isEmpty) return []; | |
| 674 | |
| 675 var matched = _match(pattern); | |
| 676 if (matched == null) return []; | |
| 677 | |
| 678 var parsed = | |
| 679 _parsePatternHelper(pattern.substring(matched.fullPattern().length)); | |
| 680 parsed.add(matched); | |
| 681 return parsed; | |
| 682 } | |
| 683 | |
| 684 /** Find elements in a string that are patterns for specific fields.*/ | |
| 685 _DateFormatField _match(String pattern) { | |
| 686 for (var i = 0; i < _matchers.length; i++) { | |
| 687 var regex = _matchers[i]; | |
| 688 var match = regex.firstMatch(pattern); | |
| 689 if (match != null) { | |
| 690 return _fieldConstructors[i](match.group(0), this); | |
| 691 } | |
| 692 } | |
| 693 } | |
| 694 } | |
| OLD | NEW |