8033662: DateTimeFormatter parsing ignores withZone()
authorsherman
Mon, 24 Mar 2014 12:47:02 -0700
changeset 23573 f42dcef4fb7f
parent 23572 0b4b0e183619
child 23574 089eeff81e86
8033662: DateTimeFormatter parsing ignores withZone() Summary: to include the set "zone" in resolved result Reviewed-by: sherman, chegar Contributed-by: scolebourne@joda.org
jdk/src/share/classes/java/time/format/DateTimeFormatter.java
jdk/src/share/classes/java/time/format/DateTimeParseContext.java
jdk/src/share/classes/java/time/format/Parsed.java
jdk/test/java/time/tck/java/time/format/TCKDateTimeParseResolver.java
--- a/jdk/src/share/classes/java/time/format/DateTimeFormatter.java	Mon Mar 24 17:07:08 2014 +0100
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatter.java	Mon Mar 24 12:47:02 2014 -0700
@@ -1932,8 +1932,8 @@
      */
     private TemporalAccessor parseResolved0(final CharSequence text, final ParsePosition position) {
         ParsePosition pos = (position != null ? position : new ParsePosition(0));
-        Parsed unresolved = parseUnresolved0(text, pos);
-        if (unresolved == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
+        DateTimeParseContext context = parseUnresolved0(text, pos);
+        if (context == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
             String abbr;
             if (text.length() > 64) {
                 abbr = text.subSequence(0, 64).toString() + "...";
@@ -1948,7 +1948,7 @@
                         pos.getIndex(), text, pos.getIndex());
             }
         }
-        return unresolved.resolve(resolverStyle, resolverFields);
+        return context.toResolved(resolverStyle, resolverFields);
     }
 
     /**
@@ -1991,10 +1991,14 @@
      * @throws IndexOutOfBoundsException if the position is invalid
      */
     public TemporalAccessor parseUnresolved(CharSequence text, ParsePosition position) {
-        return parseUnresolved0(text, position);
+        DateTimeParseContext context = parseUnresolved0(text, position);
+        if (context == null) {
+            return null;
+        }
+        return context.toUnresolved();
     }
 
-    private Parsed parseUnresolved0(CharSequence text, ParsePosition position) {
+    private DateTimeParseContext parseUnresolved0(CharSequence text, ParsePosition position) {
         Objects.requireNonNull(text, "text");
         Objects.requireNonNull(position, "position");
         DateTimeParseContext context = new DateTimeParseContext(this);
@@ -2005,7 +2009,7 @@
             return null;
         }
         position.setIndex(pos);  // errorIndex not updated from input
-        return context.toParsed();
+        return context;
     }
 
     //-----------------------------------------------------------------------
