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