| 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 /// A class for holding onto the data for a date so that it can be built |
| 8 * A class for holding onto the data for a date so that it can be built | 8 /// up incrementally. |
| 9 * up incrementally. | |
| 10 */ | |
| 11 class _DateBuilder { | 9 class _DateBuilder { |
| 12 // Default the date values to the EPOCH so that there's a valid date | 10 // Default the date values to the EPOCH so that there's a valid date |
| 13 // in case the format doesn't set them. | 11 // in case the format doesn't set them. |
| 14 int year = 1970, | 12 int year = 1970, |
| 15 month = 1, | 13 month = 1, |
| 16 day = 1, | 14 day = 1, |
| 17 hour = 0, | 15 hour = 0, |
| 18 minute = 0, | 16 minute = 0, |
| 19 second = 0, | 17 second = 0, |
| 20 fractionalSecond = 0; | 18 fractionalSecond = 0; |
| 21 bool pm = false; | 19 bool pm = false; |
| 22 bool utc = false; | 20 bool utc = false; |
| 23 | 21 |
| 24 // Functions that exist just to be closurized so we can pass them to a general | 22 // Functions that exist just to be closurized so we can pass them to a general |
| 25 // method. | 23 // method. |
| 26 void setYear(x) { | 24 void setYear(x) { |
| 27 year = x; | 25 year = x; |
| 28 } | 26 } |
| 27 |
| 29 void setMonth(x) { | 28 void setMonth(x) { |
| 30 month = x; | 29 month = x; |
| 31 } | 30 } |
| 31 |
| 32 void setDay(x) { | 32 void setDay(x) { |
| 33 day = x; | 33 day = x; |
| 34 } | 34 } |
| 35 |
| 35 void setHour(x) { | 36 void setHour(x) { |
| 36 hour = x; | 37 hour = x; |
| 37 } | 38 } |
| 39 |
| 38 void setMinute(x) { | 40 void setMinute(x) { |
| 39 minute = x; | 41 minute = x; |
| 40 } | 42 } |
| 43 |
| 41 void setSecond(x) { | 44 void setSecond(x) { |
| 42 second = x; | 45 second = x; |
| 43 } | 46 } |
| 47 |
| 44 void setFractionalSecond(x) { | 48 void setFractionalSecond(x) { |
| 45 fractionalSecond = x; | 49 fractionalSecond = x; |
| 46 } | 50 } |
| 47 | 51 |
| 48 get hour24 => pm ? hour + 12 : hour; | 52 get hour24 => pm ? hour + 12 : hour; |
| 49 | 53 |
| 50 /** | 54 /// Verify that we correspond to a valid date. This will reject out of |
| 51 * Verify that we correspond to a valid date. This will reject out of | 55 /// range values, even if the DateTime constructor would accept them. An |
| 52 * range values, even if the DateTime constructor would accept them. An | 56 /// invalid message will result in throwing a [FormatException]. |
| 53 * invalid message will result in throwing a [FormatException]. | |
| 54 */ | |
| 55 verify(String s) { | 57 verify(String s) { |
| 56 _verify(month, 1, 12, "month", s); | 58 _verify(month, 1, 12, "month", s); |
| 57 _verify(hour24, 0, 23, "hour", s); | 59 _verify(hour24, 0, 23, "hour", s); |
| 58 _verify(minute, 0, 59, "minute", s); | 60 _verify(minute, 0, 59, "minute", s); |
| 59 _verify(second, 0, 59, "second", s); | 61 _verify(second, 0, 59, "second", s); |
| 60 _verify(fractionalSecond, 0, 999, "fractional second", s); | 62 _verify(fractionalSecond, 0, 999, "fractional second", s); |
| 61 // Verifying the day is tricky, because it depends on the month. Create | 63 // Verifying the day is tricky, because it depends on the month. Create |
| 62 // our resulting date and then verify that our values agree with it | 64 // our resulting date and then verify that our values agree with it |
| 63 // as an additional verification. And since we're doing that, also | 65 // as an additional verification. And since we're doing that, also |
| 64 // check the year, which we otherwise can't verify, and the hours, | 66 // check the year, which we otherwise can't verify, and the hours, |
| 65 // which will catch cases like "14:00:00 PM". | 67 // which will catch cases like "14:00:00 PM". |
| 66 var date = asDate(); | 68 var date = asDate(); |
| 67 _verify(hour24, date.hour, date.hour, "hour", s); | 69 _verify(hour24, date.hour, date.hour, "hour", s, date); |
| 68 _verify(day, date.day, date.day, "day", s); | 70 _verify(day, date.day, date.day, "day", s, date); |
| 69 _verify(year, date.year, date.year, "year", s); | 71 _verify(year, date.year, date.year, "year", s, date); |
| 70 } | 72 } |
| 71 | 73 |
| 72 _verify(int value, int min, int max, String desc, String originalInput) { | 74 _verify(int value, int min, int max, String desc, String originalInput, |
| 75 [DateTime parsed]) { |
| 73 if (value < min || value > max) { | 76 if (value < min || value > max) { |
| 77 var parsedDescription = parsed == null ? "" : " Date parsed as $parsed."; |
| 74 throw new FormatException( | 78 throw new FormatException( |
| 75 "Error parsing $originalInput, invalid $desc value: $value"); | 79 "Error parsing $originalInput, invalid $desc value: $value." |
| 80 " Expected value between $min and $max.$parsedDescription"); |
| 76 } | 81 } |
| 77 } | 82 } |
| 78 | 83 |
| 79 /** | 84 /// Return a date built using our values. If no date portion is set, |
| 80 * Return a date built using our values. If no date portion is set, | 85 /// use the "Epoch" of January 1, 1970. |
| 81 * use the "Epoch" of January 1, 1970. | 86 DateTime asDate({int retries: 10}) { |
| 82 */ | |
| 83 DateTime asDate({retry: true}) { | |
| 84 // TODO(alanknight): Validate the date, especially for things which | 87 // TODO(alanknight): Validate the date, especially for things which |
| 85 // can crash the VM, e.g. large month values. | 88 // can crash the VM, e.g. large month values. |
| 86 var result; | 89 var result; |
| 87 if (utc) { | 90 if (utc) { |
| 88 result = new DateTime.utc( | 91 result = new DateTime.utc( |
| 89 year, month, day, hour24, minute, second, fractionalSecond); | 92 year, month, day, hour24, minute, second, fractionalSecond); |
| 90 } else { | 93 } else { |
| 91 result = new DateTime( | 94 result = new DateTime( |
| 92 year, month, day, hour24, minute, second, fractionalSecond); | 95 year, month, day, hour24, minute, second, fractionalSecond); |
| 93 // TODO(alanknight): Issue 15560 means non-UTC dates occasionally come | 96 // TODO(alanknight): Issue 15560 means non-UTC dates occasionally come out |
| 94 // out in UTC. If that happens, retry once. This will always happen if | 97 // in UTC, or, alternatively, are constructed as if in UTC and then have |
| 95 // the local time zone is UTC, but that's ok. | 98 // the offset subtracted. If that happens, retry, several times if |
| 96 if (result.toUtc() == result) { | 99 // necessary. |
| 97 result = asDate(retry: false); | 100 if (retries > 0 && (result.hour != hour24 || result.day != day)) { |
| 101 result = asDate(retries: retries - 1); |
| 98 } | 102 } |
| 99 } | 103 } |
| 100 return result; | 104 return result; |
| 101 } | 105 } |
| 102 } | 106 } |
| 103 | 107 |
| 104 /** | 108 /// A simple and not particularly general stream class to make parsing |
| 105 * A simple and not particularly general stream class to make parsing | 109 /// dates from strings simpler. It is general enough to operate on either |
| 106 * dates from strings simpler. It is general enough to operate on either | 110 /// lists or strings. |
| 107 * lists or strings. | |
| 108 */ | |
| 109 // TODO(alanknight): With the improvements to the collection libraries | 111 // TODO(alanknight): With the improvements to the collection libraries |
| 110 // since this was written we might be able to get rid of it entirely | 112 // since this was written we might be able to get rid of it entirely |
| 111 // in favor of e.g. aString.split('') giving us an iterable of one-character | 113 // in favor of e.g. aString.split('') giving us an iterable of one-character |
| 112 // strings, or else make the implementation trivial. And consider renaming, | 114 // strings, or else make the implementation trivial. And consider renaming, |
| 113 // as _Stream is now just confusing with the system Streams. | 115 // as _Stream is now just confusing with the system Streams. |
| 114 class _Stream { | 116 class _Stream { |
| 115 var contents; | 117 var contents; |
| 116 int index = 0; | 118 int index = 0; |
| 117 | 119 |
| 118 _Stream(this.contents); | 120 _Stream(this.contents); |
| 119 | 121 |
| 120 bool atEnd() => index >= contents.length; | 122 bool atEnd() => index >= contents.length; |
| 121 | 123 |
| 122 next() => contents[index++]; | 124 next() => contents[index++]; |
| 123 | 125 |
| 124 /** | 126 /// Return the next [howMany] items, or as many as there are remaining. |
| 125 * Return the next [howMany] items, or as many as there are remaining. | 127 /// Advance the stream by that many positions. |
| 126 * Advance the stream by that many positions. | |
| 127 */ | |
| 128 read([int howMany = 1]) { | 128 read([int howMany = 1]) { |
| 129 var result = peek(howMany); | 129 var result = peek(howMany); |
| 130 index += howMany; | 130 index += howMany; |
| 131 return result; | 131 return result; |
| 132 } | 132 } |
| 133 | 133 |
| 134 /** | 134 /// Does the input start with the given string, if we start from the |
| 135 * Does the input start with the given string, if we start from the | 135 /// current position. |
| 136 * current position. | |
| 137 */ | |
| 138 bool startsWith(String pattern) { | 136 bool startsWith(String pattern) { |
| 139 if (contents is String) return contents.startsWith(pattern, index); | 137 if (contents is String) return contents.startsWith(pattern, index); |
| 140 return pattern == peek(pattern.length); | 138 return pattern == peek(pattern.length); |
| 141 } | 139 } |
| 142 | 140 |
| 143 /** | 141 /// Return the next [howMany] items, or as many as there are remaining. |
| 144 * Return the next [howMany] items, or as many as there are remaining. | 142 /// Does not modify the stream position. |
| 145 * Does not modify the stream position. | |
| 146 */ | |
| 147 peek([int howMany = 1]) { | 143 peek([int howMany = 1]) { |
| 148 var result; | 144 var result; |
| 149 if (contents is String) { | 145 if (contents is String) { |
| 150 result = contents.substring(index, min(index + howMany, contents.length)); | 146 String stringContents = contents; |
| 147 result = stringContents.substring( |
| 148 index, min(index + howMany, stringContents.length)); |
| 151 } else { | 149 } else { |
| 152 // Assume List | 150 // Assume List |
| 153 result = contents.sublist(index, index + howMany); | 151 result = contents.sublist(index, index + howMany); |
| 154 } | 152 } |
| 155 return result; | 153 return result; |
| 156 } | 154 } |
| 157 | 155 |
| 158 /** Return the remaining contents of the stream */ | 156 /// Return the remaining contents of the stream |
| 159 rest() => peek(contents.length - index); | 157 rest() => peek(contents.length - index); |
| 160 | 158 |
| 161 /** | 159 /// Find the index of the first element for which [f] returns true. |
| 162 * Find the index of the first element for which [f] returns true. | 160 /// Advances the stream to that position. |
| 163 * Advances the stream to that position. | |
| 164 */ | |
| 165 int findIndex(Function f) { | 161 int findIndex(Function f) { |
| 166 while (!atEnd()) { | 162 while (!atEnd()) { |
| 167 if (f(next())) return index - 1; | 163 if (f(next())) return index - 1; |
| 168 } | 164 } |
| 169 return null; | 165 return null; |
| 170 } | 166 } |
| 171 | 167 |
| 172 /** | 168 /// Find the indexes of all the elements for which [f] returns true. |
| 173 * Find the indexes of all the elements for which [f] returns true. | 169 /// Leaves the stream positioned at the end. |
| 174 * Leaves the stream positioned at the end. | |
| 175 */ | |
| 176 List findIndexes(Function f) { | 170 List findIndexes(Function f) { |
| 177 var results = []; | 171 var results = []; |
| 178 while (!atEnd()) { | 172 while (!atEnd()) { |
| 179 if (f(next())) results.add(index - 1); | 173 if (f(next())) results.add(index - 1); |
| 180 } | 174 } |
| 181 return results; | 175 return results; |
| 182 } | 176 } |
| 183 | 177 |
| 184 /** | 178 /// Assuming that the contents are characters, read as many digits as we |
| 185 * Assuming that the contents are characters, read as many digits as we | 179 /// can see and then return the corresponding integer. Advance the stream. |
| 186 * can see and then return the corresponding integer. Advance the stream. | 180 var digitMatcher = new RegExp(r'^\d+'); |
| 187 */ | |
| 188 var digitMatcher = new RegExp(r'\d+'); | |
| 189 int nextInteger() { | 181 int nextInteger() { |
| 190 var string = digitMatcher.stringMatch(rest()); | 182 var string = digitMatcher.stringMatch(rest()); |
| 191 if (string == null || string.isEmpty) return null; | 183 if (string == null || string.isEmpty) return null; |
| 192 read(string.length); | 184 read(string.length); |
| 193 return int.parse(string); | 185 return int.parse(string); |
| 194 } | 186 } |
| 195 } | 187 } |
| OLD | NEW |