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 |