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 |