OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * DateFormat is for formatting and parsing dates in a locale-sensitive | 6 * DateFormat is for formatting and parsing dates in a locale-sensitive |
7 * manner. | 7 * manner. |
8 * It allows the user to choose from a set of standard date time formats as well | 8 * It allows the user to choose from a set of standard date time formats as well |
9 * as specify a customized pattern under certain locales. Date elements that | 9 * as specify a customized pattern under certain locales. Date elements that |
10 * vary across locales include month name, week name, field order, etc. | 10 * vary across locales include month name, week name, field order, etc. |
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 } | |
OLD | NEW |