Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/java/time/format/Parsed.java
67848 views
1
/*
2
* Copyright (c) 2012, 2021, 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.format.DateTimeFormatterBuilder.DayPeriod;
65
import static java.time.temporal.ChronoField.AMPM_OF_DAY;
66
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
67
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
68
import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
69
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
70
import static java.time.temporal.ChronoField.INSTANT_SECONDS;
71
import static java.time.temporal.ChronoField.MICRO_OF_DAY;
72
import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
73
import static java.time.temporal.ChronoField.MILLI_OF_DAY;
74
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
75
import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
76
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
77
import static java.time.temporal.ChronoField.NANO_OF_DAY;
78
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
79
import static java.time.temporal.ChronoField.OFFSET_SECONDS;
80
import static java.time.temporal.ChronoField.SECOND_OF_DAY;
81
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
82
83
import java.time.DateTimeException;
84
import java.time.Instant;
85
import java.time.LocalDate;
86
import java.time.LocalTime;
87
import java.time.Period;
88
import java.time.ZoneId;
89
import java.time.ZoneOffset;
90
import java.time.chrono.ChronoLocalDate;
91
import java.time.chrono.ChronoLocalDateTime;
92
import java.time.chrono.ChronoZonedDateTime;
93
import java.time.chrono.Chronology;
94
import java.time.temporal.ChronoField;
95
import java.time.temporal.TemporalAccessor;
96
import java.time.temporal.TemporalField;
97
import java.time.temporal.TemporalQueries;
98
import java.time.temporal.TemporalQuery;
99
import java.time.temporal.UnsupportedTemporalTypeException;
100
import java.util.HashMap;
101
import java.util.Iterator;
102
import java.util.Map;
103
import java.util.Map.Entry;
104
import java.util.Objects;
105
import java.util.Set;
106
107
/**
108
* A store of parsed data.
109
* <p>
110
* This class is used during parsing to collect the data. Part of the parsing process
111
* involves handling optional blocks and multiple copies of the data get created to
112
* support the necessary backtracking.
113
* <p>
114
* Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}.
115
* In most cases, it is only exposed once the fields have been resolved.
116
*
117
* @implSpec
118
* This class is a mutable context intended for use from a single thread.
119
* Usage of the class is thread-safe within standard parsing as a new instance of this class
120
* is automatically created for each parse and parsing is single-threaded
121
*
122
* @since 1.8
123
*/
124
final class Parsed implements TemporalAccessor {
125
// some fields are accessed using package scope from DateTimeParseContext
126
127
/**
128
* The parsed fields.
129
*/
130
final Map<TemporalField, Long> fieldValues = new HashMap<>();
131
/**
132
* The parsed zone.
133
*/
134
ZoneId zone;
135
/**
136
* The parsed chronology.
137
*/
138
Chronology chrono;
139
/**
140
* Whether a leap-second is parsed.
141
*/
142
boolean leapSecond;
143
/**
144
* The resolver style to use.
145
*/
146
private ResolverStyle resolverStyle;
147
/**
148
* The resolved date.
149
*/
150
private ChronoLocalDate date;
151
/**
152
* The resolved time.
153
*/
154
private LocalTime time;
155
/**
156
* The excess period from time-only parsing.
157
*/
158
Period excessDays = Period.ZERO;
159
/**
160
* The parsed day period.
161
*/
162
DayPeriod dayPeriod;
163
164
/**
165
* Creates an instance.
166
*/
167
Parsed() {
168
}
169
170
/**
171
* Creates a copy.
172
*/
173
Parsed copy() {
174
// only copy fields used in parsing stage
175
Parsed cloned = new Parsed();
176
cloned.fieldValues.putAll(this.fieldValues);
177
cloned.zone = this.zone;
178
cloned.chrono = this.chrono;
179
cloned.leapSecond = this.leapSecond;
180
cloned.dayPeriod = this.dayPeriod;
181
return cloned;
182
}
183
184
//-----------------------------------------------------------------------
185
@Override
186
public boolean isSupported(TemporalField field) {
187
if (fieldValues.containsKey(field) ||
188
(date != null && date.isSupported(field)) ||
189
(time != null && time.isSupported(field))) {
190
return true;
191
}
192
return field != null && (!(field instanceof ChronoField)) && field.isSupportedBy(this);
193
}
194
195
@Override
196
public long getLong(TemporalField field) {
197
Objects.requireNonNull(field, "field");
198
Long value = fieldValues.get(field);
199
if (value != null) {
200
return value;
201
}
202
if (date != null && date.isSupported(field)) {
203
return date.getLong(field);
204
}
205
if (time != null && time.isSupported(field)) {
206
return time.getLong(field);
207
}
208
if (field instanceof ChronoField) {
209
throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
210
}
211
return field.getFrom(this);
212
}
213
214
@SuppressWarnings("unchecked")
215
@Override
216
public <R> R query(TemporalQuery<R> query) {
217
if (query == TemporalQueries.zoneId()) {
218
return (R) zone;
219
} else if (query == TemporalQueries.chronology()) {
220
return (R) chrono;
221
} else if (query == TemporalQueries.localDate()) {
222
return (R) (date != null ? LocalDate.from(date) : null);
223
} else if (query == TemporalQueries.localTime()) {
224
return (R) time;
225
} else if (query == TemporalQueries.offset()) {
226
Long offsetSecs = fieldValues.get(OFFSET_SECONDS);
227
if (offsetSecs != null) {
228
return (R) ZoneOffset.ofTotalSeconds(offsetSecs.intValue());
229
}
230
if (zone instanceof ZoneOffset) {
231
return (R)zone;
232
}
233
return query.queryFrom(this);
234
} else if (query == TemporalQueries.zone()) {
235
return query.queryFrom(this);
236
} else if (query == TemporalQueries.precision()) {
237
return null; // not a complete date/time
238
}
239
// inline TemporalAccessor.super.query(query) as an optimization
240
// non-JDK classes are not permitted to make this optimization
241
return query.queryFrom(this);
242
}
243
244
//-----------------------------------------------------------------------
245
/**
246
* Resolves the fields in this context.
247
*
248
* @param resolverStyle the resolver style, not null
249
* @param resolverFields the fields to use for resolving, null for all fields
250
* @return this, for method chaining
251
* @throws DateTimeException if resolving one field results in a value for
252
* another field that is in conflict
253
*/
254
TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {
255
if (resolverFields != null) {
256
fieldValues.keySet().retainAll(resolverFields);
257
}
258
this.resolverStyle = resolverStyle;
259
resolveFields();
260
resolveTimeLenient();
261
crossCheck();
262
resolvePeriod();
263
resolveFractional();
264
resolveInstant();
265
return this;
266
}
267
268
//-----------------------------------------------------------------------
269
private void resolveFields() {
270
// resolve ChronoField
271
resolveInstantFields();
272
resolveDateFields();
273
resolveTimeFields();
274
275
// if any other fields, handle them
276
// any lenient date resolution should return epoch-day
277
if (fieldValues.size() > 0) {
278
int changedCount = 0;
279
outer:
280
while (changedCount < 50) {
281
for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
282
TemporalField targetField = entry.getKey();
283
TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle);
284
if (resolvedObject != null) {
285
if (resolvedObject instanceof ChronoZonedDateTime<?> czdt) {
286
if (zone == null) {
287
zone = czdt.getZone();
288
} else if (zone.equals(czdt.getZone()) == false) {
289
throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone);
290
}
291
resolvedObject = czdt.toLocalDateTime();
292
}
293
if (resolvedObject instanceof ChronoLocalDateTime<?> cldt) {
294
updateCheckConflict(cldt.toLocalTime(), Period.ZERO);
295
updateCheckConflict(cldt.toLocalDate());
296
changedCount++;
297
continue outer; // have to restart to avoid concurrent modification
298
}
299
if (resolvedObject instanceof ChronoLocalDate) {
300
updateCheckConflict((ChronoLocalDate) resolvedObject);
301
changedCount++;
302
continue outer; // have to restart to avoid concurrent modification
303
}
304
if (resolvedObject instanceof LocalTime) {
305
updateCheckConflict((LocalTime) resolvedObject, Period.ZERO);
306
changedCount++;
307
continue outer; // have to restart to avoid concurrent modification
308
}
309
throw new DateTimeException("Method resolve() can only return ChronoZonedDateTime, " +
310
"ChronoLocalDateTime, ChronoLocalDate or LocalTime");
311
} else if (fieldValues.containsKey(targetField) == false) {
312
changedCount++;
313
continue outer; // have to restart to avoid concurrent modification
314
}
315
}
316
break;
317
}
318
if (changedCount == 50) { // catch infinite loops
319
throw new DateTimeException("One of the parsed fields has an incorrectly implemented resolve method");
320
}
321
// if something changed then have to redo ChronoField resolve
322
if (changedCount > 0) {
323
resolveInstantFields();
324
resolveDateFields();
325
resolveTimeFields();
326
}
327
}
328
}
329
330
private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) {
331
Long old = fieldValues.put(changeField, changeValue);
332
if (old != null && old.longValue() != changeValue.longValue()) {
333
throw new DateTimeException("Conflict found: " + changeField + " " + old +
334
" differs from " + changeField + " " + changeValue +
335
" while resolving " + targetField);
336
}
337
}
338
339
340
//-----------------------------------------------------------------------
341
private void resolveInstantFields() {
342
// resolve parsed instant seconds to date and time if zone available
343
if (fieldValues.containsKey(INSTANT_SECONDS)) {
344
if (zone != null) {
345
resolveInstantFields0(zone);
346
} else {
347
Long offsetSecs = fieldValues.get(OFFSET_SECONDS);
348
if (offsetSecs != null) {
349
ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue());
350
resolveInstantFields0(offset);
351
}
352
}
353
}
354
}
355
356
private void resolveInstantFields0(ZoneId selectedZone) {
357
Instant instant = Instant.ofEpochSecond(fieldValues.get(INSTANT_SECONDS));
358
ChronoZonedDateTime<?> zdt = chrono.zonedDateTime(instant, selectedZone);
359
updateCheckConflict(zdt.toLocalDate());
360
updateCheckConflict(INSTANT_SECONDS, SECOND_OF_DAY, (long) zdt.toLocalTime().toSecondOfDay());
361
updateCheckConflict(INSTANT_SECONDS, OFFSET_SECONDS, (long) zdt.getOffset().getTotalSeconds());
362
}
363
364
//-----------------------------------------------------------------------
365
private void resolveDateFields() {
366
updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle));
367
}
368
369
private void updateCheckConflict(ChronoLocalDate cld) {
370
if (date != null) {
371
if (cld != null && date.equals(cld) == false) {
372
throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld);
373
}
374
} else if (cld != null) {
375
if (chrono.equals(cld.getChronology()) == false) {
376
throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono);
377
}
378
date = cld;
379
}
380
}
381
382
//-----------------------------------------------------------------------
383
private void resolveTimeFields() {
384
// simplify fields
385
if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) {
386
// lenient allows anything, smart allows 0-24, strict allows 1-24
387
long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY);
388
if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
389
CLOCK_HOUR_OF_DAY.checkValidValue(ch);
390
}
391
updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch);
392
}
393
if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) {
394
// lenient allows anything, smart allows 0-12, strict allows 1-12
395
long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM);
396
if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
397
CLOCK_HOUR_OF_AMPM.checkValidValue(ch);
398
}
399
updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch);
400
}
401
if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) {
402
long ap = fieldValues.remove(AMPM_OF_DAY);
403
long hap = fieldValues.remove(HOUR_OF_AMPM);
404
if (resolverStyle == ResolverStyle.LENIENT) {
405
updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap));
406
} else { // STRICT or SMART
407
AMPM_OF_DAY.checkValidValue(ap);
408
HOUR_OF_AMPM.checkValidValue(hap);
409
updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap);
410
}
411
}
412
if (fieldValues.containsKey(NANO_OF_DAY)) {
413
long nod = fieldValues.remove(NANO_OF_DAY);
414
if (resolverStyle != ResolverStyle.LENIENT) {
415
NANO_OF_DAY.checkValidValue(nod);
416
}
417
updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L);
418
updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60);
419
updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60);
420
updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L);
421
}
422
if (fieldValues.containsKey(MICRO_OF_DAY)) {
423
long cod = fieldValues.remove(MICRO_OF_DAY);
424
if (resolverStyle != ResolverStyle.LENIENT) {
425
MICRO_OF_DAY.checkValidValue(cod);
426
}
427
updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L);
428
updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L);
429
}
430
if (fieldValues.containsKey(MILLI_OF_DAY)) {
431
long lod = fieldValues.remove(MILLI_OF_DAY);
432
if (resolverStyle != ResolverStyle.LENIENT) {
433
MILLI_OF_DAY.checkValidValue(lod);
434
}
435
updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000);
436
updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000);
437
}
438
if (fieldValues.containsKey(SECOND_OF_DAY)) {
439
long sod = fieldValues.remove(SECOND_OF_DAY);
440
if (resolverStyle != ResolverStyle.LENIENT) {
441
SECOND_OF_DAY.checkValidValue(sod);
442
}
443
updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600);
444
updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60);
445
updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60);
446
}
447
if (fieldValues.containsKey(MINUTE_OF_DAY)) {
448
long mod = fieldValues.remove(MINUTE_OF_DAY);
449
if (resolverStyle != ResolverStyle.LENIENT) {
450
MINUTE_OF_DAY.checkValidValue(mod);
451
}
452
updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60);
453
updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60);
454
}
455
456
// combine partial second fields strictly, leaving lenient expansion to later
457
if (fieldValues.containsKey(NANO_OF_SECOND)) {
458
long nos = fieldValues.get(NANO_OF_SECOND);
459
if (resolverStyle != ResolverStyle.LENIENT) {
460
NANO_OF_SECOND.checkValidValue(nos);
461
}
462
if (fieldValues.containsKey(MICRO_OF_SECOND)) {
463
long cos = fieldValues.remove(MICRO_OF_SECOND);
464
if (resolverStyle != ResolverStyle.LENIENT) {
465
MICRO_OF_SECOND.checkValidValue(cos);
466
}
467
nos = cos * 1000 + (nos % 1000);
468
updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos);
469
}
470
if (fieldValues.containsKey(MILLI_OF_SECOND)) {
471
long los = fieldValues.remove(MILLI_OF_SECOND);
472
if (resolverStyle != ResolverStyle.LENIENT) {
473
MILLI_OF_SECOND.checkValidValue(los);
474
}
475
updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L));
476
}
477
}
478
479
if (dayPeriod != null && fieldValues.containsKey(HOUR_OF_AMPM)) {
480
long hoap = fieldValues.remove(HOUR_OF_AMPM);
481
if (resolverStyle != ResolverStyle.LENIENT) {
482
HOUR_OF_AMPM.checkValidValue(hoap);
483
}
484
Long mohObj = fieldValues.get(MINUTE_OF_HOUR);
485
long moh = mohObj != null ? Math.floorMod(mohObj, 60) : 0;
486
long excessHours = dayPeriod.includes((Math.floorMod(hoap, 12) + 12) * 60 + moh) ? 12 : 0;
487
long hod = Math.addExact(hoap, excessHours);
488
updateCheckConflict(HOUR_OF_AMPM, HOUR_OF_DAY, hod);
489
dayPeriod = null;
490
}
491
492
// convert to time if all four fields available (optimization)
493
if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&
494
fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) {
495
long hod = fieldValues.remove(HOUR_OF_DAY);
496
long moh = fieldValues.remove(MINUTE_OF_HOUR);
497
long som = fieldValues.remove(SECOND_OF_MINUTE);
498
long nos = fieldValues.remove(NANO_OF_SECOND);
499
resolveTime(hod, moh, som, nos);
500
}
501
}
502
503
private void resolveTimeLenient() {
504
// leniently create a time from incomplete information
505
// done after everything else as it creates information from nothing
506
// which would break updateCheckConflict(field)
507
508
if (time == null) {
509
// NANO_OF_SECOND merged with MILLI/MICRO above
510
if (fieldValues.containsKey(MILLI_OF_SECOND)) {
511
long los = fieldValues.remove(MILLI_OF_SECOND);
512
if (fieldValues.containsKey(MICRO_OF_SECOND)) {
513
// merge milli-of-second and micro-of-second for better error message
514
long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000);
515
updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos);
516
fieldValues.remove(MICRO_OF_SECOND);
517
fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
518
} else {
519
// convert milli-of-second to nano-of-second
520
fieldValues.put(NANO_OF_SECOND, los * 1_000_000L);
521
}
522
} else if (fieldValues.containsKey(MICRO_OF_SECOND)) {
523
// convert micro-of-second to nano-of-second
524
long cos = fieldValues.remove(MICRO_OF_SECOND);
525
fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
526
}
527
528
// Set the hour-of-day, if not exist and not in STRICT, to the mid point of the day period or am/pm.
529
if (!fieldValues.containsKey(HOUR_OF_DAY) &&
530
!fieldValues.containsKey(MINUTE_OF_HOUR) &&
531
!fieldValues.containsKey(SECOND_OF_MINUTE) &&
532
!fieldValues.containsKey(NANO_OF_SECOND) &&
533
resolverStyle != ResolverStyle.STRICT) {
534
if (dayPeriod != null) {
535
long midpoint = dayPeriod.mid();
536
resolveTime(midpoint / 60, midpoint % 60, 0, 0);
537
dayPeriod = null;
538
} else if (fieldValues.containsKey(AMPM_OF_DAY)) {
539
long ap = fieldValues.remove(AMPM_OF_DAY);
540
if (resolverStyle == ResolverStyle.LENIENT) {
541
resolveTime(Math.addExact(Math.multiplyExact(ap, 12), 6), 0, 0, 0);
542
} else { // SMART
543
AMPM_OF_DAY.checkValidValue(ap);
544
resolveTime(ap * 12 + 6, 0, 0, 0);
545
}
546
}
547
}
548
549
// merge hour/minute/second/nano leniently
550
Long hod = fieldValues.get(HOUR_OF_DAY);
551
if (hod != null) {
552
Long moh = fieldValues.get(MINUTE_OF_HOUR);
553
Long som = fieldValues.get(SECOND_OF_MINUTE);
554
Long nos = fieldValues.get(NANO_OF_SECOND);
555
556
// check for invalid combinations that cannot be defaulted
557
if ((moh == null && (som != null || nos != null)) ||
558
(moh != null && som == null && nos != null)) {
559
return;
560
}
561
562
// default as necessary and build time
563
long mohVal = (moh != null ? moh : 0);
564
long somVal = (som != null ? som : 0);
565
long nosVal = (nos != null ? nos : 0);
566
567
if (dayPeriod != null && resolverStyle != ResolverStyle.LENIENT) {
568
// Check whether the hod/mohVal is within the day period
569
if (!dayPeriod.includes(hod * 60 + mohVal)) {
570
throw new DateTimeException("Conflict found: Resolved time %02d:%02d".formatted(hod, mohVal) +
571
" conflicts with " + dayPeriod);
572
}
573
}
574
575
resolveTime(hod, mohVal, somVal, nosVal);
576
fieldValues.remove(HOUR_OF_DAY);
577
fieldValues.remove(MINUTE_OF_HOUR);
578
fieldValues.remove(SECOND_OF_MINUTE);
579
fieldValues.remove(NANO_OF_SECOND);
580
}
581
}
582
583
// validate remaining
584
if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) {
585
for (Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
586
TemporalField field = entry.getKey();
587
if (field instanceof ChronoField && field.isTimeBased()) {
588
((ChronoField) field).checkValidValue(entry.getValue());
589
}
590
}
591
}
592
}
593
594
private void resolveTime(long hod, long moh, long som, long nos) {
595
if (resolverStyle == ResolverStyle.LENIENT) {
596
long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L);
597
totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L));
598
totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L));
599
totalNanos = Math.addExact(totalNanos, nos);
600
int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L); // safe int cast
601
long nod = Math.floorMod(totalNanos, 86400_000_000_000L);
602
updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays));
603
} else { // STRICT or SMART
604
int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh);
605
int nosVal = NANO_OF_SECOND.checkValidIntValue(nos);
606
// handle 24:00 end of day
607
if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) {
608
updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1));
609
} else {
610
int hodVal = HOUR_OF_DAY.checkValidIntValue(hod);
611
int somVal = SECOND_OF_MINUTE.checkValidIntValue(som);
612
updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO);
613
}
614
}
615
}
616
617
private void resolvePeriod() {
618
// add whole days if we have both date and time
619
if (date != null && time != null && excessDays.isZero() == false) {
620
date = date.plus(excessDays);
621
excessDays = Period.ZERO;
622
}
623
}
624
625
private void resolveFractional() {
626
// ensure fractional seconds available as ChronoField requires
627
// resolveTimeLenient() will have merged MICRO_OF_SECOND/MILLI_OF_SECOND to NANO_OF_SECOND
628
if (time == null &&
629
(fieldValues.containsKey(INSTANT_SECONDS) ||
630
fieldValues.containsKey(SECOND_OF_DAY) ||
631
fieldValues.containsKey(SECOND_OF_MINUTE))) {
632
if (fieldValues.containsKey(NANO_OF_SECOND)) {
633
long nos = fieldValues.get(NANO_OF_SECOND);
634
fieldValues.put(MICRO_OF_SECOND, nos / 1000);
635
fieldValues.put(MILLI_OF_SECOND, nos / 1000000);
636
} else {
637
fieldValues.put(NANO_OF_SECOND, 0L);
638
fieldValues.put(MICRO_OF_SECOND, 0L);
639
fieldValues.put(MILLI_OF_SECOND, 0L);
640
}
641
}
642
}
643
644
private void resolveInstant() {
645
// add instant seconds (if not present) if we have date, time and zone
646
// Offset (if present) will be given priority over the zone.
647
if (!fieldValues.containsKey(INSTANT_SECONDS) && date != null && time != null) {
648
Long offsetSecs = fieldValues.get(OFFSET_SECONDS);
649
if (offsetSecs != null) {
650
ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue());
651
long instant = date.atTime(time).atZone(offset).toEpochSecond();
652
fieldValues.put(INSTANT_SECONDS, instant);
653
} else {
654
if (zone != null) {
655
long instant = date.atTime(time).atZone(zone).toEpochSecond();
656
fieldValues.put(INSTANT_SECONDS, instant);
657
}
658
}
659
}
660
}
661
662
private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) {
663
if (time != null) {
664
if (time.equals(timeToSet) == false) {
665
throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet);
666
}
667
if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) {
668
throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet);
669
} else {
670
excessDays = periodToSet;
671
}
672
} else {
673
time = timeToSet;
674
excessDays = periodToSet;
675
}
676
}
677
678
//-----------------------------------------------------------------------
679
private void crossCheck() {
680
// only cross-check date, time and date-time
681
// avoid object creation if possible
682
if (date != null) {
683
crossCheck(date);
684
}
685
if (time != null) {
686
crossCheck(time);
687
if (date != null && fieldValues.size() > 0) {
688
crossCheck(date.atTime(time));
689
}
690
}
691
}
692
693
private void crossCheck(TemporalAccessor target) {
694
for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {
695
Entry<TemporalField, Long> entry = it.next();
696
TemporalField field = entry.getKey();
697
if (target.isSupported(field)) {
698
long val1;
699
try {
700
val1 = target.getLong(field);
701
} catch (RuntimeException ex) {
702
continue;
703
}
704
long val2 = entry.getValue();
705
if (val1 != val2) {
706
throw new DateTimeException("Conflict found: Field " + field + " " + val1 +
707
" differs from " + field + " " + val2 + " derived from " + target);
708
}
709
it.remove();
710
}
711
}
712
}
713
714
//-----------------------------------------------------------------------
715
@Override
716
public String toString() {
717
StringBuilder buf = new StringBuilder(64);
718
buf.append(fieldValues).append(',').append(chrono);
719
if (zone != null) {
720
buf.append(',').append(zone);
721
}
722
if (date != null || time != null) {
723
buf.append(" resolved to ");
724
if (date != null) {
725
buf.append(date);
726
if (time != null) {
727
buf.append('T').append(time);
728
}
729
} else {
730
buf.append(time);
731
}
732
}
733
return buf.toString();
734
}
735
736
}
737
738