jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java
changeset 37907 643c10927a1a
parent 37812 6fd749bfad2d
child 39831 6ea15d593819
--- a/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Fri May 13 11:32:41 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Fri May 13 18:58:32 2016 +0000
@@ -81,9 +81,11 @@
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoLocalDateTime;
 import java.time.chrono.Chronology;
 import java.time.chrono.Era;
 import java.time.chrono.IsoChronology;
@@ -1157,10 +1159,11 @@
      * result of {@link ZoneOffset#getId()}.
      * If the zone is not an offset, the textual name will be looked up
      * for the locale set in the {@link DateTimeFormatter}.
-     * If the temporal object being printed represents an instant, then the text
-     * will be the summer or winter time text as appropriate.
+     * If the temporal object being printed represents an instant, or if it is a
+     * local date-time that is not in a daylight saving gap or overlap then
+     * the text will be the summer or winter time text as appropriate.
      * If the lookup for text does not find any suitable result, then the
-     * {@link ZoneId#getId() ID} will be printed instead.
+     * {@link ZoneId#getId() ID} will be printed.
      * If the zone cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
      * <p>
@@ -1177,7 +1180,7 @@
      * @return this, for chaining, not null
      */
     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
-        appendInternal(new ZoneTextPrinterParser(textStyle, null));
+        appendInternal(new ZoneTextPrinterParser(textStyle, null, false));
         return this;
     }
 
@@ -1193,10 +1196,11 @@
      * result of {@link ZoneOffset#getId()}.
      * If the zone is not an offset, the textual name will be looked up
      * for the locale set in the {@link DateTimeFormatter}.
-     * If the temporal object being printed represents an instant, then the text
+     * If the temporal object being printed represents an instant, or if it is a
+     * local date-time that is not in a daylight saving gap or overlap, then the text
      * will be the summer or winter time text as appropriate.
      * If the lookup for text does not find any suitable result, then the
-     * {@link ZoneId#getId() ID} will be printed instead.
+     * {@link ZoneId#getId() ID} will be printed.
      * If the zone cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
      * <p>
@@ -1220,7 +1224,70 @@
     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle,
                                                    Set<ZoneId> preferredZones) {
         Objects.requireNonNull(preferredZones, "preferredZones");
-        appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones));
+        appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, false));
+        return this;
+    }
+    //----------------------------------------------------------------------
+    /**
+     * Appends the generic time-zone name, such as 'Pacific Time', to the formatter.
+     * <p>
+     * This appends an instruction to format/parse the generic textual
+     * name of the zone to the builder. The generic name is the same throughout the whole
+     * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the
+     * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the
+     * specific names, see {@link #appendZoneText(TextStyle)}.
+     * <p>
+     * During formatting, the zone is obtained using a mechanism equivalent
+     * to querying the temporal with {@link TemporalQueries#zoneId()}.
+     * If the zone is a {@code ZoneOffset} it will be printed using the
+     * result of {@link ZoneOffset#getId()}.
+     * If the zone is not an offset, the textual name will be looked up
+     * for the locale set in the {@link DateTimeFormatter}.
+     * If the lookup for text does not find any suitable result, then the
+     * {@link ZoneId#getId() ID} will be printed.
+     * If the zone cannot be obtained then an exception is thrown unless the
+     * section of the formatter is optional.
+     * <p>
+     * During parsing, either the textual zone name, the zone ID or the offset
+     * is accepted. Many textual zone names are not unique, such as CST can be
+     * for both "Central Standard Time" and "China Standard Time". In this
+     * situation, the zone id will be determined by the region information from
+     * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
+     * zone id for that area, for example, America/New_York for the America Eastern zone.
+     * The {@link #appendGenericZoneText(TextStyle, Set)} may be used
+     * to specify a set of preferred {@link ZoneId} in this situation.
+     *
+     * @param textStyle  the text style to use, not null
+     * @return this, for chaining, not null
+     */
+    public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle) {
+        appendInternal(new ZoneTextPrinterParser(textStyle, null, true));
+        return this;
+    }
+
+    /**
+     * Appends the generic time-zone name, such as 'Pacific Time', to the formatter.
+     * <p>
+     * This appends an instruction to format/parse the generic textual
+     * name of the zone to the builder. The generic name is the same throughout the whole
+     * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the
+     * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the
+     * specific names, see {@link #appendZoneText(TextStyle)}.
+     * <p>
+     * This method also allows a set of preferred {@link ZoneId} to be
+     * specified for parsing. The matched preferred zone id will be used if the
+     * textural zone name being parsed is not unique.
+     * <p>
+     * See {@link #appendGenericZoneText(TextStyle)} for details about
+     * formatting and parsing.
+     *
+     * @param textStyle  the text style to use, not null
+     * @param preferredZones  the set of preferred zone ids, not null
+     * @return this, for chaining, not null
+     */
+    public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle,
+                                                          Set<ZoneId> preferredZones) {
+        appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, true));
         return this;
     }
 
