Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(34)

Side by Side Diff: pkg/intl/date_format.dart

Issue 10832430: Allow multiple patterns for date formats (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/intl/test/date_time_format_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/intl/test/date_time_format_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698