8024834: Better return type for TemporalField resolve
Summary: Allow resolve method to return more than just ChronoLocalDate
Reviewed-by: sherman
Contributed-by: scolebourne@joda.org
--- a/jdk/src/share/classes/java/time/format/Parsed.java Sat Sep 14 22:50:40 2013 +0100
+++ b/jdk/src/share/classes/java/time/format/Parsed.java Sat Sep 14 22:54:38 2013 +0100
@@ -83,6 +83,8 @@
import java.time.Period;
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.chrono.ChronoZonedDateTime;
import java.time.chrono.Chronology;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
@@ -260,11 +262,34 @@
while (changedCount < 50) {
for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
TemporalField targetField = entry.getKey();
- ChronoLocalDate resolvedDate = targetField.resolve(fieldValues, chrono, zone, resolverStyle);
- if (resolvedDate != null) {
- updateCheckConflict(resolvedDate);
- changedCount++;
- continue outer; // have to restart to avoid concurrent modification
+ TemporalAccessor resolvedObject = targetField.resolve(fieldValues, chrono, zone, resolverStyle);
+ if (resolvedObject != null) {
+ if (resolvedObject instanceof ChronoZonedDateTime) {
+ ChronoZonedDateTime czdt = (ChronoZonedDateTime) resolvedObject;
+ if (zone.equals(czdt.getZone()) == false) {
+ throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone);
+ }
+ resolvedObject = czdt.toLocalDateTime();
+ }
+ if (resolvedObject instanceof ChronoLocalDateTime) {
+ ChronoLocalDateTime cldt = (ChronoLocalDateTime) resolvedObject;
+ updateCheckConflict(cldt.toLocalTime(), Period.ZERO);
+ updateCheckConflict(cldt.toLocalDate());
+ changedCount++;
+ continue outer; // have to restart to avoid concurrent modification
+ }
+ if (resolvedObject instanceof ChronoLocalDate) {
+ updateCheckConflict((ChronoLocalDate) resolvedObject);
+ changedCount++;
+ continue outer; // have to restart to avoid concurrent modification
+ }
+ if (resolvedObject instanceof LocalTime) {
+ updateCheckConflict((LocalTime) resolvedObject, Period.ZERO);
+ changedCount++;
+ continue outer; // have to restart to avoid concurrent modification
+ }
+ throw new DateTimeException("Method resolveFields() can only return ChronoZonedDateTime," +
+ "ChronoLocalDateTime, ChronoLocalDate or LocalTime");
} else if (fieldValues.containsKey(targetField) == false) {
changedCount++;
continue outer; // have to restart to avoid concurrent modification
@@ -302,7 +327,10 @@
if (cld != null && date.equals(cld) == false) {
throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld);
}
- } else {
+ } else if (cld != null) {
+ if (chrono.equals(cld.getChronology()) == false) {
+ throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono);
+ }
date = cld;
}
}
--- a/jdk/src/share/classes/java/time/temporal/TemporalField.java Sat Sep 14 22:50:40 2013 +0100
+++ b/jdk/src/share/classes/java/time/temporal/TemporalField.java Sat Sep 14 22:54:38 2013 +0100
@@ -63,7 +63,6 @@
import java.time.DateTimeException;
import java.time.ZoneId;
-import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.format.ResolverStyle;
import java.util.Locale;
@@ -350,6 +349,10 @@
* be acceptable for the date fields to be resolved into other {@code ChronoField}
* instances that can produce a date, such as {@code EPOCH_DAY}.
* <p>
+ * Not all {@code TemporalAccessor} implementations are accepted as return values.
+ * Implementations must accept {@code ChronoLocalDate}, {@code ChronoLocalDateTime},
+ * {@code ChronoZonedDateTime} and {@code LocalTime}.
+ * <p>
* The zone is not normally required for resolution, but is provided for completeness.
* <p>
* The default implementation must return null.
@@ -358,13 +361,13 @@
* @param chronology the effective chronology, not null
* @param zone the effective zone, not null
* @param resolverStyle the requested type of resolve, not null
- * @return the resolved date; null if resolving only changed the map,
- * or no resolve occurred
+ * @return the resolved temporal object; null if resolving only
+ * changed the map, or no resolve occurred
* @throws ArithmeticException if numeric overflow occurs
* @throws DateTimeException if resolving results in an error. This must not be thrown
* by querying a field on the temporal without first checking if it is supported
*/
- default ChronoLocalDate resolve(
+ default TemporalAccessor resolve(
Map<TemporalField, Long> fieldValues, Chronology chronology,
ZoneId zone, ResolverStyle resolverStyle) {
return null;
--- a/jdk/test/java/time/tck/java/time/format/TCKDateTimeParseResolver.java Sat Sep 14 22:50:40 2013 +0100
+++ b/jdk/test/java/time/tck/java/time/format/TCKDateTimeParseResolver.java Sat Sep 14 22:54:38 2013 +0100
@@ -90,20 +90,33 @@
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoField.YEAR_OF_ERA;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.FOREVER;
+import static java.time.temporal.ChronoUnit.NANOS;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.chrono.Chronology;
+import java.time.chrono.ThaiBuddhistChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
+import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields;
+import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.ValueRange;
+import java.util.Map;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -872,4 +885,210 @@
}
}
+ //-----------------------------------------------------------------------
+ @Test
+ public void test_fieldResolvesToLocalTime() {
+ TemporalField field = new TemporalField() {
+ @Override
+ public TemporalUnit getBaseUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalUnit getRangeUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange range() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isDateBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isTimeBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public <R extends Temporal> R adjustInto(R temporal, long newValue) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalAccessor resolve(
+ Map<TemporalField, Long> fieldValues, Chronology chronology,
+ ZoneId zone, ResolverStyle resolverStyle) {
+ return LocalTime.MIDNIGHT.plusNanos(fieldValues.remove(this));
+ }
+ };
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
+ TemporalAccessor accessor = f.parse("1234567890");
+ assertEquals(accessor.query(TemporalQuery.localDate()), null);
+ assertEquals(accessor.query(TemporalQuery.localTime()), LocalTime.of(0, 0, 1, 234_567_890));
+ }
+
+ @Test
+ public void test_fieldResolvesToChronoLocalDateTime() {
+ TemporalField field = new TemporalField() {
+ @Override
+ public TemporalUnit getBaseUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalUnit getRangeUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange range() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isDateBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isTimeBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public <R extends Temporal> R adjustInto(R temporal, long newValue) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalAccessor resolve(
+ Map<TemporalField, Long> fieldValues, Chronology chronology,
+ ZoneId zone, ResolverStyle resolverStyle) {
+ fieldValues.remove(this);
+ return LocalDateTime.of(2010, 6, 30, 12, 30);
+ }
+ };
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
+ TemporalAccessor accessor = f.parse("1234567890");
+ assertEquals(accessor.query(TemporalQuery.localDate()), LocalDate.of(2010, 6, 30));
+ assertEquals(accessor.query(TemporalQuery.localTime()), LocalTime.of(12, 30));
+ }
+
+ @Test(expectedExceptions = DateTimeParseException.class)
+ public void test_fieldResolvesWrongChrono() {
+ TemporalField field = new TemporalField() {
+ @Override
+ public TemporalUnit getBaseUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalUnit getRangeUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange range() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isDateBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isTimeBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public <R extends Temporal> R adjustInto(R temporal, long newValue) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalAccessor resolve(
+ Map<TemporalField, Long> fieldValues, Chronology chronology,
+ ZoneId zone, ResolverStyle resolverStyle) {
+ return ThaiBuddhistChronology.INSTANCE.dateNow();
+ }
+ };
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
+ f.parse("1234567890");
+ }
+
+ @Test(expectedExceptions = DateTimeParseException.class)
+ public void test_fieldResolvesWrongZone() {
+ TemporalField field = new TemporalField() {
+ @Override
+ public TemporalUnit getBaseUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalUnit getRangeUnit() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange range() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isDateBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isTimeBased() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public <R extends Temporal> R adjustInto(R temporal, long newValue) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public TemporalAccessor resolve(
+ Map<TemporalField, Long> fieldValues, Chronology chronology,
+ ZoneId zone, ResolverStyle resolverStyle) {
+ return ZonedDateTime.of(2010, 6, 30, 12, 30, 0, 0, ZoneId.of("Europe/Paris"));
+ }
+ };
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter().withZone(ZoneId.of("Europe/London"));
+ f.parse("1234567890");
+ }
+
}