| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * DateFormat is for formatting and parsing dates in a locale-sensitive | 6 * DateFormat is for formatting and parsing dates in a locale-sensitive |
| 7 * manner. | 7 * manner. |
| 8 * It allows the user to choose from a set of standard date time formats as well | 8 * It allows the user to choose from a set of standard date time formats as well |
| 9 * as specify a customized pattern under certain locales. Date elements that | 9 * as specify a customized pattern under certain locales. Date elements that |
| 10 * vary across locales include month name, week name, field order, etc. | 10 * vary across locales include month name, week name, field order, etc. |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 59 * HOUR_MINUTE_GENERIC_TZ jmv | 59 * HOUR_MINUTE_GENERIC_TZ jmv |
| 60 * HOUR_MINUTE_TZ jmz | 60 * HOUR_MINUTE_TZ jmz |
| 61 * HOUR_GENERIC_TZ jv | 61 * HOUR_GENERIC_TZ jv |
| 62 * HOUR_TZ jz | 62 * HOUR_TZ jz |
| 63 * MINUTE m | 63 * MINUTE m |
| 64 * MINUTE_SECOND ms | 64 * MINUTE_SECOND ms |
| 65 * SECOND s | 65 * SECOND s |
| 66 * | 66 * |
| 67 * Examples Using the US Locale: | 67 * Examples Using the US Locale: |
| 68 * | 68 * |
| 69 * Pattern Result | 69 * Pattern Result |
| 70 * ---------------- ------- | 70 * ---------------- ------- |
| 71 * "yMd" -> 07/10/1996 | 71 * new DateFormat.yMd() -> 07/10/1996 |
| 72 * "yMMMMd" -> July 10, 1996 | 72 * new DateFormat("yMd") -> 07/10/1996 |
| 73 * "Hm" -> 12:08 PM | 73 * new DateFormat.yMMMMd("en_US") -> July 10, 1996 |
| 74 * new DateFormat("Hm", "en_US") -> 12:08 PM |
| 75 * new DateFormat.yMd().Hm() -> 07/10/1996 12:08 PM |
| 74 * | 76 * |
| 75 * Explicit Pattern Syntax: Formats can also be specified with a pattern string. | 77 * Explicit Pattern Syntax: Formats can also be specified with a pattern string. |
| 76 * The skeleton forms will resolve to explicit patterns of this form, but will | 78 * The skeleton forms will resolve to explicit patterns of this form, but will |
| 77 * also adapt to different patterns in different locales. | 79 * also adapt to different patterns in different locales. |
| 78 * The following characters are reserved: | 80 * The following characters are reserved: |
| 79 * | 81 * |
| 80 * Symbol Meaning Presentation Example | 82 * Symbol Meaning Presentation Example |
| 81 * ------ ------- ------------ ------- | 83 * ------ ------- ------------ ------- |
| 82 * G era designator (Text) AD | 84 * G era designator (Text) AD |
| 83 * y year (Number) 1996 | 85 * y year (Number) 1996 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string | 145 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string |
| 144 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only | 146 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only |
| 145 * strings consisting of exactly two digits, as defined by {@link | 147 * strings consisting of exactly two digits, as defined by {@link |
| 146 * java.lang.Character#isDigit(char)}, will be parsed into the default | 148 * java.lang.Character#isDigit(char)}, will be parsed into the default |
| 147 * century. Any other numeric string, such as a one digit string, a three or | 149 * century. Any other numeric string, such as a one digit string, a three or |
| 148 * more digit string will be interpreted as its face value. | 150 * more digit string will be interpreted as its face value. |
| 149 * | 151 * |
| 150 * If the year pattern does not have exactly two 'y' characters, the year is | 152 * If the year pattern does not have exactly two 'y' characters, the year is |
| 151 * interpreted literally, regardless of the number of digits. So using the | 153 * interpreted literally, regardless of the number of digits. So using the |
| 152 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. | 154 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. |
| 153 * | |
| 154 * When numeric fields abut one another directly, with no intervening | |
| 155 * delimiter characters, they constitute a run of abutting numeric fields. Such | |
| 156 * runs are parsed specially. For example, the format "HHmmss" parses the input | |
| 157 * text "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and | |
| 158 * fails to parse "1234". In other words, the leftmost field of the run is | |
| 159 * flexible, while the others keep a fixed width. If the parse fails anywhere in | |
| 160 * the run, then the leftmost field is shortened by one character, and the | |
| 161 * entire run is parsed again. This is repeated until either the parse succeeds | |
| 162 * or the leftmost field is one character in length. If the parse still fails at | |
| 163 * that point, the parse of the run fails. | |
| 164 */ | 155 */ |
| 165 | 156 |
| 166 #library('date_format'); | 157 #library('date_format'); |
| 167 | 158 |
| 168 #import('intl.dart'); | 159 #import('intl.dart'); |
| 169 #import('date_time_patterns.dart'); | 160 #import('date_time_patterns.dart'); |
| 170 #import('date_symbols.dart'); | 161 #import('date_symbols.dart'); |
| 171 #import('date_symbol_data.dart'); | 162 #import('date_symbol_data.dart'); |
| 172 | 163 |
| 173 #source('lib/date_format_field.dart'); | 164 #source('lib/date_format_field.dart'); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 194 */ | 185 */ |
| 195 DateFormat([String newPattern, String locale]) { | 186 DateFormat([String newPattern, String locale]) { |
| 196 // TODO(alanknight): It should be possible to specify multiple skeletons eg | 187 // TODO(alanknight): It should be possible to specify multiple skeletons eg |
| 197 // date, time, timezone all separately. Adding many or named parameters to | 188 // date, time, timezone all separately. Adding many or named parameters to |
| 198 // the constructor seems awkward, especially with the possibility of | 189 // the constructor seems awkward, especially with the possibility of |
| 199 // confusion with the locale. A "fluent" interface with cascading on an | 190 // confusion with the locale. A "fluent" interface with cascading on an |
| 200 // instance might work better? A list of patterns is also possible. | 191 // instance might work better? A list of patterns is also possible. |
| 201 // TODO(alanknight): There will need to be at least setup type async | 192 // TODO(alanknight): There will need to be at least setup type async |
| 202 // operations to avoid the need to bring along every locale in every program | 193 // operations to avoid the need to bring along every locale in every program |
| 203 _locale = Intl.verifiedLocale(locale); | 194 _locale = Intl.verifiedLocale(locale); |
| 204 _setPattern(newPattern); | 195 addPattern(newPattern); |
| 205 } | 196 } |
| 206 | 197 |
| 207 /** | 198 /** |
| 208 * Return a string representing [date] formatted according to our locale | 199 * Return a string representing [date] formatted according to our locale |
| 209 * and internal format. | 200 * and internal format. |
| 210 */ | 201 */ |
| 211 String format(Date date) { | 202 String format(Date date) { |
| 212 // TODO(efortuna): read optional TimeZone argument (or similar)? | 203 // TODO(efortuna): read optional TimeZone argument (or similar)? |
| 213 var result = new StringBuffer(); | 204 var result = new StringBuffer(); |
| 214 _formatFields.forEach((field) => result.add(field.format(date))); | 205 _formatFields.forEach((field) => result.add(field.format(date))); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 231 */ | 222 */ |
| 232 String formatDurationFrom(Duration duration, Date date) { | 223 String formatDurationFrom(Duration duration, Date date) { |
| 233 return ''; | 224 return ''; |
| 234 } | 225 } |
| 235 | 226 |
| 236 /** | 227 /** |
| 237 * Given user input, attempt to parse the [inputString] into the anticipated | 228 * Given user input, attempt to parse the [inputString] into the anticipated |
| 238 * format, treating it as being in the local timezone. | 229 * format, treating it as being in the local timezone. |
| 239 */ | 230 */ |
| 240 Date parse(String inputString, [utc = false]) { | 231 Date parse(String inputString, [utc = false]) { |
| 232 // TODO(alanknight): The Closure code refers to special parsing of numeric |
| 233 // values with no delimiters, which we currently don't do. Should we? |
| 241 var dateFields = new _DateBuilder(); | 234 var dateFields = new _DateBuilder(); |
| 242 if (utc) dateFields.utc=true; | 235 if (utc) dateFields.utc=true; |
| 243 var stream = new _Stream(inputString); | 236 var stream = new _Stream(inputString); |
| 244 _formatFields.forEach( | 237 _formatFields.forEach( |
| 245 (each) => each.parse(stream, dateFields)); | 238 (each) => each.parse(stream, dateFields)); |
| 246 return dateFields.asDate(); | 239 return dateFields.asDate(); |
| 247 } | 240 } |
| 248 | 241 |
| 249 /** | 242 /** |
| 250 * Given user input, attempt to parse the [inputString] into the anticipated | 243 * Given user input, attempt to parse the [inputString] into the anticipated |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 350 DateFormat.jms([locale]) : this("jms", locale); | 343 DateFormat.jms([locale]) : this("jms", locale); |
| 351 DateFormat.jmv([locale]) : this("jmv", locale); | 344 DateFormat.jmv([locale]) : this("jmv", locale); |
| 352 DateFormat.jmz([locale]) : this("jmz", locale); | 345 DateFormat.jmz([locale]) : this("jmz", locale); |
| 353 DateFormat.jv([locale]) : this("jv", locale); | 346 DateFormat.jv([locale]) : this("jv", locale); |
| 354 DateFormat.jz([locale]) : this("jz", locale); | 347 DateFormat.jz([locale]) : this("jz", locale); |
| 355 DateFormat.m([locale]) : this("m", locale); | 348 DateFormat.m([locale]) : this("m", locale); |
| 356 DateFormat.ms([locale]) : this("ms", locale); | 349 DateFormat.ms([locale]) : this("ms", locale); |
| 357 DateFormat.s([locale]) : this("s", locale); | 350 DateFormat.s([locale]) : this("s", locale); |
| 358 | 351 |
| 359 /** | 352 /** |
| 353 * Methods for appending a particular skeleton to the format, or setting |
| 354 * it as the only format if none was previously set. These are primarily |
| 355 * useful for creating compound formats. For example |
| 356 * new DateFormat.yMd().hms(); |
| 357 * would create a date format that prints both the date and the time. |
| 358 */ |
| 359 DateFormat d() => addPattern("d"); |
| 360 DateFormat E() => addPattern("E"); |
| 361 DateFormat EEEE() => addPattern("EEEE"); |
| 362 DateFormat LLL() => addPattern("LLL"); |
| 363 DateFormat LLLL() => addPattern("LLLL"); |
| 364 DateFormat M() => addPattern("M"); |
| 365 DateFormat Md() => addPattern("Md"); |
| 366 DateFormat MEd() => addPattern("MEd"); |
| 367 DateFormat MMM() => addPattern("MMM"); |
| 368 DateFormat MMMd() => addPattern("MMMd"); |
| 369 DateFormat MMMEd() => addPattern("MMMEd"); |
| 370 DateFormat MMMM() => addPattern("MMMM"); |
| 371 DateFormat MMMMd() => addPattern("MMMMd"); |
| 372 DateFormat MMMMEEEEd() => addPattern("MMMMEEEEd"); |
| 373 DateFormat QQQ() => addPattern("QQQ"); |
| 374 DateFormat QQQQ() => addPattern("QQQQ"); |
| 375 DateFormat y() => addPattern("y"); |
| 376 DateFormat yM() => addPattern("yM"); |
| 377 DateFormat yMd() => addPattern("yMd"); |
| 378 DateFormat yMEd() => addPattern("yMEd"); |
| 379 DateFormat yMMM() => addPattern("yMMM"); |
| 380 DateFormat yMMMd() => addPattern("yMMMd"); |
| 381 DateFormat yMMMEd() => addPattern("yMMMEd"); |
| 382 DateFormat yMMMM() => addPattern("yMMMM"); |
| 383 DateFormat yMMMMd() => addPattern("yMMMMd"); |
| 384 DateFormat yMMMMEEEEd() => addPattern("yMMMMEEEEd"); |
| 385 DateFormat yQQQ() => addPattern("yQQQ"); |
| 386 DateFormat yQQQQ() => addPattern("yQQQQ"); |
| 387 DateFormat H() => addPattern("H"); |
| 388 DateFormat Hm() => addPattern("Hm"); |
| 389 DateFormat Hms() => addPattern("Hms"); |
| 390 DateFormat j() => addPattern("j"); |
| 391 DateFormat jm() => addPattern("jm"); |
| 392 DateFormat jms() => addPattern("jms"); |
| 393 DateFormat jmv() => addPattern("jmv"); |
| 394 DateFormat jmz() => addPattern("jmz"); |
| 395 DateFormat jv() => addPattern("jv"); |
| 396 DateFormat jz() => addPattern("jz"); |
| 397 DateFormat m() => addPattern("m"); |
| 398 DateFormat ms() => addPattern("ms"); |
| 399 DateFormat s() => addPattern("s"); |
| 400 |
| 401 /** |
| 360 * ICU constants for format names, resolving to the corresponding skeletons. | 402 * ICU constants for format names, resolving to the corresponding skeletons. |
| 361 */ | 403 */ |
| 362 static final String DAY = 'd'; | 404 static final String DAY = 'd'; |
| 363 static final String ABBR_WEEKDAY = 'E'; | 405 static final String ABBR_WEEKDAY = 'E'; |
| 364 static final String WEEKDAY = 'EEEE'; | 406 static final String WEEKDAY = 'EEEE'; |
| 365 static final String ABBR_STANDALONE_MONTH = 'LLL'; | 407 static final String ABBR_STANDALONE_MONTH = 'LLL'; |
| 366 static final String STANDALONE_MONTH = 'LLLL'; | 408 static final String STANDALONE_MONTH = 'LLLL'; |
| 367 static final String NUM_MONTH = 'M'; | 409 static final String NUM_MONTH = 'M'; |
| 368 static final String NUM_MONTH_DAY = 'Md'; | 410 static final String NUM_MONTH_DAY = 'Md'; |
| 369 static final String NUM_MONTH_WEEKDAY_DAY = 'MEd'; | 411 static final String NUM_MONTH_WEEKDAY_DAY = 'MEd'; |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 String _locale; | 447 String _locale; |
| 406 | 448 |
| 407 /** | 449 /** |
| 408 * The full template string. This may have been specified directly, or | 450 * The full template string. This may have been specified directly, or |
| 409 * it may have been derived from a skeleton and the locale information | 451 * it may have been derived from a skeleton and the locale information |
| 410 * on how to interpret that skeleton. | 452 * on how to interpret that skeleton. |
| 411 */ | 453 */ |
| 412 String _pattern; | 454 String _pattern; |
| 413 | 455 |
| 414 /** | 456 /** |
| 415 * We parse the format string into individual fields and store them here. | 457 * We parse the format string into individual [_DateFormatField] objects |
| 416 * This is what is actually used to do the formatting. | 458 * that are used to do the actual formatting and parsing. Do not use |
| 459 * this variable directly, use the getter [_formatFields]. |
| 417 */ | 460 */ |
| 418 List<_DateFormatField> _formatFields; | 461 List<_DateFormatField> _formatFieldsPrivate; |
| 462 |
| 463 /** |
| 464 * Getter for [_formatFieldsPrivate] that lazily initializes it. |
| 465 */ |
| 466 get _formatFields() { |
| 467 if (_formatFieldsPrivate == null) { |
| 468 _formatFieldsPrivate = parsePattern(_pattern); |
| 469 } |
| 470 return _formatFieldsPrivate; |
| 471 } |
| 419 | 472 |
| 420 /** | 473 /** |
| 421 * A series of regular expressions used to parse a format string into its | 474 * A series of regular expressions used to parse a format string into its |
| 422 * component fields. | 475 * component fields. |
| 423 */ | 476 */ |
| 424 static var _matchers = const [ | 477 static var _matchers = const [ |
| 425 // Quoted String - anything between single quotes, with escaping | 478 // Quoted String - anything between single quotes, with escaping |
| 426 // of single quotes by doubling them. | 479 // of single quotes by doubling them. |
| 427 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock' | 480 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock' |
| 428 const RegExp("^\'(?:[^\']|\'\')*\'"), | 481 const RegExp("^\'(?:[^\']|\'\')*\'"), |
| 429 // Fields - any sequence of 1 or more of the same field characters. | 482 // Fields - any sequence of 1 or more of the same field characters. |
| 430 // e.g. in "hh:mm:ss" will match hh, mm, and ss. But in "hms" would | 483 // e.g. in "hh:mm:ss" will match hh, mm, and ss. But in "hms" would |
| 431 // match each letter individually. | 484 // match each letter individually. |
| 432 const RegExp( | 485 const RegExp( |
| 433 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|z+|Z+)"), | 486 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|z+|Z+)"), |
| 434 // Everything else - A sequence that is not quotes or field characters. | 487 // Everything else - A sequence that is not quotes or field characters. |
| 435 // e.g. in "hh:mm:ss" will match the colons. | 488 // e.g. in "hh:mm:ss" will match the colons. |
| 436 const RegExp("^[^\'GyMkSEahKHcLQdmsvzZ]+") | 489 const RegExp("^[^\'GyMkSEahKHcLQdmsvzZ]+") |
| 437 ]; | 490 ]; |
| 438 | 491 |
| 439 /** | 492 /** |
| 440 * Given a format from the user look it up in our list of known skeletons. | 493 * Set our pattern, appending it to any existing patterns. Also adds a single |
| 441 * If it's there, then use the corresponding pattern for this locale. | 494 * space to separate the two. |
| 442 * If it's not, then treat it as an explicit pattern. | |
| 443 */ | 495 */ |
| 444 _setPattern(String inputPattern) { | 496 _setPattern(String inputPattern, [String separator = ' ']) { |
| 497 if (_pattern == null) { |
| 498 _pattern = inputPattern; |
| 499 } else { |
| 500 _pattern = "$_pattern$separator$inputPattern"; |
| 501 } |
| 502 } |
| 503 |
| 504 /** |
| 505 * Add [inputPattern] to this instance as a pattern. If there was a previous |
| 506 * pattern, then this appends to it, separating the two by [separator]. |
| 507 * [inputPattern] is first looked up in our list of known skeletons. |
| 508 * If it's found there, then use the corresponding pattern for this locale. |
| 509 * If it's not, then treat [inputPattern] as an explicit pattern. |
| 510 */ |
| 511 DateFormat addPattern(String inputPattern, [String separator = ' ']) { |
| 445 // TODO(alanknight): This is an expensive operation. Caching recently used | 512 // TODO(alanknight): This is an expensive operation. Caching recently used |
| 446 // formats, or possibly introducing an entire "locale" object that would | 513 // formats, or possibly introducing an entire "locale" object that would |
| 447 // cache patterns for that locale could be a good optimization. | 514 // cache patterns for that locale could be a good optimization. |
| 448 if (!_availableSkeletons.containsKey(inputPattern)) { | 515 if (!_availableSkeletons.containsKey(inputPattern)) { |
| 449 _pattern = inputPattern; | 516 _setPattern(inputPattern, separator); |
| 450 } else { | 517 } else { |
| 451 _pattern = _availableSkeletons[inputPattern]; | 518 _setPattern(_availableSkeletons[inputPattern], separator); |
| 452 } | 519 } |
| 453 _formatFields = parsePattern(_pattern); | 520 // If we have already parsed the format fields, reset them. |
| 521 _formatFieldsPrivate = null; |
| 522 return this; |
| 454 } | 523 } |
| 455 | 524 |
| 456 /** Return the pattern that we use to format dates.*/ | 525 /** Return the pattern that we use to format dates.*/ |
| 457 get pattern() => _pattern; | 526 get pattern() => _pattern; |
| 458 | 527 |
| 459 /** Return the skeletons for our current locale. */ | 528 /** Return the skeletons for our current locale. */ |
| 460 Map get _availableSkeletons() { | 529 Map get _availableSkeletons() { |
| 461 return dateTimePatterns[locale]; | 530 return dateTimePatterns[locale]; |
| 462 } | 531 } |
| 463 | 532 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 521 List _reverse(List list) { | 590 List _reverse(List list) { |
| 522 // TODO(alanknight): Use standardized list reverse when implemented. | 591 // TODO(alanknight): Use standardized list reverse when implemented. |
| 523 // See Issue 2804. | 592 // See Issue 2804. |
| 524 var result = new List(); | 593 var result = new List(); |
| 525 for (var i = list.length-1; i >= 0; i--) { | 594 for (var i = list.length-1; i >= 0; i--) { |
| 526 result.addLast(list[i]); | 595 result.addLast(list[i]); |
| 527 } | 596 } |
| 528 return result; | 597 return result; |
| 529 } | 598 } |
| 530 } | 599 } |
| OLD | NEW |