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

Side by Side Diff: lib/i18n/date_format.dart

Issue 10807096: Add date formatting and parsing code. (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
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.
11 * <!-- TODO(efortuna): Customized pattern system -- suggested by i18n needs 11 * <!-- TODO(efortuna): Customized pattern system -- suggested by i18n needs
12 *feedback on appropriateness. --> 12 * feedback on appropriateness. -->
13 * We also allow the user to use any customized pattern to parse or format 13 * We also allow the user to use any customized pattern to parse or format
14 * date-time strings under certain locales. Date elements that vary across 14 * date-time strings under certain locales. Date elements that vary across
15 * locales include month name, weekname, field, order, etc. 15 * locales include month name, weekname, field, order, etc.
16 * 16 *
17 * This library uses the ICU/JDK date/time pattern specification as described 17 * This library uses the ICU/JDK date/time pattern specification both for
18 * below. 18 * complete format specifications and also the abbreviated "skeleton" form
19 * which can also adapt to different locales and is preferred where available.
19 * 20 *
20 * Time Format Syntax: To specify the time format use a time pattern string. 21 * Skeletons: These can be specified either as the ICU constant name or as the
21 * In this pattern, following letters are reserved as pattern letters, which 22 * skeleton to which it resolves. The supported set of skeletons is as follows
22 * are defined in the following manner: 23 * ICU Name Skeleton
24 * -------- --------
25 * DAY d
26 * ABBR_WEEKDAY E
27 * WEEKDAY EEEE
28 * ABBR_STANDALONE_MONTH LLL
29 * STANDALONE_MONTH LLLL
30 * NUM_MONTH M
31 * NUM_MONTH_DAY Md
32 * NUM_MONTH_WEEKDAY_DAY MEd
33 * ABBR_MONTH MMM
34 * ABBR_MONTH_DAY MMMd
35 * ABBR_MONTH_WEEKDAY_DAY MMMEd
36 * MONTH MMMM
37 * MONTH_DAY MMMMd
38 * MONTH_WEEKDAY_DAY MMMMEEEEd
39 * ABBR_QUARTER QQQ
40 * QUARTER QQQQ
41 * YEAR y
42 * YEAR_NUM_MONTH yM
43 * YEAR_NUM_MONTH_DAY yMd
44 * YEAR_NUM_MONTH_WEEKDAY_DAY yMEd
45 * YEAR_ABBR_MONTH yMMM
46 * YEAR_ABBR_MONTH_DAY yMMMd
47 * YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd
48 * YEAR_MONTH yMMMM
49 * YEAR_MONTH_DAY yMMMMd
50 * YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd
51 * YEAR_ABBR_QUARTER yQQQ
52 * YEAR_QUARTER yQQQQ
53 * HOUR24 H
54 * HOUR24_MINUTE Hm
55 * HOUR24_MINUTE_SECOND Hms
56 * HOUR j
57 * HOUR_MINUTE jm
58 * HOUR_MINUTE_SECOND jms
59 * HOUR_MINUTE_GENERIC_TZ jmv
60 * HOUR_MINUTE_TZ jmz
61 * HOUR_GENERIC_TZ jv
62 * HOUR_TZ jz
63 * MINUTE m
64 * MINUTE_SECOND ms
65 * SECOND s
66 *
67 * Examples Using the US Locale:
68 *
69 * Pattern Result
70 * ---------------- -------
71 * "yMd" ->07/10/1996
Emily Fortuna 2012/08/06 22:43:00 nit: do you want the -> to be immediately adjacent
Alan Knight 2012/08/08 00:47:44 Done.
72 * "yMMMMd" ->July 10, 1996
73 * "Hm" ->12:08 PM
74 *
75 * 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
77 * also adapt to different patterns in different locales.
78 * The following characters are reserved:
23 * 79 *
24 * Symbol Meaning Presentation Example 80 * Symbol Meaning Presentation Example
25 * ------ ------- ------------ ------- 81 * ------ ------- ------------ -------
26 * G era designator (Text) AD 82 * G era designator (Text) AD
27 * y# year (Number) 1996 83 * y year (Number) 1996
Emily Fortuna 2012/08/06 22:43:00 add back 1 space here to replace the character rem
Alan Knight 2012/08/08 00:47:44 Done.
28 * M month in year (Text & Number) July & 07 84 * M month in year (Text & Number) July & 07
85 * L standalone month (Text & Number) July & 07
29 * d day in month (Number) 10 86 * d day in month (Number) 10
87 * c standalone day (Number) 10
30 * h hour in am/pm (1~12) (Number) 12 88 * h hour in am/pm (1~12) (Number) 12
31 * H hour in day (0~23) (Number) 0 89 * H hour in day (0~23) (Number) 0
32 * m minute in hour (Number) 30 90 * m minute in hour (Number) 30
33 * s second in minute (Number) 55 91 * s second in minute (Number) 55
34 * S fractional second (Number) 978 92 * S fractional second (Number) 978
35 * E day of week (Text) Tuesday 93 * E day of week (Text) Tuesday
36 * D day in year (Number) 189 94 * D day in year (Number) 189
37 * a am/pm marker (Text) PM 95 * a am/pm marker (Text) PM
38 * k hour in day (1~24) (Number) 24 96 * k hour in day (1~24) (Number) 24
39 * K hour in am/pm (0~11) (Number) 0 97 * K hour in am/pm (0~11) (Number) 0
40 * z time zone (Text) Pacific Standard Time 98 * z time zone (Text) Pacific Standard Time
41 * Z time zone (RFC 822) (Number) -0800 99 * Z time zone (RFC 822) (Number) -0800
42 * v time zone (generic) (Text) Pacific Time 100 * v time zone (generic) (Text) Pacific Time
101 * Q quarter (Text) Q3
43 * ' escape for text (Delimiter) 'Date=' 102 * ' escape for text (Delimiter) 'Date='
44 * '' single quote (Literal) 'o''clock' 103 * '' single quote (Literal) 'o''clock'
45 * 104 *
46 * Items marked with '#' work differently than in Java.
47 *
48 * The count of pattern letters determine the format. 105 * The count of pattern letters determine the format.
49 * **Text**: 106 * **Text**:
107 * * 5 pattern letters--use narrow form for standalone. Otherwise does not apply
50 * * 4 or more pattern letters--use full form, 108 * * 4 or more pattern letters--use full form,
51 * * less than 4--use short or abbreviated form if one exists. 109 * * 3 pattern letters--use short or abbreviated form if one exists
52 * In parsing, we will always try long format, then short. 110 * * less than 3--use numeric form if one exists
53 * (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")
54 * 111 *
55 * **Number**: the minimum number of digits. Shorter numbers are zero-padded to 112 * **Number**: the minimum number of digits. Shorter numbers are zero-padded to
56 * this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled 113 * this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled
57 * specially; that is, if the count of 'y' is 2, the Year will be truncated to 114 * specially; that is, if the count of 'y' is 2, the Year will be truncated to
58 * 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other 115 * 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other
59 * fields, fractional seconds are padded on the right with zero. 116 * fields, fractional seconds are padded on the right with zero.
60 * 117 *
61 * (Text & Number): 3 or over, use text, otherwise use number. 118 * (Text & Number): 3 or over, use text, otherwise use number.
62 * 119 *
63 * Any characters that not in the pattern will be treated as quoted text. For 120 * Any characters that not in the pattern will be treated as quoted text. For
64 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the 121 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the
65 * resulting time text even they are not embraced within single quotes. In our 122 * resulting time text even they are not embraced within single quotes. In our
66 * current pattern usage, we didn't use up all letters. But those unused 123 * current pattern usage, we didn't use up all letters. But those unused
67 * letters are strongly discouraged to be used as quoted text without quote. 124 * letters are strongly discouraged to be used as quoted text without quote.
68 * That's because we may use other letter for pattern in future. 125 * That's because we may use other letter for pattern in future.
69 * 126 *
70 * Examples Using the US Locale: 127 * Examples Using the US Locale:
71 * 128 *
72 * Format Pattern Result 129 * Format Pattern Result
73 * -------------- ------- 130 * -------------- -------
74 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->1996.07.10 AD at 15:08:56 Pacific Time 131 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->1996.07.10 AD at 15:08:56 Pacific Time
75 * "EEE, MMM d, ''yy" ->Wed, July 10, '96 132 * "EEE, MMM d, ''yy" ->Wed, July 10, '96
76 * "h:mm a" ->12:08 PM 133 * "h:mm a" ->12:08 PM
77 * "hh 'o''clock' a, zzzz" ->12 o'clock PM, Pacific Daylight Time 134 * "hh 'o''clock' a, zzzz" ->12 o'clock PM, Pacific Daylight Time
78 * "K:mm a, vvv" ->0:00 PM, PT 135 * "K:mm a, vvv" ->0:00 PM, PT
79 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->01996.July.10 AD 12:08 PM 136 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->01996.July.10 AD 12:08 PM
80 * 137 *
81 * When parsing a date string using the abbreviated year pattern ("yy"), 138 * When parsing a date string using the abbreviated year pattern ("yy"),
82 * DateTimeParse must interpret the abbreviated year relative to some 139 * DateFormat must interpret the abbreviated year relative to some
83 * century. It does this by adjusting dates to be within 80 years before and 20 140 * century. It does this by adjusting dates to be within 80 years before and 20
84 * years after the time the parse function is called. For example, using a 141 * years after the time the parse function is called. For example, using a
85 * pattern of "MM/dd/yy" and a DateTimeParse instance created on Jan 1, 1997, 142 * pattern of "MM/dd/yy" and a DateTimeParse instance created on Jan 1, 1997,
86 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string 143 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string
87 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only 144 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only
88 * strings consisting of exactly two digits, as defined by {@link 145 * strings consisting of exactly two digits, as defined by {@link
89 * java.lang.Character#isDigit(char)}, will be parsed into the default 146 * java.lang.Character#isDigit(char)}, will be parsed into the default
90 * century. Any other numeric string, such as a one digit string, a three or 147 * century. Any other numeric string, such as a one digit string, a three or
91 * more digit string will be interpreted as its face value. 148 * more digit string will be interpreted as its face value.
92 * 149 *
93 * If the year pattern does not have exactly two 'y' characters, the year is 150 * If the year pattern does not have exactly two 'y' characters, the year is
94 * interpreted literally, regardless of the number of digits. So using the 151 * interpreted literally, regardless of the number of digits. So using the
95 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. 152 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
96 * 153 *
97 * When numeric fields abut one another directly, with no intervening 154 * When numeric fields abut one another directly, with no intervening
98 * delimiter characters, they constitute a run of abutting numeric fields. Such 155 * delimiter characters, they constitute a run of abutting numeric fields. Such
99 * runs are parsed specially. For example, the format "HHmmss" parses the input 156 * runs are parsed specially. For example, the format "HHmmss" parses the input
100 * text "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and 157 * text "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and
101 * fails to parse "1234". In other words, the leftmost field of the run is 158 * fails to parse "1234". In other words, the leftmost field of the run is
102 * flexible, while the others keep a fixed width. If the parse fails anywhere in 159 * flexible, while the others keep a fixed width. If the parse fails anywhere in
103 * the run, then the leftmost field is shortened by one character, and the 160 * the run, then the leftmost field is shortened by one character, and the
104 * entire run is parsed again. This is repeated until either the parse succeeds 161 * entire run is parsed again. This is repeated until either the parse succeeds
105 * or the leftmost field is one character in length. If the parse still fails at 162 * or the leftmost field is one character in length. If the parse still fails at
106 * that point, the parse of the run fails. 163 * that point, the parse of the run fails.
107 */ 164 */
108 165
166 #library('date_format');
167
168 #import('intl.dart');
169 #import('date_time_patterns.dart');
170 #import('date_symbols.dart');
171 #import('date_symbol_data.dart');
172
173 #source('_date_format_field.dart');
Emily Fortuna 2012/08/06 22:43:00 Suggestion: Let's try following the pub scheme her
Alan Knight 2012/08/08 00:47:44 Yes, I like that much better. Done.
174 #source('_date_format_helpers.dart');
175
109 class DateFormat { 176 class DateFormat {
110 177
111 /** Definition of how this object formats dates. */
112 String _formatDefinition;
113
114 /** 178 /**
115 * The locale code with which the message is to be formatted (such as en-CA). 179 * If [newPattern] matches one of the skeleton forms, it is looked up
180 * in the [locale] or in a default if none is specified, and the corresponding
Emily Fortuna 2012/08/06 22:43:00 "in a default" locale?
Alan Knight 2012/08/08 00:47:44 Done.
181 * full format string is used. If [newPattern] does not match one
182 * of the supported skeleton forms then it is used as a format directly.
183 * For example, in an en_US locale, specifying the skeleton
Emily Fortuna 2012/08/06 22:43:00 new "paragraph"? so: * of the supported skeleton f
Alan Knight 2012/08/08 00:47:44 Done.
184 * new DateFormat('yMEd');
Emily Fortuna 2012/08/06 22:43:00 Use markdown code indicators: http://daringfirebal
Alan Knight 2012/08/08 00:47:44 Done.
185 * or the explicit
186 * new DateFormat('EEE, M/d/y');
187 * would produce the same result, a date of the form
188 * Wed, 6/27/2012
189 * However, the skeleton version would also adapt to other locales.
190 * If [locale] does not exist in our set of supported locales then an
191 * IllegalArgumentException is thrown.
Emily Fortuna 2012/08/06 22:43:00 [IllegalArgumentException] also... Bob is in the p
Alan Knight 2012/08/08 00:47:44 Done.
116 */ 192 */
117 String _locale; 193 DateFormat([String newPattern, String locale]) {
118 194 // TODO(alanknight): It should be possible to specify multiple skeletons eg
119 /** 195 // date, time, timezone all separately. Adding many or named parameters to
120 * Date/Time format "skeleton" patterns. Also specifiable by String, but 196 // the constructor seems awkward, especially with the possibility of
121 * written this way so that they can be discoverable via autocomplete. These 197 // confusion with the locale. A "fluent" interface with cascading on an
122 * follow the ICU syntax described at the top of the file. These skeletons can 198 // instance might work better? A list of patterns is also possible.
123 * be combined and we will attempt to find the best format for each locale 199 // TODO(alanknight): There will need to be at least setup type async
124 * given the pattern. 200 // operations to avoid the need to bring along every locale in every program
125 */ 201 _locale = Intl.verifiedLocale(locale);
126 // TODO(efortuna): Hear back from i18n about Time Zones and the "core set" 202 pattern = newPattern;
127 // of skeleton patterns.
128 // Example of how this looks in the US
129 // locale.
130 static final String Hm = 'Hm'; // HH:mm
131 static final String Hms = 'Hms'; // HH:mm:ss
132 static final String M = 'M'; // L
133 static final String MEd = 'MEd'; // E, M/d
134 static final String MMM = 'MMM'; // LLL
135 static final String MMMEd = 'MMMEd'; // E, MMM d
136 static final String MMMMEd = 'MMMMEd'; // E, MMMM d
137 static final String MMMMd = 'MMMMd'; // MMMM d
138 static final String MMMd = 'MMMd'; // MMM d
139 static final String Md = 'Md'; // M/d
140 static final String d = 'd'; // d
141 static final String hm = 'hm'; // h:mm a
142 static final String ms = 'ms'; // mm:ss
143 static final String y = 'y'; // yyyy
144 static final String yM = 'yM'; // M/yyyy
145 static final String yMEd = 'yMEd'; // EEE, M/d/yyyy
146 static final String yMMM = 'yMMM'; // MMM yyyy
147 static final String yMMMEd = 'yMMMEd'; // EEE, MM d, yyyy
148 static final String yMMMM = 'yMMMM'; // MMMM yyyy
149 static final String yQ = 'yQ'; // Q yyyy
150 static final String yQQQ = 'yQQQ'; // QQQ yyyy
151
152 /** Date/Time format patterns. */
153 // TODO(efortuna): This are just guesses of what a full date, long date is.
154 // Do the proper homework on ICU to find the proper set "Hms"/"yMMd"
155 // applicable to each case.
156 static final String fullDate = '$y$MMMMd';
157 static final String longDate = yMMMEd;
158 static final String mediumDate = '$y$Md';
159 static final String shortDate = Md;
160 static final String fullTime = '$Hms a';
161 static final String longTime = '$Hms a zzzz';
162 static final String mediumTime = Hms;
163 static final String shortTime = Hm;
164 static final String fullDateTime = '$fullDate$fullTime';
165 static final String longDateTime = '$longDate$longTime';
166 static final String mediumDateTime = '$mediumDate$mediumTime';
167 static final String shortDateTime = '$shortDate$shortTime';
168
169 /**
170 * Constructors for dates/times that use a default format.
171 */
172 DateFormat.Hm([locale='']) : _locale = locale, _formatDefinition = Hm;
173 DateFormat.Hms([locale='']) : _locale = locale, _formatDefinition = Hms;
174 DateFormat.M([locale='']) : _locale = locale, _formatDefinition = M;
175 DateFormat.MEd([locale='']) : _locale = locale, _formatDefinition = MEd;
176 DateFormat.MMM([locale='']) : _locale = locale, _formatDefinition = MMM;
177 DateFormat.MMMEd([locale='']) : _locale = locale, _formatDefinition = MMMEd;
178 DateFormat.MMMMEd([locale='']) : _locale = locale, _formatDefinition = MMMMEd;
179 DateFormat.MMMMd([locale='']) : _locale = locale, _formatDefinition = MMMMd;
180 DateFormat.MMMd([locale='']) : _locale = locale, _formatDefinition = MMMd;
181 DateFormat.Md([locale='']) : _locale = locale, _formatDefinition = Md;
182 DateFormat.d([locale='']) : _locale = locale, _formatDefinition = d;
183 DateFormat.hm([locale='']) : _locale = locale, _formatDefinition = hm;
184 DateFormat.ms([locale='']) : _locale = locale, _formatDefinition = ms;
185 DateFormat.y([locale='']) : _locale = locale, _formatDefinition = y;
186 DateFormat.yM([locale='']) : _locale = locale, _formatDefinition = yM;
187 DateFormat.yMEd([locale='']) : _locale = locale, _formatDefinition = yMEd;
188 DateFormat.yMMM([locale='']) : _locale = locale, _formatDefinition = yMMM;
189 DateFormat.yMMMEd([locale='']) : _locale = locale, _formatDefinition = yMMMEd;
190 DateFormat.yMMMM([locale='']) : _locale = locale, _formatDefinition = yMMMM;
191 DateFormat.yQ([locale='']) : _locale = locale, _formatDefinition = yQ;
192 DateFormat.yQQQ([locale='']) : _locale = locale, _formatDefinition = yQQQ;
193
194 DateFormat.fullDate([locale='']) : _locale = locale,
195 _formatDefinition = fullDate;
196 DateFormat.longDate([locale='']) : _locale = locale,
197 _formatDefinition = longDate;
198 DateFormat.mediumDate([locale='']) : _locale = locale,
199 _formatDefinition = mediumDate;
200 DateFormat.shortDate([locale='']) : _locale = locale,
201 _formatDefinition = shortDate;
202 DateFormat.fullTime([locale='']) : _locale = locale,
203 _formatDefinition = fullTime;
204 DateFormat.longTime([locale='']) : _locale = locale,
205 _formatDefinition = longTime;
206 DateFormat.mediumTime([locale='']) : _locale = locale,
207 _formatDefinition = mediumTime;
208 DateFormat.shortTime([locale='']) : _locale = locale,
209 _formatDefinition = shortTime;
210 DateFormat.fullDateTime([locale='']) : _locale = locale,
211 _formatDefinition = fullDateTime;
212 DateFormat.longDateTime([locale='']) : _locale = locale,
213 _formatDefinition = longDateTime;
214 DateFormat.mediumDateTime([locale='']) : _locale = locale,
215 _formatDefinition = mediumDateTime;
216 DateFormat.shortDateTime([locale='']) : _locale = locale,
217 _formatDefinition = shortDateTime;
218
219 /**
220 * Constructor accepts a [formatDefinition], which can be a String, one of the
221 * predefined static forms, or a custom date format using the syntax described
222 * above. An optional [locale] can be provided for specifics of the language
223 * locale to be used, otherwise, we will attempt to infer it (acceptable if
224 * Dart is running on the client, we can infer from the browser).
225 */
226 DateFormat([formatDefinition = fullDate, locale = '']) {
227 this._formatDefinition = formatDefinition;
228 this._locale = locale;
229 } 203 }
230 204
231 /** 205 /**
232 * Given user input, attempt to parse the [inputString] into the anticipated 206 * Return a string representing [date] formatted according to our locale
233 * format. 207 * and internal format.
234 */ 208 */
235 String parse(String inputString) {
236 return inputString;
237 }
238
239 /**
240 * Format the given [date] object according to preset pattern and current
241 * locale and return a formated string for the given date.
242 */
243 String format(Date date) { 209 String format(Date date) {
244 // TODO(efortuna): readd optional TimeZone argument (or similar)? 210 // TODO(efortuna): read optional TimeZone argument (or similar)?
245 return date.toString(); 211 var result = new StringBuffer();
212 _formatFields.forEach(
Emily Fortuna 2012/08/06 22:43:00 nit: can line 212 and 213 all be one line?
Alan Knight 2012/08/08 00:47:44 Done.
213 (field) => result.add(field.format(date)));
214 return result.toString();
246 } 215 }
247 216
248 /** 217 /**
249 * Returns a date string indicating how long ago (3 hours, 2 minutes) 218 * Returns a date string indicating how long ago (3 hours, 2 minutes)
250 * something has happened or how long in the future something will happen 219 * something has happened or how long in the future something will happen
251 * given a [reference] Date relative to the current time. 220 * given a [reference] Date relative to the current time.
252 */ 221 */
253 String formatDuration(Date reference) { 222 String formatDuration(Date reference) {
254 return ''; 223 return '';
255 } 224 }
256 225
257 /** 226 /**
258 * Formats a string indicating how long ago (negative [duration]) or how far 227 * Formats a string indicating how long ago (negative [duration]) or how far
259 * in the future (positive [duration]) some time is with respect to a 228 * in the future (positive [duration]) some time is with respect to a
260 * reference [date]. 229 * reference [date].
261 */ 230 */
262 String formatDurationFrom(Duration duration, Date date) { 231 String formatDurationFrom(Duration duration, Date date) {
263 return ''; 232 return '';
264 } 233 }
265 } 234
235 /**
236 * Given user input, attempt to parse the [inputString] into the anticipated
237 * format, treating it as being in the local timezone.
238 */
239 Date parse(String inputString, [utc = false]) {
240 var dateFields = new _DateBuilder();
241 if (utc) dateFields.utc=true;
242 var stream = new _Stream(inputString);
243 _formatFields.forEach(
244 (each) => each.parse(stream, dateFields));
245 return dateFields.asDate();
246 }
247
248 /**
249 * Given user input, attempt to parse the [inputString] into the anticipated
250 * format, treating it as being in UTC.
251 */
252 Date parseUTC(String inputString) {
253 return parse(inputString, true);
254 }
255
256 /**
257 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
258 */
259 get locale() => _locale;
Emily Fortuna 2012/08/06 22:43:00 I know you can look up the type of _locale, but li
Alan Knight 2012/08/08 00:47:44 I had an idea that the style guide said not to put
260
261 /**
262 * Constructors for a set of predefined formats for which
263 * internationalized forms are known. These can be specified
264 * either as ICU constants, or as skeletons.
265 */
266 DateFormat.DAY([locale]) : this(DAY, locale);
267 DateFormat.ABBR_WEEKDAY([locale]) : this(ABBR_WEEKDAY, locale);
268 DateFormat.WEEKDAY([locale]) : this(WEEKDAY, locale);
269 DateFormat.ABBR_STANDALONE_MONTH([locale]) :
270 this(ABBR_STANDALONE_MONTH, locale);
271 DateFormat.STANDALONE_MONTH([locale]) : this(STANDALONE_MONTH, locale);
272 DateFormat.NUM_MONTH([locale]) : this(NUM_MONTH, locale);
273 DateFormat.NUM_MONTH_DAY([locale]) : this(NUM_MONTH_DAY, locale);
274 DateFormat.NUM_MONTH_WEEKDAY_DAY([locale]) :
275 this(NUM_MONTH_WEEKDAY_DAY, locale);
276 DateFormat.ABBR_MONTH([locale]) : this(ABBR_MONTH, locale);
277 DateFormat.ABBR_MONTH_DAY([locale]) : this(ABBR_MONTH_DAY, locale);
278 DateFormat.ABBR_MONTH_WEEKDAY_DAY([locale]) :
279 this(ABBR_MONTH_WEEKDAY_DAY, locale);
280 DateFormat.MONTH([locale]) : this(MONTH, locale);
281 DateFormat.MONTH_DAY([locale]) : this(MONTH_DAY, locale);
282 DateFormat.MONTH_WEEKDAY_DAY([locale]) : this(MONTH_WEEKDAY_DAY, locale);
283 DateFormat.ABBR_QUARTER([locale]) : this(ABBR_QUARTER, locale);
284 DateFormat.QUARTER([locale]) : this(QUARTER, locale);
285 DateFormat.YEAR([locale]) : this(YEAR, locale);
286 DateFormat.YEAR_NUM_MONTH([locale]) : this(YEAR_NUM_MONTH, locale);
287 DateFormat.YEAR_NUM_MONTH_DAY([locale]) : this(YEAR_NUM_MONTH_DAY, locale);
288 DateFormat.YEAR_NUM_MONTH_WEEKDAY_DAY([locale]) :
289 this(YEAR_NUM_MONTH_WEEKDAY_DAY, locale);
290 DateFormat.YEAR_ABBR_MONTH([locale]) : this(YEAR_ABBR_MONTH, locale);
291 DateFormat.YEAR_ABBR_MONTH_DAY([locale]) : this(YEAR_ABBR_MONTH_DAY, locale);
292 DateFormat.YEAR_ABBR_MONTH_WEEKDAY_DAY([locale]) :
293 this(YEAR_ABBR_MONTH_WEEKDAY_DAY, locale);
294 DateFormat.YEAR_MONTH([locale]) : this(YEAR_MONTH, locale);
295 DateFormat.YEAR_MONTH_DAY([locale]) : this(YEAR_MONTH_DAY, locale);
296 DateFormat.YEAR_MONTH_WEEKDAY_DAY([locale]) :
297 this(YEAR_MONTH_WEEKDAY_DAY, locale);
298 DateFormat.YEAR_ABBR_QUARTER([locale]) : this(YEAR_ABBR_QUARTER, locale);
299 DateFormat.YEAR_QUARTER([locale]) : this(YEAR_QUARTER, locale);
300 DateFormat.HOUR24([locale]) : this(HOUR24, locale);
301 DateFormat.HOUR24_MINUTE([locale]) : this(HOUR24_MINUTE, locale);
302 DateFormat.HOUR24_MINUTE_SECOND([locale]) :
303 this(HOUR24_MINUTE_SECOND, locale);
304 DateFormat.HOUR([locale]) : this(HOUR, locale);
305 DateFormat.HOUR_MINUTE([locale]) : this(HOUR_MINUTE, locale);
306 DateFormat.HOUR_MINUTE_SECOND([locale]) : this(HOUR_MINUTE_SECOND, locale);
307 DateFormat.HOUR_MINUTE_GENERIC_TZ([locale]) :
308 this(HOUR_MINUTE_GENERIC_TZ, locale);
309 DateFormat.HOUR_MINUTE_TZ([locale]) : this(HOUR_MINUTE_TZ, locale);
310 DateFormat.HOUR_GENERIC_TZ([locale]) : this(HOUR_GENERIC_TZ, locale);
311 DateFormat.HOUR_TZ([locale]) : this(HOUR_TZ, locale);
312 DateFormat.MINUTE([locale]) : this(MINUTE, locale);
313 DateFormat.MINUTE_SECOND([locale]) : this(MINUTE_SECOND, locale);
314 DateFormat.SECOND([locale]) : this(SECOND, locale);
315
316 DateFormat.d([locale]) : this("d", locale);
317 DateFormat.E([locale]) : this("E", locale);
318 DateFormat.EEEE([locale]) : this("EEEE", locale);
319 DateFormat.LLL([locale]) : this("LLL", locale);
320 DateFormat.LLLL([locale]) : this("LLLL", locale);
321 DateFormat.M([locale]) : this("M", locale);
322 DateFormat.Md([locale]) : this("Md", locale);
323 DateFormat.MEd([locale]) : this("MEd", locale);
324 DateFormat.MMM([locale]) : this("MMM", locale);
325 DateFormat.MMMd([locale]) : this("MMMd", locale);
326 DateFormat.MMMEd([locale]) : this("MMMEd", locale);
327 DateFormat.MMMM([locale]) : this("MMMM", locale);
328 DateFormat.MMMMd([locale]) : this("MMMMd", locale);
329 DateFormat.MMMMEEEEd([locale]) : this("MMMMEEEEd", locale);
330 DateFormat.QQQ([locale]) : this("QQQ", locale);
331 DateFormat.QQQQ([locale]) : this("QQQQ", locale);
332 DateFormat.y([locale]) : this("y", locale);
333 DateFormat.yM([locale]) : this("yM", locale);
334 DateFormat.yMd([locale]) : this("yMd", locale);
335 DateFormat.yMEd([locale]) : this("yMEd", locale);
336 DateFormat.yMMM([locale]) : this("yMMM", locale);
337 DateFormat.yMMMd([locale]) : this("yMMMd", locale);
338 DateFormat.yMMMEd([locale]) : this("yMMMEd", locale);
339 DateFormat.yMMMM([locale]) : this("yMMMM", locale);
340 DateFormat.yMMMMd([locale]) : this("yMMMMd", locale);
341 DateFormat.yMMMMEEEEd([locale]) : this("yMMMMEEEEd", locale);
342 DateFormat.yQQQ([locale]) : this("yQQQ", locale);
343 DateFormat.yQQQQ([locale]) : this("yQQQQ", locale);
344 DateFormat.H([locale]) : this("H", locale);
345 DateFormat.Hm([locale]) : this("Hm", locale);
346 DateFormat.Hms([locale]) : this("Hms", locale);
347 DateFormat.j([locale]) : this("j", locale);
348 DateFormat.jm([locale]) : this("jm", locale);
349 DateFormat.jms([locale]) : this("jms", locale);
350 DateFormat.jmv([locale]) : this("jmv", locale);
351 DateFormat.jmz([locale]) : this("jmz", locale);
352 DateFormat.jv([locale]) : this("jv", locale);
353 DateFormat.jz([locale]) : this("jz", locale);
354 DateFormat.m([locale]) : this("m", locale);
355 DateFormat.ms([locale]) : this("ms", locale);
356 DateFormat.s([locale]) : this("s", locale);
357
358 /**
359 * ICU constants for format names, resolving to the corresponding skeletons.
360 */
361 static final String DAY = 'd';
362 static final String ABBR_WEEKDAY = 'E';
363 static final String WEEKDAY = 'EEEE';
364 static final String ABBR_STANDALONE_MONTH = 'LLL';
365 static final String STANDALONE_MONTH = 'LLLL';
366 static final String NUM_MONTH = 'M';
367 static final String NUM_MONTH_DAY = 'Md';
368 static final String NUM_MONTH_WEEKDAY_DAY = 'MEd';
369 static final String ABBR_MONTH = 'MMM';
370 static final String ABBR_MONTH_DAY = 'MMMd';
371 static final String ABBR_MONTH_WEEKDAY_DAY = 'MMMEd';
372 static final String MONTH = 'MMMM';
373 static final String MONTH_DAY = 'MMMMd';
374 static final String MONTH_WEEKDAY_DAY = 'MMMMEEEEd';
375 static final String ABBR_QUARTER = 'QQQ';
376 static final String QUARTER = 'QQQQ';
377 static final String YEAR = 'y';
378 static final String YEAR_NUM_MONTH = 'yM';
379 static final String YEAR_NUM_MONTH_DAY = 'yMd';
380 static final String YEAR_NUM_MONTH_WEEKDAY_DAY = 'yMEd';
381 static final String YEAR_ABBR_MONTH = 'yMMM';
382 static final String YEAR_ABBR_MONTH_DAY = 'yMMMd';
383 static final String YEAR_ABBR_MONTH_WEEKDAY_DAY = 'yMMMEd';
384 static final String YEAR_MONTH = 'yMMMM';
385 static final String YEAR_MONTH_DAY = 'yMMMMd';
386 static final String YEAR_MONTH_WEEKDAY_DAY = 'yMMMMEEEEd';
387 static final String YEAR_ABBR_QUARTER = 'yQQQ';
388 static final String YEAR_QUARTER = 'yQQQQ';
389 static final String HOUR24 = 'H';
390 static final String HOUR24_MINUTE = 'Hm';
391 static final String HOUR24_MINUTE_SECOND = 'Hms';
392 static final String HOUR = 'j';
393 static final String HOUR_MINUTE = 'jm';
394 static final String HOUR_MINUTE_SECOND = 'jms';
395 static final String HOUR_MINUTE_GENERIC_TZ = 'jmv';
396 static final String HOUR_MINUTE_TZ = 'jmz';
397 static final String HOUR_GENERIC_TZ = 'jv';
398 static final String HOUR_TZ = 'jz';
399 static final String MINUTE = 'm';
400 static final String MINUTE_SECOND = 'ms';
401 static final String SECOND = 's';
402
403 /** The locale in which we operate, e.g. 'en_US', or 'pt'. */
404 String _locale;
405
406 /**
407 * The full template string. This may have been specified directly, or
408 * it may have been derived from a skeleton and the locale information
409 * on how to interpret that skeleton.
410 */
411 String _pattern;
412
413 /**
414 * We parse the format string into individual fields and store them here.
415 * This is what is actually used to do the formatting.
416 */
417 List<_DateFormatField> _formatFields;
418
419 /**
420 * Given a format from the user look it up in our list of known skeletons.
421 * If it's there, then use the corresponding pattern for this locale.
422 * If it's not, then treat it as an explicit pattern.
423 */
424 set pattern(String inputPattern) {
Emily Fortuna 2012/08/06 22:43:00 I'd prefer to make this private unless we have a c
Alan Knight 2012/08/08 00:47:44 Done.
425 // TODO(alanknight): This is an expensive operation. Caching recently used
426 // formats, or possibly introducing an entire "locale" object that would
427 // cache patterns for that locale could be a good optimization.
428 if (!_availableSkeletons.containsKey(inputPattern)) {
429 _pattern = inputPattern;
430 } else {
431 _pattern = _availableSkeletons[inputPattern];
432 }
433 _formatFields = parsePattern(_pattern);
434 }
435
436 /** Return the pattern that we use to format dates.*/
437 get pattern() => _pattern;
Emily Fortuna 2012/08/06 22:43:00 again, in theory the user should already know this
Alan Knight 2012/08/08 00:47:44 Somebody probably at some point called a construct
438
439 /** Return the skeletons for our current locale. */
440 Map get _availableSkeletons() {
441 return dateTimePatterns[locale];
442 }
443
444 /**
445 * Set the locale. If the locale can't be found, we also look up
446 * based on alternative versions, e.g. if we have no 'en_CA' we will
447 * look for 'en' as a fallback. It will also translate en-ca into en_CA.
448 * Null is also considered a valid value for [newLocale], indicating
449 * to use the default.
450 */
451 _setLocale(String newLocale) {
452 _locale = Intl.verifiedLocale(newLocale);
453 }
454
455 /**
456 * Return true if the locale exists, or if it is null. The null case
457 * is interpreted to mean that we use the default locale.
458 */
459 static bool localeExists(localeName) {
460 if (localeName == null) return false;
461 return dateTimeSymbols.containsKey(localeName);
462 }
463
464 /**
465 * A series of regular expression used to parse a format string into its
Emily Fortuna 2012/08/06 22:43:00 expression => expressions
Alan Knight 2012/08/08 00:47:44 Done.
466 * component fields.
467 */
468 static var _matchers = const [
469 // Quoted String - anything between single quotes, with escaping
Emily Fortuna 2012/08/06 22:43:00 too much indentation?
Alan Knight 2012/08/08 00:47:44 Done.
470 // of single quotes by doubling them.
471 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock'
472 const RegExp("^\'(?:[^\']|\'\')*\'"),
473 // Fields - any sequence of 1 or more of the same field characters.
474 // e.g. in "hh:mm:ss" will match hh,mm, and ss. But in "hms" would
475 // match each letter individually.
476 const RegExp(
477 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|z+|Z+)"),
478 // Everything else - A sequence that is not quotes or field characters.
479 // e.g. in "hh:mm:ss" will match the colons.
480 const RegExp("^[^\'GyMkSEahKHcLQdmsvzZ]+")
481 ];
482
483 // TODO(alanknight): This can be a variable once that's permitted.
484 static List get _fieldConstructors() => [
485 (pattern,parent) => new _DateFormatQuotedField(pattern, parent),
486 (pattern,parent) => new _DateFormatPatternField(pattern,parent),
487 (pattern,parent) => new _DateFormatLiteralField(pattern,parent)];
488
489 /** Parse the template pattern and return a list of field objects.*/
490 List parsePattern(String pattern) {
491 if (pattern == null) return null;
492 return _reverse(_parsePatternHelper(pattern));
493 }
494
495 /** Recursive helper for parsing the template pattern. */
496 List _parsePatternHelper(String pattern) {
497 if (pattern.isEmpty()) return [];
498
499 var matched = _match(pattern);
500 if (matched == null) return [];
501
502 var parsed = _parsePatternHelper(
503 pattern.substring(matched.fullPattern().length));
504 parsed.add(matched);
505 return parsed;
506 }
507
508 /** Find elements in a string that are patterns for specific fields.*/
509 _DateFormatField _match(String pattern) {
510 for (var i = 0; i < _matchers.length; i++) {
511 var regex = _matchers[i];
512 var match = regex.firstMatch(pattern);
513 if (match != null) {
514 return _fieldConstructors[i](match.group(0),this);
Emily Fortuna 2012/08/06 22:43:00 space after ,
Emily Fortuna 2012/08/06 22:43:00 space after ,
Alan Knight 2012/08/08 00:47:44 Done.
Alan Knight 2012/08/08 00:47:44 Done.
515 }
516 }
517 }
518
519 /** Polyfill for missing library function. Issue 2804 */
Emily Fortuna 2012/08/06 22:43:00 Thanks for adding the issue number. Adding a comme
Alan Knight 2012/08/08 00:47:44 Done.
520 List _reverse(List list) {
521 var result = new List();
522 for (var i = list.length-1; i >= 0; i--) {
523 result.addLast(list[i]);
524 }
525 return result;
526 }
527 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698