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.ERA;
  78 import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
  79 import static java.time.temporal.ChronoField.HOUR_OF_DAY;
  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.SECOND_OF_DAY;
  90 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
  91 import static java.time.temporal.ChronoField.YEAR;
  92 import static java.time.temporal.ChronoField.YEAR_OF_ERA;
  93 
  94 import java.time.DateTimeException;
  95 import java.time.DayOfWeek;
  96 import java.time.LocalDate;
  97 import java.time.LocalTime;
  98 import java.time.ZoneId;
  99 import java.time.chrono.ChronoLocalDate;
 100 import java.time.chrono.Chronology;
 101 import java.time.chrono.Era;
 102 import java.time.chrono.IsoChronology;
 103 import java.time.chrono.JapaneseChronology;
 104 import java.time.temporal.ChronoField;
 105 import java.time.temporal.ChronoUnit;
 106 import java.time.temporal.Queries;
 107 import java.time.temporal.TemporalAccessor;
 108 import java.time.temporal.TemporalField;
 109 import java.time.temporal.TemporalQuery;
 110 import java.util.EnumMap;
 111 import java.util.HashMap;
 112 import java.util.LinkedHashMap;
 113 import java.util.List;
 114 import java.util.Map;
 115 import java.util.Objects;
 116 
 117 /**
 118  * Builder that can holds date and time fields and related date and time objects.
 119  * <p>
 120  * <b>This class still needs major revision before JDK1.8 ships.</b>
 121  * <p>
 122  * The builder is used to hold onto different elements of date and time.
 123  * It holds two kinds of object:
 124  * <p><ul>
 125  * <li>a {@code Map} from {@link TemporalField} to {@code long} value, where the
 126  *  value may be outside the valid range for the field
 127  * <li>a list of objects, such as {@code Chronology} or {@code ZoneId}
 128  * </ul><p>
 129  *
 130  * <h3>Specification for implementors</h3>
 131  * This class is mutable and not thread-safe.
 132  * It should only be used from a single thread.
 133  *
 134  * @since 1.8
 135  */
 136 final class DateTimeBuilder
 137         implements TemporalAccessor, Cloneable {
 138 
 139     /**
 140      * The map of other fields.
 141      */
 142     private Map<TemporalField, Long> otherFields;
 143     /**
 144      * The map of date-time fields.
 145      */
 146     private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class);
 147     /**
 148      * The chronology.
 149      */
 150     private Chronology chrono;
 151     /**
 152      * The zone.
 153      */
 154     private ZoneId zone;
 155     /**
 156      * The date.
 157      */
 158     private LocalDate date;
 159     /**
 160      * The time.
 161      */
 162     private LocalTime time;
 163 
 164     //-----------------------------------------------------------------------
 165     /**
 166      * Creates an empty instance of the builder.
 167      */
 168     public DateTimeBuilder() {
 169     }
 170 
 171     //-----------------------------------------------------------------------
 172     private Long getFieldValue0(TemporalField field) {
 173         if (field instanceof ChronoField) {
 174             return standardFields.get(field);
 175         } else if (otherFields != null) {
 176             return otherFields.get(field);
 177         }
 178         return null;
 179     }
 180 
 181     /**
 182      * Adds a field-value pair to the builder.
 183      * <p>
 184      * This adds a field to the builder.
 185      * If the field is not already present, then the field-value pair is added to the map.
 186      * If the field is already present and it has the same value as that specified, no action occurs.
 187      * If the field is already present and it has a different value to that specified, then
 188      * an exception is thrown.
 189      *
 190      * @param field  the field to add, not null
 191      * @param value  the value to add, not null
 192      * @return {@code this}, for method chaining
 193      * @throws DateTimeException if the field is already present with a different value
 194      */
 195     DateTimeBuilder addFieldValue(TemporalField field, long value) {
 196         Objects.requireNonNull(field, "field");
 197         Long old = getFieldValue0(field);  // check first for better error message
 198         if (old != null && old.longValue() != value) {
 199             throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
 200         }
 201         return putFieldValue0(field, value);
 202     }
 203 
 204     private DateTimeBuilder putFieldValue0(TemporalField field, long value) {
 205         if (field instanceof ChronoField) {
 206             standardFields.put((ChronoField) field, value);
 207         } else {
 208             if (otherFields == null) {
 209                 otherFields = new LinkedHashMap<TemporalField, Long>();
 210             }
 211             otherFields.put(field, value);
 212         }
 213         return this;
 214     }
 215 
 216     //-----------------------------------------------------------------------
 217     void addObject(Chronology chrono) {
 218         this.chrono = chrono;
 219     }
 220 
 221     void addObject(ZoneId zone) {
 222         this.zone = zone;
 223     }
 224 
 225     void addObject(LocalDate date) {
 226         this.date = date;
 227     }
 228 
 229     void addObject(LocalTime time) {
 230         this.time = time;
 231     }
 232 
 233     //-----------------------------------------------------------------------
 234     /**
 235      * Resolves the builder, evaluating the date and time.
 236      * <p>
 237      * This examines the contents of the builder and resolves it to produce the best
 238      * available date and time, throwing an exception if a problem occurs.
 239      * Calling this method changes the state of the builder.
 240      *
 241      * @return {@code this}, for method chaining
 242      */
 243     DateTimeBuilder resolve() {
 244         // handle standard fields
 245         mergeDate();
 246         mergeTime();
 247         // TODO: cross validate remaining fields?
 248         return this;
 249     }
 250 
 251     private void mergeDate() {
 252         if (standardFields.containsKey(EPOCH_DAY)) {
 253             checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY)));
 254             return;
 255         }
 256 
 257         Era era = null;
 258         if (chrono == IsoChronology.INSTANCE) {
 259             // normalize fields
 260             if (standardFields.containsKey(EPOCH_MONTH)) {
 261                 long em = standardFields.remove(EPOCH_MONTH);
 262                 addFieldValue(MONTH_OF_YEAR, (em % 12) + 1);
 263                 addFieldValue(YEAR, (em / 12) + 1970);
 264             }
 265         } else {
 266             // TODO: revisit EPOCH_MONTH calculation in non-ISO chronology
 267             // Handle EPOCH_MONTH here for non-ISO Chronology
 268             if (standardFields.containsKey(EPOCH_MONTH)) {
 269                 long em = standardFields.remove(EPOCH_MONTH);
 270                 ChronoLocalDate<?> chronoDate = chrono.date(LocalDate.ofEpochDay(0L));
 271                 chronoDate = chronoDate.plus(em, ChronoUnit.MONTHS);
 272                 LocalDate date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 273                 checkDate(date);
 274                 return;
 275             }
 276             List<Era> eras = chrono.eras();
 277             if (!eras.isEmpty()) {
 278                 if (standardFields.containsKey(ERA)) {
 279                     long index = standardFields.remove(ERA);
 280                     era = chrono.eraOf((int) index);
 281                 } else {
 282                     era = eras.get(eras.size() - 1); // current Era
 283                 }
 284                 if (standardFields.containsKey(YEAR_OF_ERA)) {
 285                     Long y = standardFields.remove(YEAR_OF_ERA);
 286                     putFieldValue0(YEAR, y);
 287                 }
 288             }
 289 
 290         }
 291 
 292         // build date
 293         if (standardFields.containsKey(YEAR)) {
 294             if (standardFields.containsKey(MONTH_OF_YEAR)) {
 295                 if (standardFields.containsKey(DAY_OF_MONTH)) {
 296                     int y = Math.toIntExact(standardFields.remove(YEAR));
 297                     int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
 298                     int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH));
 299                     LocalDate date;
 300                     if (chrono == IsoChronology.INSTANCE) {
 301                         date = LocalDate.of(y, moy, dom);
 302                     } else {
 303                         ChronoLocalDate<?> chronoDate;
 304                         if (era == null) {
 305                             chronoDate = chrono.date(y, moy, dom);
 306                         } else {
 307                             chronoDate = era.date(y, moy, dom);
 308                         }
 309                         date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 310                     }
 311                     checkDate(date);
 312                     return;
 313                 }
 314                 if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) {
 315                     if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
 316                         int y = Math.toIntExact(standardFields.remove(YEAR));
 317                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
 318                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
 319                         int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH));
 320                         LocalDate date;
 321                         if (chrono == IsoChronology.INSTANCE) {
 322                             date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1));
 323                         } else {
 324                             ChronoLocalDate<?> chronoDate;
 325                             if (era == null) {
 326                                 chronoDate = chrono.date(y, moy, 1);
 327                             } else {
 328                                 chronoDate = era.date(y, moy, 1);
 329                             }
 330                             chronoDate = chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
 331                             date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 332                         }
 333                         checkDate(date);
 334                         return;
 335                     }
 336                     if (standardFields.containsKey(DAY_OF_WEEK)) {
 337                         int y = Math.toIntExact(standardFields.remove(YEAR));
 338                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
 339                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
 340                         int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK));
 341                         LocalDate date;
 342                         if (chrono == IsoChronology.INSTANCE) {
 343                             date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)));
 344                         } else {
 345                             ChronoLocalDate<?> chronoDate;
 346                             if (era == null) {
 347                                 chronoDate = chrono.date(y, moy, 1);
 348                             } else {
 349                                 chronoDate = era.date(y, moy, 1);
 350                             }
 351                             chronoDate = chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
 352                             date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 353                         }
 354                         checkDate(date);
 355                         return;
 356                     }
 357                 }
 358             }
 359             if (standardFields.containsKey(DAY_OF_YEAR)) {
 360                 int y = Math.toIntExact(standardFields.remove(YEAR));
 361                 int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR));
 362                 LocalDate date;
 363                 if (chrono == IsoChronology.INSTANCE) {
 364                     date = LocalDate.ofYearDay(y, doy);
 365                 } else {
 366                     ChronoLocalDate<?> chronoDate;
 367                     if (era == null) {
 368                         chronoDate = chrono.dateYearDay(y, doy);
 369                     } else {
 370                         chronoDate = era.dateYearDay(y, doy);
 371                     }
 372                     date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 373                 }
 374                 checkDate(date);
 375                 return;
 376             }
 377             if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) {
 378                 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
 379                     int y = Math.toIntExact(standardFields.remove(YEAR));
 380                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
 381                     int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR));
 382                     LocalDate date;
 383                     if (chrono == IsoChronology.INSTANCE) {
 384                         date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1));
 385                     } else {
 386                         ChronoLocalDate<?> chronoDate;
 387                         if (era == null) {
 388                             chronoDate = chrono.dateYearDay(y, 1);
 389                         } else {
 390                             chronoDate = era.dateYearDay(y, 1);
 391                         }
 392                         chronoDate = chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
 393                         date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 394                     }
 395                     checkDate(date);
 396                     return;
 397                 }
 398                 if (standardFields.containsKey(DAY_OF_WEEK)) {
 399                     int y = Math.toIntExact(standardFields.remove(YEAR));
 400                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
 401                     int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK));
 402                     LocalDate date;
 403                     if (chrono == IsoChronology.INSTANCE) {
 404                         date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)));
 405                     } else {
 406                         ChronoLocalDate<?> chronoDate;
 407                         if (era == null) {
 408                             chronoDate = chrono.dateYearDay(y, 1);
 409                         } else {
 410                             chronoDate = era.dateYearDay(y, 1);
 411                         }
 412                         chronoDate = chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
 413                         date = LocalDate.ofEpochDay(chronoDate.toEpochDay());
 414                     }
 415                     checkDate(date);
 416                     return;
 417                 }
 418             }
 419         }
 420     }
 421 
 422     private void checkDate(LocalDate date) {
 423         addObject(date);
 424         for (ChronoField field : standardFields.keySet()) {
 425             long val1;
 426             try {
 427                 val1 = date.getLong(field);
 428             } catch (DateTimeException ex) {
 429                 continue;
 430             }
 431             Long val2 = standardFields.get(field);
 432             if (val1 != val2) {
 433                 throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
 434             }
 435         }
 436     }
 437 
 438     private void mergeTime() {
 439         if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) {
 440             long ch = standardFields.remove(CLOCK_HOUR_OF_DAY);
 441             addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch);
 442         }
 443         if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) {
 444             long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM);
 445             addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch);
 446         }
 447         if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) {
 448             long ap = standardFields.remove(AMPM_OF_DAY);
 449             long hap = standardFields.remove(HOUR_OF_AMPM);
 450             addFieldValue(HOUR_OF_DAY, ap * 12 + hap);
 451         }
 452 //        if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) {
 453 //            long hod = timeFields.remove(HOUR_OF_DAY);
 454 //            long moh = timeFields.remove(MINUTE_OF_HOUR);
 455 //            addFieldValue(MINUTE_OF_DAY, hod * 60 + moh);
 456 //        }
 457 //        if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) {
 458 //            long mod = timeFields.remove(MINUTE_OF_DAY);
 459 //            long som = timeFields.remove(SECOND_OF_MINUTE);
 460 //            addFieldValue(SECOND_OF_DAY, mod * 60 + som);
 461 //        }
 462         if (standardFields.containsKey(NANO_OF_DAY)) {
 463             long nod = standardFields.remove(NANO_OF_DAY);
 464             addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L);
 465             addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
 466         }
 467         if (standardFields.containsKey(MICRO_OF_DAY)) {
 468             long cod = standardFields.remove(MICRO_OF_DAY);
 469             addFieldValue(SECOND_OF_DAY, cod / 1000_000L);
 470             addFieldValue(MICRO_OF_SECOND, cod % 1000_000L);
 471         }
 472         if (standardFields.containsKey(MILLI_OF_DAY)) {
 473             long lod = standardFields.remove(MILLI_OF_DAY);
 474             addFieldValue(SECOND_OF_DAY, lod / 1000);
 475             addFieldValue(MILLI_OF_SECOND, lod % 1000);
 476         }
 477         if (standardFields.containsKey(SECOND_OF_DAY)) {
 478             long sod = standardFields.remove(SECOND_OF_DAY);
 479             addFieldValue(HOUR_OF_DAY, sod / 3600);
 480             addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
 481             addFieldValue(SECOND_OF_MINUTE, sod % 60);
 482         }
 483         if (standardFields.containsKey(MINUTE_OF_DAY)) {
 484             long mod = standardFields.remove(MINUTE_OF_DAY);
 485             addFieldValue(HOUR_OF_DAY, mod / 60);
 486             addFieldValue(MINUTE_OF_HOUR, mod % 60);
 487         }
 488 
 489 //            long sod = nod / 1000_000_000L;
 490 //            addFieldValue(HOUR_OF_DAY, sod / 3600);
 491 //            addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
 492 //            addFieldValue(SECOND_OF_MINUTE, sod % 60);
 493 //            addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
 494         if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) {
 495             long los = standardFields.remove(MILLI_OF_SECOND);
 496             long cos = standardFields.get(MICRO_OF_SECOND);
 497             addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000));
 498         }
 499 
 500         Long hod = standardFields.get(HOUR_OF_DAY);
 501         Long moh = standardFields.get(MINUTE_OF_HOUR);
 502         Long som = standardFields.get(SECOND_OF_MINUTE);
 503         Long nos = standardFields.get(NANO_OF_SECOND);
 504         if (hod != null) {
 505             int hodVal = Math.toIntExact(hod);
 506             if (moh != null) {
 507                 int mohVal = Math.toIntExact(moh);
 508                 if (som != null) {
 509                     int somVal = Math.toIntExact(som);
 510                     if (nos != null) {
 511                         int nosVal = Math.toIntExact(nos);
 512                         addObject(LocalTime.of(hodVal, mohVal, somVal, nosVal));
 513                     } else {
 514                         addObject(LocalTime.of(hodVal, mohVal, somVal));
 515                     }
 516                 } else {
 517                     addObject(LocalTime.of(hodVal, mohVal));
 518                 }
 519             } else {
 520                 addObject(LocalTime.of(hodVal, 0));
 521             }
 522         }
 523     }
 524 
 525     //-----------------------------------------------------------------------
 526     @Override
 527     public boolean isSupported(TemporalField field) {
 528         if (field == null) {
 529             return false;
 530         }
 531         return standardFields.containsKey(field) ||
 532                 (otherFields != null && otherFields.containsKey(field)) ||
 533                 (date != null && date.isSupported(field)) ||
 534                 (time != null && time.isSupported(field));
 535     }
 536 
 537     @Override
 538     public long getLong(TemporalField field) {
 539         Objects.requireNonNull(field, "field");
 540         Long value = getFieldValue0(field);
 541         if (value == null) {
 542             if (date != null && date.isSupported(field)) {
 543                 return date.getLong(field);
 544             }
 545             if (time != null && time.isSupported(field)) {
 546                 return time.getLong(field);
 547             }
 548             throw new DateTimeException("Field not found: " + field);
 549         }
 550         return value;
 551     }
 552 
 553     @SuppressWarnings("unchecked")
 554     @Override
 555     public <R> R query(TemporalQuery<R> query) {
 556         if (query == Queries.zoneId()) {
 557             return (R) zone;
 558         } else if (query == Queries.chronology()) {
 559             return (R) chrono;
 560         } else if (query == Queries.localDate()) {
 561             return (R) date;
 562         } else if (query == Queries.localTime()) {
 563             return (R) time;
 564         } else if (query == Queries.zone() || query == Queries.offset()) {
 565             return query.queryFrom(this);
 566         } else if (query == Queries.precision()) {
 567             return null;  // not a complete date/time
 568         }
 569         // inline TemporalAccessor.super.query(query) as an optimization
 570         // non-JDK classes are not permitted to make this optimization
 571         return query.queryFrom(this);
 572     }
 573 
 574     //-----------------------------------------------------------------------
 575     @Override
 576     public String toString() {
 577         StringBuilder buf = new StringBuilder(128);
 578         buf.append("DateTimeBuilder[");
 579         Map<TemporalField, Long> fields = new HashMap<>();
 580         fields.putAll(standardFields);
 581         if (otherFields != null) {
 582             fields.putAll(otherFields);
 583         }
 584         if (fields.size() > 0) {
 585             buf.append("fields=").append(fields);
 586         }
 587         buf.append(", ").append(chrono);
 588         buf.append(", ").append(zone);
 589         buf.append(", ").append(date);
 590         buf.append(", ").append(time);
 591         buf.append(']');
 592         return buf.toString();
 593     }
 594 
 595 }