jdk/src/share/classes/java/time/format/DateTimeBuilder.java
changeset 15658 55b829ca2334
parent 15289 3ac550392e43
equal deleted inserted replaced
15657:c588664d547e 15658:55b829ca2334
    72 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
    72 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
    73 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
    73 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
    74 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
    74 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
    75 import static java.time.temporal.ChronoField.EPOCH_DAY;
    75 import static java.time.temporal.ChronoField.EPOCH_DAY;
    76 import static java.time.temporal.ChronoField.EPOCH_MONTH;
    76 import static java.time.temporal.ChronoField.EPOCH_MONTH;
       
    77 import static java.time.temporal.ChronoField.ERA;
    77 import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
    78 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.HOUR_OF_DAY;
    79 import static java.time.temporal.ChronoField.INSTANT_SECONDS;
       
    80 import static java.time.temporal.ChronoField.MICRO_OF_DAY;
    80 import static java.time.temporal.ChronoField.MICRO_OF_DAY;
    81 import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
    81 import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
    82 import static java.time.temporal.ChronoField.MILLI_OF_DAY;
    82 import static java.time.temporal.ChronoField.MILLI_OF_DAY;
    83 import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
    83 import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
    84 import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
    84 import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
    85 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
    85 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
    86 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
    86 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
    87 import static java.time.temporal.ChronoField.NANO_OF_DAY;
    87 import static java.time.temporal.ChronoField.NANO_OF_DAY;
    88 import static java.time.temporal.ChronoField.NANO_OF_SECOND;
    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;
    89 import static java.time.temporal.ChronoField.SECOND_OF_DAY;
    91 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
    90 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
    92 import static java.time.temporal.ChronoField.YEAR;
    91 import static java.time.temporal.ChronoField.YEAR;
       
    92 import static java.time.temporal.ChronoField.YEAR_OF_ERA;
    93 
    93 
    94 import java.time.DateTimeException;
    94 import java.time.DateTimeException;
    95 import java.time.DayOfWeek;
    95 import java.time.DayOfWeek;
    96 import java.time.Instant;
       
    97 import java.time.LocalDate;
    96 import java.time.LocalDate;
    98 import java.time.LocalTime;
    97 import java.time.LocalTime;
    99 import java.time.ZoneId;
    98 import java.time.ZoneId;
   100 import java.time.ZoneOffset;
    99 import java.time.chrono.ChronoLocalDate;
   101 import java.time.temporal.Chrono;
   100 import java.time.chrono.Chronology;
       
   101 import java.time.chrono.Era;
       
   102 import java.time.chrono.IsoChronology;
       
   103 import java.time.chrono.JapaneseChronology;
   102 import java.time.temporal.ChronoField;
   104 import java.time.temporal.ChronoField;
       
   105 import java.time.temporal.ChronoUnit;
   103 import java.time.temporal.Queries;
   106 import java.time.temporal.Queries;
   104 import java.time.temporal.TemporalAccessor;
   107 import java.time.temporal.TemporalAccessor;
   105 import java.time.temporal.TemporalField;
   108 import java.time.temporal.TemporalField;
   106 import java.time.temporal.TemporalQuery;
   109 import java.time.temporal.TemporalQuery;
   107 import java.util.ArrayList;
       
   108 import java.util.EnumMap;
   110 import java.util.EnumMap;
   109 import java.util.HashMap;
   111 import java.util.HashMap;
   110 import java.util.HashSet;
       
   111 import java.util.LinkedHashMap;
   112 import java.util.LinkedHashMap;
   112 import java.util.List;
   113 import java.util.List;
   113 import java.util.Map;
   114 import java.util.Map;
   114 import java.util.Map.Entry;
       
   115 import java.util.Objects;
   115 import java.util.Objects;
   116 import java.util.Set;
       
   117 
   116 
   118 /**
   117 /**
   119  * Builder that can holds date and time fields and related date and time objects.
   118  * Builder that can holds date and time fields and related date and time objects.
   120  * <p>
   119  * <p>
   121  * <b>This class still needs major revision before JDK1.8 ships.</b>
   120  * <b>This class still needs major revision before JDK1.8 ships.</b>
   122  * <p>
   121  * <p>
   123  * The builder is used to hold onto different elements of date and time.
   122  * The builder is used to hold onto different elements of date and time.
   124  * It is designed as two separate maps:
   123  * It holds two kinds of object:
   125  * <p><ul>
   124  * <p><ul>
   126  * <li>from {@link java.time.temporal.TemporalField} to {@code long} value, where the value may be
   125  * <li>a {@code Map} from {@link TemporalField} to {@code long} value, where the
   127  * outside the valid range for the field
   126  *  value may be outside the valid range for the field
   128  * <li>from {@code Class} to {@link java.time.temporal.TemporalAccessor}, holding larger scale objects
   127  * <li>a list of objects, such as {@code Chronology} or {@code ZoneId}
   129  * like {@code LocalDateTime}.
       
   130  * </ul><p>
   128  * </ul><p>
   131  *
   129  *
   132  * <h3>Specification for implementors</h3>
   130  * <h3>Specification for implementors</h3>
   133  * This class is mutable and not thread-safe.
   131  * This class is mutable and not thread-safe.
   134  * It should only be used from a single thread.
   132  * It should only be used from a single thread.
   135  *
   133  *
   136  * @since 1.8
   134  * @since 1.8
   137  */
   135  */
   138 public final class DateTimeBuilder
   136 final class DateTimeBuilder
   139         implements TemporalAccessor, Cloneable {
   137         implements TemporalAccessor, Cloneable {
   140 
   138 
   141     /**
   139     /**
   142      * The map of other fields.
   140      * The map of other fields.
   143      */
   141      */
   145     /**
   143     /**
   146      * The map of date-time fields.
   144      * The map of date-time fields.
   147      */
   145      */
   148     private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class);
   146     private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class);
   149     /**
   147     /**
   150      * The list of complete date-time objects.
   148      * The chronology.
   151      */
   149      */
   152     private final List<Object> objects = new ArrayList<>(2);
   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;
   153 
   163 
   154     //-----------------------------------------------------------------------
   164     //-----------------------------------------------------------------------
   155     /**
   165     /**
   156      * Creates an empty instance of the builder.
   166      * Creates an empty instance of the builder.
   157      */
   167      */
   158     public DateTimeBuilder() {
   168     public DateTimeBuilder() {
   159     }
   169     }
   160 
   170 
   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     //-----------------------------------------------------------------------
   171     //-----------------------------------------------------------------------
   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) {
   172     private Long getFieldValue0(TemporalField field) {
   230         if (field instanceof ChronoField) {
   173         if (field instanceof ChronoField) {
   231             return standardFields.get(field);
   174             return standardFields.get(field);
   232         } else if (otherFields != null) {
   175         } else if (otherFields != null) {
   233             return otherFields.get(field);
   176             return otherFields.get(field);
   234         }
   177         }
   235         return null;
   178         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     }
   179     }
   249 
   180 
   250     /**
   181     /**
   251      * Adds a field-value pair to the builder.
   182      * Adds a field-value pair to the builder.
   252      * <p>
   183      * <p>
   259      * @param field  the field to add, not null
   190      * @param field  the field to add, not null
   260      * @param value  the value to add, not null
   191      * @param value  the value to add, not null
   261      * @return {@code this}, for method chaining
   192      * @return {@code this}, for method chaining
   262      * @throws DateTimeException if the field is already present with a different value
   193      * @throws DateTimeException if the field is already present with a different value
   263      */
   194      */
   264     public DateTimeBuilder addFieldValue(TemporalField field, long value) {
   195     DateTimeBuilder addFieldValue(TemporalField field, long value) {
   265         Objects.requireNonNull(field, "field");
   196         Objects.requireNonNull(field, "field");
   266         Long old = getFieldValue0(field);  // check first for better error message
   197         Long old = getFieldValue0(field);  // check first for better error message
   267         if (old != null && old.longValue() != value) {
   198         if (old != null && old.longValue() != value) {
   268             throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
   199             throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
   269         }
   200         }
   280             otherFields.put(field, value);
   211             otherFields.put(field, value);
   281         }
   212         }
   282         return this;
   213         return this;
   283     }
   214     }
   284 
   215 
   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     //-----------------------------------------------------------------------
   216     //-----------------------------------------------------------------------
   310     /**
   217     void addObject(Chronology chrono) {
   311      * Removes a list of fields from the builder.
   218         this.chrono = chrono;
   312      * <p>
   219     }
   313      * This removes the specified fields from the builder.
   220 
   314      * No exception is thrown if the fields are not present.
   221     void addObject(ZoneId zone) {
   315      *
   222         this.zone = zone;
   316      * @param fields  the fields to remove, not null
   223     }
   317      */
   224 
   318     public void removeFieldValues(TemporalField... fields) {
   225     void addObject(LocalDate date) {
   319         for (TemporalField field : fields) {
   226         this.date = date;
   320             if (field instanceof ChronoField) {
   227     }
   321                 standardFields.remove(field);
   228 
   322             } else if (otherFields != null) {
   229     void addObject(LocalTime time) {
   323                 otherFields.remove(field);
   230         this.time = time;
   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     }
   231     }
   405 
   232 
   406     //-----------------------------------------------------------------------
   233     //-----------------------------------------------------------------------
   407     /**
   234     /**
   408      * Resolves the builder, evaluating the date and time.
   235      * Resolves the builder, evaluating the date and time.
   411      * available date and time, throwing an exception if a problem occurs.
   238      * available date and time, throwing an exception if a problem occurs.
   412      * Calling this method changes the state of the builder.
   239      * Calling this method changes the state of the builder.
   413      *
   240      *
   414      * @return {@code this}, for method chaining
   241      * @return {@code this}, for method chaining
   415      */
   242      */
   416     public DateTimeBuilder resolve() {
   243     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
   244         // handle standard fields
   432         mergeDate();
   245         mergeDate();
   433         mergeTime();
   246         mergeTime();
   434         // TODO: cross validate remaining fields?
   247         // TODO: cross validate remaining fields?
   435         return this;
   248         return this;
   439         if (standardFields.containsKey(EPOCH_DAY)) {
   252         if (standardFields.containsKey(EPOCH_DAY)) {
   440             checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY)));
   253             checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY)));
   441             return;
   254             return;
   442         }
   255         }
   443 
   256 
   444         // normalize fields
   257         Era era = null;
   445         if (standardFields.containsKey(EPOCH_MONTH)) {
   258         if (chrono == IsoChronology.INSTANCE) {
   446             long em = standardFields.remove(EPOCH_MONTH);
   259             // normalize fields
   447             addFieldValue(MONTH_OF_YEAR, (em % 12) + 1);
   260             if (standardFields.containsKey(EPOCH_MONTH)) {
   448             addFieldValue(YEAR, (em / 12) + 1970);
   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 
   449         }
   290         }
   450 
   291 
   451         // build date
   292         // build date
   452         if (standardFields.containsKey(YEAR)) {
   293         if (standardFields.containsKey(YEAR)) {
   453             if (standardFields.containsKey(MONTH_OF_YEAR)) {
   294             if (standardFields.containsKey(MONTH_OF_YEAR)) {
   454                 if (standardFields.containsKey(DAY_OF_MONTH)) {
   295                 if (standardFields.containsKey(DAY_OF_MONTH)) {
   455                     int y = Math.toIntExact(standardFields.remove(YEAR));
   296                     int y = Math.toIntExact(standardFields.remove(YEAR));
   456                     int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   297                     int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   457                     int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH));
   298                     int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH));
   458                     checkDate(LocalDate.of(y, moy, dom));
   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);
   459                     return;
   312                     return;
   460                 }
   313                 }
   461                 if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) {
   314                 if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) {
   462                     if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
   315                     if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
   463                         int y = Math.toIntExact(standardFields.remove(YEAR));
   316                         int y = Math.toIntExact(standardFields.remove(YEAR));
   464                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   317                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   465                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
   318                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
   466                         int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH));
   319                         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)));
   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);
   468                         return;
   334                         return;
   469                     }
   335                     }
   470                     if (standardFields.containsKey(DAY_OF_WEEK)) {
   336                     if (standardFields.containsKey(DAY_OF_WEEK)) {
   471                         int y = Math.toIntExact(standardFields.remove(YEAR));
   337                         int y = Math.toIntExact(standardFields.remove(YEAR));
   472                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   338                         int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR));
   473                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
   339                         int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
   474                         int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK));
   340                         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))));
   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);
   476                         return;
   355                         return;
   477                     }
   356                     }
   478                 }
   357                 }
   479             }
   358             }
   480             if (standardFields.containsKey(DAY_OF_YEAR)) {
   359             if (standardFields.containsKey(DAY_OF_YEAR)) {
   481                 int y = Math.toIntExact(standardFields.remove(YEAR));
   360                 int y = Math.toIntExact(standardFields.remove(YEAR));
   482                 int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR));
   361                 int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR));
   483                 checkDate(LocalDate.ofYearDay(y, doy));
   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);
   484                 return;
   375                 return;
   485             }
   376             }
   486             if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) {
   377             if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) {
   487                 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
   378                 if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
   488                     int y = Math.toIntExact(standardFields.remove(YEAR));
   379                     int y = Math.toIntExact(standardFields.remove(YEAR));
   489                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
   380                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
   490                     int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR));
   381                     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)));
   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);
   492                     return;
   396                     return;
   493                 }
   397                 }
   494                 if (standardFields.containsKey(DAY_OF_WEEK)) {
   398                 if (standardFields.containsKey(DAY_OF_WEEK)) {
   495                     int y = Math.toIntExact(standardFields.remove(YEAR));
   399                     int y = Math.toIntExact(standardFields.remove(YEAR));
   496                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
   400                     int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
   497                     int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK));
   401                     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))));
   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);
   499                     return;
   416                     return;
   500                 }
   417                 }
   501             }
   418             }
   502         }
   419         }
   503     }
   420     }
   504 
   421 
   505     private void checkDate(LocalDate date) {
   422     private void checkDate(LocalDate date) {
   506         // TODO: this doesn't handle aligned weeks over into next month which would otherwise be valid
   423         addObject(date);
   507 
       
   508         addCalendrical(date);
       
   509         for (ChronoField field : standardFields.keySet()) {
   424         for (ChronoField field : standardFields.keySet()) {
   510             long val1;
   425             long val1;
   511             try {
   426             try {
   512                 val1 = date.getLong(field);
   427                 val1 = date.getLong(field);
   513             } catch (DateTimeException ex) {
   428             } catch (DateTimeException ex) {
   592                 int mohVal = Math.toIntExact(moh);
   507                 int mohVal = Math.toIntExact(moh);
   593                 if (som != null) {
   508                 if (som != null) {
   594                     int somVal = Math.toIntExact(som);
   509                     int somVal = Math.toIntExact(som);
   595                     if (nos != null) {
   510                     if (nos != null) {
   596                         int nosVal = Math.toIntExact(nos);
   511                         int nosVal = Math.toIntExact(nos);
   597                         addCalendrical(LocalTime.of(hodVal, mohVal, somVal, nosVal));
   512                         addObject(LocalTime.of(hodVal, mohVal, somVal, nosVal));
   598                     } else {
   513                     } else {
   599                         addCalendrical(LocalTime.of(hodVal, mohVal, somVal));
   514                         addObject(LocalTime.of(hodVal, mohVal, somVal));
   600                     }
   515                     }
   601                 } else {
   516                 } else {
   602                     addCalendrical(LocalTime.of(hodVal, mohVal));
   517                     addObject(LocalTime.of(hodVal, mohVal));
   603                 }
   518                 }
   604             } else {
   519             } else {
   605                 addCalendrical(LocalTime.of(hodVal, 0));
   520                 addObject(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             }
   521             }
   633         }
   522         }
   634     }
   523     }
   635 
   524 
   636     //-----------------------------------------------------------------------
   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")
   637     @Override
   554     @Override
   638     public <R> R query(TemporalQuery<R> query) {
   555     public <R> R query(TemporalQuery<R> query) {
   639         if (query == Queries.zoneId()) {
   556         if (query == Queries.zoneId()) {
   640             return (R) extract(ZoneId.class);
   557             return (R) zone;
   641         }
   558         } else if (query == Queries.chronology()) {
   642         if (query == Queries.offset()) {
   559             return (R) chrono;
   643             ZoneOffset offset = extract(ZoneOffset.class);
   560         } else if (query == Queries.localDate()) {
   644             if (offset == null && standardFields.containsKey(OFFSET_SECONDS)) {
   561             return (R) date;
   645                 offset = ZoneOffset.ofTotalSeconds(Math.toIntExact(standardFields.get(OFFSET_SECONDS)));
   562         } else if (query == Queries.localTime()) {
   646             }
   563             return (R) time;
   647             return (R) offset;
   564         } else if (query == Queries.zone() || query == Queries.offset()) {
   648         }
   565             return query.queryFrom(this);
   649         if (query == Queries.chrono()) {
   566         } else if (query == Queries.precision()) {
   650             return extract(Chrono.class);
   567             return null;  // not a complete date/time
   651         }
   568         }
   652         // incomplete, so no need to handle PRECISION
   569         // inline TemporalAccessor.super.query(query) as an optimization
   653         return TemporalAccessor.super.query(query);
   570         // non-JDK classes are not permitted to make this optimization
   654     }
   571         return query.queryFrom(this);
   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     }
   572     }
   688 
   573 
   689     //-----------------------------------------------------------------------
   574     //-----------------------------------------------------------------------
   690     @Override
   575     @Override
   691     public String toString() {
   576     public String toString() {
   692         StringBuilder buf = new StringBuilder(128);
   577         StringBuilder buf = new StringBuilder(128);
   693         buf.append("DateTimeBuilder[");
   578         buf.append("DateTimeBuilder[");
   694         Map<TemporalField, Long> fields = getFieldValueMap();
   579         Map<TemporalField, Long> fields = new HashMap<>();
       
   580         fields.putAll(standardFields);
       
   581         if (otherFields != null) {
       
   582             fields.putAll(otherFields);
       
   583         }
   695         if (fields.size() > 0) {
   584         if (fields.size() > 0) {
   696             buf.append("fields=").append(fields);
   585             buf.append("fields=").append(fields);
   697         }
   586         }
   698         if (objects.size() > 0) {
   587         buf.append(", ").append(chrono);
   699             if (fields.size() > 0) {
   588         buf.append(", ").append(zone);
   700                 buf.append(", ");
   589         buf.append(", ").append(date);
   701             }
   590         buf.append(", ").append(time);
   702             buf.append("objects=").append(objects);
       
   703         }
       
   704         buf.append(']');
   591         buf.append(']');
   705         return buf.toString();
   592         return buf.toString();
   706     }
   593     }
   707 
   594 
   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 }
   595 }