16852
|
1 |
/*
|
|
2 |
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 |
*
|
|
5 |
* This code is free software; you can redistribute it and/or modify it
|
|
6 |
* under the terms of the GNU General Public License version 2 only, as
|
|
7 |
* published by the Free Software Foundation. Oracle designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Oracle in the LICENSE file that accompanied this code.
|
|
10 |
*
|
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that
|
|
15 |
* accompanied this code).
|
|
16 |
*
|
|
17 |
* You should have received a copy of the GNU General Public License version
|
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
20 |
*
|
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
22 |
* or visit www.oracle.com if you need additional information or have any
|
|
23 |
* questions.
|
|
24 |
*/
|
|
25 |
|
|
26 |
/*
|
|
27 |
* This file is available under and governed by the GNU General Public
|
|
28 |
* License version 2 only, as published by the Free Software Foundation.
|
|
29 |
* However, the following notice accompanied the original version of this
|
|
30 |
* file:
|
|
31 |
*
|
|
32 |
* Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos
|
|
33 |
*
|
|
34 |
* All rights reserved.
|
|
35 |
*
|
|
36 |
* Redistribution and use in source and binary forms, with or without
|
|
37 |
* modification, are permitted provided that the following conditions are met:
|
|
38 |
*
|
|
39 |
* * Redistributions of source code must retain the above copyright notice,
|
|
40 |
* this list of conditions and the following disclaimer.
|
|
41 |
*
|
|
42 |
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
43 |
* this list of conditions and the following disclaimer in the documentation
|
|
44 |
* and/or other materials provided with the distribution.
|
|
45 |
*
|
|
46 |
* * Neither the name of JSR-310 nor the names of its contributors
|
|
47 |
* may be used to endorse or promote products derived from this software
|
|
48 |
* without specific prior written permission.
|
|
49 |
*
|
|
50 |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
51 |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
52 |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
53 |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
54 |
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
55 |
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
56 |
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
57 |
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
58 |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
59 |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
60 |
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
61 |
*/
|
|
62 |
package java.time.format;
|
|
63 |
|
|
64 |
import static java.time.temporal.ChronoField.AMPM_OF_DAY;
|
|
65 |
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
|
|
66 |
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
|
|
67 |
import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
|
|
68 |
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
|
|
69 |
import static java.time.temporal.ChronoField.MICRO_OF_DAY;
|
|
70 |
import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
|
|
71 |
import static java.time.temporal.ChronoField.MILLI_OF_DAY;
|
|
72 |
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
|
|
73 |
import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
|
|
74 |
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
|
|
75 |
import static java.time.temporal.ChronoField.NANO_OF_DAY;
|
|
76 |
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
|
|
77 |
import static java.time.temporal.ChronoField.SECOND_OF_DAY;
|
|
78 |
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
|
|
79 |
|
|
80 |
import java.time.DateTimeException;
|
|
81 |
import java.time.LocalDate;
|
|
82 |
import java.time.LocalTime;
|
17474
|
83 |
import java.time.Period;
|
16852
|
84 |
import java.time.ZoneId;
|
|
85 |
import java.time.chrono.ChronoLocalDate;
|
|
86 |
import java.time.chrono.Chronology;
|
|
87 |
import java.time.temporal.ChronoField;
|
|
88 |
import java.time.temporal.TemporalAccessor;
|
|
89 |
import java.time.temporal.TemporalField;
|
|
90 |
import java.time.temporal.TemporalQuery;
|
|
91 |
import java.time.temporal.UnsupportedTemporalTypeException;
|
|
92 |
import java.util.HashMap;
|
|
93 |
import java.util.Iterator;
|
|
94 |
import java.util.Map;
|
|
95 |
import java.util.Map.Entry;
|
|
96 |
import java.util.Objects;
|
|
97 |
import java.util.Set;
|
|
98 |
|
|
99 |
/**
|
|
100 |
* A store of parsed data.
|
|
101 |
* <p>
|
|
102 |
* This class is used during parsing to collect the data. Part of the parsing process
|
|
103 |
* involves handling optional blocks and multiple copies of the data get created to
|
|
104 |
* support the necessary backtracking.
|
|
105 |
* <p>
|
|
106 |
* Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}.
|
|
107 |
* In most cases, it is only exposed once the fields have been resolved.
|
|
108 |
*
|
17474
|
109 |
* @implSpec
|
16852
|
110 |
* This class is a mutable context intended for use from a single thread.
|
|
111 |
* Usage of the class is thread-safe within standard parsing as a new instance of this class
|
|
112 |
* is automatically created for each parse and parsing is single-threaded
|
|
113 |
*
|
|
114 |
* @since 1.8
|
|
115 |
*/
|
|
116 |
final class Parsed implements TemporalAccessor {
|
|
117 |
// some fields are accessed using package scope from DateTimeParseContext
|
|
118 |
|
|
119 |
/**
|
|
120 |
* The parsed fields.
|
|
121 |
*/
|
|
122 |
final Map<TemporalField, Long> fieldValues = new HashMap<>();
|
|
123 |
/**
|
|
124 |
* The parsed zone.
|
|
125 |
*/
|
|
126 |
ZoneId zone;
|
|
127 |
/**
|
|
128 |
* The parsed chronology.
|
|
129 |
*/
|
|
130 |
Chronology chrono;
|
|
131 |
/**
|
17474
|
132 |
* Whether a leap-second is parsed.
|
|
133 |
*/
|
|
134 |
boolean leapSecond;
|
|
135 |
/**
|
16852
|
136 |
* The effective chronology.
|
|
137 |
*/
|
|
138 |
Chronology effectiveChrono;
|
|
139 |
/**
|
|
140 |
* The resolver style to use.
|
|
141 |
*/
|
|
142 |
private ResolverStyle resolverStyle;
|
|
143 |
/**
|
|
144 |
* The resolved date.
|
|
145 |
*/
|
|
146 |
private ChronoLocalDate<?> date;
|
|
147 |
/**
|
|
148 |
* The resolved time.
|
|
149 |
*/
|
|
150 |
private LocalTime time;
|
17474
|
151 |
/**
|
|
152 |
* The excess period from time-only parsing.
|
|
153 |
*/
|
|
154 |
Period excessDays = Period.ZERO;
|
16852
|
155 |
|
|
156 |
/**
|
|
157 |
* Creates an instance.
|
|
158 |
*/
|
|
159 |
Parsed() {
|
|
160 |
}
|
|
161 |
|
|
162 |
/**
|
|
163 |
* Creates a copy.
|
|
164 |
*/
|
|
165 |
Parsed copy() {
|
|
166 |
// only copy fields used in parsing stage
|
|
167 |
Parsed cloned = new Parsed();
|
|
168 |
cloned.fieldValues.putAll(this.fieldValues);
|
|
169 |
cloned.zone = this.zone;
|
|
170 |
cloned.chrono = this.chrono;
|
17474
|
171 |
cloned.leapSecond = this.leapSecond;
|
16852
|
172 |
return cloned;
|
|
173 |
}
|
|
174 |
|
|
175 |
//-----------------------------------------------------------------------
|
|
176 |
@Override
|
|
177 |
public boolean isSupported(TemporalField field) {
|
|
178 |
if (fieldValues.containsKey(field) ||
|
|
179 |
(date != null && date.isSupported(field)) ||
|
|
180 |
(time != null && time.isSupported(field))) {
|
|
181 |
return true;
|
|
182 |
}
|
|
183 |
return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this);
|
|
184 |
}
|
|
185 |
|
|
186 |
@Override
|
|
187 |
public long getLong(TemporalField field) {
|
|
188 |
Objects.requireNonNull(field, "field");
|
|
189 |
Long value = fieldValues.get(field);
|
|
190 |
if (value != null) {
|
|
191 |
return value;
|
|
192 |
}
|
|
193 |
if (date != null && date.isSupported(field)) {
|
|
194 |
return date.getLong(field);
|
|
195 |
}
|
|
196 |
if (time != null && time.isSupported(field)) {
|
|
197 |
return time.getLong(field);
|
|
198 |
}
|
|
199 |
if (field instanceof ChronoField) {
|
|
200 |
throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName());
|
|
201 |
}
|
|
202 |
return field.getFrom(this);
|
|
203 |
}
|
|
204 |
|
|
205 |
@SuppressWarnings("unchecked")
|
|
206 |
@Override
|
|
207 |
public <R> R query(TemporalQuery<R> query) {
|
|
208 |
if (query == TemporalQuery.zoneId()) {
|
|
209 |
return (R) zone;
|
|
210 |
} else if (query == TemporalQuery.chronology()) {
|
|
211 |
return (R) chrono;
|
|
212 |
} else if (query == TemporalQuery.localDate()) {
|
|
213 |
return (R) (date != null ? LocalDate.from(date) : null);
|
|
214 |
} else if (query == TemporalQuery.localTime()) {
|
|
215 |
return (R) time;
|
|
216 |
} else if (query == TemporalQuery.zone() || query == TemporalQuery.offset()) {
|
|
217 |
return query.queryFrom(this);
|
|
218 |
} else if (query == TemporalQuery.precision()) {
|
|
219 |
return null; // not a complete date/time
|
|
220 |
}
|
|
221 |
// inline TemporalAccessor.super.query(query) as an optimization
|
|
222 |
// non-JDK classes are not permitted to make this optimization
|
|
223 |
return query.queryFrom(this);
|
|
224 |
}
|
|
225 |
|
|
226 |
//-----------------------------------------------------------------------
|
|
227 |
/**
|
|
228 |
* Resolves the fields in this context.
|
|
229 |
*
|
|
230 |
* @param resolverStyle the resolver style, not null
|
|
231 |
* @param resolverFields the fields to use for resolving, null for all fields
|
|
232 |
* @return this, for method chaining
|
|
233 |
* @throws DateTimeException if resolving one field results in a value for
|
|
234 |
* another field that is in conflict
|
|
235 |
*/
|
|
236 |
TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {
|
|
237 |
if (resolverFields != null) {
|
|
238 |
fieldValues.keySet().retainAll(resolverFields);
|
|
239 |
}
|
|
240 |
this.resolverStyle = resolverStyle;
|
|
241 |
chrono = effectiveChrono;
|
|
242 |
resolveFields();
|
|
243 |
resolveTimeLenient();
|
|
244 |
crossCheck();
|
17474
|
245 |
resolvePeriod();
|
16852
|
246 |
return this;
|
|
247 |
}
|
|
248 |
|
|
249 |
//-----------------------------------------------------------------------
|
|
250 |
private void resolveFields() {
|
|
251 |
// resolve ChronoField
|
|
252 |
resolveDateFields();
|
|
253 |
resolveTimeFields();
|
|
254 |
|
|
255 |
// if any other fields, handle them
|
|
256 |
// any lenient date resolution should return epoch-day
|
|
257 |
if (fieldValues.size() > 0) {
|
|
258 |
boolean changed = false;
|
|
259 |
outer:
|
|
260 |
while (true) {
|
|
261 |
for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
|
|
262 |
TemporalField targetField = entry.getKey();
|
|
263 |
Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue(), resolverStyle);
|
|
264 |
if (changes != null) {
|
|
265 |
changed = true;
|
|
266 |
resolveFieldsMakeChanges(targetField, changes);
|
|
267 |
fieldValues.remove(targetField); // helps avoid infinite loops
|
|
268 |
continue outer; // have to restart to avoid concurrent modification
|
|
269 |
}
|
|
270 |
}
|
|
271 |
break;
|
|
272 |
}
|
|
273 |
// if something changed then have to redo ChronoField resolve
|
|
274 |
if (changed) {
|
|
275 |
resolveDateFields();
|
|
276 |
resolveTimeFields();
|
|
277 |
}
|
|
278 |
}
|
|
279 |
}
|
|
280 |
|
|
281 |
private void resolveFieldsMakeChanges(TemporalField targetField, Map<TemporalField, Long> changes) {
|
|
282 |
for (Map.Entry<TemporalField, Long> change : changes.entrySet()) {
|
|
283 |
TemporalField changeField = change.getKey();
|
|
284 |
Long changeValue = change.getValue();
|
|
285 |
Objects.requireNonNull(changeField, "changeField");
|
|
286 |
if (changeValue != null) {
|
|
287 |
updateCheckConflict(targetField, changeField, changeValue);
|
|
288 |
} else {
|
|
289 |
fieldValues.remove(changeField);
|
|
290 |
}
|
|
291 |
}
|
|
292 |
}
|
|
293 |
|
|
294 |
private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) {
|
|
295 |
Long old = fieldValues.put(changeField, changeValue);
|
|
296 |
if (old != null && old.longValue() != changeValue.longValue()) {
|
|
297 |
throw new DateTimeException("Conflict found: " + changeField + " " + old +
|
|
298 |
" differs from " + changeField + " " + changeValue +
|
|
299 |
" while resolving " + targetField);
|
|
300 |
}
|
|
301 |
}
|
|
302 |
|
|
303 |
//-----------------------------------------------------------------------
|
|
304 |
private void resolveDateFields() {
|
|
305 |
updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle));
|
|
306 |
}
|
|
307 |
|
|
308 |
private void updateCheckConflict(ChronoLocalDate<?> cld) {
|
|
309 |
if (date != null) {
|
|
310 |
if (cld != null && date.equals(cld) == false) {
|
|
311 |
throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld);
|
|
312 |
}
|
|
313 |
} else {
|
|
314 |
date = cld;
|
|
315 |
}
|
|
316 |
}
|
|
317 |
|
|
318 |
//-----------------------------------------------------------------------
|
|
319 |
private void resolveTimeFields() {
|
|
320 |
// simplify fields
|
|
321 |
if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) {
|
17474
|
322 |
// lenient allows anything, smart allows 0-24, strict allows 1-24
|
16852
|
323 |
long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY);
|
17474
|
324 |
if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
|
|
325 |
CLOCK_HOUR_OF_DAY.checkValidValue(ch);
|
|
326 |
}
|
16852
|
327 |
updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch);
|
|
328 |
}
|
|
329 |
if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) {
|
17474
|
330 |
// lenient allows anything, smart allows 0-12, strict allows 1-12
|
16852
|
331 |
long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM);
|
17474
|
332 |
if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
|
|
333 |
CLOCK_HOUR_OF_AMPM.checkValidValue(ch);
|
|
334 |
}
|
16852
|
335 |
updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch);
|
|
336 |
}
|
|
337 |
if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) {
|
|
338 |
long ap = fieldValues.remove(AMPM_OF_DAY);
|
|
339 |
long hap = fieldValues.remove(HOUR_OF_AMPM);
|
17474
|
340 |
if (resolverStyle == ResolverStyle.LENIENT) {
|
|
341 |
updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap));
|
|
342 |
} else { // STRICT or SMART
|
|
343 |
AMPM_OF_DAY.checkValidValue(ap);
|
|
344 |
HOUR_OF_AMPM.checkValidValue(ap);
|
|
345 |
updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap);
|
|
346 |
}
|
|
347 |
}
|
|
348 |
if (fieldValues.containsKey(NANO_OF_DAY)) {
|
|
349 |
long nod = fieldValues.remove(NANO_OF_DAY);
|
|
350 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
351 |
NANO_OF_DAY.checkValidValue(nod);
|
|
352 |
}
|
|
353 |
updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L);
|
|
354 |
updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60);
|
|
355 |
updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60);
|
|
356 |
updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L);
|
16852
|
357 |
}
|
|
358 |
if (fieldValues.containsKey(MICRO_OF_DAY)) {
|
|
359 |
long cod = fieldValues.remove(MICRO_OF_DAY);
|
17474
|
360 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
361 |
MICRO_OF_DAY.checkValidValue(cod);
|
|
362 |
}
|
16852
|
363 |
updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L);
|
|
364 |
updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L);
|
|
365 |
}
|
|
366 |
if (fieldValues.containsKey(MILLI_OF_DAY)) {
|
|
367 |
long lod = fieldValues.remove(MILLI_OF_DAY);
|
17474
|
368 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
369 |
MILLI_OF_DAY.checkValidValue(lod);
|
|
370 |
}
|
16852
|
371 |
updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000);
|
|
372 |
updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000);
|
|
373 |
}
|
|
374 |
if (fieldValues.containsKey(SECOND_OF_DAY)) {
|
|
375 |
long sod = fieldValues.remove(SECOND_OF_DAY);
|
17474
|
376 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
377 |
SECOND_OF_DAY.checkValidValue(sod);
|
|
378 |
}
|
16852
|
379 |
updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600);
|
|
380 |
updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60);
|
|
381 |
updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60);
|
|
382 |
}
|
|
383 |
if (fieldValues.containsKey(MINUTE_OF_DAY)) {
|
|
384 |
long mod = fieldValues.remove(MINUTE_OF_DAY);
|
17474
|
385 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
386 |
MINUTE_OF_DAY.checkValidValue(mod);
|
|
387 |
}
|
16852
|
388 |
updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60);
|
|
389 |
updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60);
|
|
390 |
}
|
|
391 |
|
|
392 |
// combine partial second fields strictly, leaving lenient expansion to later
|
|
393 |
if (fieldValues.containsKey(NANO_OF_SECOND)) {
|
|
394 |
long nos = fieldValues.get(NANO_OF_SECOND);
|
17474
|
395 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
396 |
NANO_OF_SECOND.checkValidValue(nos);
|
|
397 |
}
|
16852
|
398 |
if (fieldValues.containsKey(MICRO_OF_SECOND)) {
|
|
399 |
long cos = fieldValues.remove(MICRO_OF_SECOND);
|
17474
|
400 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
401 |
MICRO_OF_SECOND.checkValidValue(cos);
|
|
402 |
}
|
16852
|
403 |
nos = cos * 1000 + (nos % 1000);
|
|
404 |
updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos);
|
|
405 |
}
|
|
406 |
if (fieldValues.containsKey(MILLI_OF_SECOND)) {
|
|
407 |
long los = fieldValues.remove(MILLI_OF_SECOND);
|
17474
|
408 |
if (resolverStyle != ResolverStyle.LENIENT) {
|
|
409 |
MILLI_OF_SECOND.checkValidValue(los);
|
|
410 |
}
|
16852
|
411 |
updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L));
|
|
412 |
}
|
|
413 |
}
|
|
414 |
|
17474
|
415 |
// convert to time if all four fields available (optimization)
|
16852
|
416 |
if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&
|
|
417 |
fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) {
|
17474
|
418 |
long hod = fieldValues.remove(HOUR_OF_DAY);
|
|
419 |
long moh = fieldValues.remove(MINUTE_OF_HOUR);
|
|
420 |
long som = fieldValues.remove(SECOND_OF_MINUTE);
|
|
421 |
long nos = fieldValues.remove(NANO_OF_SECOND);
|
|
422 |
resolveTime(hod, moh, som, nos);
|
16852
|
423 |
}
|
|
424 |
}
|
|
425 |
|
|
426 |
private void resolveTimeLenient() {
|
|
427 |
// leniently create a time from incomplete information
|
|
428 |
// done after everything else as it creates information from nothing
|
|
429 |
// which would break updateCheckConflict(field)
|
|
430 |
|
|
431 |
if (time == null) {
|
17474
|
432 |
// NANO_OF_SECOND merged with MILLI/MICRO above
|
16852
|
433 |
if (fieldValues.containsKey(MILLI_OF_SECOND)) {
|
|
434 |
long los = fieldValues.remove(MILLI_OF_SECOND);
|
|
435 |
if (fieldValues.containsKey(MICRO_OF_SECOND)) {
|
|
436 |
// merge milli-of-second and micro-of-second for better error message
|
|
437 |
long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000);
|
|
438 |
updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos);
|
|
439 |
fieldValues.remove(MICRO_OF_SECOND);
|
|
440 |
fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
|
|
441 |
} else {
|
|
442 |
// convert milli-of-second to nano-of-second
|
|
443 |
fieldValues.put(NANO_OF_SECOND, los * 1_000_000L);
|
|
444 |
}
|
|
445 |
} else if (fieldValues.containsKey(MICRO_OF_SECOND)) {
|
|
446 |
// convert micro-of-second to nano-of-second
|
|
447 |
long cos = fieldValues.remove(MICRO_OF_SECOND);
|
|
448 |
fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
|
|
449 |
}
|
|
450 |
|
17474
|
451 |
// merge hour/minute/second/nano leniently
|
|
452 |
Long hod = fieldValues.get(HOUR_OF_DAY);
|
|
453 |
if (hod != null) {
|
|
454 |
Long moh = fieldValues.get(MINUTE_OF_HOUR);
|
|
455 |
Long som = fieldValues.get(SECOND_OF_MINUTE);
|
|
456 |
Long nos = fieldValues.get(NANO_OF_SECOND);
|
16852
|
457 |
|
17474
|
458 |
// check for invalid combinations that cannot be defaulted
|
16852
|
459 |
if ((moh == null && (som != null || nos != null)) ||
|
|
460 |
(moh != null && som == null && nos != null)) {
|
|
461 |
return;
|
|
462 |
}
|
|
463 |
|
17474
|
464 |
// default as necessary and build time
|
|
465 |
long mohVal = (moh != null ? moh : 0);
|
|
466 |
long somVal = (som != null ? som : 0);
|
|
467 |
long nosVal = (nos != null ? nos : 0);
|
|
468 |
resolveTime(hod, mohVal, somVal, nosVal);
|
|
469 |
fieldValues.remove(HOUR_OF_DAY);
|
|
470 |
fieldValues.remove(MINUTE_OF_HOUR);
|
|
471 |
fieldValues.remove(SECOND_OF_MINUTE);
|
|
472 |
fieldValues.remove(NANO_OF_SECOND);
|
|
473 |
}
|
|
474 |
}
|
|
475 |
|
|
476 |
// validate remaining
|
|
477 |
if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) {
|
|
478 |
for (Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
|
|
479 |
TemporalField field = entry.getKey();
|
|
480 |
if (field instanceof ChronoField && field.isTimeBased()) {
|
|
481 |
((ChronoField) field).checkValidValue(entry.getValue());
|
|
482 |
}
|
|
483 |
}
|
16852
|
484 |
}
|
|
485 |
}
|
|
486 |
|
17474
|
487 |
private void resolveTime(long hod, long moh, long som, long nos) {
|
|
488 |
if (resolverStyle == ResolverStyle.LENIENT) {
|
|
489 |
long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L);
|
|
490 |
totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L));
|
|
491 |
totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L));
|
|
492 |
totalNanos = Math.addExact(totalNanos, nos);
|
|
493 |
int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L); // safe int cast
|
|
494 |
long nod = Math.floorMod(totalNanos, 86400_000_000_000L);
|
|
495 |
updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays));
|
|
496 |
} else { // STRICT or SMART
|
|
497 |
int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh);
|
|
498 |
int nosVal = NANO_OF_SECOND.checkValidIntValue(nos);
|
|
499 |
// handle 24:00 end of day
|
|
500 |
if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) {
|
|
501 |
updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1));
|
|
502 |
} else {
|
|
503 |
int hodVal = HOUR_OF_DAY.checkValidIntValue(hod);
|
|
504 |
int somVal = SECOND_OF_MINUTE.checkValidIntValue(som);
|
|
505 |
updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO);
|
|
506 |
}
|
|
507 |
}
|
|
508 |
}
|
|
509 |
|
|
510 |
private void resolvePeriod() {
|
|
511 |
// add whole days if we have both date and time
|
|
512 |
if (date != null && time != null && excessDays.isZero() == false) {
|
|
513 |
date = date.plus(excessDays);
|
|
514 |
excessDays = Period.ZERO;
|
|
515 |
}
|
|
516 |
}
|
|
517 |
|
|
518 |
private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) {
|
16852
|
519 |
if (time != null) {
|
17474
|
520 |
if (time.equals(timeToSet) == false) {
|
|
521 |
throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet);
|
|
522 |
}
|
|
523 |
if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) {
|
|
524 |
throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet);
|
|
525 |
} else {
|
|
526 |
excessDays = periodToSet;
|
16852
|
527 |
}
|
|
528 |
} else {
|
17474
|
529 |
time = timeToSet;
|
|
530 |
excessDays = periodToSet;
|
16852
|
531 |
}
|
|
532 |
}
|
|
533 |
|
|
534 |
//-----------------------------------------------------------------------
|
|
535 |
private void crossCheck() {
|
|
536 |
// only cross-check date, time and date-time
|
|
537 |
// avoid object creation if possible
|
|
538 |
if (date != null) {
|
|
539 |
crossCheck(date);
|
|
540 |
}
|
|
541 |
if (time != null) {
|
|
542 |
crossCheck(time);
|
|
543 |
if (date != null && fieldValues.size() > 0) {
|
|
544 |
crossCheck(date.atTime(time));
|
|
545 |
}
|
|
546 |
}
|
|
547 |
}
|
|
548 |
|
|
549 |
private void crossCheck(TemporalAccessor target) {
|
|
550 |
for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {
|
|
551 |
Entry<TemporalField, Long> entry = it.next();
|
|
552 |
TemporalField field = entry.getKey();
|
|
553 |
long val1;
|
|
554 |
try {
|
|
555 |
val1 = target.getLong(field);
|
|
556 |
} catch (RuntimeException ex) {
|
|
557 |
continue;
|
|
558 |
}
|
|
559 |
long val2 = entry.getValue();
|
|
560 |
if (val1 != val2) {
|
|
561 |
throw new DateTimeException("Conflict found: Field " + field + " " + val1 +
|
|
562 |
" differs from " + field + " " + val2 + " derived from " + target);
|
|
563 |
}
|
|
564 |
it.remove();
|
|
565 |
}
|
|
566 |
}
|
|
567 |
|
|
568 |
//-----------------------------------------------------------------------
|
|
569 |
@Override
|
|
570 |
public String toString() {
|
|
571 |
String str = fieldValues.toString() + "," + chrono + "," + zone;
|
|
572 |
if (date != null || time != null) {
|
|
573 |
str += " resolved to " + date + "," + time;
|
|
574 |
}
|
|
575 |
return str;
|
|
576 |
}
|
|
577 |
|
|
578 |
}
|