src/share/classes/java/time/format/DateTimeBuilder.java

Print this page




  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)) {


 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 }


  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)) {


 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 }