# HG changeset patch # User ntv # Date 1463165912 0 # Node ID 643c10927a1a55d07b11f341cd3b1ae2acc43d86 # Parent c2ea5f68c2ff5886ee49d05eae1b8477ea904099 8155823: Add date-time patterns 'v' and 'vvvv' Summary: Addded necessary methodss Reviewed-by: rriggs, scolebourne diff -r c2ea5f68c2ff -r 643c10927a1a jdk/src/java.base/share/classes/java/time/format/DateTimeFormatter.java --- a/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatter.java Fri May 13 11:32:41 2016 -0700 +++ b/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatter.java Fri May 13 18:58:32 2016 +0000 @@ -308,6 +308,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 Pacific Time; PT * 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 @@ -365,9 +366,17 @@ * letters throws {@code IllegalArgumentException}. *

* Zone names: This outputs the display name of the time-zone ID. If the - * count of letters is one, two or three, then the short name is output. If the - * count of letters is four, then the full name is output. Five or more letters - * throws {@code IllegalArgumentException}. + * pattern letter is 'z' the output is the daylight savings aware zone name. + * If there is insufficient information to determine whether DST applies, + * the name ignoring daylight savings time will be used. + * If the count of letters is one, two or three, then the short name is output. + * If the count of letters is four, then the full name is output. + * Five or more letters throws {@code IllegalArgumentException}. + *

+ * If the pattern letter is 'v' the output provides the zone name ignoring + * daylight savings time. If the count of letters is one, then the short name is output. + * If the count of letters is four, then the full name is output. + * Two, three and five or more letters throw {@code IllegalArgumentException}. *

* Offset X and x: This formats the offset based on the number of pattern * letters. One letter outputs just the hour, such as '+01', unless the minute diff -r c2ea5f68c2ff -r 643c10927a1a jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java --- 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. *

@@ -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. *

@@ -1220,7 +1224,70 @@ public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle, Set 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. + *

+ * 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)}. + *

+ * 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. + *

+ * 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. + *

+ * 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)}. + *

+ * 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. + *

