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 }