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) 2012, 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.Adjusters.nextOrSame; 65 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; 66 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; 67 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; 68 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; 69 import static java.time.temporal.ChronoField.AMPM_OF_DAY; 70 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; 71 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; 72 import static java.time.temporal.ChronoField.DAY_OF_MONTH; 73 import static java.time.temporal.ChronoField.DAY_OF_WEEK; 74 import static java.time.temporal.ChronoField.DAY_OF_YEAR; 75 import static java.time.temporal.ChronoField.EPOCH_DAY; 76 import static java.time.temporal.ChronoField.EPOCH_MONTH; 77 import static java.time.temporal.ChronoField.HOUR_OF_AMPM; 78 import static java.time.temporal.ChronoField.HOUR_OF_DAY; 79 import static java.time.temporal.ChronoField.INSTANT_SECONDS; 80 import static java.time.temporal.ChronoField.MICRO_OF_DAY; 81 import static java.time.temporal.ChronoField.MICRO_OF_SECOND; 82 import static java.time.temporal.ChronoField.MILLI_OF_DAY; 83 import static java.time.temporal.ChronoField.MILLI_OF_SECOND; 84 import static java.time.temporal.ChronoField.MINUTE_OF_DAY; 85 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; 86 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 87 import static java.time.temporal.ChronoField.NANO_OF_DAY; 88 import static java.time.temporal.ChronoField.NANO_OF_SECOND; 89 import static java.time.temporal.ChronoField.OFFSET_SECONDS; 90 import static java.time.temporal.ChronoField.SECOND_OF_DAY; 91 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; 92 import static java.time.temporal.ChronoField.YEAR; 93 94 import java.time.DateTimeException; 95 import java.time.DayOfWeek; 96 import java.time.Instant; 97 import java.time.LocalDate; 98 import java.time.LocalTime; 99 import java.time.ZoneId; 100 import java.time.ZoneOffset; 101 import java.time.temporal.Chrono; 102 import java.time.temporal.ChronoField; 103 import java.time.temporal.Queries; 104 import java.time.temporal.TemporalAccessor; 105 import java.time.temporal.TemporalField; 106 import java.time.temporal.TemporalQuery; 107 import java.util.ArrayList; 108 import java.util.EnumMap; 109 import java.util.HashMap; 110 import java.util.HashSet; 111 import java.util.LinkedHashMap; 112 import java.util.List; 113 import java.util.Map; 114 import java.util.Map.Entry; 115 import java.util.Objects; 116 import java.util.Set; 117 118 /** 119 * Builder that can holds date and time fields and related date and time objects. 120 * <p> 121 * <b>This class still needs major revision before JDK1.8 ships.</b> 122 * <p> 123 * The builder is used to hold onto different elements of date and time. 124 * It is designed as two separate maps: 125 * <p><ul> 126 * <li>from {@link java.time.temporal.TemporalField} to {@code long} value, where the value may be 127 * outside the valid range for the field 128 * <li>from {@code Class} to {@link java.time.temporal.TemporalAccessor}, holding larger scale objects 129 * like {@code LocalDateTime}. 130 * </ul><p> 131 * 132 * <h3>Specification for implementors</h3> 133 * This class is mutable and not thread-safe. 134 * It should only be used from a single thread. 135 * 136 * @since 1.8 137 */ 138 public final class DateTimeBuilder 139 implements TemporalAccessor, Cloneable { 140 141 /** 142 * The map of other fields. 143 */ 144 private Map<TemporalField, Long> otherFields; 145 /** 146 * The map of date-time fields. 147 */ 148 private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class); 149 /** 150 * The list of complete date-time objects. 151 */ 152 private final List<Object> objects = new ArrayList<>(2); 153 154 //----------------------------------------------------------------------- 155 /** 156 * Creates an empty instance of the builder. 157 */ 158 public DateTimeBuilder() { 159 } 160 161 /** 162 * Creates a new instance of the builder with a single field-value. 163 * <p> 164 * This is equivalent to using {@link #addFieldValue(TemporalField, long)} on an empty builder. 165 * 166 * @param field the field to add, not null 167 * @param value the value to add, not null 168 */ 169 public DateTimeBuilder(TemporalField field, long value) { 170 addFieldValue(field, value); 171 } 172 173 /** 174 * Creates a new instance of the builder. 175 * 176 * @param zone the zone, may be null 177 * @param chrono the chronology, may be null 178 */ 179 public DateTimeBuilder(ZoneId zone, Chrono<?> chrono) { 180 if (zone != null) { 181 objects.add(zone); 182 } 183 if (chrono != null) { 184 objects.add(chrono); 185 } 186 } 187 188 //----------------------------------------------------------------------- 189 /** 190 * Gets the map of field-value pairs in the builder. 191 * 192 * @return a modifiable copy of the field-value map, not null 193 */ 194 public Map<TemporalField, Long> getFieldValueMap() { 195 Map<TemporalField, Long> map = new HashMap<TemporalField, Long>(standardFields); 196 if (otherFields != null) { 197 map.putAll(otherFields); 198 } 199 return map; 200 } 201 202 /** 203 * Checks whether the specified field is present in the builder. 204 * 205 * @param field the field to find in the field-value map, not null 206 * @return true if the field is present 207 */ 208 public boolean containsFieldValue(TemporalField field) { 209 Objects.requireNonNull(field, "field"); 210 return standardFields.containsKey(field) || (otherFields != null && otherFields.containsKey(field)); 211 } 212 213 /** 214 * Gets the value of the specified field from the builder. 215 * 216 * @param field the field to query in the field-value map, not null 217 * @return the value of the field, may be out of range 218 * @throws DateTimeException if the field is not present 219 */ 220 public long getFieldValue(TemporalField field) { 221 Objects.requireNonNull(field, "field"); 222 Long value = getFieldValue0(field); 223 if (value == null) { 224 throw new DateTimeException("Field not found: " + field); 225 } 226 return value; 227 } 228 229 private Long getFieldValue0(TemporalField field) { 230 if (field instanceof ChronoField) { 231 return standardFields.get(field); 232 } else if (otherFields != null) { 233 return otherFields.get(field); 234 } 235 return null; 236 } 237 238 /** 239 * Gets the value of the specified field from the builder ensuring it is valid. 240 * 241 * @param field the field to query in the field-value map, not null 242 * @return the value of the field, may be out of range 243 * @throws DateTimeException if the field is not present 244 */ 245 public long getValidFieldValue(TemporalField field) { 246 long value = getFieldValue(field); 247 return field.range().checkValidValue(value, field); 248 } 249 250 /** 251 * Adds a field-value pair to the builder. 252 * <p> 253 * This adds a field to the builder. 254 * If the field is not already present, then the field-value pair is added to the map. 255 * If the field is already present and it has the same value as that specified, no action occurs. 256 * If the field is already present and it has a different value to that specified, then 257 * an exception is thrown. 258 * 259 * @param field the field to add, not null 260 * @param value the value to add, not null 261 * @return {@code this}, for method chaining 262 * @throws DateTimeException if the field is already present with a different value 263 */ 264 public DateTimeBuilder addFieldValue(TemporalField field, long value) { 265 Objects.requireNonNull(field, "field"); 266 Long old = getFieldValue0(field); // check first for better error message 267 if (old != null && old.longValue() != value) { 268 throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this); 269 } 270 return putFieldValue0(field, value); 271 } 272 273 private DateTimeBuilder putFieldValue0(TemporalField field, long value) { 274 if (field instanceof ChronoField) { 275 standardFields.put((ChronoField) field, value); 276 } else { 277 if (otherFields == null) { 278 otherFields = new LinkedHashMap<TemporalField, Long>(); 279 } 280 otherFields.put(field, value); 281 } 282 return this; 283 } 284 285 /** 286 * Removes a field-value pair from the builder. 287 * <p> 288 * This removes a field, which must exist, from the builder. 289 * See {@link #removeFieldValues(TemporalField...)} for a version which does not throw an exception 290 * 291 * @param field the field to remove, not null 292 * @return the previous value of the field 293 * @throws DateTimeException if the field is not found 294 */ 295 public long removeFieldValue(TemporalField field) { 296 Objects.requireNonNull(field, "field"); 297 Long value = null; 298 if (field instanceof ChronoField) { 299 value = standardFields.remove(field); 300 } else if (otherFields != null) { 301 value = otherFields.remove(field); 302 } 303 if (value == null) { 304 throw new DateTimeException("Field not found: " + field); 305 } 306 return value; 307 } 308 309 //----------------------------------------------------------------------- 310 /** 311 * Removes a list of fields from the builder. 312 * <p> 313 * This removes the specified fields from the builder. 314 * No exception is thrown if the fields are not present. 315 * 316 * @param fields the fields to remove, not null 317 */ 318 public void removeFieldValues(TemporalField... fields) { 319 for (TemporalField field : fields) { 320 if (field instanceof ChronoField) { 321 standardFields.remove(field); 322 } else if (otherFields != null) { 323 otherFields.remove(field); 324 } 325 } 326 } 327 328 /** 329 * Queries a list of fields from the builder. 330 * <p> 331 * This gets the value of the specified fields from the builder into 332 * an array where the positions match the order of the fields. 333 * If a field is not present, the array will contain null in that position. 334 * 335 * @param fields the fields to query, not null 336 * @return the array of field values, not null 337 */ 338 public Long[] queryFieldValues(TemporalField... fields) { 339 Long[] values = new Long[fields.length]; 340 int i = 0; 341 for (TemporalField field : fields) { 342 values[i++] = getFieldValue0(field); 343 } 344 return values; 345 } 346 347 //----------------------------------------------------------------------- 348 /** 349 * Gets the list of date-time objects in the builder. 350 * <p> 351 * This map is intended for use with {@link ZoneOffset} and {@link ZoneId}. 352 * The returned map is live and may be edited. 353 * 354 * @return the editable list of date-time objects, not null 355 */ 356 public List<Object> getCalendricalList() { 357 return objects; 358 } 359 360 /** 361 * Adds a date-time object to the builder. 362 * <p> 363 * This adds a date-time object to the builder. 364 * If the object is a {@code DateTimeBuilder}, each field is added using {@link #addFieldValue}. 365 * If the object is not already present, then the object is added. 366 * If the object is already present and it is equal to that specified, no action occurs. 367 * If the object is already present and it is not equal to that specified, then an exception is thrown. 368 * 369 * @param object the object to add, not null 370 * @return {@code this}, for method chaining 371 * @throws DateTimeException if the field is already present with a different value 372 */ 373 public DateTimeBuilder addCalendrical(Object object) { 374 Objects.requireNonNull(object, "object"); 375 // special case 376 if (object instanceof DateTimeBuilder) { 377 DateTimeBuilder dtb = (DateTimeBuilder) object; 378 for (TemporalField field : dtb.getFieldValueMap().keySet()) { 379 addFieldValue(field, dtb.getFieldValue(field)); 380 } 381 return this; 382 } 383 if (object instanceof Instant) { 384 addFieldValue(INSTANT_SECONDS, ((Instant) object).getEpochSecond()); 385 addFieldValue(NANO_OF_SECOND, ((Instant) object).getNano()); 386 } else { 387 objects.add(object); 388 } 389 // TODO 390 // // preserve state of builder until validated 391 // Class<?> cls = dateTime.extract(Class.class); 392 // if (cls == null) { 393 // throw new DateTimeException("Invalid dateTime, unable to extract Class"); 394 // } 395 // Object obj = objects.get(cls); 396 // if (obj != null) { 397 // if (obj.equals(dateTime) == false) { 398 // throw new DateTimeException("Conflict found: " + dateTime.getClass().getSimpleName() + " " + obj + " differs from " + dateTime + ": " + this); 399 // } 400 // } else { 401 // objects.put(cls, dateTime); 402 // } 403 return this; 404 } 405 406 //----------------------------------------------------------------------- 407 /** 408 * Resolves the builder, evaluating the date and time. 409 * <p> 410 * This examines the contents of the builder and resolves it to produce the best 411 * available date and time, throwing an exception if a problem occurs. 412 * Calling this method changes the state of the builder. 413 * 414 * @return {@code this}, for method chaining 415 */ 416 public DateTimeBuilder resolve() { 417 splitObjects(); 418 // handle unusual fields 419 if (otherFields != null) { 420 outer: 421 while (true) { 422 Set<Entry<TemporalField, Long>> entrySet = new HashSet<>(otherFields.entrySet()); 423 for (Entry<TemporalField, Long> entry : entrySet) { 424 if (entry.getKey().resolve(this, entry.getValue())) { 425 continue outer; 426 } 427 } 428 break; 429 } 430 } 431 // handle standard fields 432 mergeDate(); 433 mergeTime(); 434 // TODO: cross validate remaining fields? 435 return this; 436 } 437 438 private void mergeDate() { 439 if (standardFields.containsKey(EPOCH_DAY)) { 440 checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY))); 441 return; 442 } 443 444 // normalize fields 445 if (standardFields.containsKey(EPOCH_MONTH)) { 446 long em = standardFields.remove(EPOCH_MONTH); 447 addFieldValue(MONTH_OF_YEAR, (em % 12) + 1); 448 addFieldValue(YEAR, (em / 12) + 1970); 449 } 450 451 // build date 452 if (standardFields.containsKey(YEAR)) { 453 if (standardFields.containsKey(MONTH_OF_YEAR)) { 454 if (standardFields.containsKey(DAY_OF_MONTH)) { 455 int y = Math.toIntExact(standardFields.remove(YEAR)); 456 int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); 457 int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH)); 458 checkDate(LocalDate.of(y, moy, dom)); 459 return; 460 } 461 if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) { 462 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { 463 int y = Math.toIntExact(standardFields.remove(YEAR)); 464 int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); 465 int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); 466 int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH)); 467 checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1))); 468 return; 469 } 470 if (standardFields.containsKey(DAY_OF_WEEK)) { 471 int y = Math.toIntExact(standardFields.remove(YEAR)); 472 int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); 473 int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); 474 int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); 475 checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); 476 return; 477 } 478 } 479 } 480 if (standardFields.containsKey(DAY_OF_YEAR)) { 481 int y = Math.toIntExact(standardFields.remove(YEAR)); 482 int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR)); 483 checkDate(LocalDate.ofYearDay(y, doy)); 484 return; 485 } 486 if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) { 487 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { 488 int y = Math.toIntExact(standardFields.remove(YEAR)); 489 int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); 490 int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR)); 491 checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1))); 492 return; 493 } 494 if (standardFields.containsKey(DAY_OF_WEEK)) { 495 int y = Math.toIntExact(standardFields.remove(YEAR)); 496 int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); 497 int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); 498 checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); 499 return; 500 } 501 } 502 } 503 } 504 505 private void checkDate(LocalDate date) { 506 // TODO: this doesn't handle aligned weeks over into next month which would otherwise be valid 507 508 addCalendrical(date); 509 for (ChronoField field : standardFields.keySet()) { 510 long val1; 511 try { 512 val1 = date.getLong(field); 513 } catch (DateTimeException ex) { 514 continue; 515 } 516 Long val2 = standardFields.get(field); 517 if (val1 != val2) { 518 throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date); 519 } 520 } 521 } 522 523 private void mergeTime() { 524 if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) { 525 long ch = standardFields.remove(CLOCK_HOUR_OF_DAY); 526 addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch); 527 } 528 if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) { 529 long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM); 530 addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch); 531 } 532 if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) { 533 long ap = standardFields.remove(AMPM_OF_DAY); 534 long hap = standardFields.remove(HOUR_OF_AMPM); 535 addFieldValue(HOUR_OF_DAY, ap * 12 + hap); 536 } 537 // if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) { 538 // long hod = timeFields.remove(HOUR_OF_DAY); 539 // long moh = timeFields.remove(MINUTE_OF_HOUR); 540 // addFieldValue(MINUTE_OF_DAY, hod * 60 + moh); 541 // } 542 // if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) { 543 // long mod = timeFields.remove(MINUTE_OF_DAY); 544 // long som = timeFields.remove(SECOND_OF_MINUTE); 545 // addFieldValue(SECOND_OF_DAY, mod * 60 + som); 546 // } 547 if (standardFields.containsKey(NANO_OF_DAY)) { 548 long nod = standardFields.remove(NANO_OF_DAY); 549 addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L); 550 addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); 551 } 552 if (standardFields.containsKey(MICRO_OF_DAY)) { 553 long cod = standardFields.remove(MICRO_OF_DAY); 554 addFieldValue(SECOND_OF_DAY, cod / 1000_000L); 555 addFieldValue(MICRO_OF_SECOND, cod % 1000_000L); 556 } 557 if (standardFields.containsKey(MILLI_OF_DAY)) { 558 long lod = standardFields.remove(MILLI_OF_DAY); 559 addFieldValue(SECOND_OF_DAY, lod / 1000); 560 addFieldValue(MILLI_OF_SECOND, lod % 1000); 561 } 562 if (standardFields.containsKey(SECOND_OF_DAY)) { 563 long sod = standardFields.remove(SECOND_OF_DAY); 564 addFieldValue(HOUR_OF_DAY, sod / 3600); 565 addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); 566 addFieldValue(SECOND_OF_MINUTE, sod % 60); 567 } 568 if (standardFields.containsKey(MINUTE_OF_DAY)) { 569 long mod = standardFields.remove(MINUTE_OF_DAY); 570 addFieldValue(HOUR_OF_DAY, mod / 60); 571 addFieldValue(MINUTE_OF_HOUR, mod % 60); 572 } 573 574 // long sod = nod / 1000_000_000L; 575 // addFieldValue(HOUR_OF_DAY, sod / 3600); 576 // addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); 577 // addFieldValue(SECOND_OF_MINUTE, sod % 60); 578 // addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); 579 if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) { 580 long los = standardFields.remove(MILLI_OF_SECOND); 581 long cos = standardFields.get(MICRO_OF_SECOND); 582 addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000)); 583 } 584 585 Long hod = standardFields.get(HOUR_OF_DAY); 586 Long moh = standardFields.get(MINUTE_OF_HOUR); 587 Long som = standardFields.get(SECOND_OF_MINUTE); 588 Long nos = standardFields.get(NANO_OF_SECOND); 589 if (hod != null) { 590 int hodVal = Math.toIntExact(hod); 591 if (moh != null) { 592 int mohVal = Math.toIntExact(moh); 593 if (som != null) { 594 int somVal = Math.toIntExact(som); 595 if (nos != null) { 596 int nosVal = Math.toIntExact(nos); 597 addCalendrical(LocalTime.of(hodVal, mohVal, somVal, nosVal)); 598 } else { 599 addCalendrical(LocalTime.of(hodVal, mohVal, somVal)); 600 } 601 } else { 602 addCalendrical(LocalTime.of(hodVal, mohVal)); 603 } 604 } else { 605 addCalendrical(LocalTime.of(hodVal, 0)); 606 } 607 } 608 } 609 610 private void splitObjects() { 611 List<Object> objectsToAdd = new ArrayList<>(); 612 for (Object object : objects) { 613 if (object instanceof LocalDate || object instanceof LocalTime || 614 object instanceof ZoneId || object instanceof Chrono) { 615 continue; 616 } 617 if (object instanceof ZoneOffset || object instanceof Instant) { 618 objectsToAdd.add(object); 619 620 } else if (object instanceof TemporalAccessor) { 621 // TODO 622 // TemporalAccessor dt = (TemporalAccessor) object; 623 // objectsToAdd.add(dt.extract(LocalDate.class)); 624 // objectsToAdd.add(dt.extract(LocalTime.class)); 625 // objectsToAdd.add(dt.extract(ZoneId.class)); 626 // objectsToAdd.add(dt.extract(Chrono.class)); 627 } 628 } 629 for (Object object : objectsToAdd) { 630 if (object != null) { 631 addCalendrical(object); 632 } 633 } 634 } 635 636 //----------------------------------------------------------------------- 637 @Override 638 public <R> R query(TemporalQuery<R> query) { 639 if (query == Queries.zoneId()) { 640 return (R) extract(ZoneId.class); 641 } 642 if (query == Queries.offset()) { 643 ZoneOffset offset = extract(ZoneOffset.class); 644 if (offset == null && standardFields.containsKey(OFFSET_SECONDS)) { 645 offset = ZoneOffset.ofTotalSeconds(Math.toIntExact(standardFields.get(OFFSET_SECONDS))); 646 } 647 return (R) offset; 648 } 649 if (query == Queries.chrono()) { 650 return extract(Chrono.class); 651 } 652 // incomplete, so no need to handle PRECISION 653 return TemporalAccessor.super.query(query); 654 } 655 656 @SuppressWarnings("unchecked") 657 public <R> R extract(Class<?> type) { 658 R result = null; 659 for (Object obj : objects) { 660 if (type.isInstance(obj)) { 661 if (result != null && result.equals(obj) == false) { 662 throw new DateTimeException("Conflict found: " + type.getSimpleName() + " differs " + result + " vs " + obj + ": " + this); 663 } 664 result = (R) obj; 665 } 666 } 667 return result; 668 } 669 670 //----------------------------------------------------------------------- 671 /** 672 * Clones this builder, creating a new independent copy referring to the 673 * same map of fields and objects. 674 * 675 * @return the cloned builder, not null 676 */ 677 @Override 678 public DateTimeBuilder clone() { 679 DateTimeBuilder dtb = new DateTimeBuilder(); 680 dtb.objects.addAll(this.objects); 681 dtb.standardFields.putAll(this.standardFields); 682 dtb.standardFields.putAll(this.standardFields); 683 if (this.otherFields != null) { 684 dtb.otherFields.putAll(this.otherFields); 685 } 686 return dtb; 687 } 688 689 //----------------------------------------------------------------------- 690 @Override 691 public String toString() { 692 StringBuilder buf = new StringBuilder(128); 693 buf.append("DateTimeBuilder["); 694 Map<TemporalField, Long> fields = getFieldValueMap(); 695 if (fields.size() > 0) { 696 buf.append("fields=").append(fields); 697 } 698 if (objects.size() > 0) { 699 if (fields.size() > 0) { 700 buf.append(", "); 701 } 702 buf.append("objects=").append(objects); 703 } 704 buf.append(']'); 705 return buf.toString(); 706 } 707 708 //----------------------------------------------------------------------- 709 @Override 710 public boolean isSupported(TemporalField field) { 711 return field != null && containsFieldValue(field); 712 } 713 714 @Override 715 public long getLong(TemporalField field) { 716 return getFieldValue(field); 717 } 718 719 }