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