OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * This is a private class internal to DateFormat which is used for formatting | |
7 * particular fields in a template. e.g. if the format is hh:mm:ss then the | |
8 * fields would be "hh", ":", "mm", ":", and "ss". Each type of field knows | |
9 * how to format that portion of a date. | |
10 */ | |
11 class _DateFormatField{ | |
Emily Fortuna
2012/08/06 22:43:00
space between _DateFormatField and {
Alan Knight
2012/08/08 00:47:44
Done.
| |
12 /** The format string that defines us, e.g. "hh" */ | |
13 String pattern; | |
14 | |
15 /** The DateFormat that we are part of.*/ | |
16 DateFormat parent; | |
17 | |
18 _DateFormatField(this.pattern, this.parent); | |
19 | |
20 /** | |
21 * Return the width of [pattern]. Different widths represent different | |
22 * formatting options. See the comment for DateFormat for details. | |
23 */ | |
24 int get width() => pattern.length; | |
25 | |
26 String fullPattern() => pattern; | |
27 | |
28 String toString() => pattern; | |
29 | |
30 /** Format date according to our specification and return the result. */ | |
31 String format(Date date) { | |
Emily Fortuna
2012/08/06 22:43:00
String format(Date date) => pattern; (?)
seems lik
Alan Knight
2012/08/08 00:47:44
It's a default implementation in the superclass th
| |
32 return pattern; | |
33 } | |
34 | |
35 abstract void parse(_Stream input, _DateBuilder dateFields); | |
36 | |
37 /** Parse a literal field. We just look for the exact input. */ | |
38 void parseLiteral(_Stream input) { | |
39 var found = input.read(width); | |
40 if (found != pattern) throw | |
Emily Fortuna
2012/08/06 22:43:00
if your "if" statement is going to span more than
Alan Knight
2012/08/08 00:47:44
Done.
| |
41 new FormatException(input, this); | |
42 } | |
43 } | |
44 | |
45 /** | |
46 * Represents a literal field - a sequence of characters that doesn't | |
47 * change according to the date's data. As such, the implementation | |
48 * is extremely simple. | |
49 */ | |
50 class _DateFormatLiteralField extends _DateFormatField{ | |
Emily Fortuna
2012/08/06 22:43:00
space before { and elsewhere
Alan Knight
2012/08/08 00:47:44
Done.
| |
51 | |
52 _DateFormatLiteralField(pattern,parent): super(pattern,parent); | |
Emily Fortuna
2012/08/06 22:43:00
give these variables some space to breathe! space
Alan Knight
2012/08/08 00:47:44
Done.
| |
53 | |
54 parse(_Stream input, _DateBuilder dateFields) { | |
55 return parseLiteral(input); | |
56 } | |
57 } | |
58 | |
59 /** | |
60 * Represents a literal field with quoted characters in it. This is | |
61 * only slightly more complex than a _DateFormatLiteralField. | |
62 */ | |
63 class _DateFormatQuotedField extends _DateFormatField{ | |
64 | |
65 String _fullPattern; | |
66 | |
67 String fullPattern() => _fullPattern; | |
68 | |
69 _DateFormatQuotedField(pattern,parent): super(pattern,parent) { | |
70 _fullPattern = pattern; | |
71 patchQuotes(); | |
72 } | |
73 | |
74 parse(_Stream input, _DateBuilder dateFields) { | |
75 return parseLiteral(input); | |
76 } | |
77 | |
78 void patchQuotes() { | |
79 if (pattern == "''") { | |
80 pattern = "'"; | |
81 } else { | |
82 pattern = pattern.substring(1,pattern.length - 1); | |
83 var twoEscapedQuotes = new RegExp("\'\'"); | |
Emily Fortuna
2012/08/06 22:43:00
nit: could also be written as new RegExp(@"''");
Alan Knight
2012/08/08 00:47:44
Done.
| |
84 pattern = pattern.replaceAll(twoEscapedQuotes, "'"); | |
85 } | |
86 } | |
87 } | |
88 | |
89 /* | |
90 * Represents a field in the pattern that formats some aspect of the | |
91 * date. Consists primarily of a switch on the particular pattern characters | |
92 * to determine what to do. | |
93 */ | |
94 class _DateFormatPatternField extends _DateFormatField { | |
95 | |
96 _DateFormatPatternField(pattern,parent): super(pattern,parent); | |
Emily Fortuna
2012/08/06 22:43:00
space between parameters:
"pattern, parent"
Alan Knight
2012/08/08 00:47:44
Done.
| |
97 | |
98 /** Format date according to our specification and return the result. */ | |
99 String format(Date date) { | |
100 return formatField(date); | |
101 } | |
102 | |
103 /** | |
104 * Parse the date according to our specification and put the result | |
105 * into the correct place in dateFields. | |
106 */ | |
107 void parse(_Stream input, _DateBuilder dateFields) { | |
108 parseField(input,dateFields); | |
109 } | |
110 | |
111 /** | |
112 * Parse a field representing part of a date pattern. Note that we do not | |
113 * return a value, but rather build up the result in [builder]. | |
114 */ | |
115 void parseField(_Stream input, _DateBuilder builder) { | |
116 try { | |
117 switch(pattern[0]) { | |
118 case 'a': parseAmPm(input,builder); break; | |
119 case 'c': parseStandaloneDay(input); break; | |
120 case 'd': handleNumericField(input,builder.setDay); break; // day | |
121 case 'E': parseDayOfWeek(input); break; | |
122 case 'G': break; // era | |
123 case 'h': parse1To12Hours(input,builder); break; | |
124 case 'H': handleNumericField(input,builder.setHour); break; // hour 0-23 | |
125 case 'K': handleNumericField(input,builder.setHour); break; //hour 0-11 | |
126 case 'k': handleNumericField(input,builder.setHour,-1); break; //hr 1-24 | |
127 case 'L': parseStandaloneMonth(input,builder); break; | |
128 case 'M': parseMonth(input,builder); break; | |
129 case 'm': handleNumericField(input,builder.setMinute); break; // minutes | |
130 case 'Q': break; // quarter | |
131 case 'S': handleNumericField(input,builder.setFractionalSecond); break; | |
132 case 's': handleNumericField(input,builder.setSecond); break; | |
133 case 'v': break; // time zone id | |
134 case 'y': handleNumericField(input,builder.setYear); break; | |
135 case 'z': break; // time zone | |
136 case 'Z': break; // time zone RFC | |
137 default: return; | |
138 } | |
139 } catch (var e) {throw new FormatException(input,this);} | |
140 } | |
141 | |
142 /** Formatting logic if we are of type FIELD */ | |
143 String formatField(Date date) { | |
144 switch (pattern[0]) { | |
145 case 'a': return formatAmPm(date); | |
146 case 'c': return formatStandaloneDay(date); | |
147 case 'd': return formatDayOfMonth(date); | |
148 case 'E': return formatDayOfWeek(date); | |
149 case 'G': return formatEra(date); | |
150 case 'h': return format1To12Hours(date); | |
151 case 'H': return format0To23Hours(date); | |
152 case 'K': return format0To11Hours(date); | |
153 case 'k': return format24Hours(date); | |
154 case 'L': return formatStandaloneMonth(date); | |
155 case 'M': return formatMonth(date); | |
156 case 'm': return formatMinutes(date); | |
157 case 'Q': return formatQuarter(date); | |
158 case 'S': return formatFractionalSeconds(date); | |
159 case 's': return formatSeconds(date); | |
160 case 'v': return formatTimeZoneId(date); | |
161 case 'y': return formatYear(date); | |
162 case 'z': return formatTimeZone(date); | |
163 case 'Z': return formatTimeZoneRFC(date); | |
164 default: return ''; | |
165 } | |
166 } | |
167 | |
168 /** Return the symbols for our current locale. */ | |
169 DateSymbols get symbols() => dateTimeSymbols[parent.locale]; | |
170 | |
171 formatEra(Date date) { | |
172 var era = date.year > 0 ? 1 : 0; | |
173 return width >= 4 ? symbols.ERANAMES[era] : | |
174 symbols.ERAS[era]; | |
175 } | |
176 | |
177 formatYear(Date date) { | |
178 // TODO(alanknight): Proper handling of years <= 0 | |
179 var year = date.year; | |
180 if (year < 0) { | |
181 year = -year; | |
182 } | |
183 return width == 2 ? padTo(2, year % 100) : year.toString(); | |
184 } | |
185 | |
186 /** | |
187 * We are given [input] as a stream from which we want to read a date. We | |
188 * can't dynamically build up a date, so we are given a list [dateFields] of | |
189 * the constructor arguments and an [position] at which to set it | |
190 * (year,month,day,hour,minute,second,fractionalSecond) | |
191 * then after all parsing is done we construct a date from the arguments. | |
192 * This method handles reading any of the numeric fields. The [offset] | |
193 * argument allows us to compensate for zero-based versus one-based values. | |
194 */ | |
195 void handleNumericField( | |
196 _Stream input, | |
Emily Fortuna
2012/08/06 22:43:00
curious indentation
Alan Knight
2012/08/08 00:47:44
Done. It fits into 80 characters now, so no need t
| |
197 Function setter, | |
198 [int offset = 0]) { | |
199 var result = input.nextInteger(); | |
200 setter(result + offset); | |
201 } | |
202 | |
203 /** | |
204 * We are given [input] as a stream from which we want to read a date. We | |
205 * can't dynamically build up a date, so we are given a list [dateFields] of | |
206 * the constructor arguments and an [position] at which to set it | |
207 * (year,month,day,hour,minute,second,fractionalSecond) | |
208 * then after all parsing is done we construct a date from the arguments. | |
209 * This method handles reading any of string fields from an enumerated set. | |
210 */ | |
211 int parseEnumeratedString(_Stream input, List possibilities) { | |
212 var results = new _Stream(possibilities).findIndexes( | |
213 (each) => input.peek(each.length) == each); | |
214 if (results.isEmpty()) throw new FormatException(input,this); | |
215 results.sort( | |
216 (a, b) => possibilities[a].length.compareTo(possibilities[b].length)); | |
217 var longestResult = results.last(); | |
218 input.read(possibilities[longestResult].length); | |
219 return longestResult; | |
220 } | |
221 | |
222 String formatMonth(Date date) { | |
223 switch (width) { | |
224 case 5: return symbols.NARROWMONTHS[date.month-1]; | |
225 case 4: return symbols.MONTHS[date.month-1]; | |
226 case 3: return symbols.SHORTMONTHS[date.month-1]; | |
227 default: | |
228 return padTo(width, date.month); | |
229 } | |
230 } | |
231 | |
232 void parseMonth(input,dateFields) { | |
233 var possibilities; | |
234 switch(width) { | |
235 case 5: possibilities = symbols.NARROWMONTHS; break; | |
236 case 4: possibilities = symbols.MONTHS; break; | |
237 case 3: possibilities = symbols.SHORTMONTHS; break; | |
238 default: return handleNumericField(input,dateFields.setMonth); | |
239 } | |
240 dateFields.month = parseEnumeratedString(input,possibilities) + 1; | |
241 } | |
242 | |
243 String format24Hours(Date date) { | |
244 return padTo(width, date.hour); | |
245 } | |
246 | |
247 String formatFractionalSeconds(Date date) { | |
248 // Always print at least 3 digits. If the width is greater, append 0s | |
249 var basic = padTo(3,date.millisecond); | |
250 if (width - 3 > 0) { | |
251 var extra = padTo(width - 3, 0); | |
252 return basic.concat(extra); | |
253 } else { | |
254 return basic; | |
255 } | |
256 } | |
257 | |
258 String formatAmPm(Date date) { | |
259 var hours = date.hour; | |
260 var index = (date.hour >= 12) && (date.hour < 24) ? 1 : 0; | |
261 var ampm = symbols.AMPMS; | |
262 return ampm[index]; | |
263 } | |
264 | |
265 void parseAmPm(input,dateFields) { | |
266 // If we see a "PM" note it in an extra field. | |
267 var ampm = parseEnumeratedString(input,symbols.AMPMS); | |
268 if (ampm == 1) dateFields.pm = true; | |
269 } | |
270 | |
271 String format1To12Hours(Date date) { | |
272 var hours = date.hour; | |
273 if (date.hour > 12) hours = hours - 12; | |
274 if (hours == 0) hours = 12; | |
275 return padTo(width,hours); | |
276 } | |
277 | |
278 void parse1To12Hours(_Stream input, _DateBuilder dateFields) { | |
279 handleNumericField(input,dateFields.setHour); | |
280 if (dateFields.hour == 12) dateFields.hour = 0; | |
281 } | |
282 | |
283 String format0To11Hours(Date date) { | |
284 return padTo(width,date.hour % 12); | |
285 } | |
286 | |
287 String format0To23Hours(Date date) { | |
288 return padTo(width,date.hour); | |
289 } | |
290 | |
291 String formatStandaloneDay(Date date) { | |
292 switch (width) { | |
293 case 5: return symbols.STANDALONENARROWWEEKDAYS[date.weekday % 7]; | |
294 case 4: return symbols.STANDALONEWEEKDAYS[date.weekday % 7]; | |
295 case 3: return symbols.STANDALONESHORTWEEKDAYS[date.weekday % 7]; | |
296 default: | |
297 return padTo(1, date.day); | |
298 } | |
299 } | |
300 | |
301 void parseStandaloneDay(_Stream input) { | |
302 // This is ignored, but we still have to skip over it the correct amount. | |
303 var possibilities; | |
304 switch(width) { | |
305 case 5: possibilities = symbols.STANDALONENARROWWEEKDAYS; break; | |
306 case 4: possibilities = symbols.STANDALONEWEEKDAYS; break; | |
307 case 3: possibilities = symbols.STANDALONESHORTWEEKDAYS; break; | |
308 default: return handleNumericField(input,(x)=>x); | |
309 } | |
310 parseEnumeratedString(input,possibilities); | |
311 } | |
312 | |
313 String formatStandaloneMonth(Date date) { | |
314 switch (width) { | |
315 case 5: | |
316 return symbols.STANDALONENARROWMONTHS[date.month-1]; | |
317 case 4: | |
318 return symbols.STANDALONEMONTHS[date.month-1]; | |
319 case 3: | |
320 return symbols.STANDALONESHORTMONTHS[date.month-1]; | |
321 default: | |
322 return padTo(width, date.month); | |
323 } | |
324 } | |
325 | |
326 void parseStandaloneMonth(input,dateFields) { | |
327 var possibilities; | |
328 switch(width) { | |
329 case 5: possibilities = symbols.STANDALONENARROWMONTHS; break; | |
330 case 4: possibilities = symbols.STANDALONEMONTHS; break; | |
331 case 3: possibilities = symbols.STANDALONESHORTMONTHS; break; | |
332 default: return handleNumericField(input,dateFields.setMonth); | |
333 } | |
334 dateFields.month = parseEnumeratedString(input,possibilities) + 1; | |
335 } | |
336 | |
337 String formatQuarter(Date date) { | |
338 var quarter = (date.month / 3).truncate().toInt(); | |
339 if (width < 4) { | |
340 return symbols.SHORTQUARTERS[quarter]; | |
341 } else { | |
342 return symbols.QUARTERS[quarter]; | |
343 } | |
344 } | |
345 String formatDayOfMonth(Date date) { | |
346 return padTo(width,date.day); | |
347 } | |
348 | |
349 String formatDayOfWeek(Date date) { | |
350 // Note that Dart's weekday returns 1 for Monday and 7 for Sunday. | |
351 return (width >= 4 ? symbols.WEEKDAYS : | |
352 symbols.SHORTWEEKDAYS)[(date.weekday) % 7]; | |
353 } | |
354 | |
355 void parseDayOfWeek(_Stream input) { | |
356 // This is IGNORED, but we still have to skip over it the correct amount. | |
357 var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS; | |
358 parseEnumeratedString(input,possibilities); | |
359 } | |
360 | |
361 String formatMinutes(Date date) { | |
362 return padTo(width,date.minute); | |
363 } | |
364 | |
365 String formatSeconds(Date date) { | |
366 return padTo(width,date.second); | |
367 } | |
368 | |
369 String formatTimeZoneId(Date date) { | |
370 // TODO(alanknight): implement time zone support | |
371 throw new NotImplementedException(); | |
372 } | |
373 | |
374 String formatTimeZone(Date date) { | |
375 throw new NotImplementedException(); | |
376 } | |
377 | |
378 String formatTimeZoneRFC(Date date) { | |
379 throw new NotImplementedException(); | |
380 } | |
381 | |
382 /** | |
383 * Return a string representation of the object padded to the left with | |
384 * zeros. Primarily useful for numbers. | |
385 */ | |
386 String padTo(int width, Object toBePrinted) { | |
387 var basicString = toBePrinted.toString(); | |
388 if (basicString.length >= width) return basicString; | |
389 var buffer = new StringBuffer(); | |
390 for (var i = 0; i < width - basicString.length; i++) { | |
391 buffer.add('0'); | |
392 } | |
393 buffer.add(basicString); | |
394 return buffer.toString(); | |
395 } | |
396 } | |
OLD | NEW |