@@ -1416,6 +1483,7 @@
      *   N       nano-of-day                 number            1234000000
      *
      *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
+     *   v       generic time-zone name      zone-name         PT, Pacific Time
      *   z       time-zone name              zone-name         Pacific Standard Time; PST
      *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
      *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15
@@ -1537,6 +1605,8 @@
      *  Pattern  Count  Equivalent builder methods
      *  -------  -----  --------------------------
      *    VV      2      appendZoneId()
+     *    v       1      appendGenericZoneText(TextStyle.SHORT)
+     *    vvvv    4      appendGenericZoneText(TextStyle.FULL)
      *    z       1      appendZoneText(TextStyle.SHORT)
      *    zz      2      appendZoneText(TextStyle.SHORT)
      *    zzz     3      appendZoneText(TextStyle.SHORT)
@@ -1643,6 +1713,14 @@
                         throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
                     }
                     appendZoneId();
+                } else if (cur == 'v') {
+                    if (count == 1) {
+                        appendGenericZoneText(TextStyle.SHORT);
+                    } else if (count == 4) {
+                        appendGenericZoneText(TextStyle.FULL);
+                    } else {
+                        throw new IllegalArgumentException("Wrong number of  pattern letters: " + cur);
+                    }
                 } else if (cur == 'Z') {
                     if (count < 4) {
                         appendOffset("+HHMM", "+0000");
@@ -1894,6 +1972,8 @@
         // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
         // 310 - Z - matches SimpleDateFormat and LDML
         // 310 - V - time-zone id, matches LDML
+        // 310 - v - general timezone names, not matching exactly with LDML because LDML specify to fall back
+        //           to 'VVVV' if general-nonlocation unavailable but here it's not falling back because of lack of data
         // 310 - p - prefix for padding
         // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
         // 310 - x - matches LDML
@@ -1901,7 +1981,6 @@
         // LDML - U - cycle year name, not supported by 310 yet
         // LDML - l - deprecated
         // LDML - j - not relevant
-        // LDML - v,V - extended time-zone names
     }
 
     //-----------------------------------------------------------------------
@@ -3723,9 +3802,12 @@
         /** The preferred zoneid map */
         private Set<String> preferredZones;
 
-        ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
+        /**  Display in generic time-zone format. True in case of pattern letter 'v' */
+        private final boolean isGeneric;
+        ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones, boolean isGeneric) {
             super(TemporalQueries.zone(), "ZoneText(" + textStyle + ")");
             this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
+            this.isGeneric = isGeneric;
             if (preferredZones != null && preferredZones.size() != 0) {
                 this.preferredZones = new HashSet<>();
                 for (ZoneId id : preferredZones) {
@@ -3788,11 +3870,21 @@
             String zname = zone.getId();
             if (!(zone instanceof ZoneOffset)) {
                 TemporalAccessor dt = context.getTemporal();
-                String name = getDisplayName(zname,
-                                             dt.isSupported(ChronoField.INSTANT_SECONDS)
-                                             ? (zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD)
-                                             : GENERIC,
-                                             context.getLocale());
+                int type = GENERIC;
+                if (!isGeneric) {
+                    if (dt.isSupported(ChronoField.INSTANT_SECONDS)) {
+                        type = zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD;
+                    } else if (dt.isSupported(ChronoField.EPOCH_DAY) &&
+                               dt.isSupported(ChronoField.NANO_OF_DAY)) {
+                        LocalDate date = LocalDate.ofEpochDay(dt.getLong(ChronoField.EPOCH_DAY));
+                        LocalTime time = LocalTime.ofNanoOfDay(dt.getLong(ChronoField.NANO_OF_DAY));
+                        LocalDateTime ldt = date.atTime(time);
+                        if (zone.getRules().getTransition(ldt) == null) {
+                            type = zone.getRules().isDaylightSavings(ldt.atZone(zone).toInstant()) ? DST : STD;
+                        }
+                    }
+                }
+                String name = getDisplayName(zname, type, context.getLocale());
                 if (name != null) {
                     zname = name;
                 }