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

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
« no previous file with comments | « no previous file | lib/i18n/date_symbol_data.dart » ('j') | lib/i18n/date_symbol_data.dart » ('J')
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.
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
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
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. 105 * Items marked with '#' work differently than in Java.
Emily Fortuna 2012/07/31 05:52:53 Now that this is implemented, does it in fact work
Alan Knight 2012/08/03 23:02:15 Good question. Shanjian believes this refers to is
47 * 106 *
48 * The count of pattern letters determine the format. 107 * The count of pattern letters determine the format.
49 * **Text**: 108 * **Text**:
109 * * 5 pattern letters--use narrow form for standalone. Otherwise does not apply
50 * * 4 or more pattern letters--use full form, 110 * * 4 or more pattern letters--use full form,
51 * * less than 4--use short or abbreviated form if one exists. 111 * * 3 pattern letters--use short or abbreviated form if one exists
52 * In parsing, we will always try long format, then short. 112 * * less than 3--use numeric form if one exists
53 * (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")
54 * 113 *
55 * **Number**: the minimum number of digits. Shorter numbers are zero-padded to 114 * **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 115 * 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 116 * 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 117 * 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other
59 * fields, fractional seconds are padded on the right with zero. 118 * fields, fractional seconds are padded on the right with zero.
60 * 119 *
61 * (Text & Number): 3 or over, use text, otherwise use number. 120 * (Text & Number): 3 or over, use text, otherwise use number.
62 * 121 *
63 * Any characters that not in the pattern will be treated as quoted text. For 122 * Any characters that not in the pattern will be treated as quoted text. For
64 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the 123 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the
65 * resulting time text even they are not embraced within single quotes. In our 124 * 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 125 * 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. 126 * 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. 127 * That's because we may use other letter for pattern in future.
69 * 128 *
70 * Examples Using the US Locale: 129 * Examples Using the US Locale:
71 * 130 *
72 * Format Pattern Result 131 * Format Pattern Result
73 * -------------- ------- 132 * -------------- -------
74 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->> 1996.07.10 AD at 15:08:56 Pacific Ti me 133 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->1996.07.10 AD at 15:08:56 Pacific Time
Emily Fortuna 2012/07/31 05:52:53 When was the last time you sync'd? This was fixed
Alan Knight 2012/08/03 23:02:15 Yes, it was before you left.
75 * "EEE, MMM d, ''yy" ->> Wed, July 10, '96 134 * "EEE, MMM d, ''yy" ->Wed, July 10, '96
76 * "h:mm a" ->> 12:08 PM 135 * "h:mm a" ->12:08 PM
77 * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time 136 * "hh 'o''clock' a, zzzz" ->12 o'clock PM, Pacific Daylight Time
78 * "K:mm a, vvv" ->> 0:00 PM, PT 137 * "K:mm a, vvv" ->0:00 PM, PT
79 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM 138 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->01996.July.10 AD 12:08 PM
80 * 139 *
81 * When parsing a date string using the abbreviated year pattern ("yy"), 140 * When parsing a date string using the abbreviated year pattern ("yy"),
82 * DateTimeParse must interpret the abbreviated year relative to some 141 * 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 142 * 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 143 * 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, 144 * 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 145 * 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 146 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only
88 * strings consisting of exactly two digits, as defined by {@link 147 * strings consisting of exactly two digits, as defined by {@link
89 * java.lang.Character#isDigit(char)}, will be parsed into the default 148 * 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 149 * 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. 150 * more digit string will be interpreted as its face value.
92 * 151 *
93 * 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
94 * interpreted literally, regardless of the number of digits. So using the 153 * 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. 154 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
96 * 155 *
97 * When numeric fields abut one another directly, with no intervening 156 * When numeric fields abut one another directly, with no intervening
98 * delimiter characters, they constitute a run of abutting numeric fields. Such 157 * delimiter characters, they constitute a run of abutting numeric fields. Such
99 * runs are parsed specially. For example, the format "HHmmss" parses the input 158 * 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 159 * 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 160 * 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 161 * 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 162 * 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 163 * 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 164 * or the leftmost field is one character in length. If the parse still fails at
106 * that point, the parse of the run fails. 165 * that point, the parse of the run fails.
107 */ 166 */
108 167
109 #library('date_format'); 168 #library('date_format');
169 #import('intl.dart');
170 #import('date_time_patterns.dart');
171 #import('date_symbols.dart');
172 #import('date_symbol_data.dart');
110 173
111 class DateFormat { 174 class DateFormat {
112 175
113 /** Definition of how this object formats dates. */ 176 /**
114 String _formatDefinition; 177 * The constructor accepts a [formatDefinition] which is a String. If the
Emily Fortuna 2012/07/31 05:52:53 1) No need to say formatDefinition is a string, ju
Alan Knight 2012/08/03 23:02:15 Done.
115 178 * [formatDefinition] matches one of the skeleton forms, it is looked up
116 /** 179 * in the [locale] or in a default if none is specified, and the corresponding
117 * The locale code with which the message is to be formatted (such as en-CA). 180 * full format string is used. If the [formatDefinition] does not match one
118 */ 181 * of the supported skeleton forms then it is used as a format directly.
119 String _locale; 182 * For example, in an en_US locale, specifying the skeleton
120 183 * new DateFormat('yMEd');
121 /** 184 * or the explicit
122 * Date/Time format "skeleton" patterns. Also specifiable by String, but 185 * new DateFormat('EEE, M/d/y');
123 * written this way so that they can be discoverable via autocomplete. These 186 * would produce the same result, a date of the form
124 * follow the ICU syntax described at the top of the file. These skeletons can 187 * Wed, 6/27/2012
125 * be combined and we will attempt to find the best format for each locale 188 * However, the skeleton version would also adapt to other locales.
126 * given the pattern. 189 * If [locale] does not exist in our set of supported locales then an
127 */ 190 * IllegalArgumentException is thrown.
128 // TODO(efortuna): Hear back from i18n about Time Zones and the "core set" 191 */
129 // of skeleton patterns. 192 DateFormat([String newPattern, String locale]) {
130 // Example of how this looks in the US 193 //TODO(alanknight): It should be possible to specify multiple skeletons, e.g
Emily Fortuna 2012/07/31 05:52:53 indent these comments two more spaces. Also, add a
Alan Knight 2012/08/03 23:02:15 Done, along with other occurrences.
131 // locale. 194 // date, time, timezone all separately. Adding many or named parameters to
132 static final String Hm = 'Hm'; // HH:mm 195 // the constructor seems awkward, especially with the possibility of confusion
133 static final String Hms = 'Hms'; // HH:mm:ss 196 // with the locale. A "fluent" interface with cascading on an instance might
134 static final String M = 'M'; // L 197 // work better? A list of patterns is also possible.
135 static final String MEd = 'MEd'; // E, M/d 198 //TODO(alanknight): There will need to be at least setup type async operations
136 static final String MMM = 'MMM'; // LLL 199 // to avoid the need to bring along every locale in every program using this.
137 static final String MMMEd = 'MMMEd'; // E, MMM d 200 _setLocale(locale);
138 static final String MMMMEd = 'MMMMEd'; // E, MMMM d 201 pattern = newPattern;
139 static final String MMMMd = 'MMMMd'; // MMMM d 202 }
140 static final String MMMd = 'MMMd'; // MMM d 203
141 static final String Md = 'Md'; // M/d 204 /**
142 static final String d = 'd'; // d 205 * Return a string representing [date] formatted according to our locale
143 static final String hm = 'hm'; // h:mm a 206 * and internal format.
144 static final String ms = 'ms'; // mm:ss 207 */
145 static final String y = 'y'; // yyyy
146 static final String yM = 'yM'; // M/yyyy
147 static final String yMEd = 'yMEd'; // EEE, M/d/yyyy
148 static final String yMMM = 'yMMM'; // MMM yyyy
149 static final String yMMMEd = 'yMMMEd'; // EEE, MM d, yyyy
150 static final String yMMMM = 'yMMMM'; // MMMM yyyy
151 static final String yQ = 'yQ'; // Q yyyy
152 static final String yQQQ = 'yQQQ'; // QQQ yyyy
153
154 /** Date/Time format patterns. */
155 // TODO(efortuna): This are just guesses of what a full date, long date is.
156 // Do the proper homework on ICU to find the proper set "Hms"/"yMMd"
157 // applicable to each case.
158 static final String fullDate = '$y$MMMMd';
159 static final String longDate = yMMMEd;
160 static final String mediumDate = '$y$Md';
161 static final String shortDate = Md;
162 static final String fullTime = '$Hms a';
163 static final String longTime = '$Hms a zzzz';
164 static final String mediumTime = Hms;
165 static final String shortTime = Hm;
166 static final String fullDateTime = '$fullDate$fullTime';
167 static final String longDateTime = '$longDate$longTime';
168 static final String mediumDateTime = '$mediumDate$mediumTime';
169 static final String shortDateTime = '$shortDate$shortTime';
170
171 /**
172 * Constructors for dates/times that use a default format.
173 */
174 DateFormat.Hm([this._locale]) : _formatDefinition = Hm;
175 DateFormat.Hms([this._locale]) : _formatDefinition = Hms;
176 DateFormat.M([this._locale]) : _formatDefinition = M;
177 DateFormat.MEd([this._locale]) : _formatDefinition = MEd;
178 DateFormat.MMM([this._locale]) : _formatDefinition = MMM;
179 DateFormat.MMMEd([this._locale]) : _formatDefinition = MMMEd;
180 DateFormat.MMMMEd([this._locale]) : _formatDefinition = MMMMEd;
181 DateFormat.MMMMd([this._locale]) : _formatDefinition = MMMMd;
182 DateFormat.MMMd([this._locale]) : _formatDefinition = MMMd;
183 DateFormat.Md([this._locale]) : _formatDefinition = Md;
184 DateFormat.d([this._locale]) : _formatDefinition = d;
185 DateFormat.hm([this._locale]) : _formatDefinition = hm;
186 DateFormat.ms([this._locale]) : _formatDefinition = ms;
187 DateFormat.y([this._locale]) : _formatDefinition = y;
188 DateFormat.yM([this._locale]) : _formatDefinition = yM;
189 DateFormat.yMEd([this._locale]) : _formatDefinition = yMEd;
190 DateFormat.yMMM([this._locale]) : _formatDefinition = yMMM;
191 DateFormat.yMMMEd([this._locale]) : _formatDefinition = yMMMEd;
192 DateFormat.yMMMM([this._locale]) : _formatDefinition = yMMMM;
193 DateFormat.yQ([this._locale]) : _formatDefinition = yQ;
194 DateFormat.yQQQ([this._locale]) : _formatDefinition = yQQQ;
195
196 DateFormat.fullDate([this._locale]) : _formatDefinition = fullDate;
197 DateFormat.longDate([this._locale]) : _formatDefinition = longDate;
198 DateFormat.mediumDate([this._locale]) : _formatDefinition = mediumDate;
199 DateFormat.shortDate([this._locale]) : _formatDefinition = shortDate;
200 DateFormat.fullTime([this._locale]) : _formatDefinition = fullTime;
201 DateFormat.longTime([this._locale]) : _formatDefinition = longTime;
202 DateFormat.mediumTime([this._locale]) : _formatDefinition = mediumTime;
203 DateFormat.shortTime([this._locale]) : _formatDefinition = shortTime;
204 DateFormat.fullDateTime([this._locale]) : _formatDefinition = fullDateTime;
205 DateFormat.longDateTime([this._locale]) : _formatDefinition = longDateTime;
206 DateFormat.mediumDateTime([this._locale]) : _formatDefinition = mediumDateTime ;
207 DateFormat.shortDateTime([this._locale]) : _formatDefinition = shortDateTime;
208
209 /**
210 * Constructor accepts a [formatDefinition], which can be a String, one of the
211 * predefined static forms, or a custom date format using the syntax described
212 * above. An optional [locale] can be provided for specifics of the language
213 * locale to be used, otherwise, we will attempt to infer it (acceptable if
214 * Dart is running on the client, we can infer from the browser).
215 */
216 DateFormat([formatDefinition = fullDate, locale]) {
217 this._formatDefinition = formatDefinition;
218 this._locale = locale;
219 }
220
221 /**
222 * Given user input, attempt to parse the [inputString] into the anticipated
223 * format.
224 */
225 String parse(String inputString) {
226 return inputString;
227 }
228
229 /**
230 * Format the given [date] object according to preset pattern and current
231 * locale and return a formated string for the given date.
232 */
233 String format(Date date) { 208 String format(Date date) {
234 // TODO(efortuna): readd optional TimeZone argument (or similar)? 209 // TODO(efortuna): read optional TimeZone argument (or similar)?
235 return date.toString(); 210 var result = new StringBuffer();
236 } 211 _formatFields.forEach(
237 212 (field) => result.add(field.format(date))
238 /** 213 );
239 * Returns a date string indicating how long ago (3 hours, 2 minutes) 214 return result.toString();
240 * something has happened or how long in the future something will happen 215 }
241 * given a [reference] Date relative to the current time. 216
242 */ 217 /**
218 * Returns a date string indicating how long ago (3 hours, 2 minutes)
Emily Fortuna 2012/07/31 05:52:53 remove the extra whitespace here. comments should
Alan Knight 2012/08/03 23:02:15 Done.
219 * something has happened or how long in the future something will happen
220 * given a [reference] Date relative to the current time.
221 */
243 String formatDuration(Date reference) { 222 String formatDuration(Date reference) {
244 return ''; 223 return '';
245 } 224 }
246 225
247 /** 226 /**
248 * 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
249 * 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
250 * reference [date]. 229 * reference [date].
251 */ 230 */
252 String formatDurationFrom(Duration duration, Date date) { 231 String formatDurationFrom(Duration duration, Date date) {
253 return ''; 232 return '';
254 } 233 }
234
235 /**
236 * Given user input, attempt to parse the [inputString] into the anticipated
237 * format, using the local machine timezone.
238 */
239 Date parse(String inputString) {
240 var dateFields = new _DateBuilder();
241 var stream = new _Stream(inputString);
242 _formatFields.forEach(
Emily Fortuna 2012/07/31 05:52:53 indentation??
Alan Knight 2012/08/03 23:02:15 Done.
243 (each) {
Emily Fortuna 2012/07/31 05:52:53 this looks like a great case for (each) => each.pa
Alan Knight 2012/08/03 23:02:15 Done.
244 each.parse(stream, dateFields);
245 }
246 );
247 return dateFields.asDate();
248 }
249
250 /**
251 * Given user input, attempt to parse the [inputString] into the anticipated
252 * format, using UTC.
Emily Fortuna 2012/07/31 05:52:53 The inputString is in UTC, or we parse the result
Alan Knight 2012/08/03 23:02:15 That is rather ambiguous. Changed it so that it's
253 */
254 Date parseUTC(String inputString) {
255 return parse(inputString).toUtc();
256 }
257
258 /**
259 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
260 */
261 String get locale() {
262 if (_locale == null) {
263 return Intl.defaultLocale;
264 } else {
265 return _locale;}
Emily Fortuna 2012/07/31 05:52:53 move } to new line.
Alan Knight 2012/08/03 23:02:15 Done.
266 }
267
268 /**
269 * Constructors for a set of predefined formats for which
270 * internationalized forms are known. These can be specified
271 * either as ICU constants, or as skeletons.
272 */
273 DateFormat.DAY([locale]) : this(DAY, locale);
274 DateFormat.ABBR_WEEKDAY([locale]) : this(ABBR_WEEKDAY, locale);
275 DateFormat.WEEKDAY([locale]) : this(WEEKDAY, locale);
276 DateFormat.ABBR_STANDALONE_MONTH([locale]) :
277 this(ABBR_STANDALONE_MONTH, locale);
278 DateFormat.STANDALONE_MONTH([locale]) : this(STANDALONE_MONTH, locale);
279 DateFormat.NUM_MONTH([locale]) : this(NUM_MONTH, locale);
280 DateFormat.NUM_MONTH_DAY([locale]) : this(NUM_MONTH_DAY, locale);
281 DateFormat.NUM_MONTH_WEEKDAY_DAY([locale]) :
282 this(NUM_MONTH_WEEKDAY_DAY, locale);
283 DateFormat.ABBR_MONTH([locale]) : this(ABBR_MONTH, locale);
284 DateFormat.ABBR_MONTH_DAY([locale]) : this(ABBR_MONTH_DAY, locale);
285 DateFormat.ABBR_MONTH_WEEKDAY_DAY([locale]) :
286 this(ABBR_MONTH_WEEKDAY_DAY, locale);
287 DateFormat.MONTH([locale]) : this(MONTH, locale);
288 DateFormat.MONTH_DAY([locale]) : this(MONTH_DAY, locale);
289 DateFormat.MONTH_WEEKDAY_DAY([locale]) : this(MONTH_WEEKDAY_DAY, locale);
290 DateFormat.ABBR_QUARTER([locale]) : this(ABBR_QUARTER, locale);
291 DateFormat.QUARTER([locale]) : this(QUARTER, locale);
292 DateFormat.YEAR([locale]) : this(YEAR, locale);
293 DateFormat.YEAR_NUM_MONTH([locale]) : this(YEAR_NUM_MONTH, locale);
294 DateFormat.YEAR_NUM_MONTH_DAY([locale]) : this(YEAR_NUM_MONTH_DAY, locale);
295 DateFormat.YEAR_NUM_MONTH_WEEKDAY_DAY([locale]) :
296 this(YEAR_NUM_MONTH_WEEKDAY_DAY, locale);
Emily Fortuna 2012/07/31 05:52:53 if you have to break a line, the next line should
Alan Knight 2012/08/03 23:02:15 Done.
297 DateFormat.YEAR_ABBR_MONTH([locale]) : this(YEAR_ABBR_MONTH, locale);
298 DateFormat.YEAR_ABBR_MONTH_DAY([locale]) : this(YEAR_ABBR_MONTH_DAY, locale);
299 DateFormat.YEAR_ABBR_MONTH_WEEKDAY_DAY([locale]) :
300 this(YEAR_ABBR_MONTH_WEEKDAY_DAY, locale);
301 DateFormat.YEAR_MONTH([locale]) : this(YEAR_MONTH, locale);
302 DateFormat.YEAR_MONTH_DAY([locale]) : this(YEAR_MONTH_DAY, locale);
303 DateFormat.YEAR_MONTH_WEEKDAY_DAY([locale]) :
304 this(YEAR_MONTH_WEEKDAY_DAY, locale);
305 DateFormat.YEAR_ABBR_QUARTER([locale]) : this(YEAR_ABBR_QUARTER, locale);
306 DateFormat.YEAR_QUARTER([locale]) : this(YEAR_QUARTER, locale);
307 DateFormat.HOUR24([locale]) : this(HOUR24, locale);
308 DateFormat.HOUR24_MINUTE([locale]) : this(HOUR24_MINUTE, locale);
309 DateFormat.HOUR24_MINUTE_SECOND([locale]) :
310 this(HOUR24_MINUTE_SECOND, locale);
311 DateFormat.HOUR([locale]) : this(HOUR, locale);
312 DateFormat.HOUR_MINUTE([locale]) : this(HOUR_MINUTE, locale);
313 DateFormat.HOUR_MINUTE_SECOND([locale]) : this(HOUR_MINUTE_SECOND, locale);
314 DateFormat.HOUR_MINUTE_GENERIC_TZ([locale]) :
315 this(HOUR_MINUTE_GENERIC_TZ, locale);
316 DateFormat.HOUR_MINUTE_TZ([locale]) : this(HOUR_MINUTE_TZ, locale);
317 DateFormat.HOUR_GENERIC_TZ([locale]) : this(HOUR_GENERIC_TZ, locale);
318 DateFormat.HOUR_TZ([locale]) : this(HOUR_TZ, locale);
319 DateFormat.MINUTE([locale]) : this(MINUTE, locale);
320 DateFormat.MINUTE_SECOND([locale]) : this(MINUTE_SECOND, locale);
321 DateFormat.SECOND([locale]) : this(SECOND, locale);
322
323 DateFormat.d([locale]) : this("d", locale);
324 DateFormat.E([locale]) : this("E", locale);
325 DateFormat.EEEE([locale]) : this("EEEE", locale);
326 DateFormat.LLL([locale]) : this("LLL", locale);
327 DateFormat.LLLL([locale]) : this("LLLL", locale);
328 DateFormat.M([locale]) : this("M", locale);
329 DateFormat.Md([locale]) : this("Md", locale);
330 DateFormat.MEd([locale]) : this("MEd", locale);
331 DateFormat.MMM([locale]) : this("MMM", locale);
332 DateFormat.MMMd([locale]) : this("MMMd", locale);
333 DateFormat.MMMEd([locale]) : this("MMMEd", locale);
334 DateFormat.MMMM([locale]) : this("MMMM", locale);
335 DateFormat.MMMMd([locale]) : this("MMMMd", locale);
336 DateFormat.MMMMEEEEd([locale]) : this("MMMMEEEEd", locale);
337 DateFormat.QQQ([locale]) : this("QQQ", locale);
338 DateFormat.QQQQ([locale]) : this("QQQQ", locale);
339 DateFormat.y([locale]) : this("y", locale);
340 DateFormat.yM([locale]) : this("yM", locale);
341 DateFormat.yMd([locale]) : this("yMd", locale);
342 DateFormat.yMEd([locale]) : this("yMEd", locale);
343 DateFormat.yMMM([locale]) : this("yMMM", locale);
344 DateFormat.yMMMd([locale]) : this("yMMMd", locale);
345 DateFormat.yMMMEd([locale]) : this("yMMMEd", locale);
346 DateFormat.yMMMM([locale]) : this("yMMMM", locale);
347 DateFormat.yMMMMd([locale]) : this("yMMMMd", locale);
348 DateFormat.yMMMMEEEEd([locale]) : this("yMMMMEEEEd", locale);
349 DateFormat.yQQQ([locale]) : this("yQQQ", locale);
350 DateFormat.yQQQQ([locale]) : this("yQQQQ", locale);
351 DateFormat.H([locale]) : this("H", locale);
352 DateFormat.Hm([locale]) : this("Hm", locale);
353 DateFormat.Hms([locale]) : this("Hms", locale);
354 DateFormat.j([locale]) : this("j", locale);
355 DateFormat.jm([locale]) : this("jm", locale);
356 DateFormat.jms([locale]) : this("jms", locale);
357 DateFormat.jmv([locale]) : this("jmv", locale);
358 DateFormat.jmz([locale]) : this("jmz", locale);
359 DateFormat.jv([locale]) : this("jv", locale);
360 DateFormat.jz([locale]) : this("jz", locale);
361 DateFormat.m([locale]) : this("m", locale);
362 DateFormat.ms([locale]) : this("ms", locale);
363 DateFormat.s([locale]) : this("s", locale);
364
365 /**
366 * ICU constants for format names, resolving to the corresponding skeletons.
367 */
368 static final String DAY = 'd';
Emily Fortuna 2012/07/31 05:52:53 Perhaps I'm missing something here. Why doesn't th
Alan Knight 2012/08/03 23:02:15 An interesting point. But apparently it doesn't. C
369 static final String ABBR_WEEKDAY = 'E';
370 static final String WEEKDAY = 'EEEE';
371 static final String ABBR_STANDALONE_MONTH = 'LLL';
372 static final String STANDALONE_MONTH = 'LLLL';
373 static final String NUM_MONTH = 'M';
374 static final String NUM_MONTH_DAY = 'Md';
375 static final String NUM_MONTH_WEEKDAY_DAY = 'MEd';
376 static final String ABBR_MONTH = 'MMM';
377 static final String ABBR_MONTH_DAY = 'MMMd';
378 static final String ABBR_MONTH_WEEKDAY_DAY = 'MMMEd';
379 static final String MONTH = 'MMMM';
380 static final String MONTH_DAY = 'MMMMd';
381 static final String MONTH_WEEKDAY_DAY = 'MMMMEEEEd';
382 static final String ABBR_QUARTER = 'QQQ';
383 static final String QUARTER = 'QQQQ';
384 static final String YEAR = 'y';
385 static final String YEAR_NUM_MONTH = 'yM';
386 static final String YEAR_NUM_MONTH_DAY = 'yMd';
387 static final String YEAR_NUM_MONTH_WEEKDAY_DAY = 'yMEd';
388 static final String YEAR_ABBR_MONTH = 'yMMM';
389 static final String YEAR_ABBR_MONTH_DAY = 'yMMMd';
390 static final String YEAR_ABBR_MONTH_WEEKDAY_DAY = 'yMMMEd';
391 static final String YEAR_MONTH = 'yMMMM';
392 static final String YEAR_MONTH_DAY = 'yMMMMd';
393 static final String YEAR_MONTH_WEEKDAY_DAY = 'yMMMMEEEEd';
394 static final String YEAR_ABBR_QUARTER = 'yQQQ';
395 static final String YEAR_QUARTER = 'yQQQQ';
396 static final String HOUR24 = 'H';
397 static final String HOUR24_MINUTE = 'Hm';
398 static final String HOUR24_MINUTE_SECOND = 'Hms';
399 static final String HOUR = 'j';
400 static final String HOUR_MINUTE = 'jm';
401 static final String HOUR_MINUTE_SECOND = 'jms';
402 static final String HOUR_MINUTE_GENERIC_TZ = 'jmv';
403 static final String HOUR_MINUTE_TZ = 'jmz';
404 static final String HOUR_GENERIC_TZ = 'jv';
405 static final String HOUR_TZ = 'jz';
406 static final String MINUTE = 'm';
407 static final String MINUTE_SECOND = 'ms';
408 static final String SECOND = 's';
409
410 /** The locale in which we operate, e.g. 'en_US', or 'pt'. */
411 String _locale;
412
413 /**
414 * The full template string. This may have been specified directly, or
415 * it may have been derived from a skeleton and the locale information
416 * on how to interpret that skeleton.
417 */
418 String _pattern;
Emily Fortuna 2012/07/31 05:52:53 -1 space indentation.
Alan Knight 2012/08/03 23:02:15 Done.
419
420 /**
421 * We parse the format string into individual fields and store them here.
422 * This is what is actually used to do the formatting.
423 */
424 List<_DateFormatField> _formatFields;
425
426 /**
427 * Given a format from the user look it up in our list of known skeletons.
428 * If it's there, then use the corresponding pattern for this locale.
429 * If it's not, then treat it as an explicit pattern.
430 */
431 set pattern(String inputPattern) {
432 //TODO(alanknight): This is an expensive operation. Caching recently used
Emily Fortuna 2012/07/31 05:52:53 this never gets used/tested? Why provide this func
Alan Knight 2012/08/03 23:02:15 The constructor calls this. It's also possible, an
433 // formats, or possibly introducing an entire "locale" object that would
434 // cache patterns for that locale could be a good optimization.
435 if (!availableSkeletons.containsKey(inputPattern)) {
436 _pattern = inputPattern;
437 } else {
438 _pattern = availableSkeletons[inputPattern];
439 }
440 _formatFields = parsePattern(_pattern);
441 }
442
443 /** Return the pattern that we use to format dates.*/
444 get pattern() => _pattern;
445
446 /** Return the skeletons for our current locale. */
447 Map get availableSkeletons() {
Emily Fortuna 2012/07/31 05:52:53 do we want this to be a public function? or is th
Alan Knight 2012/08/03 23:02:15 Changed to private. I had a lot more private, but
448 return dateTimePatterns[locale];
449 }
450
451 /**
452 * Return true if the locale exists, or if it is null. The null case
Emily Fortuna 2012/07/31 05:52:53 seems like all of this locale code is not unique t
Alan Knight 2012/08/03 23:02:15 As per this and later comment, moved into Intl as
453 * is interpreted to mean that we use the default locale.
454 */
455 bool _localeExists(localeName) {
Emily Fortuna 2012/07/31 05:52:53 -1 space indentation. Please check that your code
Alan Knight 2012/08/03 23:02:15 Sorry, I did try to find formatting issues, but am
Emily Fortuna 2012/08/06 22:43:00 Last I heard the editor team was going to be worki
456 if (localeName == null) return true;
457 return dateTimeSymbols.containsKey(localeName);
458 }
459
460 /**
461 * Set the locale. If the locale can't be found, we also look up
462 * based on alternative versions, e.g. if we have no 'en_CA' we will
463 * look for 'en' as a fallback. It will also translate en-ca into en_CA.
464 * Null is also considered a valid value for [newLocale], indicating
465 * to use the default.
466 */
467 _setLocale(String newLocale) {
468 //TODO(alanknight): We may want to make this available more generally in
469 // i18n
Emily Fortuna 2012/07/31 05:52:53 comments are a complete sentence and end in "." Pl
470 if (_localeExists(newLocale)) {
471 return _locale = newLocale;
472 }
473 for (var each in [_canonicalized(newLocale), _shortLocale(newLocale)]) {
474 if (_localeExists(each)) {
475 return _locale = each;
Emily Fortuna 2012/07/31 05:52:53 don't combine assignment with return values. It's
Alan Knight 2012/08/03 23:02:15 Done.
476 }
477 }
478 throw new IllegalArgumentException("Invalid locale '$newLocale'");
479 }
480
481 /** Return the short version of a locale name, e.g. 'en_US' => 'en' */
482 String _shortLocale(aLocale) {
483 if (aLocale.length < 2) return aLocale;
Emily Fortuna 2012/07/31 05:52:53 are there locales that are just one letter? If not
Alan Knight 2012/08/03 23:02:15 Locales should be at least two letters. This deals
484 return aLocale.substring(0,2).toLowerCase();
485 }
486
487 /**
488 * Return a locale name turned into xx_YY where it might possibly be
489 * in the wrong case or with a hyphen instead of an underscore.
490 */
491 String _canonicalized(aLocale) {
492 if (aLocale.length != 5) return aLocale;
Emily Fortuna 2012/07/31 05:52:53 what other acceptable locales are there? if it's j
Alan Knight 2012/08/03 23:02:15 Locales should be of the form either 'en' or 'en_U
Emily Fortuna 2012/08/06 22:43:00 This is fine. I just wanted to understand the case
493 if (aLocale[2] != '-' && (aLocale[2] != '_')) return aLocale;
494 return '${aLocale[0]}${aLocale[1]}_${aLocale[3].toUpperCase()}'
495 '${aLocale[4].toUpperCase()}';
496 }
497
498 /**
499 * A series of regular expression used to parse a format string into its
500 * component fields.
501 */
502 static var _matchers = const [
503 // Quoted String
Emily Fortuna 2012/07/31 05:52:53 an example for this?
Alan Knight 2012/08/03 23:02:15 Done.
504 const RegExp("^\'(?:[^\']|\'\')*\'"),
505 // Fields
506 const RegExp(
507 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|z+|Z+)"),
508 // Everything Else
509 const RegExp("^[^\'GyMkSEahKHcLQdmsvzZ]+")
510 ];
511
512 /** Parse the template pattern and return a list of field objects.*/
513 List parsePattern(String pattern) {
514 if (pattern == null) return null;
515 return _reverse(_parsePatternHelper(pattern));
516 }
517
518 /** Recursive helper for parsing the template pattern. */
519 List _parsePatternHelper(String pattern) {
520 if (pattern.isEmpty()) return [ ];
Emily Fortuna 2012/07/31 05:52:53 []
Alan Knight 2012/08/03 23:02:15 Done.
521
522 var matched = _match(pattern);
523 if (matched == null) return [ ];
524
525 var parsed = _parsePatternHelper(pattern.substring(matched.string.length));
526 matched.patchQuotes();
527 parsed.add(matched);
528 return parsed;
529 }
530
531 /** Find elements in a string that are patterns for specific fields.*/
532 _DateFormatField _match(String pattern) {
533 for (var i = 0; i < _matchers.length; i++) {
534 var regex = _matchers[i];
535 var match = regex.firstMatch(pattern);
536 if (match != null) {
537 return new _DateFormatField(
538 match.group(0),
539 _DateFormatField._matchTypes[i],
540 this);
541 }
542 }
543 }
544
545 /** Polyfill for missing library function. */
Emily Fortuna 2012/07/31 05:52:53 is there a bug filed /feature request? If so, plea
Alan Knight 2012/08/03 23:02:15 Done.
546 List _reverse(List list) {
547 var result = new List();
548 for (var i = list.length-1; i >= 0; i--) {
549 result.addLast(list[i]);
550 }
551 return result;
552 }
255 } 553 }
554
555 /**
556 * This is a private class internal to DateFormat which is used for formatting
557 * particular fields in a template. e.g. if the format is hh:mm:ss then the
558 * fields would be "hh", ":", "mm", ":", and "ss". Each type of field knows
559 * how to format that portion of a date.
560 */
561
562 //TODO(alanknight): Is including these all in the same file as private the best
Emily Fortuna 2012/07/31 05:52:53 source seems more readable than a really long file
Alan Knight 2012/08/03 23:02:15 OK, split out into two additional files, one for t
563 // way to organize this? Would a separate library but public make it easier
564 // to test them independently?
565 class _DateFormatField {
566 /** The possible types of field. Any instance of this must be one of these */
567 static List<String> _matchTypes = const ['QUOTED_STRING', 'FIELD', 'LITERAL'];
Emily Fortuna 2012/07/31 05:52:53 Use an enum pattern here. See https://groups.googl
Alan Knight 2012/08/03 23:02:15 Rather than an enum, split the field class into th
568
569 /** The format string that defines us, e.g. "hh" */
570 String string;
Emily Fortuna 2012/07/31 05:52:53 can you give this variable a different name?
Alan Knight 2012/08/03 23:02:15 Done.
571
572 /** The type of field we represent. See _matchTypes for possible values. */
573 String type;
574
575 /** The DateFormat that we are part of.*/
576 DateFormat parent;
577
578 /**
579 * Return the width of our string field. Different widths represent different
Emily Fortuna 2012/07/31 05:52:53 [string]
Alan Knight 2012/08/03 23:02:15 Done.
580 * formatting options. See the comment for DateFormat for details.
Emily Fortuna 2012/07/31 05:52:53 where is this comment for DateFormat?
Alan Knight 2012/08/03 23:02:15 Lines 80 through 112, particularly the 107-112 par
581 */
582 int get width() => string.length;
583
584 _DateFormatField(this.string, this.type,this.parent);
Emily Fortuna 2012/07/31 05:52:53 space after ,
Alan Knight 2012/08/03 23:02:15 Done.
585
586 String toString() => string;
587
588 void patchQuotes() {
589 if (type != "QUOTED_STRING") return;
590 if (string == "''") {
591 string = "'";
592 } else {
593 string = string.substring(1,string.length - 1);
594 var someRegex = new RegExp("\'\'");
Emily Fortuna 2012/07/31 05:52:53 @ before a string also can denote a raw string
Alan Knight 2012/08/03 23:02:15 I'm not clear what the implications of that are. D
595 string = string.replaceAll(someRegex, "'");
596 }
597 }
598
599 /** Format date according to our specification and return the result. */
600 String format(Date date) {
601 if(type != "FIELD") {
602 return string;
603 } else {
604 return formatField(date);
605 }
606 }
607
608 /**
609 * Parse the date according to our specification and put the result
610 * into the correct place in dateFields.
611 */
612 void parse(_Stream input, _DateBuilder dateFields) {
613 if (type == 'LITERAL') return parseLiteral(input);
614 if (type == 'QUOTED_STRING') return parseLiteral(input);
615 parseField(input,dateFields);
616 }
617
618 /** Parse a literal field. We just look for the exact input. */
619 void parseLiteral(_Stream input) {
620 var found = input.read(width);
621 if (found != string) throw
622 new FormatException(input, this);
623 }
624
625 /**
626 * Parse a field representing part of a date pattern. Note that we do not
627 * return a value, but rather build up the result in [builder].
628 */
629 void parseField(_Stream input, _DateBuilder builder) {
630 try {
631 switch(string[0]) {
632 case 'a': parseAmPm(input,builder); break;
633 case 'c': parseStandaloneDay(input); break;
634 case 'd': handleNumericField(input,builder.setDay); break; // day
635 case 'E': parseDayOfWeek(input); break;
636 case 'G': break; // era
637 case 'h': parse1To12Hours(input,builder); break;
638 case 'H': handleNumericField(input,builder.setHour); break; // hour 0-23
639 case 'K': handleNumericField(input,builder.setHour); break; //hour 0-11
640 case 'k': handleNumericField(input,builder.setHour,-1); break; //hr 1-24
641 case 'L': parseStandaloneMonth(input,builder); break;
642 case 'M': parseMonth(input,builder); break;
643 case 'm': handleNumericField(input,builder.setMinute); break; // minutes
644 case 'Q': break; // quarter
645 case 'S': handleNumericField(input,builder.setFractionalSecond); break;
646 case 's': handleNumericField(input,builder.setSecond); break;
647 case 'v': break; // time zone id
648 case 'y': handleNumericField(input,builder.setYear); break;
649 case 'z': break; // time zone
650 case 'Z': break; // time zone RFC
651 default: return;
652 }
653 } catch (var e) {throw new FormatException(input,this);}
654 }
655
656 /** Formatting logic if we are of type FIELD */
657 String formatField(Date date) {
658 switch (string[0]) {
659 case 'a': return formatAmPm(date);
660 case 'c': return formatStandaloneDay(date);
661 case 'd': return formatDayOfMonth(date);
662 case 'E': return formatDayOfWeek(date);
663 case 'G': return formatEra(date);
664 case 'h': return format1To12Hours(date);
665 case 'H': return format0To23Hours(date);
666 case 'K': return format0To11Hours(date);
667 case 'k': return format24Hours(date);
668 case 'L': return formatStandaloneMonth(date);
669 case 'M': return formatMonth(date);
670 case 'm': return formatMinutes(date);
671 case 'Q': return formatQuarter(date);
672 case 'S': return formatFractionalSeconds(date);
673 case 's': return formatSeconds(date);
674 case 'v': return formatTimeZoneId(date);
675 case 'y': return formatYear(date);
676 case 'z': return formatTimeZone(date);
677 case 'Z': return formatTimeZoneRFC(date);
678 default: return '';
679 }
680 }
681
682 /** Return the symbols for our current locale. */
683 DateSymbols get symbols() => dateTimeSymbols[parent.locale];
684
685 formatEra(Date date) {
686 var era = date.year > 0 ? 1 : 0;
687 return width >= 4 ? symbols.ERANAMES[era] :
688 symbols.ERAS[era];
689 }
690
691 formatYear(Date date) {
692 var year = date.year;
693 if (year < 0) {
694 year = -year;
695 }
696 return width == 2 ? padTo(2, year % 100) : year.toString();
697 }
698
699 /**
700 * We are given [input] as a stream from which we want to read a date. We
701 * can't dynamically build up a date, so we are given a list [dateFields] of
702 * the constructor arguments and an [position] at which to set it
703 * (year,month,day,hour,minute,second,fractionalSecond)
704 * then after all parsing is done we construct a date from the arguments.
705 * This method handles reading any of the numeric fields. The [offset]
706 * argument allows us to compensate for zero-based versus one-based values.
707 */
708 void handleNumericField(
709 _Stream input,
710 Function setter,
711 [int offset = 0]) {
712 var result = input.nextInteger();
713 setter(result + offset);
714 }
715
716 /**
717 * We are given [input] as a stream from which we want to read a date. We
718 * can't dynamically build up a date, so we are given a list [dateFields] of
719 * the constructor arguments and an [position] at which to set it
720 * (year,month,day,hour,minute,second,fractionalSecond)
721 * then after all parsing is done we construct a date from the arguments.
722 * This method handles reading any of string fields from an enumerated set.
723 */
724 int parseEnumeratedString(_Stream input, List possibilities) {
725 var results = new _Stream(possibilities).findIndexes(
726 (each) => input.peek(each.length) == each);
727 if (results.isEmpty()) throw new FormatException(input,this);
728 results.sort(
729 (a, b) => possibilities[a].length.compareTo(possibilities[b].length));
730 var longestResult = results.last();
731 input.read(possibilities[longestResult].length);
732 return longestResult;
733 }
734
735 String formatMonth(Date date) {
736 switch (width) {
737 case 5: return symbols.NARROWMONTHS[date.month-1];
738 case 4: return symbols.MONTHS[date.month-1];
739 case 3: return symbols.SHORTMONTHS[date.month-1];
740 default:
741 return padTo(width, date.month);
742 }
743 }
744
745 void parseMonth(input,dateFields) {
746 var possibilities;
747 switch(width) {
748 case 5: possibilities = symbols.NARROWMONTHS; break;
749 case 4: possibilities = symbols.MONTHS; break;
750 case 3: possibilities = symbols.SHORTMONTHS; break;
751 default: return handleNumericField(input,dateFields.setMonth);
752 }
753 dateFields.month = parseEnumeratedString(input,possibilities) + 1;
754 }
755
756 String format24Hours(Date date) {
757 return padTo(width, date.hour);
758 }
759
760 String formatFractionalSeconds(Date date) {
761 // Always print at least 3 digits. If the width is greater, append 0s
762 var basic = padTo(3,date.millisecond);
763 if (width - 3 > 0) {
764 var extra = padTo(width - 3, 0);
765 return basic.concat(extra);
766 } else {
767 return basic;
768 }
769 }
770
771 String formatAmPm(Date date) {
772 var hours = date.hour;
773 var index = (date.hour >= 12) && (date.hour < 24) ? 1 : 0;
774 var ampm = symbols.AMPMS;
775 return ampm[index];
776 }
777
778 void parseAmPm(input,dateFields) {
779 // If we see a "PM" note it in an extra field.
780 var ampm = parseEnumeratedString(input,symbols.AMPMS);
781 if (ampm == 1) dateFields.pm = true;
782 }
783
784 String format1To12Hours(Date date) {
785 var hours = date.hour;
786 if (date.hour > 12) hours = hours - 12;
787 if (hours == 0) hours = 12;
788 return padTo(width,hours);
789 }
790
791 void parse1To12Hours(_Stream input, _DateBuilder dateFields) {
792 handleNumericField(input,dateFields.setHour);
793 if (dateFields.hour == 12) dateFields.hour = 0;
794 }
795
796 String format0To11Hours(Date date) {
797 return padTo(width,date.hour % 12);
798 }
799
800 String format0To23Hours(Date date) {
801 return padTo(width,date.hour);
802 }
803
804 String formatStandaloneDay(Date date) {
805 switch (width) {
806 case 5: return symbols.STANDALONENARROWWEEKDAYS[date.weekday % 7];
807 case 4: return symbols.STANDALONEWEEKDAYS[date.weekday % 7];
808 case 3: return symbols.STANDALONESHORTWEEKDAYS[date.weekday % 7];
809 default:
810 return padTo(1, date.day);
811 }
812 }
813
814 void parseStandaloneDay(_Stream input) {
815 // This is ignored, but we still have to skip over it the correct amount.
816 var possibilities;
817 switch(width) {
818 case 5: possibilities = symbols.STANDALONENARROWWEEKDAYS; break;
819 case 4: possibilities = symbols.STANDALONEWEEKDAYS; break;
820 case 3: possibilities = symbols.STANDALONESHORTWEEKDAYS; break;
821 default: return handleNumericField(input,(x)=>x);
822 }
823 parseEnumeratedString(input,possibilities);
824 }
825
826 String formatStandaloneMonth(Date date) {
827 switch (width) {
828 case 5:
829 return symbols.STANDALONENARROWMONTHS[date.month-1];
830 case 4:
831 return symbols.STANDALONEMONTHS[date.month-1];
832 case 3:
833 return symbols.STANDALONESHORTMONTHS[date.month-1];
834 default:
835 return padTo(width, date.month);
836 }
837 }
838
839 void parseStandaloneMonth(input,dateFields) {
840 var possibilities;
841 switch(width) {
842 case 5: possibilities = symbols.STANDALONENARROWMONTHS; break;
843 case 4: possibilities = symbols.STANDALONEMONTHS; break;
844 case 3: possibilities = symbols.STANDALONESHORTMONTHS; break;
845 default: return handleNumericField(input,dateFields.setMonth);
846 }
847 dateFields.month = parseEnumeratedString(input,possibilities) + 1;
848 }
849
850 String formatQuarter(Date date) {
851 var quarter = (date.month / 3).truncate().toInt();
852 if (width < 4) {
853 return symbols.SHORTQUARTERS[quarter];
854 } else {
855 return symbols.QUARTERS[quarter];
856 }
857 }
858 String formatDayOfMonth(Date date) {
859 return padTo(width,date.day);
860 }
861
862 String formatDayOfWeek(Date date) {
863 // Note that Dart's weekday returns 1 for Monday and 7 for Sunday.
864 return (width >= 4 ? symbols.WEEKDAYS :
865 symbols.SHORTWEEKDAYS)[(date.weekday) % 7];
866 }
867
868 void parseDayOfWeek(_Stream input) {
869 // This is IGNORED, but we still have to skip over it the correct amount.
870 var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS;
871 parseEnumeratedString(input,possibilities);
872 }
873
874 String formatMinutes(Date date) {
875 return padTo(width,date.minute);
876 }
877
878 String formatSeconds(Date date) {
879 return padTo(width,date.second);
880 }
881
882 //TODO(alanknight): implement time zone support
883 String formatTimeZoneId(Date date) {
884 return 'not implemented';
885 }
886
887 String formatTimeZone(Date date) {
888 return 'not implemented';
Emily Fortuna 2012/07/31 05:52:53 throw NotImplementedException
Alan Knight 2012/08/03 23:02:15 Done.
889 }
890
891 String formatTimeZoneRFC(Date date) {
892 return 'not implemented';
893 }
894
895 /**
896 * Return a string representation of the object padded to the left with
897 * zeros. Primarily useful for numbers.
898 */
899 String padTo(int width, Object toBePrinted) {
900 var basicString = toBePrinted.toString();
901 if (basicString.length >= width) return basicString;
902 var buffer = new StringBuffer();
903 for (var i = 0; i < width - basicString.length; i++) {
904 buffer.add('0');
905 }
906 buffer.add(basicString);
907 return buffer.toString();
908 }
909 }
910
911 /** A class for holding onto the data for a date so that it can be built
Emily Fortuna 2012/07/31 05:52:53 separate line /**
Alan Knight 2012/08/03 23:02:15 Done.
912 * up incrementally.
913 */
914 class _DateBuilder {
915 int year = 0,
916 month = 0,
917 day = 0,
918 hour = 0,
919 minute = 0,
920 second = 0,
921 fractionalSecond = 0;
922 bool pm = false;
923
924 // Functions that exist just be closurized so we can pass them to a general
925 // method.
926 void setYear(x) {year = x;}
Emily Fortuna 2012/07/31 05:52:53 void setYear(x) => year = x or void setYear(x) {
Alan Knight 2012/08/03 23:02:15 Done. I'd prefer the => form, but using it with a
Emily Fortuna 2012/08/06 22:43:00 Oh.. weird. I thought that it was okay to have =>
927 void setMonth(x) {month = x;}
928 void setDay(x) {day = x;}
929 void setHour(x) {hour = x;}
930 void setMinute(x) {minute = x;}
931 void setSecond(x) {second = x;}
932 void setFractionalSecond(x) {fractionalSecond = x;}
933
934 /**
935 * Return a date built using our values. If no date portion is set,
936 * use today's date, as otherwise the constructor will fail.
937 */
938 Date asDate() {
939 if (year == 0) {
Emily Fortuna 2012/07/31 05:52:53 if year !=0 but month or day == 0?
Alan Knight 2012/08/03 23:02:15 Done.
940 var today = new Date.now();
941 year = today.year;
942 if (month == 0) month = today.month;
943 if (day == 0) day = today.day;
944 }
945 return new Date(
946 year,
947 month,
948 day,
949 pm ? hour + 12 : hour,
950 minute,
951 second,
952 fractionalSecond,
953 false);
954 }
955 }
956
957 /**
958 * A simple and not particularly general stream class to make parsing
959 * dates from strings simpler. It is general enough to operate on either
960 * lists or strings.
961 */
962 class _Stream {
963 var contents;
964 int index = 0;
965
966 _Stream(this.contents);
967
968 bool atEnd() => index >= contents.length;
969
970 Dynamic next() => contents[index++];
971
972 /** Return the next [howMany] items, or as many as there are remaining.
Emily Fortuna 2012/07/31 05:52:53 /** on own line
Alan Knight 2012/08/03 23:02:15 Done.
973 * Advance the stream by that many positions.
974 */
975 read([howMany = 1]) {
976 var result = peek(howMany);
977 index += howMany;
978 return result;
979 }
980
981 /**
982 * Return the next [howMany] items, or as many as there are remaining.
983 * Does not modify the stream position.
984 */
985 peek([howMany = 1]) {
986 var result;
987 if (contents is String) {
988 result = contents.substring(
989 index,
990 Math.min(index + howMany, contents.length));
991 } else {
992 // Assume List
993 result = contents.getRange(index,howMany);
994 }
995 return result;
996 }
997
998 /** Return the remaining contents of the stream */
999 rest() => peek(contents.length - index);
1000
1001 /** Find the index of the first element for which [f] returns true.*/
1002 int findIndex(Function f) {
Emily Fortuna 2012/07/31 05:52:53 This also has the side effect of modifying index/a
Alan Knight 2012/08/03 23:02:15 Done.
1003 while (!atEnd()) {
1004 if (f(next())) return index - 1;
1005 }
1006 return null;
1007 }
1008
1009 /** Find the indexes of all the elements for which [f] returns true*/
1010 List findIndexes(Function f) {
1011 var results = [];
1012 while (!atEnd()) {
1013 if (f(next())) results.add(index - 1);
1014 }
1015 return results;
1016 }
1017
1018 /**
1019 * Assuming that the contents are characters, read as many digits as we
1020 * can see and then return the corresponding integer. Advance the stream.
1021 */
1022 int nextInteger() {
1023 var validDigits = '0123456789';
1024 var digits = [];
1025 while (!atEnd() && (validDigits.contains(peek()))) {
1026 digits.add(next().charCodeAt(0));
Emily Fortuna 2012/07/31 05:52:53 this could be done faster with a regular expressio
Alan Knight 2012/08/03 23:02:15 It's not obvious to me how to do that without comp
Emily Fortuna 2012/08/06 22:43:00 Alrighty. Nevermind then. Seemed to make more sens
1027 }
1028 return Math.parseInt(new String.fromCharCodes(digits));
1029 }
1030 }
1031
1032 /**
1033 * Exception indicating that we could not parse a particular string into a
1034 * Date according to the specified pattern.
1035 */
1036 class FormatException implements Exception {
1037 final _Stream input;
1038 final _DateFormatField field;
1039
1040 const FormatException(this.input, this.field);
1041
1042 toString() => "FormatException: $field matching ${input.rest}";
1043
Emily Fortuna 2012/07/31 05:52:53 whitespace
Alan Knight 2012/08/03 23:02:15 Done.
1044 }
OLDNEW
« no previous file with comments | « no previous file | lib/i18n/date_symbol_data.dart » ('j') | lib/i18n/date_symbol_data.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698