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> |
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. |
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 } |