| 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 |