@@ -2126,23 +2130,23 @@
         @Override
         public Object parseObject(String text, ParsePosition pos) {
             Objects.requireNonNull(text, "text");
-            Parsed unresolved;
+            DateTimeParseContext context;
             try {
-                unresolved = formatter.parseUnresolved0(text, pos);
+                context = formatter.parseUnresolved0(text, pos);
             } catch (IndexOutOfBoundsException ex) {
                 if (pos.getErrorIndex() < 0) {
                     pos.setErrorIndex(0);
                 }
                 return null;
             }
-            if (unresolved == null) {
+            if (context == null) {
                 if (pos.getErrorIndex() < 0) {
                     pos.setErrorIndex(0);
                 }
                 return null;
             }
             try {
-                TemporalAccessor resolved = unresolved.resolve(formatter.resolverStyle, formatter.resolverFields);
+                TemporalAccessor resolved = context.toResolved(formatter.resolverStyle, formatter.resolverFields);
                 if (parseType == null) {
                     return resolved;
                 }
--- a/jdk/src/share/classes/java/time/format/DateTimeParseContext.java	Mon Mar 24 17:07:08 2014 +0100
+++ b/jdk/src/share/classes/java/time/format/DateTimeParseContext.java	Mon Mar 24 12:47:02 2014 -0700
@@ -64,10 +64,12 @@
 import java.time.ZoneId;
 import java.time.chrono.Chronology;
 import java.time.chrono.IsoChronology;
+import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -77,8 +79,8 @@
  * It has the ability to store and retrieve the parsed values and manage optional segments.
  * It also provides key information to the parsing methods.
  * <p>
- * Once parsing is complete, the {@link #toParsed()} is used to obtain the data.
- * It contains a method to resolve  the separate parsed fields into meaningful values.
+ * Once parsing is complete, the {@link #toUnresolved()} is used to obtain the unresolved
+ * result data. The {@link #toResolved()} is used to obtain the resolved result.
  *
  * @implSpec
  * This class is a mutable context intended for use from a single thread.
@@ -309,16 +311,27 @@
     }
 
     /**
-     * Gets the result of the parse.
+     * Gets the unresolved result of the parse.
      *
      * @return the result of the parse, not null
      */
-    Parsed toParsed() {
+    Parsed toUnresolved() {
+        return currentParsed();
+    }
+
+    /**
+     * Gets the resolved result of the parse.
+     *
+     * @return the result of the parse, not null
+     */
+    TemporalAccessor toResolved(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {
         Parsed parsed = currentParsed();
-        parsed.effectiveChrono = getEffectiveChronology();
-        return parsed;
+        parsed.chrono = getEffectiveChronology();
+        parsed.zone = (parsed.zone != null ? parsed.zone : formatter.getZone());
+        return parsed.resolve(resolverStyle, resolverFields);
     }
 
+
     //-----------------------------------------------------------------------
     /**
      * Gets the first value that was parsed for the specified field.
--- a/jdk/src/share/classes/java/time/format/Parsed.java	Mon Mar 24 17:07:08 2014 +0100
+++ b/jdk/src/share/classes/java/time/format/Parsed.java	Mon Mar 24 12:47:02 2014 -0700
@@ -136,10 +136,6 @@
      */
     boolean leapSecond;
     /**
-     * The effective chronology.
-     */
-    Chronology effectiveChrono;
-    /**
      * The resolver style to use.
      */
     private ResolverStyle resolverStyle;
@@ -241,7 +237,6 @@
             fieldValues.keySet().retainAll(resolverFields);
         }
         this.resolverStyle = resolverStyle;
-        chrono = effectiveChrono;
         resolveFields();
         resolveTimeLenient();
         crossCheck();
@@ -266,14 +261,16 @@
                     TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle);
                     if (resolvedObject != null) {
                         if (resolvedObject instanceof ChronoZonedDateTime) {
-                            ChronoZonedDateTime<?> czdt = (ChronoZonedDateTime) resolvedObject;
-                            if (zone.equals(czdt.getZone()) == false) {
+                            ChronoZonedDateTime<?> czdt = (ChronoZonedDateTime<?>) resolvedObject;
+                            if (zone == null) {
+                                zone = czdt.getZone();
+                            } else 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;
+                            ChronoLocalDateTime<?> cldt = (ChronoLocalDateTime<?>) resolvedObject;
                             updateCheckConflict(cldt.toLocalTime(), Period.ZERO);
                             updateCheckConflict(cldt.toLocalDate());
                             changedCount++;
--- a/jdk/test/java/time/tck/java/time/format/TCKDateTimeParseResolver.java	Mon Mar 24 17:07:08 2014 +0100
+++ b/jdk/test/java/time/tck/java/time/format/TCKDateTimeParseResolver.java	Mon Mar 24 12:47:02 2014 -0700
@@ -90,9 +90,6 @@
 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;
 
@@ -102,13 +99,17 @@
 import java.time.Period;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import java.time.chrono.Chronology;
+import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.IsoChronology;
+import java.time.chrono.MinguoChronology;
+import java.time.chrono.MinguoDate;
 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;
@@ -129,6 +130,9 @@
     // TODO: tests with weird TenporalField implementations
     // TODO: tests with non-ISO chronologies
 
+    private static final ZoneId EUROPE_ATHENS = ZoneId.of("Europe/Athens");
+    private static final ZoneId EUROPE_PARIS = ZoneId.of("Europe/Paris");
+
     //-----------------------------------------------------------------------
     @DataProvider(name="resolveOneNoChange")
     Object[][] data_resolveOneNoChange() {
@@ -886,205 +890,273 @@
     }
 
     //-----------------------------------------------------------------------
+    // SPEC: DateTimeFormatter.withChronology()
     @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, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
-                return LocalTime.MIDNIGHT.plusNanos(fieldValues.remove(this));
-            }
-        };
-        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
-        TemporalAccessor accessor = f.parse("1234567890");
-        assertEquals(accessor.query(TemporalQueries.localDate()), null);
-        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.of(0, 0, 1, 234_567_890));
+    public void test_withChronology_noOverride() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).toFormatter();
+        TemporalAccessor accessor = f.parse("");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), IsoChronology.INSTANCE);
+    }
+
+    @Test
+    public void test_withChronology_override() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        TemporalAccessor accessor = f.parse("");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), MinguoChronology.INSTANCE);
+    }
+
+    @Test
+    public void test_withChronology_parsedChronology_noOverride() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).appendChronologyId().toFormatter();
+        TemporalAccessor accessor = f.parse("ThaiBuddhist");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), ThaiBuddhistChronology.INSTANCE);
+    }
+
+    @Test
+    public void test_withChronology_parsedChronology_override() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).appendChronologyId().toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        TemporalAccessor accessor = f.parse("ThaiBuddhist");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), ThaiBuddhistChronology.INSTANCE);
+    }
+
+    //-----------------------------------------------------------------------
+    // SPEC: DateTimeFormatter.withZone()
+    @Test
+    public void test_withZone_noOverride() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).toFormatter();
+        TemporalAccessor accessor = f.parse("");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), null);
     }
 
     @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, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