+ * 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 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 preferredZones; - ZoneTextPrinterParser(TextStyle textStyle, Set preferredZones) { + /** Display in generic time-zone format. True in case of pattern letter 'v' */ + private final boolean isGeneric; + ZoneTextPrinterParser(TextStyle textStyle, Set 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; } diff -r c2ea5f68c2ff -r 643c10927a1a jdk/src/java.base/share/classes/java/time/zone/ZoneRules.java --- a/jdk/src/java.base/share/classes/java/time/zone/ZoneRules.java Fri May 13 11:32:41 2016 -0700 +++ b/jdk/src/java.base/share/classes/java/time/zone/ZoneRules.java Fri May 13 18:58:32 2016 +0000 @@ -614,7 +614,7 @@ * One technique, using this method, would be: *

      *  ZoneOffsetTransition trans = rules.getTransition(localDT);
-     *  if (trans == null) {
+     *  if (trans != null) {
      *    // Gap or Overlap: determine what to do from transition
      *  } else {
      *    // Normal case: only one valid offset
diff -r c2ea5f68c2ff -r 643c10927a1a jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
--- a/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Fri May 13 11:32:41 2016 -0700
+++ b/jdk/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java	Fri May 13 18:58:32 2016 +0000
@@ -73,7 +73,10 @@
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.Month;
 import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
@@ -459,6 +462,98 @@
     //-----------------------------------------------------------------------
     //-----------------------------------------------------------------------
     //-----------------------------------------------------------------------
+    @DataProvider(name = "formatGenericTimeZonePatterns")
+    Object[][] data_formatGenericNonLocationPatterns() {
+        return new Object[][] {
+                {"v", "America/Los_Angeles", "PT"},
+                {"vvvv", "America/Los_Angeles", "Pacific Time"},
+                {"v", "America/New_York", "ET"},
+                {"vvvv", "America/New_York", "Eastern Time"},
+        };
+    }
+
+    @Test(dataProvider = "formatGenericTimeZonePatterns")
+    public void test_appendZoneText_formatGenericTimeZonePatterns(String pattern, String input, String expected) {
+        ZonedDateTime zdt = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of(input));
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern);
+        assertEquals(zdt.format(df), expected);
+    }
+
+    @DataProvider(name = "parseGenericTimeZonePatterns")
+    Object[][]  data_parseGenericTimeZonePatterns() {
+        return new Object[][] {
+                {"yyyy DDD HH mm v", LocalDateTime.of(2015, Month.MARCH, 10, 12, 13), ZoneId.of("America/Los_Angeles"),
+                 "2015 069 12 13 PT"},
+                {"yyyy DDD HH mm vvvv", LocalDateTime.of(2015, Month.MARCH, 10, 12, 13), ZoneId.of("America/Los_Angeles"),
+                 "2015 069 12 13 Pacific Time"},
+                {"yyyy DDD HH mm v", LocalDateTime.of(2015, Month.NOVEMBER, 10, 12, 13), ZoneId.of("America/Los_Angeles"),
+                 "2015 314 12 13 PT"},
+                {"yyyy DDD HH mm vvvv", LocalDateTime.of(2015, Month.NOVEMBER, 10, 12, 13), ZoneId.of("America/Los_Angeles"),
+                 "2015 314 12 13 Pacific Time"},
+        };
+    }
+
+    @Test(dataProvider = "parseGenericTimeZonePatterns")
+    public void test_appendZoneText_parseGenericTimeZonePatterns(String pattern, LocalDateTime ldt, ZoneId zId, String input) {
+        DateTimeFormatter df = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
+        ZonedDateTime expected = ZonedDateTime.parse(input, df);
+        ZonedDateTime actual = ZonedDateTime.of(ldt, zId);
+        assertEquals(actual, expected);
+    }
+
+    @DataProvider(name = "formatNonGenericTimeZonePatterns_1")
+    Object[][]  data_formatNonGenericTimeZonePatterns_1() {
+        return new Object[][] {
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 0, 30),
+                 "2015-11-01 00:30:00 PDT"},
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 1, 30),
+                 "2015-11-01 01:30:00 PDT"},
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 2, 30),
+                 "2015-11-01 02:30:00 PST"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 0, 30),
+                 "2015-11-01 00:30:00 Pacific Daylight Time"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 1, 30),
+                 "2015-11-01 01:30:00 Pacific Daylight Time"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 2, 30),
+                 "2015-11-01 02:30:00 Pacific Standard Time"},
+        };
+    }
+
+    @Test(dataProvider = "formatNonGenericTimeZonePatterns_1")
+    public void test_appendZoneText_parseNonGenricTimeZonePatterns_1(String pattern, LocalDateTime ldt, String expected) {
+        ZoneId  zId = ZoneId.of("America/Los_Angeles");
+        DateTimeFormatter df = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
+        ZonedDateTime zdt = ZonedDateTime.of(ldt, zId);
+        String actual = df.format(zdt);
+        assertEquals(actual, expected);
+    }
+
+    @DataProvider(name = "formatNonGenericTimeZonePatterns_2")
+    Object[][]  data_formatNonGenericTimeZonePatterns_2() {
+        return new Object[][] {
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 0, 30),
+                 "2015-11-01 00:30:00 PDT"},
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 1, 30),
+                 "2015-11-01 01:30:00 PT"},
+                {"yyyy-MM-dd HH:mm:ss z", LocalDateTime.of(2015, Month.NOVEMBER, 1, 2, 30),
+                 "2015-11-01 02:30:00 PST"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 0, 30),
+                 "2015-11-01 00:30:00 Pacific Daylight Time"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 1, 30),
+                 "2015-11-01 01:30:00 Pacific Time"},
+                {"yyyy-MM-dd HH:mm:ss zzzz", LocalDateTime.of(2015, Month.NOVEMBER, 1, 2, 30),
+                 "2015-11-01 02:30:00 Pacific Standard Time"},
+        };
+    }
+
+    @Test(dataProvider = "formatNonGenericTimeZonePatterns_2")
+    public void test_appendZoneText_parseNonGenricTimeZonePatterns_2(String pattern, LocalDateTime ldt, String expected) {
+        ZoneId  zId = ZoneId.of("America/Los_Angeles");
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern).withZone(zId);
+        String actual = df.format(ldt);
+        assertEquals(actual, expected);
+    }
+
     @Test(expectedExceptions=NullPointerException.class)
     public void test_appendZoneText_1arg_nullText() throws Exception {
         builder.appendZoneText(null);
@@ -734,6 +829,9 @@
 
             {"www"},
             {"WW"},
+
+            {"vv"},
+            {"vvv"},
         };
     }