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 /** | 7 /// This is a private class internal to DateFormat which is used for formatting |
8 * This is a private class internal to DateFormat which is used for formatting | 8 /// particular fields in a template. e.g. if the format is hh:mm:ss then the |
9 * particular fields in a template. e.g. if the format is hh:mm:ss then the | 9 /// fields would be "hh", ":", "mm", ":", and "ss". Each type of field knows |
10 * fields would be "hh", ":", "mm", ":", and "ss". Each type of field knows | 10 /// how to format that portion of a date. |
11 * how to format that portion of a date. | |
12 */ | |
13 abstract class _DateFormatField { | 11 abstract class _DateFormatField { |
14 /** The format string that defines us, e.g. "hh" */ | 12 /// The format string that defines us, e.g. "hh" |
15 String pattern; | 13 final String pattern; |
16 | 14 |
17 /** The DateFormat that we are part of.*/ | 15 /// The DateFormat that we are part of. |
18 DateFormat parent; | 16 DateFormat parent; |
19 | 17 |
20 _DateFormatField(this.pattern, this.parent); | 18 /// Trimmed version of [pattern]. |
| 19 String _trimmedPattern; |
21 | 20 |
22 /** | 21 _DateFormatField(this.pattern, this.parent) { |
23 * Return the width of [pattern]. Different widths represent different | 22 _trimmedPattern = pattern.trim(); |
24 * formatting options. See the comment for DateFormat for details. | 23 } |
25 */ | 24 |
| 25 /// Return the width of [pattern]. Different widths represent different |
| 26 /// formatting options. See the comment for DateFormat for details. |
26 int get width => pattern.length; | 27 int get width => pattern.length; |
27 | 28 |
28 String fullPattern() => pattern; | 29 String fullPattern() => pattern; |
29 | 30 |
30 String toString() => pattern; | 31 String toString() => pattern; |
31 | 32 |
32 /** Format date according to our specification and return the result. */ | 33 /// Format date according to our specification and return the result. |
33 String format(DateTime date) { | 34 String format(DateTime date) { |
34 // Default implementation in the superclass, works for both types of | 35 // Default implementation in the superclass, works for both types of |
35 // literal patterns, and is overridden by _DateFormatPatternField. | 36 // literal patterns, and is overridden by _DateFormatPatternField. |
36 return pattern; | 37 return pattern; |
37 } | 38 } |
38 | 39 |
39 /** Abstract method for subclasses to implementing parsing for their format.*/ | 40 /// Abstract method for subclasses to implementing parsing for their format. |
40 void parse(_Stream input, _DateBuilder dateFields); | 41 void parse(_Stream input, _DateBuilder dateFields); |
41 | 42 |
42 /** | 43 /// Abstract method for subclasses to implementing 'loose' parsing for |
43 * Abstract method for subclasses to implementing 'loose' parsing for | 44 /// their format, accepting input case-insensitively, and allowing some |
44 * their format, accepting input case-insensitively, and allowing some | 45 /// delimiters to be skipped. |
45 * delimiters to be skipped. | |
46 */ | |
47 void parseLoose(_Stream input, _DateBuilder dateFields); | 46 void parseLoose(_Stream input, _DateBuilder dateFields); |
48 | 47 |
49 /** Parse a literal field. We just look for the exact input. */ | 48 /// Parse a literal field. We just look for the exact input. |
50 void parseLiteral(_Stream input) { | 49 void parseLiteral(_Stream input) { |
51 var found = input.read(width); | 50 var found = input.read(width); |
52 if (found != pattern) { | 51 if (found != pattern) { |
53 throwFormatException(input); | 52 throwFormatException(input); |
54 } | 53 } |
55 } | 54 } |
56 | 55 |
57 /** | 56 /// Parse a literal field. We accept either an exact match, or an arbitrary |
58 * Parse a literal field. We accept either an exact match, or an arbitrary | 57 /// amount of whitespace. |
59 * amount of whitespace. | 58 /// |
60 */ | 59 /// Any whitespace which occurs before or after the literal field is trimmed |
| 60 /// from the input stream. Any leading or trailing whitespace in the literal |
| 61 /// field's format specification is also trimmed before matching is |
| 62 /// attempted. Therefore, leading and trailing whitespace is optional, and |
| 63 /// arbitrary additional whitespace may be added before/after the literal. |
61 void parseLiteralLoose(_Stream input) { | 64 void parseLiteralLoose(_Stream input) { |
62 var found = input.peek(width); | 65 _trimWhitespace(input); |
63 if (found == pattern) { | 66 |
64 input.read(width); | 67 var found = input.peek(_trimmedPattern.length); |
| 68 if (found == _trimmedPattern) { |
| 69 input.read(_trimmedPattern.length); |
65 } | 70 } |
| 71 |
| 72 _trimWhitespace(input); |
| 73 } |
| 74 |
| 75 void _trimWhitespace(_Stream input) { |
66 while (!input.atEnd() && input.peek().trim().isEmpty) { | 76 while (!input.atEnd() && input.peek().trim().isEmpty) { |
67 input.read(); | 77 input.read(); |
68 } | 78 } |
69 } | 79 } |
70 | 80 |
71 /** Throw a format exception with an error message indicating the position.*/ | 81 /// Throw a format exception with an error message indicating the position. |
72 void throwFormatException(_Stream stream) { | 82 void throwFormatException(_Stream stream) { |
73 throw new FormatException("Trying to read $this from ${stream.contents} " | 83 throw new FormatException("Trying to read $this from ${stream.contents} " |
74 "at position ${stream.index}"); | 84 "at position ${stream.index}"); |
75 } | 85 } |
76 } | 86 } |
77 | 87 |
78 /** | 88 /// Represents a literal field - a sequence of characters that doesn't |
79 * Represents a literal field - a sequence of characters that doesn't | 89 /// change according to the date's data. As such, the implementation |
80 * change according to the date's data. As such, the implementation | 90 /// is extremely simple. |
81 * is extremely simple. | |
82 */ | |
83 class _DateFormatLiteralField extends _DateFormatField { | 91 class _DateFormatLiteralField extends _DateFormatField { |
84 _DateFormatLiteralField(pattern, parent) : super(pattern, parent); | 92 _DateFormatLiteralField(pattern, parent) : super(pattern, parent); |
85 | 93 |
86 parse(_Stream input, _DateBuilder dateFields) { | 94 parse(_Stream input, _DateBuilder dateFields) { |
87 parseLiteral(input); | 95 parseLiteral(input); |
88 } | 96 } |
89 | 97 |
90 parseLoose(_Stream input, _DateBuilder dateFields) => | 98 parseLoose(_Stream input, _DateBuilder dateFields) => |
91 parseLiteralLoose(input); | 99 parseLiteralLoose(input); |
92 } | 100 } |
93 | 101 |
94 /** | 102 /// Represents a literal field with quoted characters in it. This is |
95 * Represents a literal field with quoted characters in it. This is | 103 /// only slightly more complex than a _DateFormatLiteralField. |
96 * only slightly more complex than a _DateFormatLiteralField. | |
97 */ | |
98 class _DateFormatQuotedField extends _DateFormatField { | 104 class _DateFormatQuotedField extends _DateFormatField { |
99 String _fullPattern; | 105 String _fullPattern; |
100 | 106 |
101 String fullPattern() => _fullPattern; | 107 String fullPattern() => _fullPattern; |
102 | 108 |
103 _DateFormatQuotedField(pattern, parent) : super(pattern, parent) { | 109 _DateFormatQuotedField(pattern, parent) |
| 110 : super(_patchQuotes(pattern), parent) { |
104 _fullPattern = pattern; | 111 _fullPattern = pattern; |
105 patchQuotes(); | |
106 } | 112 } |
107 | 113 |
108 parse(_Stream input, _DateBuilder dateFields) { | 114 parse(_Stream input, _DateBuilder dateFields) { |
109 parseLiteral(input); | 115 parseLiteral(input); |
110 } | 116 } |
111 | 117 |
112 parseLoose(_Stream input, _DateBuilder dateFields) => | 118 parseLoose(_Stream input, _DateBuilder dateFields) => |
113 parseLiteralLoose(input); | 119 parseLiteralLoose(input); |
114 | 120 |
115 void patchQuotes() { | 121 static final _twoEscapedQuotes = new RegExp(r"''"); |
| 122 |
| 123 static String _patchQuotes(String pattern) { |
116 if (pattern == "''") { | 124 if (pattern == "''") { |
117 pattern = "'"; | 125 return "'"; |
118 } else { | 126 } else { |
119 pattern = pattern.substring(1, pattern.length - 1); | 127 return pattern |
120 var twoEscapedQuotes = new RegExp(r"''"); | 128 .substring(1, pattern.length - 1) |
121 pattern = pattern.replaceAll(twoEscapedQuotes, "'"); | 129 .replaceAll(_twoEscapedQuotes, "'"); |
122 } | 130 } |
123 } | 131 } |
124 } | 132 } |
125 | 133 |
126 /** | 134 /// A field that parses "loosely", meaning that we'll accept input that is |
127 * A field that parses "loosely", meaning that we'll accept input that is | 135 /// missing delimiters, has upper/lower case mixed up, and might not strictly |
128 * missing delimiters, has upper/lower case mixed up, and might not strictly | 136 /// conform to the pattern, e.g. the pattern calls for Sep we might accept |
129 * conform to the pattern, e.g. the pattern calls for Sep we might accept | 137 /// sep, september, sEPTember. Doesn't affect numeric fields. |
130 * sep, september, sEPTember. Doesn't affect numeric fields. | |
131 */ | |
132 class _LoosePatternField extends _DateFormatPatternField { | 138 class _LoosePatternField extends _DateFormatPatternField { |
133 _LoosePatternField(String pattern, parent) : super(pattern, parent); | 139 _LoosePatternField(String pattern, parent) : super(pattern, parent); |
134 | 140 |
135 /** | 141 /// Parse from a list of possibilities, but case-insensitively. |
136 * Parse from a list of possibilities, but case-insensitively. | 142 /// Assumes that input is lower case. |
137 * Assumes that input is lower case. | |
138 */ | |
139 int parseEnumeratedString(_Stream input, List possibilities) { | 143 int parseEnumeratedString(_Stream input, List possibilities) { |
140 var lowercasePossibilities = | 144 var lowercasePossibilities = |
141 possibilities.map((x) => x.toLowerCase()).toList(); | 145 possibilities.map((x) => x.toLowerCase()).toList(); |
142 try { | 146 try { |
143 return super.parseEnumeratedString(input, lowercasePossibilities); | 147 return super.parseEnumeratedString(input, lowercasePossibilities); |
144 } on FormatException { | 148 } on FormatException { |
145 return -1; | 149 return -1; |
146 } | 150 } |
147 } | 151 } |
148 | 152 |
149 /** | 153 /// Parse a month name, case-insensitively, and set it in [dateFields]. |
150 * Parse a month name, case-insensitively, and set it in [dateFields]. | 154 /// Assumes that [input] is lower case. |
151 * Assumes that [input] is lower case. | |
152 */ | |
153 void parseMonth(input, dateFields) { | 155 void parseMonth(input, dateFields) { |
154 if (width <= 2) { | 156 if (width <= 2) { |
155 handleNumericField(input, dateFields.setMonth); | 157 handleNumericField(input, dateFields.setMonth); |
156 return; | 158 return; |
157 } | 159 } |
158 var possibilities = [symbols.MONTHS, symbols.SHORTMONTHS]; | 160 var possibilities = [symbols.MONTHS, symbols.SHORTMONTHS]; |
159 for (var monthNames in possibilities) { | 161 for (var monthNames in possibilities) { |
160 var month = parseEnumeratedString(input, monthNames); | 162 var month = parseEnumeratedString(input, monthNames); |
161 if (month != -1) { | 163 if (month != -1) { |
162 dateFields.month = month + 1; | 164 dateFields.month = month + 1; |
163 return; | 165 return; |
164 } | 166 } |
165 } | 167 } |
| 168 throwFormatException(input); |
166 } | 169 } |
167 | 170 |
168 /** | 171 /// Parse a standalone day name, case-insensitively. |
169 * Parse a standalone day name, case-insensitively. | 172 /// Assumes that input is lower case. Doesn't do anything |
170 * Assumes that input is lower case. Doesn't do anything | |
171 */ | |
172 void parseStandaloneDay(input) { | 173 void parseStandaloneDay(input) { |
173 // This is ignored, but we still have to skip over it the correct amount. | 174 // This is ignored, but we still have to skip over it the correct amount. |
174 if (width <= 2) { | 175 if (width <= 2) { |
175 handleNumericField(input, (x) => x); | 176 handleNumericField(input, (x) => x); |
176 return; | 177 return; |
177 } | 178 } |
178 var possibilities = [ | 179 var possibilities = [ |
179 symbols.STANDALONEWEEKDAYS, | 180 symbols.STANDALONEWEEKDAYS, |
180 symbols.STANDALONESHORTWEEKDAYS | 181 symbols.STANDALONESHORTWEEKDAYS |
181 ]; | 182 ]; |
182 for (var dayNames in possibilities) { | 183 for (var dayNames in possibilities) { |
183 var day = parseEnumeratedString(input, dayNames); | 184 var day = parseEnumeratedString(input, dayNames); |
184 if (day != -1) { | 185 if (day != -1) { |
185 return; | 186 return; |
186 } | 187 } |
187 } | 188 } |
188 } | 189 } |
189 | 190 |
190 /** | 191 /// Parse a standalone month name, case-insensitively, and set it in |
191 * Parse a standalone month name, case-insensitively. | 192 /// [dateFields]. Assumes that input is lower case. |
192 * Assumes that input is lower case. Doesn't do anything | |
193 */ | |
194 void parseStandaloneMonth(input, dateFields) { | 193 void parseStandaloneMonth(input, dateFields) { |
195 if (width <= 2) { | 194 if (width <= 2) { |
196 handleNumericField(input, (x) => x); | 195 handleNumericField(input, dateFields.setMonth); |
197 return; | 196 return; |
198 } | 197 } |
199 var possibilities = [ | 198 var possibilities = [ |
200 symbols.STANDALONEMONTHS, | 199 symbols.STANDALONEMONTHS, |
201 symbols.STANDALONESHORTMONTHS | 200 symbols.STANDALONESHORTMONTHS |
202 ]; | 201 ]; |
203 for (var monthNames in possibilities) { | 202 for (var monthNames in possibilities) { |
204 var month = parseEnumeratedString(input, monthNames); | 203 var month = parseEnumeratedString(input, monthNames); |
205 if (month != -1) { | 204 if (month != -1) { |
206 dateFields.month = month + 1; | 205 dateFields.month = month + 1; |
207 return; | 206 return; |
208 } | 207 } |
209 } | 208 } |
| 209 throwFormatException(input); |
210 } | 210 } |
211 | 211 |
212 /** | 212 /// Parse a day of the week name, case-insensitively. |
213 * Parse a day of the week name, case-insensitively. | 213 /// Assumes that input is lower case. Doesn't do anything |
214 * Assumes that input is lower case. Doesn't do anything | |
215 */ | |
216 void parseDayOfWeek(_Stream input) { | 214 void parseDayOfWeek(_Stream input) { |
217 // This is IGNORED, but we still have to skip over it the correct amount. | 215 // This is IGNORED, but we still have to skip over it the correct amount. |
218 if (width <= 2) { | 216 if (width <= 2) { |
219 handleNumericField(input, (x) => x); | 217 handleNumericField(input, (x) => x); |
220 return; | 218 return; |
221 } | 219 } |
222 var possibilities = [symbols.WEEKDAYS, symbols.SHORTWEEKDAYS]; | 220 var possibilities = [symbols.WEEKDAYS, symbols.SHORTWEEKDAYS]; |
223 for (var dayNames in possibilities) { | 221 for (var dayNames in possibilities) { |
224 var day = parseEnumeratedString(input, dayNames); | 222 var day = parseEnumeratedString(input, dayNames); |
225 if (day != -1) { | 223 if (day != -1) { |
226 return; | 224 return; |
227 } | 225 } |
228 } | 226 } |
229 } | 227 } |
230 } | 228 } |
231 | 229 |
232 /* | 230 /* |
233 * Represents a field in the pattern that formats some aspect of the | 231 * Represents a field in the pattern that formats some aspect of the |
234 * date. Consists primarily of a switch on the particular pattern characters | 232 * date. Consists primarily of a switch on the particular pattern characters |
235 * to determine what to do. | 233 * to determine what to do. |
236 */ | 234 */ |
237 class _DateFormatPatternField extends _DateFormatField { | 235 class _DateFormatPatternField extends _DateFormatField { |
238 _DateFormatPatternField(pattern, parent) : super(pattern, parent); | 236 _DateFormatPatternField(pattern, parent) : super(pattern, parent); |
239 | 237 |
240 /** Format date according to our specification and return the result. */ | 238 /// Format date according to our specification and return the result. |
241 String format(DateTime date) { | 239 String format(DateTime date) { |
242 return formatField(date); | 240 return formatField(date); |
243 } | 241 } |
244 | 242 |
245 /** | 243 /// Parse the date according to our specification and put the result |
246 * Parse the date according to our specification and put the result | 244 /// into the correct place in dateFields. |
247 * into the correct place in dateFields. | |
248 */ | |
249 void parse(_Stream input, _DateBuilder dateFields) { | 245 void parse(_Stream input, _DateBuilder dateFields) { |
250 parseField(input, dateFields); | 246 parseField(input, dateFields); |
251 } | 247 } |
252 | 248 |
253 /** | 249 /// Parse the date according to our specification and put the result |
254 * Parse the date according to our specification and put the result | 250 /// into the correct place in dateFields. Allow looser parsing, accepting |
255 * into the correct place in dateFields. Allow looser parsing, accepting | 251 /// case-insensitive input and skipped delimiters. |
256 * case-insensitive input and skipped delimiters. | |
257 */ | |
258 void parseLoose(_Stream input, _DateBuilder dateFields) { | 252 void parseLoose(_Stream input, _DateBuilder dateFields) { |
259 new _LoosePatternField(pattern, parent).parse(input, dateFields); | 253 new _LoosePatternField(pattern, parent).parse(input, dateFields); |
260 } | 254 } |
261 | 255 |
262 /** | 256 /// Parse a field representing part of a date pattern. Note that we do not |
263 * Parse a field representing part of a date pattern. Note that we do not | 257 /// return a value, but rather build up the result in [builder]. |
264 * return a value, but rather build up the result in [builder]. | |
265 */ | |
266 void parseField(_Stream input, _DateBuilder builder) { | 258 void parseField(_Stream input, _DateBuilder builder) { |
267 try { | 259 try { |
268 switch (pattern[0]) { | 260 switch (pattern[0]) { |
269 case 'a': | 261 case 'a': |
270 parseAmPm(input, builder); | 262 parseAmPm(input, builder); |
271 break; | 263 break; |
272 case 'c': | 264 case 'c': |
273 parseStandaloneDay(input); | 265 parseStandaloneDay(input); |
274 break; | 266 break; |
275 case 'd': | 267 case 'd': |
276 handleNumericField(input, builder.setDay); | 268 handleNumericField(input, builder.setDay); |
277 break; // day | 269 break; // day |
278 // Day of year. Setting month=January with any day of the year works | 270 // Day of year. Setting month=January with any day of the year works |
279 case 'D': | 271 case 'D': |
280 handleNumericField(input, builder.setDay); | 272 handleNumericField(input, builder.setDay); |
281 break; // dayofyear | 273 break; // dayofyear |
282 case 'E': | 274 case 'E': |
283 parseDayOfWeek(input); | 275 parseDayOfWeek(input); |
284 break; | 276 break; |
285 case 'G': | 277 case 'G': |
| 278 parseEra(input); |
286 break; // era | 279 break; // era |
287 case 'h': | 280 case 'h': |
288 parse1To12Hours(input, builder); | 281 parse1To12Hours(input, builder); |
289 break; | 282 break; |
290 case 'H': | 283 case 'H': |
291 handleNumericField(input, builder.setHour); | 284 handleNumericField(input, builder.setHour); |
292 break; // hour 0-23 | 285 break; // hour 0-23 |
293 case 'K': | 286 case 'K': |
294 handleNumericField(input, builder.setHour); | 287 handleNumericField(input, builder.setHour); |
295 break; //hour 0-11 | 288 break; //hour 0-11 |
(...skipping 27 matching lines...) Expand all Loading... |
323 case 'Z': | 316 case 'Z': |
324 break; // time zone RFC | 317 break; // time zone RFC |
325 default: | 318 default: |
326 return; | 319 return; |
327 } | 320 } |
328 } catch (e) { | 321 } catch (e) { |
329 throwFormatException(input); | 322 throwFormatException(input); |
330 } | 323 } |
331 } | 324 } |
332 | 325 |
333 /** Formatting logic if we are of type FIELD */ | 326 /// Formatting logic if we are of type FIELD |
334 String formatField(DateTime date) { | 327 String formatField(DateTime date) { |
335 switch (pattern[0]) { | 328 switch (pattern[0]) { |
336 case 'a': | 329 case 'a': |
337 return formatAmPm(date); | 330 return formatAmPm(date); |
338 case 'c': | 331 case 'c': |
339 return formatStandaloneDay(date); | 332 return formatStandaloneDay(date); |
340 case 'd': | 333 case 'd': |
341 return formatDayOfMonth(date); | 334 return formatDayOfMonth(date); |
342 case 'D': | 335 case 'D': |
343 return formatDayOfYear(date); | 336 return formatDayOfYear(date); |
(...skipping 27 matching lines...) Expand all Loading... |
371 return formatYear(date); | 364 return formatYear(date); |
372 case 'z': | 365 case 'z': |
373 return formatTimeZone(date); | 366 return formatTimeZone(date); |
374 case 'Z': | 367 case 'Z': |
375 return formatTimeZoneRFC(date); | 368 return formatTimeZoneRFC(date); |
376 default: | 369 default: |
377 return ''; | 370 return ''; |
378 } | 371 } |
379 } | 372 } |
380 | 373 |
381 /** Return the symbols for our current locale. */ | 374 /// Return the symbols for our current locale. |
382 DateSymbols get symbols => dateTimeSymbols[parent.locale]; | 375 DateSymbols get symbols => parent.dateSymbols; |
383 | 376 |
384 formatEra(DateTime date) { | 377 formatEra(DateTime date) { |
385 var era = date.year > 0 ? 1 : 0; | 378 var era = date.year > 0 ? 1 : 0; |
386 return width >= 4 ? symbols.ERANAMES[era] : symbols.ERAS[era]; | 379 return width >= 4 ? symbols.ERANAMES[era] : symbols.ERAS[era]; |
387 } | 380 } |
388 | 381 |
389 formatYear(DateTime date) { | 382 formatYear(DateTime date) { |
390 // TODO(alanknight): Proper handling of years <= 0 | 383 // TODO(alanknight): Proper handling of years <= 0 |
391 var year = date.year; | 384 var year = date.year; |
392 if (year < 0) { | 385 if (year < 0) { |
393 year = -year; | 386 year = -year; |
394 } | 387 } |
395 return width == 2 ? padTo(2, year % 100) : padTo(width, year); | 388 return width == 2 ? padTo(2, year % 100) : padTo(width, year); |
396 } | 389 } |
397 | 390 |
398 /** | 391 /// We are given [input] as a stream from which we want to read a date. We |
399 * We are given [input] as a stream from which we want to read a date. We | 392 /// can't dynamically build up a date, so we are given a list [dateFields] of |
400 * can't dynamically build up a date, so we are given a list [dateFields] of | 393 /// the constructor arguments and an [position] at which to set it |
401 * the constructor arguments and an [position] at which to set it | 394 /// (year,month,day,hour,minute,second,fractionalSecond) |
402 * (year,month,day,hour,minute,second,fractionalSecond) | 395 /// then after all parsing is done we construct a date from the arguments. |
403 * then after all parsing is done we construct a date from the arguments. | 396 /// This method handles reading any of the numeric fields. The [offset] |
404 * This method handles reading any of the numeric fields. The [offset] | 397 /// argument allows us to compensate for zero-based versus one-based values. |
405 * argument allows us to compensate for zero-based versus one-based values. | |
406 */ | |
407 void handleNumericField(_Stream input, Function setter, [int offset = 0]) { | 398 void handleNumericField(_Stream input, Function setter, [int offset = 0]) { |
408 var result = input.nextInteger(); | 399 var result = input.nextInteger(); |
409 if (result == null) throwFormatException(input); | 400 if (result == null) throwFormatException(input); |
410 setter(result + offset); | 401 setter(result + offset); |
411 } | 402 } |
412 | 403 |
413 /** | 404 /// We are given [input] as a stream from which we want to read a date. We |
414 * We are given [input] as a stream from which we want to read a date. We | 405 /// can't dynamically build up a date, so we are given a list [dateFields] of |
415 * can't dynamically build up a date, so we are given a list [dateFields] of | 406 /// the constructor arguments and an [position] at which to set it |
416 * the constructor arguments and an [position] at which to set it | 407 /// (year,month,day,hour,minute,second,fractionalSecond) |
417 * (year,month,day,hour,minute,second,fractionalSecond) | 408 /// then after all parsing is done we construct a date from the arguments. |
418 * then after all parsing is done we construct a date from the arguments. | 409 /// This method handles reading any of string fields from an enumerated set. |
419 * This method handles reading any of string fields from an enumerated set. | |
420 */ | |
421 int parseEnumeratedString(_Stream input, List possibilities) { | 410 int parseEnumeratedString(_Stream input, List possibilities) { |
422 var results = new _Stream(possibilities) | 411 var results = new _Stream(possibilities) |
423 .findIndexes((each) => input.peek(each.length) == each); | 412 .findIndexes((each) => input.peek(each.length) == each); |
424 if (results.isEmpty) throwFormatException(input); | 413 if (results.isEmpty) throwFormatException(input); |
425 results.sort( | 414 results.sort( |
426 (a, b) => possibilities[a].length.compareTo(possibilities[b].length)); | 415 (a, b) => possibilities[a].length.compareTo(possibilities[b].length)); |
427 var longestResult = results.last; | 416 var longestResult = results.last; |
428 input.read(possibilities[longestResult].length); | 417 input.read(possibilities[longestResult].length); |
429 return longestResult; | 418 return longestResult; |
430 } | 419 } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
470 if (width - 3 > 0) { | 459 if (width - 3 > 0) { |
471 var extra = padTo(width - 3, 0); | 460 var extra = padTo(width - 3, 0); |
472 return basic + extra; | 461 return basic + extra; |
473 } else { | 462 } else { |
474 return basic; | 463 return basic; |
475 } | 464 } |
476 } | 465 } |
477 | 466 |
478 String formatAmPm(DateTime date) { | 467 String formatAmPm(DateTime date) { |
479 var hours = date.hour; | 468 var hours = date.hour; |
480 var index = (date.hour >= 12) && (date.hour < 24) ? 1 : 0; | 469 var index = (hours >= 12) && (hours < 24) ? 1 : 0; |
481 var ampm = symbols.AMPMS; | 470 var ampm = symbols.AMPMS; |
482 return ampm[index]; | 471 return ampm[index]; |
483 } | 472 } |
484 | 473 |
485 void parseAmPm(input, dateFields) { | 474 void parseAmPm(input, dateFields) { |
486 // If we see a "PM" note it in an extra field. | 475 // If we see a "PM" note it in an extra field. |
487 var ampm = parseEnumeratedString(input, symbols.AMPMS); | 476 var ampm = parseEnumeratedString(input, symbols.AMPMS); |
488 if (ampm == 1) dateFields.pm = true; | 477 if (ampm == 1) dateFields.pm = true; |
489 } | 478 } |
490 | 479 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
566 possibilities = symbols.STANDALONESHORTMONTHS; | 555 possibilities = symbols.STANDALONESHORTMONTHS; |
567 break; | 556 break; |
568 default: | 557 default: |
569 return handleNumericField(input, dateFields.setMonth); | 558 return handleNumericField(input, dateFields.setMonth); |
570 } | 559 } |
571 dateFields.month = parseEnumeratedString(input, possibilities) + 1; | 560 dateFields.month = parseEnumeratedString(input, possibilities) + 1; |
572 } | 561 } |
573 | 562 |
574 String formatQuarter(DateTime date) { | 563 String formatQuarter(DateTime date) { |
575 var quarter = ((date.month - 1) / 3).truncate(); | 564 var quarter = ((date.month - 1) / 3).truncate(); |
576 if (width < 4) { | 565 switch (width) { |
577 return symbols.SHORTQUARTERS[quarter]; | 566 case 4: |
578 } else { | 567 return symbols.QUARTERS[quarter]; |
579 return symbols.QUARTERS[quarter]; | 568 case 3: |
| 569 return symbols.SHORTQUARTERS[quarter]; |
| 570 default: |
| 571 return padTo(width, quarter + 1); |
580 } | 572 } |
581 } | 573 } |
| 574 |
582 String formatDayOfMonth(DateTime date) { | 575 String formatDayOfMonth(DateTime date) { |
583 return padTo(width, date.day); | 576 return padTo(width, date.day); |
584 } | 577 } |
585 | 578 |
586 String formatDayOfYear(DateTime date) => padTo(width, dayNumberInYear(date)); | 579 String formatDayOfYear(DateTime date) => padTo(width, dayNumberInYear(date)); |
587 | 580 |
588 /** Return the ordinal day, i.e. the day number in the year. */ | 581 /// Return the ordinal day, i.e. the day number in the year. |
589 int dayNumberInYear(DateTime date) { | 582 int dayNumberInYear(DateTime date) { |
590 if (date.month == 1) return date.day; | 583 if (date.month == 1) return date.day; |
591 if (date.month == 2) return date.day + 31; | 584 if (date.month == 2) return date.day + 31; |
592 return ordinalDayFromMarchFirst(date) + 59 + (isLeapYear(date) ? 1 : 0); | 585 return ordinalDayFromMarchFirst(date) + 59 + (isLeapYear(date) ? 1 : 0); |
593 } | 586 } |
594 | 587 |
595 /** | 588 /// Return the day of the year counting March 1st as 1, after which the |
596 * Return the day of the year counting March 1st as 1, after which the | 589 /// number of days per month is constant, so it's easier to calculate. |
597 * number of days per month is constant, so it's easier to calculate. | 590 /// Formula from http://en.wikipedia.org/wiki/Ordinal_date |
598 * Formula from http://en.wikipedia.org/wiki/Ordinal_date | |
599 */ | |
600 int ordinalDayFromMarchFirst(DateTime date) => | 591 int ordinalDayFromMarchFirst(DateTime date) => |
601 ((30.6 * date.month) - 91.4).floor() + date.day; | 592 ((30.6 * date.month) - 91.4).floor() + date.day; |
602 | 593 |
603 /** | 594 /// Return true if this is a leap year. Rely on [DateTime] to do the |
604 * Return true if this is a leap year. Rely on [DateTime] to do the | 595 /// underlying calculation, even though it doesn't expose the test to us. |
605 * underlying calculation, even though it doesn't expose the test to us. | |
606 */ | |
607 bool isLeapYear(DateTime date) { | 596 bool isLeapYear(DateTime date) { |
608 var feb29 = new DateTime(date.year, 2, 29); | 597 var feb29 = new DateTime(date.year, 2, 29); |
609 return feb29.month == 2; | 598 return feb29.month == 2; |
610 } | 599 } |
611 | 600 |
612 String formatDayOfWeek(DateTime date) { | 601 String formatDayOfWeek(DateTime date) { |
613 // Note that Dart's weekday returns 1 for Monday and 7 for Sunday. | 602 // Note that Dart's weekday returns 1 for Monday and 7 for Sunday. |
614 return (width >= 4 | 603 return (width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS)[ |
615 ? symbols.WEEKDAYS | 604 (date.weekday) % 7]; |
616 : symbols.SHORTWEEKDAYS)[(date.weekday) % 7]; | |
617 } | 605 } |
618 | 606 |
619 void parseDayOfWeek(_Stream input) { | 607 void parseDayOfWeek(_Stream input) { |
620 // This is IGNORED, but we still have to skip over it the correct amount. | 608 // This is IGNORED, but we still have to skip over it the correct amount. |
621 var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS; | 609 var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS; |
622 parseEnumeratedString(input, possibilities); | 610 parseEnumeratedString(input, possibilities); |
623 } | 611 } |
624 | 612 |
| 613 void parseEra(_Stream input) { |
| 614 var possibilities = width >= 4 ? symbols.ERANAMES : symbols.ERAS; |
| 615 parseEnumeratedString(input, possibilities); |
| 616 } |
| 617 |
625 String formatMinutes(DateTime date) { | 618 String formatMinutes(DateTime date) { |
626 return padTo(width, date.minute); | 619 return padTo(width, date.minute); |
627 } | 620 } |
628 | 621 |
629 String formatSeconds(DateTime date) { | 622 String formatSeconds(DateTime date) { |
630 return padTo(width, date.second); | 623 return padTo(width, date.second); |
631 } | 624 } |
632 | 625 |
633 String formatTimeZoneId(DateTime date) { | 626 String formatTimeZoneId(DateTime date) { |
634 // TODO(alanknight): implement time zone support | 627 // TODO(alanknight): implement time zone support |
635 throw new UnimplementedError(); | 628 throw new UnimplementedError(); |
636 } | 629 } |
637 | 630 |
638 String formatTimeZone(DateTime date) { | 631 String formatTimeZone(DateTime date) { |
639 throw new UnimplementedError(); | 632 throw new UnimplementedError(); |
640 } | 633 } |
641 | 634 |
642 String formatTimeZoneRFC(DateTime date) { | 635 String formatTimeZoneRFC(DateTime date) { |
643 throw new UnimplementedError(); | 636 throw new UnimplementedError(); |
644 } | 637 } |
645 | 638 |
646 /** | 639 /// Return a string representation of the object padded to the left with |
647 * Return a string representation of the object padded to the left with | 640 /// zeros. Primarily useful for numbers. |
648 * zeros. Primarily useful for numbers. | 641 static String padTo(int width, Object toBePrinted) => |
649 */ | 642 '$toBePrinted'.padLeft(width, '0'); |
650 String padTo(int width, Object toBePrinted) { | |
651 var basicString = toBePrinted.toString(); | |
652 if (basicString.length >= width) return basicString; | |
653 var buffer = new StringBuffer(); | |
654 for (var i = 0; i < width - basicString.length; i++) { | |
655 buffer.write('0'); | |
656 } | |
657 buffer.write(basicString); | |
658 return buffer.toString(); | |
659 } | |
660 } | 643 } |
OLD | NEW |