001/* 002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos 003 * 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions are met: 008 * 009 * * Redistributions of source code must retain the above copyright notice, 010 * this list of conditions and the following disclaimer. 011 * 012 * * Redistributions in binary form must reproduce the above copyright notice, 013 * this list of conditions and the following disclaimer in the documentation 014 * and/or other materials provided with the distribution. 015 * 016 * * Neither the name of JSR-310 nor the names of its contributors 017 * may be used to endorse or promote products derived from this software 018 * without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 031 */ 032package org.threeten.bp.format; 033 034import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; 035import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; 036import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; 037import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; 038import static org.threeten.bp.temporal.ChronoField.AMPM_OF_DAY; 039import static org.threeten.bp.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; 040import static org.threeten.bp.temporal.ChronoField.CLOCK_HOUR_OF_DAY; 041import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; 042import static org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK; 043import static org.threeten.bp.temporal.ChronoField.DAY_OF_YEAR; 044import static org.threeten.bp.temporal.ChronoField.EPOCH_DAY; 045import static org.threeten.bp.temporal.ChronoField.EPOCH_MONTH; 046import static org.threeten.bp.temporal.ChronoField.HOUR_OF_AMPM; 047import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY; 048import static org.threeten.bp.temporal.ChronoField.INSTANT_SECONDS; 049import static org.threeten.bp.temporal.ChronoField.MICRO_OF_DAY; 050import static org.threeten.bp.temporal.ChronoField.MICRO_OF_SECOND; 051import static org.threeten.bp.temporal.ChronoField.MILLI_OF_DAY; 052import static org.threeten.bp.temporal.ChronoField.MILLI_OF_SECOND; 053import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_DAY; 054import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_HOUR; 055import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR; 056import static org.threeten.bp.temporal.ChronoField.NANO_OF_DAY; 057import static org.threeten.bp.temporal.ChronoField.NANO_OF_SECOND; 058import static org.threeten.bp.temporal.ChronoField.OFFSET_SECONDS; 059import static org.threeten.bp.temporal.ChronoField.SECOND_OF_DAY; 060import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE; 061import static org.threeten.bp.temporal.ChronoField.YEAR; 062import static org.threeten.bp.temporal.TemporalAdjusters.nextOrSame; 063 064import java.lang.reflect.Method; 065import java.util.ArrayList; 066import java.util.EnumMap; 067import java.util.HashMap; 068import java.util.HashSet; 069import java.util.LinkedHashMap; 070import java.util.List; 071import java.util.Map; 072import java.util.Map.Entry; 073import java.util.Objects; 074import java.util.Set; 075 076import org.threeten.bp.DateTimeException; 077import org.threeten.bp.DayOfWeek; 078import org.threeten.bp.Instant; 079import org.threeten.bp.LocalDate; 080import org.threeten.bp.LocalTime; 081import org.threeten.bp.ZoneId; 082import org.threeten.bp.ZoneOffset; 083import org.threeten.bp.jdk8.DefaultInterfaceTemporalAccessor; 084import org.threeten.bp.jdk8.Jdk8Methods; 085import org.threeten.bp.temporal.Chrono; 086import org.threeten.bp.temporal.ChronoField; 087import org.threeten.bp.temporal.TemporalAccessor; 088import org.threeten.bp.temporal.TemporalField; 089import org.threeten.bp.temporal.TemporalQueries; 090import org.threeten.bp.temporal.TemporalQuery; 091 092/** 093 * Builder that can holds date and time fields and related date and time objects. 094 * <p> 095 * The builder is used to hold onto different elements of date and time. 096 * It is designed as two separate maps: 097 * <p><ul> 098 * <li>from {@link TemporalField} to {@code long} value, where the value may be 099 * outside the valid range for the field 100 * <li>from {@code Class} to {@link TemporalAccessor}, holding larger scale objects 101 * like {@code LocalDateTime}. 102 * </ul><p> 103 * 104 * <h3>Specification for implementors</h3> 105 * This class is mutable and not thread-safe. 106 * It should only be used from a single thread. 107 */ 108public final class DateTimeBuilder 109 extends DefaultInterfaceTemporalAccessor 110 implements TemporalAccessor, Cloneable { 111 112 /** 113 * The map of other fields. 114 */ 115 private Map<TemporalField, Long> otherFields; 116 /** 117 * The map of date-time fields. 118 */ 119 private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class); 120 /** 121 * The list of complete date-time objects. 122 */ 123 private final List<Object> objects = new ArrayList<>(2); 124 125 //----------------------------------------------------------------------- 126 /** 127 * Creates an empty instance of the builder. 128 */ 129 public DateTimeBuilder() { 130 } 131 132 /** 133 * Creates a new instance of the builder with a single field-value. 134 * <p> 135 * This is equivalent to using {@link #addFieldValue(TemporalField, long)} on an empty builder. 136 * 137 * @param field the field to add, not null 138 * @param value the value to add, not null 139 */ 140 public DateTimeBuilder(TemporalField field, long value) { 141 addFieldValue(field, value); 142 } 143 144 /** 145 * Creates a new instance of the builder. 146 * 147 * @param zone the zone, may be null 148 * @param chrono the chronology, may be null 149 */ 150 public DateTimeBuilder(ZoneId zone, Chrono<?> chrono) { 151 if (zone != null) { 152 objects.add(zone); 153 } 154 if (chrono != null) { 155 objects.add(chrono); 156 } 157 } 158 159 //----------------------------------------------------------------------- 160 /** 161 * Gets the map of field-value pairs in the builder. 162 * 163 * @return a modifiable copy of the field-value map, not null 164 */ 165 public Map<TemporalField, Long> getFieldValueMap() { 166 Map<TemporalField, Long> map = new HashMap<TemporalField, Long>(standardFields); 167 if (otherFields != null) { 168 map.putAll(otherFields); 169 } 170 return map; 171 } 172 173 /** 174 * Checks whether the specified field is present in the builder. 175 * 176 * @param field the field to find in the field-value map, not null 177 * @return true if the field is present 178 */ 179 public boolean containsFieldValue(TemporalField field) { 180 Objects.requireNonNull(field, "field"); 181 return standardFields.containsKey(field) || (otherFields != null && otherFields.containsKey(field)); 182 } 183 184 /** 185 * Gets the value of the specified field from the builder. 186 * 187 * @param field the field to query in the field-value map, not null 188 * @return the value of the field, may be out of range 189 * @throws DateTimeException if the field is not present 190 */ 191 public long getFieldValue(TemporalField field) { 192 Objects.requireNonNull(field, "field"); 193 Long value = getFieldValue0(field); 194 if (value == null) { 195 throw new DateTimeException("Field not found: " + field); 196 } 197 return value; 198 } 199 200 private Long getFieldValue0(TemporalField field) { 201 if (field instanceof ChronoField) { 202 return standardFields.get(field); 203 } else if (otherFields != null) { 204 return otherFields.get(field); 205 } 206 return null; 207 } 208 209 /** 210 * Gets the value of the specified field from the builder ensuring it is valid. 211 * 212 * @param field the field to query in the field-value map, not null 213 * @return the value of the field, may be out of range 214 * @throws DateTimeException if the field is not present 215 */ 216 public long getValidFieldValue(TemporalField field) { 217 long value = getFieldValue(field); 218 return field.range().checkValidValue(value, field); 219 } 220 221 /** 222 * Adds a field-value pair to the builder. 223 * <p> 224 * This adds a field to the builder. 225 * If the field is not already present, then the field-value pair is added to the map. 226 * If the field is already present and it has the same value as that specified, no action occurs. 227 * If the field is already present and it has a different value to that specified, then 228 * an exception is thrown. 229 * 230 * @param field the field to add, not null 231 * @param value the value to add, not null 232 * @return {@code this}, for method chaining 233 * @throws DateTimeException if the field is already present with a different value 234 */ 235 public DateTimeBuilder addFieldValue(TemporalField field, long value) { 236 Objects.requireNonNull(field, "field"); 237 Long old = getFieldValue0(field); // check first for better error message 238 if (old != null && old.longValue() != value) { 239 throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this); 240 } 241 return putFieldValue0(field, value); 242 } 243 244 private DateTimeBuilder putFieldValue0(TemporalField field, long value) { 245 if (field instanceof ChronoField) { 246 standardFields.put((ChronoField) field, value); 247 } else { 248 if (otherFields == null) { 249 otherFields = new LinkedHashMap<TemporalField, Long>(); 250 } 251 otherFields.put(field, value); 252 } 253 return this; 254 } 255 256 /** 257 * Removes a field-value pair from the builder. 258 * <p> 259 * This removes a field, which must exist, from the builder. 260 * See {@link #removeFieldValues(TemporalField...)} for a version which does not throw an exception 261 * 262 * @param field the field to remove, not null 263 * @return the previous value of the field 264 * @throws DateTimeException if the field is not found 265 */ 266 public long removeFieldValue(TemporalField field) { 267 Objects.requireNonNull(field, "field"); 268 Long value = null; 269 if (field instanceof ChronoField) { 270 value = standardFields.remove(field); 271 } else if (otherFields != null) { 272 value = otherFields.remove(field); 273 } 274 if (value == null) { 275 throw new DateTimeException("Field not found: " + field); 276 } 277 return value; 278 } 279 280 //----------------------------------------------------------------------- 281 /** 282 * Removes a list of fields from the builder. 283 * <p> 284 * This removes the specified fields from the builder. 285 * No exception is thrown if the fields are not present. 286 * 287 * @param fields the fields to remove, not null 288 */ 289 public void removeFieldValues(TemporalField... fields) { 290 for (TemporalField field : fields) { 291 if (field instanceof ChronoField) { 292 standardFields.remove(field); 293 } else if (otherFields != null) { 294 otherFields.remove(field); 295 } 296 } 297 } 298 299 /** 300 * Queries a list of fields from the builder. 301 * <p> 302 * This gets the value of the specified fields from the builder into 303 * an array where the positions match the order of the fields. 304 * If a field is not present, the array will contain null in that position. 305 * 306 * @param fields the fields to query, not null 307 * @return the array of field values, not null 308 */ 309 public Long[] queryFieldValues(TemporalField... fields) { 310 Long[] values = new Long[fields.length]; 311 int i = 0; 312 for (TemporalField field : fields) { 313 values[i++] = getFieldValue0(field); 314 } 315 return values; 316 } 317 318 //----------------------------------------------------------------------- 319 /** 320 * Gets the list of date-time objects in the builder. 321 * <p> 322 * This map is intended for use with {@link ZoneOffset} and {@link ZoneId}. 323 * The returned map is live and may be edited. 324 * 325 * @return the editable list of date-time objects, not null 326 */ 327 public List<Object> getCalendricalList() { 328 return objects; 329 } 330 331 /** 332 * Adds a date-time object to the builder. 333 * <p> 334 * This adds a date-time object to the builder. 335 * If the object is a {@code DateTimeBuilder}, each field is added using {@link #addFieldValue}. 336 * If the object is not already present, then the object is added. 337 * If the object is already present and it is equal to that specified, no action occurs. 338 * If the object is already present and it is not equal to that specified, then an exception is thrown. 339 * 340 * @param object the object to add, not null 341 * @return {@code this}, for method chaining 342 * @throws DateTimeException if the field is already present with a different value 343 */ 344 public DateTimeBuilder addCalendrical(Object object) { 345 Objects.requireNonNull(object, "object"); 346 // special case 347 if (object instanceof DateTimeBuilder) { 348 DateTimeBuilder dtb = (DateTimeBuilder) object; 349 for (TemporalField field : dtb.getFieldValueMap().keySet()) { 350 addFieldValue(field, dtb.getFieldValue(field)); 351 } 352 return this; 353 } 354 if (object instanceof Instant) { 355 addFieldValue(INSTANT_SECONDS, ((Instant) object).getEpochSecond()); 356 addFieldValue(NANO_OF_SECOND, ((Instant) object).getNano()); 357 } else { 358 objects.add(object); 359 } 360// TODO 361// // preserve state of builder until validated 362// Class<?> cls = dateTime.extract(Class.class); 363// if (cls == null) { 364// throw new DateTimeException("Invalid dateTime, unable to extract Class"); 365// } 366// Object obj = objects.get(cls); 367// if (obj != null) { 368// if (obj.equals(dateTime) == false) { 369// throw new DateTimeException("Conflict found: " + dateTime.getClass().getSimpleName() + " " + obj + " differs from " + dateTime + ": " + this); 370// } 371// } else { 372// objects.put(cls, dateTime); 373// } 374 return this; 375 } 376 377 //----------------------------------------------------------------------- 378 /** 379 * Resolves the builder, evaluating the date and time. 380 * <p> 381 * This examines the contents of the builder and resolves it to produce the best 382 * available date and time, throwing an exception if a problem occurs. 383 * Calling this method changes the state of the builder. 384 * 385 * @return {@code this}, for method chaining 386 */ 387 public DateTimeBuilder resolve() { 388 splitObjects(); 389 // handle unusual fields 390 if (otherFields != null) { 391 outer: 392 while (true) { 393 Set<Entry<TemporalField, Long>> entrySet = new HashSet<>(otherFields.entrySet()); 394 for (Entry<TemporalField, Long> entry : entrySet) { 395 if (entry.getKey().resolve(this, entry.getValue())) { 396 continue outer; 397 } 398 } 399 break; 400 } 401 } 402 // handle standard fields 403 mergeDate(); 404 mergeTime(); 405 // TODO: cross validate remaining fields? 406 return this; 407 } 408 409 private void mergeDate() { 410 if (standardFields.containsKey(EPOCH_DAY)) { 411 checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY))); 412 return; 413 } 414 415 // normalize fields 416 if (standardFields.containsKey(EPOCH_MONTH)) { 417 long em = standardFields.remove(EPOCH_MONTH); 418 addFieldValue(MONTH_OF_YEAR, (em % 12) + 1); 419 addFieldValue(YEAR, (em / 12) + 1970); 420 } 421 422 // build date 423 if (standardFields.containsKey(YEAR)) { 424 if (standardFields.containsKey(MONTH_OF_YEAR)) { 425 if (standardFields.containsKey(DAY_OF_MONTH)) { 426 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 427 int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR)); 428 int dom = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_MONTH)); 429 checkDate(LocalDate.of(y, moy, dom)); 430 return; 431 } 432 if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) { 433 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { 434 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 435 int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR)); 436 int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); 437 int ad = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH)); 438 checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1))); 439 return; 440 } 441 if (standardFields.containsKey(DAY_OF_WEEK)) { 442 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 443 int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR)); 444 int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); 445 int dow = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_WEEK)); 446 checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); 447 return; 448 } 449 } 450 } 451 if (standardFields.containsKey(DAY_OF_YEAR)) { 452 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 453 int doy = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_YEAR)); 454 checkDate(LocalDate.ofYearDay(y, doy)); 455 return; 456 } 457 if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) { 458 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { 459 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 460 int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); 461 int ad = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR)); 462 checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1))); 463 return; 464 } 465 if (standardFields.containsKey(DAY_OF_WEEK)) { 466 int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR)); 467 int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); 468 int dow = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_WEEK)); 469 checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); 470 return; 471 } 472 } 473 } 474 } 475 476 private void checkDate(LocalDate date) { 477 // TODO: this doesn't handle aligned weeks over into next month which would otherwise be valid 478 479 addCalendrical(date); 480 for (ChronoField field : standardFields.keySet()) { 481 long val1; 482 try { 483 val1 = date.getLong(field); 484 } catch (DateTimeException ex) { 485 continue; 486 } 487 Long val2 = standardFields.get(field); 488 if (val1 != val2) { 489 throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date); 490 } 491 } 492 } 493 494 private void mergeTime() { 495 if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) { 496 long ch = standardFields.remove(CLOCK_HOUR_OF_DAY); 497 addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch); 498 } 499 if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) { 500 long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM); 501 addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch); 502 } 503 if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) { 504 long ap = standardFields.remove(AMPM_OF_DAY); 505 long hap = standardFields.remove(HOUR_OF_AMPM); 506 addFieldValue(HOUR_OF_DAY, ap * 12 + hap); 507 } 508// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) { 509// long hod = timeFields.remove(HOUR_OF_DAY); 510// long moh = timeFields.remove(MINUTE_OF_HOUR); 511// addFieldValue(MINUTE_OF_DAY, hod * 60 + moh); 512// } 513// if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) { 514// long mod = timeFields.remove(MINUTE_OF_DAY); 515// long som = timeFields.remove(SECOND_OF_MINUTE); 516// addFieldValue(SECOND_OF_DAY, mod * 60 + som); 517// } 518 if (standardFields.containsKey(NANO_OF_DAY)) { 519 long nod = standardFields.remove(NANO_OF_DAY); 520 addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L); 521 addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); 522 } 523 if (standardFields.containsKey(MICRO_OF_DAY)) { 524 long cod = standardFields.remove(MICRO_OF_DAY); 525 addFieldValue(SECOND_OF_DAY, cod / 1000_000L); 526 addFieldValue(MICRO_OF_SECOND, cod % 1000_000L); 527 } 528 if (standardFields.containsKey(MILLI_OF_DAY)) { 529 long lod = standardFields.remove(MILLI_OF_DAY); 530 addFieldValue(SECOND_OF_DAY, lod / 1000); 531 addFieldValue(MILLI_OF_SECOND, lod % 1000); 532 } 533 if (standardFields.containsKey(SECOND_OF_DAY)) { 534 long sod = standardFields.remove(SECOND_OF_DAY); 535 addFieldValue(HOUR_OF_DAY, sod / 3600); 536 addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); 537 addFieldValue(SECOND_OF_MINUTE, sod % 60); 538 } 539 if (standardFields.containsKey(MINUTE_OF_DAY)) { 540 long mod = standardFields.remove(MINUTE_OF_DAY); 541 addFieldValue(HOUR_OF_DAY, mod / 60); 542 addFieldValue(MINUTE_OF_HOUR, mod % 60); 543 } 544 545// long sod = nod / 1000_000_000L; 546// addFieldValue(HOUR_OF_DAY, sod / 3600); 547// addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); 548// addFieldValue(SECOND_OF_MINUTE, sod % 60); 549// addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); 550 if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) { 551 long los = standardFields.remove(MILLI_OF_SECOND); 552 long cos = standardFields.get(MICRO_OF_SECOND); 553 addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000)); 554 } 555 556 Long hod = standardFields.get(HOUR_OF_DAY); 557 Long moh = standardFields.get(MINUTE_OF_HOUR); 558 Long som = standardFields.get(SECOND_OF_MINUTE); 559 Long nos = standardFields.get(NANO_OF_SECOND); 560 if (hod != null) { 561 int hodVal = Jdk8Methods.safeToInt(hod); 562 if (moh != null) { 563 int mohVal = Jdk8Methods.safeToInt(moh); 564 if (som != null) { 565 int somVal = Jdk8Methods.safeToInt(som); 566 if (nos != null) { 567 int nosVal = Jdk8Methods.safeToInt(nos); 568 addCalendrical(LocalTime.of(hodVal, mohVal, somVal, nosVal)); 569 } else { 570 addCalendrical(LocalTime.of(hodVal, mohVal, somVal)); 571 } 572 } else { 573 addCalendrical(LocalTime.of(hodVal, mohVal)); 574 } 575 } else { 576 addCalendrical(LocalTime.of(hodVal, 0)); 577 } 578 } 579 } 580 581 private void splitObjects() { 582 List<Object> objectsToAdd = new ArrayList<>(); 583 for (Object object : objects) { 584 if (object instanceof LocalDate || object instanceof LocalTime || 585 object instanceof ZoneId || object instanceof Chrono) { 586 continue; 587 } 588 if (object instanceof ZoneOffset || object instanceof Instant) { 589 objectsToAdd.add(object); 590 591 } else if (object instanceof TemporalAccessor) { 592 // TODO 593// DateTimeAccessor dt = (DateTimeAccessor) object; 594// objectsToAdd.add(dt.extract(LocalDate.class)); 595// objectsToAdd.add(dt.extract(LocalTime.class)); 596// objectsToAdd.add(dt.extract(ZoneId.class)); 597// objectsToAdd.add(dt.extract(Chrono.class)); 598 } 599 } 600 for (Object object : objectsToAdd) { 601 if (object != null) { 602 addCalendrical(object); 603 } 604 } 605 } 606 607 //----------------------------------------------------------------------- 608 @Override 609 public <R> R query(TemporalQuery<R> query) { 610 if (query == TemporalQueries.zoneId()) { 611 R zone = extract(ZoneId.class); 612 if (zone == null) { 613 zone = extract(ZoneOffset.class); 614 if (zone == null && standardFields.containsKey(OFFSET_SECONDS)) { 615 zone = (R) ZoneOffset.from(this); 616 } 617 } 618 return zone; 619 } 620 if (query == TemporalQueries.chrono()) { 621 return extract(Chrono.class); 622 } 623 // incomplete, so no need to handle TIME_PRECISION 624 return super.query(query); 625 } 626 627 @SuppressWarnings("unchecked") 628 public <R> R extract(Class<?> type) { 629 R result = null; 630 for (Object obj : objects) { 631 if (type.isInstance(obj)) { 632 if (result != null && result.equals(obj) == false) { 633 throw new DateTimeException("Conflict found: " + type.getSimpleName() + " differs " + result + " vs " + obj + ": " + this); 634 } 635 result = (R) obj; 636 } 637 } 638 return result; 639 } 640 641 //----------------------------------------------------------------------- 642 /** 643 * Builds the specified type from the values in this builder. 644 * <p> 645 * This attempts to build the specified type from this builder. 646 * If the builder cannot return the type, an exception is thrown. 647 * 648 * @param <R> the type to return 649 * @param type the type to invoke {@code from} on, not null 650 * @return the extracted value, not null 651 * @throws DateTimeException if an error occurs 652 */ 653 public <R> R build(Class<R> type) { 654 return invokeFrom(type, this); 655 } 656 657 /** 658 * Invokes the {@code from(DateTime)} method of a class. 659 * <p> 660 * This calls the {@code from} method with the specified date-time object. 661 * The from method will extract an object of the specified type if it can, 662 * 663 * @param <R> the type to return 664 * @param type the type to invoke {@code from} on, not null 665 * @param temporal the date-time to pass as the argument, not null 666 * @return the value returned from the {@code from} method, not null 667 * @throws DateTimeException if an error occurs 668 */ 669 private static <R> R invokeFrom(Class<R> type, TemporalAccessor temporal) { 670 try { 671 Method m = type.getDeclaredMethod("from", TemporalAccessor.class); 672 return type.cast(m.invoke(null, temporal)); 673 } catch (ReflectiveOperationException ex) { 674 if (ex.getCause() instanceof DateTimeException == false) { 675 throw new DateTimeException("Unable to invoke method from(DateTime)", ex); 676 } 677 throw (DateTimeException) ex.getCause(); 678 } 679 } 680 681 //----------------------------------------------------------------------- 682 /** 683 * Clones this builder, creating a new independent copy referring to the 684 * same map of fields and objects. 685 * 686 * @return the cloned builder, not null 687 */ 688 @Override 689 public DateTimeBuilder clone() { 690 DateTimeBuilder dtb = new DateTimeBuilder(); 691 dtb.objects.addAll(this.objects); 692 dtb.standardFields.putAll(this.standardFields); 693 dtb.standardFields.putAll(this.standardFields); 694 if (this.otherFields != null) { 695 dtb.otherFields.putAll(this.otherFields); 696 } 697 return dtb; 698 } 699 700 //----------------------------------------------------------------------- 701 @Override 702 public String toString() { 703 StringBuilder buf = new StringBuilder(128); 704 buf.append("DateTimeBuilder["); 705 Map<TemporalField, Long> fields = getFieldValueMap(); 706 if (fields.size() > 0) { 707 buf.append("fields=").append(fields); 708 } 709 if (objects.size() > 0) { 710 if (fields.size() > 0) { 711 buf.append(", "); 712 } 713 buf.append("objects=").append(objects); 714 } 715 buf.append(']'); 716 return buf.toString(); 717 } 718 719 //----------------------------------------------------------------------- 720 @Override 721 public boolean isSupported(TemporalField field) { 722 return field != null && containsFieldValue(field); 723 } 724 725 @Override 726 public long getLong(TemporalField field) { 727 return getFieldValue(field); 728 } 729 730}