-                fieldValues.remove(this);
-                return LocalDateTime.of(2010, 6, 30, 12, 30);
-            }
-        };
-        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
+    public void test_withZone_override() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).toFormatter();
+        f = f.withZone(EUROPE_ATHENS);
+        TemporalAccessor accessor = f.parse("");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), EUROPE_ATHENS);
+    }
+
+    @Test
+    public void test_withZone_parsedZone_noOverride() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).appendZoneId().toFormatter();
+        TemporalAccessor accessor = f.parse("Europe/Paris");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), EUROPE_PARIS);
+    }
+
+    @Test
+    public void test_withZone_parsedZone_override() {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().parseDefaulting(EPOCH_DAY, 2).appendZoneId().toFormatter();
+        f = f.withZone(EUROPE_ATHENS);
+        TemporalAccessor accessor = f.parse("Europe/Paris");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(1970, 1, 3));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), EUROPE_PARIS);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_fieldResolvesToLocalTime() {
+        LocalTime lt = LocalTime.of(12, 30, 40);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(lt)).toFormatter();
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), null);
+        assertEquals(accessor.query(TemporalQueries.localTime()), lt);
+    }
+
+    //-------------------------------------------------------------------------
+    @Test
+    public void test_fieldResolvesToChronoLocalDate_noOverrideChrono_matches() {
+        LocalDate ldt = LocalDate.of(2010, 6, 30);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(ldt)).toFormatter();
         TemporalAccessor accessor = f.parse("1234567890");
         assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(2010, 6, 30));
-        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.of(12, 30));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), IsoChronology.INSTANCE);
+    }
+
+    @Test
+    public void test_fieldResolvesToChronoLocalDate_overrideChrono_matches() {
+        MinguoDate mdt = MinguoDate.of(100, 6, 30);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(mdt)).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.from(mdt));
+        assertEquals(accessor.query(TemporalQueries.localTime()), null);
+        assertEquals(accessor.query(TemporalQueries.chronology()), MinguoChronology.INSTANCE);
     }
 
     @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, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
