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 part of intl; | 5 part of intl; |
6 | 6 |
7 // TODO(efortuna): Customized pattern system -- suggested by i18n needs | 7 // TODO(efortuna): Customized pattern system -- suggested by i18n needs |
8 // feedback on appropriateness. | 8 // feedback on appropriateness. |
9 /** | 9 /// DateFormat is for formatting and parsing dates in a locale-sensitive |
10 * DateFormat is for formatting and parsing dates in a locale-sensitive | 10 /// manner. |
11 * manner. | 11 /// |
12 * | 12 /// It allows the user to choose from a set of standard date time formats as |
13 * It allows the user to choose from a set of standard date time formats as well | 13 /// well as specify a customized pattern under certain locales. Date elements |
14 * as specify a customized pattern under certain locales. Date elements that | 14 /// that vary across locales include month name, week name, field order, etc. |
15 * vary across locales include month name, week name, field order, etc. | 15 /// We also allow the user to use any customized pattern to parse or format |
16 * We also allow the user to use any customized pattern to parse or format | 16 /// date-time strings under certain locales. Date elements that vary across |
17 * date-time strings under certain locales. Date elements that vary across | 17 /// locales include month name, weekname, field, order, etc. |
18 * locales include month name, weekname, field, order, etc. | 18 /// |
19 * | 19 /// Formatting dates in the default "en_US" format does not require any |
20 * Formatting dates in the default "en_US" format does not require any | 20 /// initialization. e.g. |
21 * initialization. e.g. | 21 /// print(new DateFormat.yMMMd().format(new DateTime.now())); |
22 * print(new DateFormat.yMMMd().format(new Date.now())); | 22 /// |
23 * | 23 /// But for other locales, the formatting data for the locale must be |
24 * But for other locales, the formatting data for the locale must be | 24 /// obtained. This can currently be done in one of three ways, determined by |
25 * obtained. This can currently be done | 25 /// which library you import. In all cases, the "initializeDateFormatting" |
26 * in one of three ways, determined by which library you import. In all cases, | 26 /// method must be called and will return a future that is complete once the |
27 * the "initializeDateFormatting" method must be called and will return a future | 27 /// locale data is available. The result of the future isn't important, but the |
28 * that is complete once the locale data is available. The result of the future | 28 /// data for that locale is available to the date formatting and parsing once it |
29 * isn't important, but the data for that locale is available to the date | 29 /// completes. |
30 * formatting and parsing once it completes. | 30 /// |
31 * | 31 /// The easiest option is that the data may be available locally, imported in a |
32 * The easiest option is that the data may be available locally, imported in a | 32 /// library that contains data for all the locales. |
33 * library that contains data for all the locales. | 33 /// import 'package:intl/date_symbol_data_local.dart'; |
34 * import 'package:intl/date_symbol_data_local.dart'; | 34 /// initializeDateFormatting("fr_FR", null).then((_) => runMyCode()); |
35 * initializeDateFormatting("fr_FR", null).then((_) => runMyCode()); | 35 /// |
36 * | 36 /// If we are running outside of a browser, we may want to read the data |
37 * If we are running outside of a browser, we may want to read the data | 37 /// from files in the file system. |
38 * from files in the file system. | 38 /// import 'package:intl/date_symbol_data_file.dart'; |
39 * import 'package:intl/date_symbol_data_file.dart'; | 39 /// initializeDateFormatting("de_DE", null).then((_) => runMyCode()); |
40 * initializeDateFormatting("de_DE", null).then((_) => runMyCode()); | 40 /// |
41 * | 41 /// If we are running in a browser, we may want to read the data from the |
42 * If we are running in a browser, we may want to read the data from the | 42 /// server using the XmlHttpRequest mechanism. |
43 * server using the XmlHttpRequest mechanism. | 43 /// import 'package:intl/date_symbol_data_http_request.dart'; |
44 * import 'package:intl/date_symbol_data_http_request.dart'; | 44 /// initializeDateFormatting("pt_BR", null).then((_) => runMyCode()); |
45 * initializeDateFormatting("pt_BR", null).then((_) => runMyCode()); | 45 /// |
46 * | 46 /// The code in example/basic/basic_example.dart shows a full example of |
47 * The code in example/basic/basic_example.dart shows a full example of | 47 /// using this mechanism. |
48 * using this mechanism. | 48 /// |
49 * | 49 /// Once we have the locale data, we need to specify the particular format. |
50 * Once we have the locale data, we need to specify the particular format. | 50 /// This library uses the ICU/JDK date/time pattern specification both for |
51 * This library uses the ICU/JDK date/time pattern specification both for | 51 /// complete format specifications and also the abbreviated "skeleton" form |
52 * complete format specifications and also the abbreviated "skeleton" form | 52 /// which can also adapt to different locales and is preferred where available. |
53 * which can also adapt to different locales and is preferred where available. | 53 /// |
54 * | 54 /// Skeletons: These can be specified either as the ICU constant name or as the |
55 * Skeletons: These can be specified either as the ICU constant name or as the | 55 /// skeleton to which it resolves. The supported set of skeletons is as follows. |
56 * skeleton to which it resolves. The supported set of skeletons is as follows. | 56 /// For each skeleton there is a named constructor that can be used to create |
57 * For each skeleton there is a named constructor that can be used to create it. | 57 /// it. It's also possible to pass the skeleton as a string, but the |
58 * It's also possible to pass the skeleton as a string, but the constructor | 58 /// constructor is preferred. |
59 * is preferred. | 59 /// |
60 * | 60 /// ICU Name Skeleton |
61 * ICU Name Skeleton | 61 /// -------- -------- |
62 * -------- -------- | 62 /// DAY d |
63 * DAY d | 63 /// ABBR_WEEKDAY E |
64 * ABBR_WEEKDAY E | 64 /// WEEKDAY EEEE |
65 * WEEKDAY EEEE | 65 /// ABBR_STANDALONE_MONTH LLL |
66 * ABBR_STANDALONE_MONTH LLL | 66 /// STANDALONE_MONTH LLLL |
67 * STANDALONE_MONTH LLLL | 67 /// NUM_MONTH M |
68 * NUM_MONTH M | 68 /// NUM_MONTH_DAY Md |
69 * NUM_MONTH_DAY Md | 69 /// NUM_MONTH_WEEKDAY_DAY MEd |
70 * NUM_MONTH_WEEKDAY_DAY MEd | 70 /// ABBR_MONTH MMM |
71 * ABBR_MONTH MMM | 71 /// ABBR_MONTH_DAY MMMd |
72 * ABBR_MONTH_DAY MMMd | 72 /// ABBR_MONTH_WEEKDAY_DAY MMMEd |
73 * ABBR_MONTH_WEEKDAY_DAY MMMEd | 73 /// MONTH MMMM |
74 * MONTH MMMM | 74 /// MONTH_DAY MMMMd |
75 * MONTH_DAY MMMMd | 75 /// MONTH_WEEKDAY_DAY MMMMEEEEd |
76 * MONTH_WEEKDAY_DAY MMMMEEEEd | 76 /// ABBR_QUARTER QQQ |
77 * ABBR_QUARTER QQQ | 77 /// QUARTER QQQQ |
78 * QUARTER QQQQ | 78 /// YEAR y |
79 * YEAR y | 79 /// YEAR_NUM_MONTH yM |
80 * YEAR_NUM_MONTH yM | 80 /// YEAR_NUM_MONTH_DAY yMd |
81 * YEAR_NUM_MONTH_DAY yMd | 81 /// YEAR_NUM_MONTH_WEEKDAY_DAY yMEd |
82 * YEAR_NUM_MONTH_WEEKDAY_DAY yMEd | 82 /// YEAR_ABBR_MONTH yMMM |
83 * YEAR_ABBR_MONTH yMMM | 83 /// YEAR_ABBR_MONTH_DAY yMMMd |
84 * YEAR_ABBR_MONTH_DAY yMMMd | 84 /// YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd |
85 * YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd | 85 /// YEAR_MONTH yMMMM |
86 * YEAR_MONTH yMMMM | 86 /// YEAR_MONTH_DAY yMMMMd |
87 * YEAR_MONTH_DAY yMMMMd | 87 /// YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd |
88 * YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd | 88 /// YEAR_ABBR_QUARTER yQQQ |
89 * YEAR_ABBR_QUARTER yQQQ | 89 /// YEAR_QUARTER yQQQQ |
90 * YEAR_QUARTER yQQQQ | 90 /// HOUR24 H |
91 * HOUR24 H | 91 /// HOUR24_MINUTE Hm |
92 * HOUR24_MINUTE Hm | 92 /// HOUR24_MINUTE_SECOND Hms |
93 * HOUR24_MINUTE_SECOND Hms | 93 /// HOUR j |
94 * HOUR j | 94 /// HOUR_MINUTE jm |
95 * HOUR_MINUTE jm | 95 /// HOUR_MINUTE_SECOND jms |
96 * HOUR_MINUTE_SECOND jms | 96 /// HOUR_MINUTE_GENERIC_TZ jmv |
97 * HOUR_MINUTE_GENERIC_TZ jmv | 97 /// HOUR_MINUTE_TZ jmz |
98 * HOUR_MINUTE_TZ jmz | 98 /// HOUR_GENERIC_TZ jv |
99 * HOUR_GENERIC_TZ jv | 99 /// HOUR_TZ jz |
100 * HOUR_TZ jz | 100 /// MINUTE m |
101 * MINUTE m | 101 /// MINUTE_SECOND ms |
102 * MINUTE_SECOND ms | 102 /// SECOND s |
103 * SECOND s | 103 /// |
104 * | 104 /// Examples Using the US Locale: |
105 * Examples Using the US Locale: | 105 /// |
106 * | 106 /// Pattern Result |
107 * Pattern Result | 107 /// ---------------- ------- |
108 * ---------------- ------- | 108 /// new DateFormat.yMd() -> 7/10/1996 |
109 * new DateFormat.yMd() -> 7/10/1996 | 109 /// new DateFormat("yMd") -> 7/10/1996 |
110 * new DateFormat("yMd") -> 7/10/1996 | 110 /// new DateFormat.yMMMMd("en_US") -> July 10, 1996 |
111 * new DateFormat.yMMMMd("en_US") -> July 10, 1996 | 111 /// new DateFormat.jm() -> 5:08 PM |
112 * new DateFormat.jm() -> 5:08 PM | 112 /// new DateFormat.yMd().add_jm() -> 7/10/1996 5:08 PM |
113 * new DateFormat.yMd().add_jm() -> 7/10/1996 5:08 PM | 113 /// new DateFormat.Hm() -> 17:08 // force 24 hour time |
114 * new DateFormat.Hm() -> 17:08 // force 24 hour time | 114 /// |
115 * | 115 /// Explicit Pattern Syntax: Formats can also be specified with a pattern |
116 * Explicit Pattern Syntax: Formats can also be specified with a pattern string. | 116 /// string. This can be used for formats that don't have a skeleton available, |
117 * This can be used for formats that don't have a skeleton available, but these | 117 /// but these will not adapt to different locales. For example, in an explicit |
118 * will not adapt to different locales. For example, in an explicit pattern the | 118 /// pattern the letters "H" and "h" are available for 24 hour and 12 hour time |
119 * letters "H" and "h" are available for 24 hour and 12 hour time formats | 119 /// formats respectively. But there isn't a way in an explicit pattern to get |
120 * respectively. But there isn't a way in an explicit pattern to get the | 120 /// the behaviour of the "j" skeleton, which prints 24 hour or 12 hour time |
121 * behaviour of the "j" skeleton, which prints 24 hour or 12 hour time according | 121 /// according to the conventions of the locale, and also includes am/pm markers |
122 * to the conventions of the locale, and also includes am/pm markers where | 122 /// where appropriate. So it is preferable to use the skeletons. |
123 * appropriate. So it is preferable to use the skeletons. | 123 /// |
124 * | 124 /// The following characters are available in explicit patterns: |
125 * The following characters are available in explicit patterns: | 125 /// |
126 * | 126 /// Symbol Meaning Presentation Example |
127 * Symbol Meaning Presentation Example | 127 /// ------ ------- ------------ ------- |
128 * ------ ------- ------------ ------- | 128 /// G era designator (Text) AD |
129 * G era designator (Text) AD | 129 /// y year (Number) 1996 |
130 * y year (Number) 1996 | 130 /// M month in year (Text & Number) July & 07 |
131 * M month in year (Text & Number) July & 07 | 131 /// L standalone month (Text & Number) July & 07 |
132 * L standalone month (Text & Number) July & 07 | 132 /// d day in month (Number) 10 |
133 * d day in month (Number) 10 | 133 /// c standalone day (Number) 10 |
134 * c standalone day (Number) 10 | 134 /// h hour in am/pm (1~12) (Number) 12 |
135 * h hour in am/pm (1~12) (Number) 12 | 135 /// H hour in day (0~23) (Number) 0 |
136 * H hour in day (0~23) (Number) 0 | 136 /// m minute in hour (Number) 30 |
137 * m minute in hour (Number) 30 | 137 /// s second in minute (Number) 55 |
138 * s second in minute (Number) 55 | 138 /// S fractional second (Number) 978 |
139 * S fractional second (Number) 978 | 139 /// E day of week (Text) Tuesday |
140 * E day of week (Text) Tuesday | 140 /// D day in year (Number) 189 |
141 * D day in year (Number) 189 | 141 /// a am/pm marker (Text) PM |
142 * a am/pm marker (Text) PM | 142 /// k hour in day (1~24) (Number) 24 |
143 * k hour in day (1~24) (Number) 24 | 143 /// K hour in am/pm (0~11) (Number) 0 |
144 * K hour in am/pm (0~11) (Number) 0 | 144 /// z time zone (Text) Pacific Standard Time |
145 * z time zone (Text) Pacific Standard Time | 145 /// Z time zone (RFC 822) (Number) -0800 |
146 * Z time zone (RFC 822) (Number) -0800 | 146 /// v time zone (generic) (Text) Pacific Time |
147 * v time zone (generic) (Text) Pacific Time | 147 /// Q quarter (Text) Q3 |
148 * Q quarter (Text) Q3 | 148 /// ' escape for text (Delimiter) 'Date=' |
149 * ' escape for text (Delimiter) 'Date=' | 149 /// '' single quote (Literal) 'o''clock' |
150 * '' single quote (Literal) 'o''clock' | 150 /// |
151 * | 151 /// The count of pattern letters determine the format. |
152 * The count of pattern letters determine the format. | 152 /// |
153 * | 153 /// **Text**: |
154 * **Text**: | 154 /// * 5 pattern letters--use narrow form for standalone. Otherwise not used. |
155 * * 5 pattern letters--use narrow form for standalone. Otherwise does not apply | 155 /// * 4 or more pattern letters--use full form, |
156 * * 4 or more pattern letters--use full form, | 156 /// * 3 pattern letters--use short or abbreviated form if one exists |
157 * * 3 pattern letters--use short or abbreviated form if one exists | 157 /// * less than 3--use numeric form if one exists |
158 * * less than 3--use numeric form if one exists | 158 /// |
159 * | 159 /// **Number**: the minimum number of digits. Shorter numbers are zero-padded to |
160 * **Number**: the minimum number of digits. Shorter numbers are zero-padded to | 160 /// this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled |
161 * this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled | 161 /// specially; that is, if the count of 'y' is 2, the Year will be truncated to |
162 * specially; that is, if the count of 'y' is 2, the Year will be truncated to | 162 /// 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike |
163 * 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other | 163 /// other fields, fractional seconds are padded on the right with zero. |
164 * fields, fractional seconds are padded on the right with zero. | 164 /// |
165 * | 165 /// **(Text & Number)**: 3 or over, use text, otherwise use number. |
166 * **(Text & Number)**: 3 or over, use text, otherwise use number. | 166 /// |
167 * | 167 /// Any characters not in the pattern will be treated as quoted text. For |
168 * Any characters not in the pattern will be treated as quoted text. For | 168 /// instance, characters like ':', '.', ' ', '#' and '@' will appear in the |
169 * instance, characters like ':', '.', ' ', '#' and '@' will appear in the | 169 /// resulting text even though they are not enclosed in single quotes. In our |
170 * resulting text even though they are not enclosed in single quotes. In our | 170 /// current pattern usage, not all letters have meanings. But those unused |
171 * current pattern usage, not all letters have meanings. But those unused | 171 /// letters are strongly discouraged to be used as quoted text without quotes, |
172 * letters are strongly discouraged to be used as quoted text without quotes, | 172 /// because we may use other letters as pattern characters in the future. |
173 * because we may use other letters as pattern characters in the future. | 173 /// |
174 * | 174 /// Examples Using the US Locale: |
175 * Examples Using the US Locale: | 175 /// |
176 * | 176 /// Format Pattern Result |
177 * Format Pattern Result | 177 /// -------------- ------- |
178 * -------------- ------- | 178 /// "yyyy.MM.dd G 'at' HH:mm:ss vvvv" 1996.07.10 AD at 15:08:56 Pacific Time |
179 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" 1996.07.10 AD at 15:08:56 Pacific Time | 179 /// "EEE, MMM d, ''yy" Wed, July 10, '96 |
180 * "EEE, MMM d, ''yy" Wed, July 10, '96 | 180 /// "h:mm a" 12:08 PM |
181 * "h:mm a" 12:08 PM | 181 /// "hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time |
182 * "hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time | 182 /// "K:mm a, vvv" 0:00 PM, PT |
183 * "K:mm a, vvv" 0:00 PM, PT | 183 /// "yyyyy.MMMMM.dd GGG hh:mm aaa" 01996.July.10 AD 12:08 PM |
184 * "yyyyy.MMMMM.dd GGG hh:mm aaa" 01996.July.10 AD 12:08 PM | 184 /// |
185 * | 185 /// When parsing a date string using the abbreviated year pattern ("yy"), |
186 * When parsing a date string using the abbreviated year pattern ("yy"), | 186 /// DateFormat must interpret the abbreviated year relative to some |
187 * DateFormat must interpret the abbreviated year relative to some | 187 /// century. It does this by adjusting dates to be within 80 years before and 20 |
188 * century. It does this by adjusting dates to be within 80 years before and 20 | 188 /// years after the time the parse function is called. For example, using a |
189 * years after the time the parse function is called. For example, using a | 189 /// pattern of "MM/dd/yy" and a DateParse instance created on Jan 1, 1997, |
190 * pattern of "MM/dd/yy" and a DateParse instance created on Jan 1, 1997, | 190 /// the string "01/11/12" would be interpreted as Jan 11, 2012 while the string |
191 * the string "01/11/12" would be interpreted as Jan 11, 2012 while the string | 191 /// "05/04/64" would be interpreted as May 4, 1964. During parsing, only |
192 * "05/04/64" would be interpreted as May 4, 1964. During parsing, only | 192 /// strings consisting of exactly two digits, as defined by {@link |
193 * strings consisting of exactly two digits, as defined by {@link | 193 /// java.lang.Character#isDigit(char)}, will be parsed into the default |
194 * java.lang.Character#isDigit(char)}, will be parsed into the default | 194 /// century. Any other numeric string, such as a one digit string, a three or |
195 * century. Any other numeric string, such as a one digit string, a three or | 195 /// more digit string will be interpreted as its face value. |
196 * more digit string will be interpreted as its face value. | 196 /// |
197 * | 197 /// If the year pattern does not have exactly two 'y' characters, the year is |
198 * If the year pattern does not have exactly two 'y' characters, the year is | 198 /// interpreted literally, regardless of the number of digits. So using the |
199 * interpreted literally, regardless of the number of digits. So using the | 199 /// pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. |
200 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D. | |
201 */ | |
202 | 200 |
203 class DateFormat { | 201 class DateFormat { |
204 | 202 /// Creates a new DateFormat, using the format specified by [newPattern]. For |
205 /** | 203 /// forms that match one of our predefined skeletons, we look up the |
206 * Creates a new DateFormat, using the format specified by [newPattern]. For | 204 /// corresponding pattern in [locale] (or in the default locale if none is |
207 * forms that match one of our predefined skeletons, we look up the | 205 /// specified) and use the resulting full format string. This is the |
208 * corresponding pattern in [locale] (or in the default locale if none is | 206 /// preferred usage, but if [newPattern] does not match one of the skeletons, |
209 * specified) and use the resulting full format string. This is the | 207 /// then it is used as a format directly, but will not be adapted to suit |
210 * preferred usage, but if [newPattern] does not match one of the skeletons, | 208 /// the locale. |
211 * then it is used as a format directly, but will not be adapted to suit | 209 /// |
212 * the locale. | 210 /// For example, in an en_US locale, specifying the skeleton |
213 * | 211 /// new DateFormat.yMEd(); |
214 * For example, in an en_US locale, specifying the skeleton | 212 /// or the explicit |
215 * new DateFormat.yMEd(); | 213 /// new DateFormat('EEE, M/d/y'); |
216 * or the explicit | 214 /// would produce the same result, a date of the form |
217 * new DateFormat('EEE, M/d/y'); | 215 /// Wed, 6/27/2012 |
218 * would produce the same result, a date of the form | 216 /// The first version would produce a different format string if used in |
219 * Wed, 6/27/2012 | 217 /// another locale, but the second format would always be the same. |
220 * The first version would produce a different format string if used in | 218 /// |
221 * another locale, but the second format would always be the same. | 219 /// If [locale] does not exist in our set of supported locales then an |
222 * | 220 /// [ArgumentError] is thrown. |
223 * If [locale] does not exist in our set of supported locales then an | |
224 * [ArgumentError] is thrown. | |
225 */ | |
226 DateFormat([String newPattern, String locale]) { | 221 DateFormat([String newPattern, String locale]) { |
227 // TODO(alanknight): It should be possible to specify multiple skeletons eg | 222 // TODO(alanknight): It should be possible to specify multiple skeletons eg |
228 // date, time, timezone all separately. Adding many or named parameters to | 223 // date, time, timezone all separately. Adding many or named parameters to |
229 // the constructor seems awkward, especially with the possibility of | 224 // the constructor seems awkward, especially with the possibility of |
230 // confusion with the locale. A "fluent" interface with cascading on an | 225 // confusion with the locale. A "fluent" interface with cascading on an |
231 // instance might work better? A list of patterns is also possible. | 226 // instance might work better? A list of patterns is also possible. |
232 _locale = Intl.verifiedLocale(locale, localeExists); | 227 _locale = Intl.verifiedLocale(locale, localeExists); |
233 addPattern(newPattern); | 228 addPattern(newPattern); |
234 } | 229 } |
235 | 230 |
236 /** | 231 /// Return a string representing [date] formatted according to our locale |
237 * Return a string representing [date] formatted according to our locale | 232 /// and internal format. |
238 * and internal format. | |
239 */ | |
240 String format(DateTime date) { | 233 String format(DateTime date) { |
241 // TODO(efortuna): read optional TimeZone argument (or similar)? | 234 // TODO(efortuna): read optional TimeZone argument (or similar)? |
242 var result = new StringBuffer(); | 235 var result = new StringBuffer(); |
243 _formatFields.forEach((field) => result.write(field.format(date))); | 236 _formatFields.forEach((field) => result.write(field.format(date))); |
244 return result.toString(); | 237 return result.toString(); |
245 } | 238 } |
246 | 239 |
247 /** | 240 /// NOT YET IMPLEMENTED. |
248 * NOT YET IMPLEMENTED. | 241 /// |
249 * | 242 /// Returns a date string indicating how long ago (3 hours, 2 minutes) |
250 * Returns a date string indicating how long ago (3 hours, 2 minutes) | 243 /// something has happened or how long in the future something will happen |
251 * something has happened or how long in the future something will happen | 244 /// given a [reference] DateTime relative to the current time. |
252 * given a [reference] DateTime relative to the current time. | |
253 */ | |
254 String formatDuration(DateTime reference) => ''; | 245 String formatDuration(DateTime reference) => ''; |
255 | 246 |
256 /** | 247 /// NOT YET IMPLEMENTED. |
257 * NOT YET IMPLEMENTED. | 248 /// |
258 * | 249 /// Formats a string indicating how long ago (negative [duration]) or how far |
259 * Formats a string indicating how long ago (negative [duration]) or how far | 250 /// in the future (positive [duration]) some time is with respect to a |
260 * in the future (positive [duration]) some time is with respect to a | 251 /// reference [date]. |
261 * reference [date]. | |
262 */ | |
263 String formatDurationFrom(Duration duration, DateTime date) => ''; | 252 String formatDurationFrom(Duration duration, DateTime date) => ''; |
264 | 253 |
265 /** | 254 /// Given user input, attempt to parse the [inputString] into the anticipated |
266 * Given user input, attempt to parse the [inputString] into the anticipated | 255 /// format, treating it as being in the local timezone. If [inputString] does |
267 * format, treating it as being in the local timezone. If [inputString] does | 256 /// not match our format, throws a [FormatException]. This will accept dates |
268 * not match our format, throws a [FormatException]. This will accept dates | 257 /// whose values are not strictly valid, or strings with additional characters |
269 * whose values are not strictly valid, or strings with additional characters | 258 /// (including whitespace) after a valid date. For stricter parsing, use |
270 * (including whitespace) after a valid date. For stricter parsing, use | 259 /// [parseStrict]. |
271 * [parseStrict]. | |
272 */ | |
273 DateTime parse(String inputString, [utc = false]) => | 260 DateTime parse(String inputString, [utc = false]) => |
274 _parse(inputString, utc: utc, strict: false); | 261 _parse(inputString, utc: utc, strict: false); |
275 | 262 |
276 /** | 263 /// Given user input, attempt to parse the [inputString] "loosely" into the |
277 * Given user input, attempt to parse the [inputString] "loosely" into the | 264 /// anticipated format, accepting some variations from the strict format. |
278 * anticipated format, accepting some variations from the strict format. | 265 /// |
279 * | 266 /// If [inputString] |
280 * If [inputString] | 267 /// is accepted by [parseStrict], just return the result. If not, attempt to |
281 * is accepted by [parseStrict], just return the result. If not, attempt to | 268 /// parse it, but accepting either upper or |
282 * parse it, but accepting either upper or | 269 /// lower case, allowing delimiters to be missing and replaced or |
283 * lower case, allowing delimiters to be missing and replaced or | 270 /// supplemented with whitespace, |
284 * supplemented with whitespace, | 271 /// and allowing arbitrary amounts of whitespace wherever whitespace is |
285 * and allowing arbitrary amounts of whitespace wherever whitespace is | 272 /// permitted. Note that this does not allow trailing characters, the way |
286 * permitted. Note that this does not allow trailing characters, the way | 273 /// [parse] does. |
287 * [parse] does. It also does not allow leading whitespace on delimiters, | 274 /// It also does not allow alternative names for months or weekdays other than |
288 * and does not allow alternative names for months or weekdays other than | 275 /// those the format knows about. The restrictions are quite arbitrary and |
289 * those the format knows about. The restrictions are quite arbitrary and | 276 /// it's not known how well they'll work for locales that aren't English-like. |
290 * it's not known how well they'll work for locales that aren't English-like. | 277 /// |
291 * | 278 /// If [inputString] does not parse, this throws a |
292 * If [inputString] does not parse, this throws a | 279 /// [FormatException]. |
293 * [FormatException]. | 280 /// |
294 * | 281 /// For example, this will accept |
295 * For example, this will accept | 282 /// |
296 * | 283 /// new DateFormat.yMMMd("en_US").parseLoose("SEp 3 2014"); |
297 * new DateFormat.yMMMd("en_US").parseLoose("SEp 3 2014"); | 284 /// new DateFormat.yMd("en_US").parseLoose("09 03/2014"); |
298 * new DateFormat.yMd("en_US").parseLoose("09 03/2014"); | 285 /// new DateFormat.yMd("en_US").parseLoose("09 / 03 / 2014"); |
299 * | 286 /// |
300 * It will NOT accept | 287 /// It will NOT accept |
301 * | 288 /// |
302 * // "Sept" is not a valid month name. | 289 /// // "Sept" is not a valid month name. |
303 * new DateFormat.yMMMd("en_US").parseLoose("Sept 3, 2014"); | 290 /// new DateFormat.yMMMd("en_US").parseLoose("Sept 3, 2014"); |
304 * // Delimiters can't have leading whitespace. | |
305 * new DateFormat.yMd("en_US").parseLoose("09 / 03 / 2014"); | |
306 */ | |
307 DateTime parseLoose(String inputString, [utc = false]) { | 291 DateTime parseLoose(String inputString, [utc = false]) { |
308 try { | 292 try { |
309 return _parse(inputString, utc: utc, strict: true); | 293 return _parse(inputString, utc: utc, strict: true); |
310 } on FormatException { | 294 } on FormatException { |
311 return _parseLoose(inputString.toLowerCase(), utc); | 295 return _parseLoose(inputString.toLowerCase(), utc); |
312 } | 296 } |
313 } | 297 } |
314 | 298 |
315 _parseLoose(String inputString, bool utc) { | 299 _parseLoose(String inputString, bool utc) { |
316 var dateFields = new _DateBuilder(); | 300 var dateFields = new _DateBuilder(); |
317 if (utc) dateFields.utc = true; | 301 if (utc) dateFields.utc = true; |
318 var stream = new _Stream(inputString); | 302 var stream = new _Stream(inputString); |
319 _formatFields.forEach((f) => f.parseLoose(stream, dateFields)); | 303 _formatFields.forEach((f) => f.parseLoose(stream, dateFields)); |
320 if (!stream.atEnd()) { | 304 if (!stream.atEnd()) { |
321 throw new FormatException( | 305 throw new FormatException( |
322 "Characters remaining after date parsing in $inputString"); | 306 "Characters remaining after date parsing in $inputString"); |
323 } | 307 } |
324 dateFields.verify(inputString); | 308 dateFields.verify(inputString); |
325 return dateFields.asDate(); | 309 return dateFields.asDate(); |
326 } | 310 } |
327 | 311 |
328 /** | 312 /// Given user input, attempt to parse the [inputString] into the anticipated |
329 * Given user input, attempt to parse the [inputString] into the anticipated | 313 /// format, treating it as being in the local timezone. If [inputString] does |
330 * format, treating it as being in the local timezone. If [inputString] does | 314 /// not match our format, throws a [FormatException]. This will reject dates |
331 * not match our format, throws a [FormatException]. This will reject dates | 315 /// whose values are not strictly valid, even if the |
332 * whose values are not strictly valid, even if the | 316 /// DateTime constructor will accept them. It will also rejct strings with |
333 * DateTime constructor will accept them. It will also rejct strings with | 317 /// additional characters (including whitespace) after a valid date. For |
334 * additional characters (including whitespace) after a valid date. For | 318 /// looser parsing, use [parse]. |
335 * looser parsing, use [parse]. | |
336 */ | |
337 DateTime parseStrict(String inputString, [utc = false]) => | 319 DateTime parseStrict(String inputString, [utc = false]) => |
338 _parse(inputString, utc: utc, strict: true); | 320 _parse(inputString, utc: utc, strict: true); |
339 | 321 |
340 DateTime _parse(String inputString, {utc: false, strict: false}) { | 322 DateTime _parse(String inputString, {utc: false, strict: false}) { |
341 // TODO(alanknight): The Closure code refers to special parsing of numeric | 323 // TODO(alanknight): The Closure code refers to special parsing of numeric |
342 // values with no delimiters, which we currently don't do. Should we? | 324 // values with no delimiters, which we currently don't do. Should we? |
343 var dateFields = new _DateBuilder(); | 325 var dateFields = new _DateBuilder(); |
344 if (utc) dateFields.utc = true; | 326 if (utc) dateFields.utc = true; |
345 var stream = new _Stream(inputString); | 327 var stream = new _Stream(inputString); |
346 _formatFields.forEach((f) => f.parse(stream, dateFields)); | 328 _formatFields.forEach((f) => f.parse(stream, dateFields)); |
347 if (strict && !stream.atEnd()) { | 329 if (strict && !stream.atEnd()) { |
348 throw new FormatException( | 330 throw new FormatException( |
349 "Characters remaining after date parsing in $inputString"); | 331 "Characters remaining after date parsing in $inputString"); |
350 } | 332 } |
351 if (strict) dateFields.verify(inputString); | 333 if (strict) dateFields.verify(inputString); |
352 return dateFields.asDate(); | 334 return dateFields.asDate(); |
353 } | 335 } |
354 | 336 |
355 /** | 337 /// Given user input, attempt to parse the [inputString] into the anticipated |
356 * Given user input, attempt to parse the [inputString] into the anticipated | 338 /// format, treating it as being in UTC. |
357 * format, treating it as being in UTC. | 339 /// |
358 * | 340 /// The canonical Dart style name |
359 * The canonical Dart style name | 341 /// is [parseUtc], but [parseUTC] is retained |
360 * is [parseUtc], but [parseUTC] is retained | 342 /// for backward-compatibility. |
361 * for backward-compatibility. | |
362 */ | |
363 DateTime parseUTC(String inputString) => parse(inputString, true); | 343 DateTime parseUTC(String inputString) => parse(inputString, true); |
364 | 344 |
365 /** | 345 /// Given user input, attempt to parse the [inputString] into the anticipated |
366 * Given user input, attempt to parse the [inputString] into the anticipated | 346 /// format, treating it as being in UTC. |
367 * format, treating it as being in UTC. | 347 /// |
368 * | 348 /// The canonical Dart style name |
369 * The canonical Dart style name | 349 /// is [parseUtc], but [parseUTC] is retained |
370 * is [parseUtc], but [parseUTC] is retained | 350 /// for backward-compatibility. |
371 * for backward-compatibility. | |
372 */ | |
373 DateTime parseUtc(String inputString) => parse(inputString, true); | 351 DateTime parseUtc(String inputString) => parse(inputString, true); |
374 | 352 |
375 /** | 353 /// Return the locale code in which we operate, e.g. 'en_US' or 'pt'. |
376 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. | |
377 */ | |
378 String get locale => _locale; | 354 String get locale => _locale; |
379 | 355 |
380 /** | 356 /// Returns a list of all locales for which we have date formatting |
381 * Returns a list of all locales for which we have date formatting | 357 /// information. |
382 * information. | 358 static List<String> allLocalesWithSymbols() => |
383 */ | 359 new List<String>.from(dateTimeSymbols.keys); |
384 static List<String> allLocalesWithSymbols() => dateTimeSymbols.keys.toList(); | |
385 | 360 |
386 /** | 361 /// The named constructors for this class are all conveniences for creating |
387 * The named constructors for this class are all conveniences for creating | 362 /// instances using one of the known "skeleton" formats, and having code |
388 * instances using one of the known "skeleton" formats, and having code | 363 /// completion support for discovering those formats. |
389 * completion support for discovering those formats. | 364 /// So, |
390 * So, | 365 /// new DateFormat.yMd("en_US") |
391 * new DateFormat.yMd("en_US") | 366 /// is equivalent to |
392 * is equivalent to | 367 /// new DateFormat("yMd", "en_US") |
393 * new DateFormat("yMd", "en_US") | 368 /// To create a compound format you can use these constructors in combination |
394 * To create a compound format you can use these constructors in combination | 369 /// with the add_ methods below. e.g. |
395 * with the add_ methods below. e.g. | 370 /// new DateFormat.yMd().add_Hms(); |
396 * new DateFormat.yMd().add_Hms(); | 371 /// If the optional [locale] is omitted, the format will be created using the |
397 * If the optional [locale] is omitted, the format will be created using the | 372 /// default locale in [Intl.systemLocale]. |
398 * default locale in [Intl.systemLocale]. | |
399 */ | |
400 DateFormat.d([locale]) : this("d", locale); | 373 DateFormat.d([locale]) : this("d", locale); |
401 DateFormat.E([locale]) : this("E", locale); | 374 DateFormat.E([locale]) : this("E", locale); |
402 DateFormat.EEEE([locale]) : this("EEEE", locale); | 375 DateFormat.EEEE([locale]) : this("EEEE", locale); |
403 DateFormat.LLL([locale]) : this("LLL", locale); | 376 DateFormat.LLL([locale]) : this("LLL", locale); |
404 DateFormat.LLLL([locale]) : this("LLLL", locale); | 377 DateFormat.LLLL([locale]) : this("LLLL", locale); |
405 DateFormat.M([locale]) : this("M", locale); | 378 DateFormat.M([locale]) : this("M", locale); |
406 DateFormat.Md([locale]) : this("Md", locale); | 379 DateFormat.Md([locale]) : this("Md", locale); |
407 DateFormat.MEd([locale]) : this("MEd", locale); | 380 DateFormat.MEd([locale]) : this("MEd", locale); |
408 DateFormat.MMM([locale]) : this("MMM", locale); | 381 DateFormat.MMM([locale]) : this("MMM", locale); |
409 DateFormat.MMMd([locale]) : this("MMMd", locale); | 382 DateFormat.MMMd([locale]) : this("MMMd", locale); |
(...skipping 22 matching lines...) Expand all Loading... |
432 DateFormat.jm([locale]) : this("jm", locale); | 405 DateFormat.jm([locale]) : this("jm", locale); |
433 DateFormat.jms([locale]) : this("jms", locale); | 406 DateFormat.jms([locale]) : this("jms", locale); |
434 DateFormat.jmv([locale]) : this("jmv", locale); | 407 DateFormat.jmv([locale]) : this("jmv", locale); |
435 DateFormat.jmz([locale]) : this("jmz", locale); | 408 DateFormat.jmz([locale]) : this("jmz", locale); |
436 DateFormat.jv([locale]) : this("jv", locale); | 409 DateFormat.jv([locale]) : this("jv", locale); |
437 DateFormat.jz([locale]) : this("jz", locale); | 410 DateFormat.jz([locale]) : this("jz", locale); |
438 DateFormat.m([locale]) : this("m", locale); | 411 DateFormat.m([locale]) : this("m", locale); |
439 DateFormat.ms([locale]) : this("ms", locale); | 412 DateFormat.ms([locale]) : this("ms", locale); |
440 DateFormat.s([locale]) : this("s", locale); | 413 DateFormat.s([locale]) : this("s", locale); |
441 | 414 |
442 /** | 415 /// The "add_*" methods append a particular skeleton to the format, or set |
443 * The "add_*" methods append a particular skeleton to the format, or set | 416 /// it as the only format if none was previously set. These are primarily |
444 * it as the only format if none was previously set. These are primarily | 417 /// useful for creating compound formats. For example |
445 * useful for creating compound formats. For example | 418 /// new DateFormat.yMd().add_Hms(); |
446 * new DateFormat.yMd().add_Hms(); | 419 /// would create a date format that prints both the date and the time. |
447 * would create a date format that prints both the date and the time. | |
448 */ | |
449 DateFormat add_d() => addPattern("d"); | 420 DateFormat add_d() => addPattern("d"); |
450 DateFormat add_E() => addPattern("E"); | 421 DateFormat add_E() => addPattern("E"); |
451 DateFormat add_EEEE() => addPattern("EEEE"); | 422 DateFormat add_EEEE() => addPattern("EEEE"); |
452 DateFormat add_LLL() => addPattern("LLL"); | 423 DateFormat add_LLL() => addPattern("LLL"); |
453 DateFormat add_LLLL() => addPattern("LLLL"); | 424 DateFormat add_LLLL() => addPattern("LLLL"); |
454 DateFormat add_M() => addPattern("M"); | 425 DateFormat add_M() => addPattern("M"); |
455 DateFormat add_Md() => addPattern("Md"); | 426 DateFormat add_Md() => addPattern("Md"); |
456 DateFormat add_MEd() => addPattern("MEd"); | 427 DateFormat add_MEd() => addPattern("MEd"); |
457 DateFormat add_MMM() => addPattern("MMM"); | 428 DateFormat add_MMM() => addPattern("MMM"); |
458 DateFormat add_MMMd() => addPattern("MMMd"); | 429 DateFormat add_MMMd() => addPattern("MMMd"); |
(...skipping 22 matching lines...) Expand all Loading... |
481 DateFormat add_jm() => addPattern("jm"); | 452 DateFormat add_jm() => addPattern("jm"); |
482 DateFormat add_jms() => addPattern("jms"); | 453 DateFormat add_jms() => addPattern("jms"); |
483 DateFormat add_jmv() => addPattern("jmv"); | 454 DateFormat add_jmv() => addPattern("jmv"); |
484 DateFormat add_jmz() => addPattern("jmz"); | 455 DateFormat add_jmz() => addPattern("jmz"); |
485 DateFormat add_jv() => addPattern("jv"); | 456 DateFormat add_jv() => addPattern("jv"); |
486 DateFormat add_jz() => addPattern("jz"); | 457 DateFormat add_jz() => addPattern("jz"); |
487 DateFormat add_m() => addPattern("m"); | 458 DateFormat add_m() => addPattern("m"); |
488 DateFormat add_ms() => addPattern("ms"); | 459 DateFormat add_ms() => addPattern("ms"); |
489 DateFormat add_s() => addPattern("s"); | 460 DateFormat add_s() => addPattern("s"); |
490 | 461 |
491 /** | 462 /// For each of the skeleton formats we also allow the use of the |
492 * For each of the skeleton formats we also allow the use of the corresponding | 463 /// corresponding ICU constant names. |
493 * ICU constant names. | |
494 */ | |
495 static const String ABBR_MONTH = 'MMM'; | 464 static const String ABBR_MONTH = 'MMM'; |
496 static const String DAY = 'd'; | 465 static const String DAY = 'd'; |
497 static const String ABBR_WEEKDAY = 'E'; | 466 static const String ABBR_WEEKDAY = 'E'; |
498 static const String WEEKDAY = 'EEEE'; | 467 static const String WEEKDAY = 'EEEE'; |
499 static const String ABBR_STANDALONE_MONTH = 'LLL'; | 468 static const String ABBR_STANDALONE_MONTH = 'LLL'; |
500 static const String STANDALONE_MONTH = 'LLLL'; | 469 static const String STANDALONE_MONTH = 'LLLL'; |
501 static const String NUM_MONTH = 'M'; | 470 static const String NUM_MONTH = 'M'; |
502 static const String NUM_MONTH_DAY = 'Md'; | 471 static const String NUM_MONTH_DAY = 'Md'; |
503 static const String NUM_MONTH_WEEKDAY_DAY = 'MEd'; | 472 static const String NUM_MONTH_WEEKDAY_DAY = 'MEd'; |
504 static const String ABBR_MONTH_DAY = 'MMMd'; | 473 static const String ABBR_MONTH_DAY = 'MMMd'; |
(...skipping 22 matching lines...) Expand all Loading... |
527 static const String HOUR_MINUTE = 'jm'; | 496 static const String HOUR_MINUTE = 'jm'; |
528 static const String HOUR_MINUTE_SECOND = 'jms'; | 497 static const String HOUR_MINUTE_SECOND = 'jms'; |
529 static const String HOUR_MINUTE_GENERIC_TZ = 'jmv'; | 498 static const String HOUR_MINUTE_GENERIC_TZ = 'jmv'; |
530 static const String HOUR_MINUTE_TZ = 'jmz'; | 499 static const String HOUR_MINUTE_TZ = 'jmz'; |
531 static const String HOUR_GENERIC_TZ = 'jv'; | 500 static const String HOUR_GENERIC_TZ = 'jv'; |
532 static const String HOUR_TZ = 'jz'; | 501 static const String HOUR_TZ = 'jz'; |
533 static const String MINUTE = 'm'; | 502 static const String MINUTE = 'm'; |
534 static const String MINUTE_SECOND = 'ms'; | 503 static const String MINUTE_SECOND = 'ms'; |
535 static const String SECOND = 's'; | 504 static const String SECOND = 's'; |
536 | 505 |
537 /** The locale in which we operate, e.g. 'en_US', or 'pt'. */ | 506 /// The locale in which we operate, e.g. 'en_US', or 'pt'. |
538 String _locale; | 507 String _locale; |
539 | 508 |
540 /** | 509 /// The full template string. This may have been specified directly, or |
541 * The full template string. This may have been specified directly, or | 510 /// it may have been derived from a skeleton and the locale information |
542 * it may have been derived from a skeleton and the locale information | 511 /// on how to interpret that skeleton. |
543 * on how to interpret that skeleton. | |
544 */ | |
545 String _pattern; | 512 String _pattern; |
546 | 513 |
547 /** | 514 /// We parse the format string into individual [_DateFormatField] objects |
548 * We parse the format string into individual [_DateFormatField] objects | 515 /// that are used to do the actual formatting and parsing. Do not use |
549 * that are used to do the actual formatting and parsing. Do not use | 516 /// this variable directly, use the getter [_formatFields]. |
550 * this variable directly, use the getter [_formatFields]. | |
551 */ | |
552 List<_DateFormatField> _formatFieldsPrivate; | 517 List<_DateFormatField> _formatFieldsPrivate; |
553 | 518 |
554 /** | 519 /// Getter for [_formatFieldsPrivate] that lazily initializes it. |
555 * Getter for [_formatFieldsPrivate] that lazily initializes it. | 520 List<_DateFormatField> get _formatFields { |
556 */ | |
557 get _formatFields { | |
558 if (_formatFieldsPrivate == null) { | 521 if (_formatFieldsPrivate == null) { |
559 if (_pattern == null) _useDefaultPattern(); | 522 if (_pattern == null) _useDefaultPattern(); |
560 _formatFieldsPrivate = parsePattern(_pattern); | 523 _formatFieldsPrivate = parsePattern(_pattern); |
561 } | 524 } |
562 return _formatFieldsPrivate; | 525 return _formatFieldsPrivate; |
563 } | 526 } |
564 | 527 |
565 /** | 528 /// We are being asked to do formatting without having set any pattern. |
566 * We are being asked to do formatting without having set any pattern. | 529 /// Use a default. |
567 * Use a default. | |
568 */ | |
569 _useDefaultPattern() { | 530 _useDefaultPattern() { |
570 add_yMMMMd(); | 531 add_yMMMMd(); |
571 add_jms(); | 532 add_jms(); |
572 } | 533 } |
573 | 534 |
574 /** | 535 /// A series of regular expressions used to parse a format string into its |
575 * A series of regular expressions used to parse a format string into its | 536 /// component fields. |
576 * component fields. | |
577 */ | |
578 static List<RegExp> _matchers = [ | 537 static List<RegExp> _matchers = [ |
579 // Quoted String - anything between single quotes, with escaping | 538 // Quoted String - anything between single quotes, with escaping |
580 // of single quotes by doubling them. | 539 // of single quotes by doubling them. |
581 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock' | 540 // e.g. in the pattern "hh 'o''clock'" will match 'o''clock' |
582 new RegExp("^\'(?:[^\']|\'\')*\'"), | 541 new RegExp("^\'(?:[^\']|\'\')*\'"), |
583 // Fields - any sequence of 1 or more of the same field characters. | 542 // Fields - any sequence of 1 or more of the same field characters. |
584 // e.g. in "hh:mm:ss" will match hh, mm, and ss. But in "hms" would | 543 // e.g. in "hh:mm:ss" will match hh, mm, and ss. But in "hms" would |
585 // match each letter individually. | 544 // match each letter individually. |
586 new RegExp( | 545 new RegExp( |
587 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|D+|m+|s+|v+|z+|Z+)"), | 546 "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|D+|m+|s+|v+|z+|Z+)"), |
588 // Everything else - A sequence that is not quotes or field characters. | 547 // Everything else - A sequence that is not quotes or field characters. |
589 // e.g. in "hh:mm:ss" will match the colons. | 548 // e.g. in "hh:mm:ss" will match the colons. |
590 new RegExp("^[^\'GyMkSEahKHcLQdDmsvzZ]+") | 549 new RegExp("^[^\'GyMkSEahKHcLQdDmsvzZ]+") |
591 ]; | 550 ]; |
592 | 551 |
593 /** | 552 /// Set our pattern, appending it to any existing patterns. Also adds a single |
594 * Set our pattern, appending it to any existing patterns. Also adds a single | 553 /// space to separate the two. |
595 * space to separate the two. | |
596 */ | |
597 _appendPattern(String inputPattern, [String separator = ' ']) { | 554 _appendPattern(String inputPattern, [String separator = ' ']) { |
598 _pattern = | 555 _pattern = |
599 _pattern == null ? inputPattern : "$_pattern$separator$inputPattern"; | 556 _pattern == null ? inputPattern : "$_pattern$separator$inputPattern"; |
600 } | 557 } |
601 | 558 |
602 /** | 559 /// Add [inputPattern] to this instance as a pattern. If there was a previous |
603 * Add [inputPattern] to this instance as a pattern. If there was a previous | 560 /// pattern, then this appends to it, separating the two by [separator]. |
604 * pattern, then this appends to it, separating the two by [separator]. | 561 /// [inputPattern] is first looked up in our list of known skeletons. |
605 * [inputPattern] is first looked up in our list of known skeletons. | 562 /// If it's found there, then use the corresponding pattern for this locale. |
606 * If it's found there, then use the corresponding pattern for this locale. | 563 /// If it's not, then treat [inputPattern] as an explicit pattern. |
607 * If it's not, then treat [inputPattern] as an explicit pattern. | |
608 */ | |
609 DateFormat addPattern(String inputPattern, [String separator = ' ']) { | 564 DateFormat addPattern(String inputPattern, [String separator = ' ']) { |
610 // TODO(alanknight): This is an expensive operation. Caching recently used | 565 // TODO(alanknight): This is an expensive operation. Caching recently used |
611 // formats, or possibly introducing an entire "locale" object that would | 566 // formats, or possibly introducing an entire "locale" object that would |
612 // cache patterns for that locale could be a good optimization. | 567 // cache patterns for that locale could be a good optimization. |
613 // If we have already parsed the format fields, reset them. | 568 // If we have already parsed the format fields, reset them. |
614 _formatFieldsPrivate = null; | 569 _formatFieldsPrivate = null; |
615 if (inputPattern == null) return this; | 570 if (inputPattern == null) return this; |
616 if (!_availableSkeletons.containsKey(inputPattern)) { | 571 if (!_availableSkeletons.containsKey(inputPattern)) { |
617 _appendPattern(inputPattern, separator); | 572 _appendPattern(inputPattern, separator); |
618 } else { | 573 } else { |
619 _appendPattern(_availableSkeletons[inputPattern], separator); | 574 _appendPattern(_availableSkeletons[inputPattern], separator); |
620 } | 575 } |
621 return this; | 576 return this; |
622 } | 577 } |
623 | 578 |
624 /** Return the pattern that we use to format dates.*/ | 579 /// Return the pattern that we use to format dates. |
625 get pattern => _pattern; | 580 get pattern => _pattern; |
626 | 581 |
627 /** Return the skeletons for our current locale. */ | 582 /// Return the skeletons for our current locale. |
628 Map get _availableSkeletons => dateTimePatterns[locale]; | 583 Map get _availableSkeletons => dateTimePatterns[locale]; |
629 | 584 |
630 /** | 585 /// Return the [DateSymbol] information for the locale. This can be useful |
631 * Return the [DateSymbol] information for the locale. This can be useful | 586 /// to find lists like the names of weekdays or months in a locale, but |
632 * to find lists like the names of weekdays or months in a locale, but | 587 /// the structure of this data may change, and it's generally better to go |
633 * the structure of this data may change, and it's generally better to go | 588 /// through the [format] and [parse] APIs. If the locale isn't present, or |
634 * through the [format] and [parse] APIs. If the locale isn't present, or | 589 /// is uninitialized, returns null; |
635 * is uninitialized, returns null; | 590 DateSymbols get dateSymbols { |
636 */ | 591 if (_locale != lastDateSymbolLocale) { |
637 DateSymbols get dateSymbols => dateTimeSymbols[_locale]; | 592 lastDateSymbolLocale = _locale; |
638 | 593 cachedDateSymbols = dateTimeSymbols[_locale]; |
639 /** | 594 } |
640 * Set the locale. If the locale can't be found, we also look up | 595 return cachedDateSymbols; |
641 * based on alternative versions, e.g. if we have no 'en_CA' we will | |
642 * look for 'en' as a fallback. It will also translate en-ca into en_CA. | |
643 * Null is also considered a valid value for [newLocale], indicating | |
644 * to use the default. | |
645 */ | |
646 _setLocale(String newLocale) { | |
647 _locale = Intl.verifiedLocale(newLocale, localeExists); | |
648 } | 596 } |
649 | 597 |
650 /** | 598 /// Return true if the locale exists, or if it is null. The null case |
651 * Return true if the locale exists, or if it is null. The null case | 599 /// is interpreted to mean that we use the default locale. |
652 * is interpreted to mean that we use the default locale. | |
653 */ | |
654 static bool localeExists(localeName) { | 600 static bool localeExists(localeName) { |
655 if (localeName == null) return false; | 601 if (localeName == null) return false; |
656 return dateTimeSymbols.containsKey(localeName); | 602 return dateTimeSymbols.containsKey(localeName); |
657 } | 603 } |
658 | 604 |
659 static List get _fieldConstructors => [ | 605 static List get _fieldConstructors => [ |
660 (pattern, parent) => new _DateFormatQuotedField(pattern, parent), | 606 (pattern, parent) => new _DateFormatQuotedField(pattern, parent), |
661 (pattern, parent) => new _DateFormatPatternField(pattern, parent), | 607 (pattern, parent) => new _DateFormatPatternField(pattern, parent), |
662 (pattern, parent) => new _DateFormatLiteralField(pattern, parent) | 608 (pattern, parent) => new _DateFormatLiteralField(pattern, parent) |
663 ]; | 609 ]; |
664 | 610 |
665 /** Parse the template pattern and return a list of field objects.*/ | 611 /// Parse the template pattern and return a list of field objects. |
666 List parsePattern(String pattern) { | 612 List<_DateFormatField> parsePattern(String pattern) { |
667 if (pattern == null) return null; | 613 if (pattern == null) return null; |
668 return _parsePatternHelper(pattern).reversed.toList(); | 614 return _parsePatternHelper(pattern).reversed.toList(); |
669 } | 615 } |
670 | 616 |
671 /** Recursive helper for parsing the template pattern. */ | 617 /// Recursive helper for parsing the template pattern. |
672 List _parsePatternHelper(String pattern) { | 618 List<_DateFormatField> _parsePatternHelper(String pattern) { |
673 if (pattern.isEmpty) return []; | 619 if (pattern.isEmpty) return []; |
674 | 620 |
675 var matched = _match(pattern); | 621 var matched = _match(pattern); |
676 if (matched == null) return []; | 622 if (matched == null) return []; |
677 | 623 |
678 var parsed = | 624 var parsed = |
679 _parsePatternHelper(pattern.substring(matched.fullPattern().length)); | 625 _parsePatternHelper(pattern.substring(matched.fullPattern().length)); |
680 parsed.add(matched); | 626 parsed.add(matched); |
681 return parsed; | 627 return parsed; |
682 } | 628 } |
683 | 629 |
684 /** Find elements in a string that are patterns for specific fields.*/ | 630 /// Find elements in a string that are patterns for specific fields. |
685 _DateFormatField _match(String pattern) { | 631 _DateFormatField _match(String pattern) { |
686 for (var i = 0; i < _matchers.length; i++) { | 632 for (var i = 0; i < _matchers.length; i++) { |
687 var regex = _matchers[i]; | 633 var regex = _matchers[i]; |
688 var match = regex.firstMatch(pattern); | 634 var match = regex.firstMatch(pattern); |
689 if (match != null) { | 635 if (match != null) { |
690 return _fieldConstructors[i](match.group(0), this); | 636 return _fieldConstructors[i](match.group(0), this); |
691 } | 637 } |
692 } | 638 } |
| 639 return null; |
693 } | 640 } |
694 } | 641 } |
OLD | NEW |