-                return ThaiBuddhistChronology.INSTANCE.dateNow();
-            }
-        };
-        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(field).toFormatter();
+    public void test_fieldResolvesToChronoLocalDate_noOverrideChrono_wrongChrono() {
+        ChronoLocalDate cld = ThaiBuddhistChronology.INSTANCE.dateNow();
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cld)).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, TemporalAccessor partialTemporal, 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"));
+    public void test_fieldResolvesToChronoLocalDate_overrideChrono_wrongChrono() {
+        ChronoLocalDate cld = ThaiBuddhistChronology.INSTANCE.dateNow();
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cld)).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        f.parse("1234567890");
+    }
+
+    //-------------------------------------------------------------------------
+    @Test
+    public void test_fieldResolvesToChronoLocalDateTime_noOverrideChrono_matches() {
+        LocalDateTime ldt = LocalDateTime.of(2010, 6, 30, 12, 30);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(ldt)).toFormatter();
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(2010, 6, 30));
+        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.of(12, 30));
+        assertEquals(accessor.query(TemporalQueries.chronology()), IsoChronology.INSTANCE);
+    }
+
+    @Test
+    public void test_fieldResolvesToChronoLocalDateTime_overrideChrono_matches() {
+        MinguoDate mdt = MinguoDate.of(100, 6, 30);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(mdt.atTime(LocalTime.NOON))).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.from(mdt));
+        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.NOON);
+        assertEquals(accessor.query(TemporalQueries.chronology()), MinguoChronology.INSTANCE);
+    }
+
+    @Test(expectedExceptions = DateTimeParseException.class)
+    public void test_fieldResolvesToChronoLocalDateTime_noOverrideChrono_wrongChrono() {
+        ChronoLocalDateTime<?> cldt = ThaiBuddhistChronology.INSTANCE.dateNow().atTime(LocalTime.NOON);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cldt)).toFormatter();
+        f.parse("1234567890");
+    }
+
+    @Test(expectedExceptions = DateTimeParseException.class)
+    public void test_fieldResolvesToChronoLocalDateTime_overrideChrono_wrongChrono() {
+        ChronoLocalDateTime<?> cldt = ThaiBuddhistChronology.INSTANCE.dateNow().atTime(LocalTime.NOON);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cldt)).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        f.parse("1234567890");
+    }
+
+    //-------------------------------------------------------------------------
+    @Test
+    public void test_fieldResolvesToChronoZonedDateTime_noOverrideChrono_matches() {
+        ZonedDateTime zdt = ZonedDateTime.of(2010, 6, 30, 12, 30, 0, 0, EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(zdt)).toFormatter();
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.of(2010, 6, 30));
+        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.of(12, 30));
+        assertEquals(accessor.query(TemporalQueries.chronology()), IsoChronology.INSTANCE);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), EUROPE_PARIS);
+    }
+
+    @Test
+    public void test_fieldResolvesToChronoZonedDateTime_overrideChrono_matches() {
+        MinguoDate mdt = MinguoDate.of(100, 6, 30);
+        ChronoZonedDateTime<MinguoDate> mzdt = mdt.atTime(LocalTime.NOON).atZone(EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(mzdt)).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        TemporalAccessor accessor = f.parse("1234567890");
+        assertEquals(accessor.query(TemporalQueries.localDate()), LocalDate.from(mdt));
+        assertEquals(accessor.query(TemporalQueries.localTime()), LocalTime.NOON);
+        assertEquals(accessor.query(TemporalQueries.chronology()), MinguoChronology.INSTANCE);
+        assertEquals(accessor.query(TemporalQueries.zoneId()), EUROPE_PARIS);
+    }
+
+    @Test(expectedExceptions = DateTimeParseException.class)
+    public void test_fieldResolvesToChronoZonedDateTime_noOverrideChrono_wrongChrono() {
+        ChronoZonedDateTime<?> cldt = ThaiBuddhistChronology.INSTANCE.dateNow().atTime(LocalTime.NOON).atZone(EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cldt)).toFormatter();
         f.parse("1234567890");
     }
 
+    @Test(expectedExceptions = DateTimeParseException.class)
+    public void test_fieldResolvesToChronoZonedDateTime_overrideChrono_wrongChrono() {
+        ChronoZonedDateTime<?> cldt = ThaiBuddhistChronology.INSTANCE.dateNow().atTime(LocalTime.NOON).atZone(EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(cldt)).toFormatter();
+        f = f.withChronology(MinguoChronology.INSTANCE);
+        f.parse("1234567890");
+    }
+
+    @Test
+    public void test_fieldResolvesToChronoZonedDateTime_overrideZone_matches() {
+        ZonedDateTime zdt = ZonedDateTime.of(2010, 6, 30, 12, 30, 0, 0, EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(zdt)).toFormatter();
+        f = f.withZone(EUROPE_PARIS);
+        assertEquals(f.parse("1234567890", ZonedDateTime::from), zdt);
+    }
+
+    @Test(expectedExceptions = DateTimeParseException.class)
+    public void test_fieldResolvesToChronoZonedDateTime_overrideZone_wrongZone() {
+        ZonedDateTime zdt = ZonedDateTime.of(2010, 6, 30, 12, 30, 0, 0, EUROPE_PARIS);
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(new ResolvingField(zdt)).toFormatter();
+        f = f.withZone(ZoneId.of("Europe/London"));
+        f.parse("1234567890");
+    }
+
+    //-------------------------------------------------------------------------
+    private static class ResolvingField implements TemporalField {
+        private final TemporalAccessor resolvedValue;
+        ResolvingField(TemporalAccessor resolvedValue) {
+            this.resolvedValue = resolvedValue;
+        }
+        @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, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
+            fieldValues.remove(this);
+            return resolvedValue;
+        }
+    };
+
 }