--- old/src/share/classes/java/util/JapaneseImperialCalendar.java 2014-07-22 14:44:19.496074318 +0900 +++ new/src/share/classes/java/util/JapaneseImperialCalendar.java 2014-07-22 14:44:19.363440112 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,20 +38,20 @@ import sun.util.calendar.ZoneInfo; /** - * JapaneseImperialCalendar implements a Japanese + * {@code JapaneseImperialCalendar} implements a Japanese * calendar system in which the imperial era-based year numbering is * supported from the Meiji era. The following are the eras supported * by this calendar system. - *

+ * 

  * ERA value   Era name    Since (in Gregorian)
  * ------------------------------------------------------
  *     0       N/A         N/A
- *     1       Meiji       1868-01-01 midnight local time
- *     2       Taisho      1912-07-30 midnight local time
- *     3       Showa       1926-12-25 midnight local time
- *     4       Heisei      1989-01-08 midnight local time
+ *     1       Meiji       1868-01-01T00:00:00 local time
+ *     2       Taisho      1912-07-30T00:00:00 local time
+ *     3       Showa       1926-12-25T00:00:00 local time
+ *     4       Heisei      1989-01-08T00:00:00 local time
  * ------------------------------------------------------
- * 
+ *
* *

ERA value 0 specifies the years before Meiji and * the Gregorian year values are used. Unlike {@link @@ -63,6 +63,31 @@ * with time differences for applying the era transitions. This * calendar implementation assumes local time for all transitions. * + *

A new era can be specified using property + * jdk.calendar.japanese.supplemental.era. The new era is added to the + * predefined eras. The syntax of the property is as follows. + *

+ *   {@code name=,abbr=,since=}
+ * 
+ * where + *
+ *
{@code :}
the full name of the new era (non-ASCII characters allowed) + *
{@code :}
the abbreviation of the new era (non-ASCII characters allowed) + *
{@code :}
the start time of the new era represented by + * milliseconds from 1970-01-01T00:00:00 local time or UTC if {@code 'u'} is + * appended to the milliseconds value. (ASCII digits only) + *
+ * + *

If the given era is invalid, such as the since value before the + * beginning of the last predefined era, the given era will be + * ignored. + * + *

The following is an example of the property usage. + *

+ *   java -Djdk.calendar.japanese.supplemental.era="name=NewEra,abbr=N,since=253374307200000"
+ * 
+ * The property specifies an era change to NewEra at 9999-02-11T00:00:00 local time. + * * @author Masayoshi Okutsu * @since 1.6 */ @@ -102,7 +127,6 @@ public static final int HEISEI = 4; private static final int EPOCH_OFFSET = 719163; // Fixed date of January 1, 1970 (Gregorian) - private static final int EPOCH_YEAR = 1970; // Useful millisecond constants. Although ONE_DAY and ONE_WEEK can fit // into ints, they must be longs in order to prevent arithmetic overflow @@ -111,7 +135,6 @@ private static final int ONE_MINUTE = 60*ONE_SECOND; private static final int ONE_HOUR = 60*ONE_MINUTE; private static final long ONE_DAY = 24*ONE_HOUR; - private static final long ONE_WEEK = 7*ONE_DAY; // Reference to the sun.util.calendar.LocalGregorianCalendar instance (singleton). private static final LocalGregorianCalendar jcal @@ -217,6 +240,7 @@ }; // Proclaim serialization compatibility with JDK 1.6 + @SuppressWarnings("FieldNameHidesFieldInSuperclass") private static final long serialVersionUID = -3364572813905467929L; static { @@ -340,6 +364,7 @@ * false otherwise. * @see Calendar#compareTo(Calendar) */ + @Override public boolean equals(Object obj) { return obj instanceof JapaneseImperialCalendar && super.equals(obj); @@ -349,6 +374,7 @@ * Generates the hash code for this * JapaneseImperialCalendar object. */ + @Override public int hashCode() { return super.hashCode() ^ jdate.hashCode(); } @@ -381,6 +407,7 @@ * or if any calendar fields have out-of-range values in * non-lenient mode. */ + @Override public void add(int field, int amount) { // If amount == 0, do nothing even the given field is out of // range. This is tested by JCK. @@ -509,6 +536,7 @@ } } + @Override public void roll(int field, boolean up) { roll(field, up ? +1 : -1); } @@ -533,6 +561,7 @@ * @see #add(int,int) * @see #set(int,int) */ + @Override public void roll(int field, int amount) { // If amount == 0, do nothing even the given field is out of // range. This is tested by JCK. --- old/src/share/classes/sun/util/calendar/Era.java 2014-07-22 14:44:19.917489071 +0900 +++ new/src/share/classes/sun/util/calendar/Era.java 2014-07-22 14:44:19.785623964 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,20 +41,15 @@ * CalendarDate. * *

The following era names are defined in this release. - * - *


+ * 

  *   Calendar system         Era name         Since (in Gregorian)
  *   -----------------------------------------------------------------------
- *   Japanese calendar       Meiji            1868-01-01 midnight local time
- *                           Taisho           1912-07-30 midnight local time
- *                           Showa            1926-12-26 midnight local time
- *                           Heisei           1989-01-08 midnight local time
- *   Julian calendar         BeforeCommonEra  -292275055-05-16T16:47:04.192Z
- *                           CommonEra        0000-12-30 midnight local time
- *   Taiwanese calendar      MinGuo           1911-01-01 midnight local time
- *   Thai Buddhist calendar  BuddhistEra      -543-01-01 midnight local time
+ *   Japanese calendar       Meiji            1868-01-01T00:00:00 local time
+ *                           Taisho           1912-07-30T00:00:00 local time
+ *                           Showa            1926-12-25T00:00:00 local time
+ *                           Heisei           1989-01-08T00:00:00 local time
  *   -----------------------------------------------------------------------
- * 
+ *
* * @author Masayoshi Okutsu * @since 1.5 --- old/src/share/classes/sun/util/calendar/LocalGregorianCalendar.java 2014-07-22 14:44:20.299431396 +0900 +++ new/src/share/classes/sun/util/calendar/LocalGregorianCalendar.java 2014-07-22 14:44:20.167348733 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,7 @@ package sun.util.calendar; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; +import java.security.AccessController; import java.util.StringTokenizer; import java.util.TimeZone; @@ -39,6 +36,28 @@ */ public class LocalGregorianCalendar extends BaseCalendar { + private static final Era[] JAPANESE_ERAS = { + new Era("Meiji", "M", -3218832000000L, true), + new Era("Taisho", "T", -1812153600000L, true), + new Era("Showa", "S", -1357603200000L, true), + new Era("Heisei", "H", 600220800000L, true), + }; + + private static boolean isValidEra(Era newEra, Era[] eras) { + Era last = eras[eras.length - 1]; + if (last.getSinceDate().getYear() >= newEra.getSinceDate().getYear()) { + return false; + } + // The new era name should be unique. Its abbr may not. + String newName = newEra.getName(); + for (Era era : eras) { + if (era.getName().equals(newName)) { + return false; + } + } + return true; + } + private String name; private Era[] eras; @@ -118,58 +137,72 @@ } static LocalGregorianCalendar getLocalGregorianCalendar(String name) { - Properties calendarProps; - try { - calendarProps = CalendarSystem.getCalendarProperties(); - } catch (IOException | IllegalArgumentException e) { - throw new InternalError(e); - } - // Parse calendar.*.eras - String props = calendarProps.getProperty("calendar." + name + ".eras"); - if (props == null) { + // Only the Japanese calendar is supported. + if (!"japanese".equals(name)) { return null; } - List eras = new ArrayList<>(); - StringTokenizer eraTokens = new StringTokenizer(props, ";"); - while (eraTokens.hasMoreTokens()) { - String items = eraTokens.nextToken().trim(); - StringTokenizer itemTokens = new StringTokenizer(items, ","); - String eraName = null; - boolean localTime = true; - long since = 0; - String abbr = null; - - while (itemTokens.hasMoreTokens()) { - String item = itemTokens.nextToken(); - int index = item.indexOf('='); - // it must be in the key=value form. - if (index == -1) { - return null; - } - String key = item.substring(0, index); - String value = item.substring(index + 1); - if ("name".equals(key)) { - eraName = value; - } else if ("since".equals(key)) { - if (value.endsWith("u")) { - localTime = false; - since = Long.parseLong(value.substring(0, value.length() - 1)); - } else { - since = Long.parseLong(value); - } - } else if ("abbr".equals(key)) { - abbr = value; - } else { - throw new RuntimeException("Unknown key word: " + key); + + // Append an era to the predefined eras if it's given by the property. + String prop = AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("jdk.calendar.japanese.supplemental.era")); + if (prop != null) { + Era era = parseEraEntry(prop); + if (era != null) { + if (isValidEra(era, JAPANESE_ERAS)) { + int length = JAPANESE_ERAS.length; + Era[] eras = new Era[length + 1]; + System.arraycopy(JAPANESE_ERAS, 0, eras, 0, length); + eras[length] = era; + return new LocalGregorianCalendar(name, eras); } } - Era era = new Era(eraName, abbr, since, localTime); - eras.add(era); } - Era[] eraArray = new Era[eras.size()]; - eras.toArray(eraArray); + return new LocalGregorianCalendar(name, JAPANESE_ERAS); + } - return new LocalGregorianCalendar(name, eraArray); + private static Era parseEraEntry(String entry) { + StringTokenizer itemTokens = new StringTokenizer(entry, ","); + String eraName = null; + boolean localTime = true; + long since = 0; + String abbr = null; + + while (itemTokens.hasMoreTokens()) { + String item = itemTokens.nextToken(); + int index = item.indexOf('='); + // it must be in the key=value form. + if (index == -1) { + throw new RuntimeException("Syntax error: " + item); + } + String key = item.substring(0, index).trim(); + String value = item.substring(index + 1).trim(); + switch (key) { + case "name": + eraName = value; + break; + case "since": + if (value.endsWith("u")) { + localTime = false; + value = value.substring(0, value.length() - 1); + } + try { + since = Long.parseLong(value); + } catch (NumberFormatException e) { + return null; + } + break; + case "abbr": + abbr = value; + break; + default: + return null; + } + } + if (eraName == null || eraName.isEmpty() + || abbr == null || abbr.isEmpty()) { + return null; + } + return new Era(eraName, abbr, since, localTime); } private LocalGregorianCalendar(String name, Era[] eras) { @@ -262,9 +295,8 @@ } private boolean validateEra(Era era) { - // Validate the era - for (int i = 0; i < eras.length; i++) { - if (era == eras[i]) { + for (Era era1 : eras) { + if (era == era1) { return true; } } @@ -333,6 +365,7 @@ } if (i >= 0) { ldate.setLocalEra(era); + @SuppressWarnings("null") int y = ldate.getNormalizedYear() - era.getSinceDate().getYear() + 1; ldate.setLocalYear(y); } else { --- old/src/share/lib/calendars.properties 2014-07-22 14:44:20.697443783 +0900 +++ new/src/share/lib/calendars.properties 2014-07-22 14:44:20.557496169 +0900 @@ -1,4 +1,4 @@ -# Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,37 +23,6 @@ # # -# Japanese imperial calendar -# -# Meiji since 1868-01-01 00:00:00 local time (Gregorian) -# Taisho since 1912-07-30 00:00:00 local time (Gregorian) -# Showa since 1926-12-25 00:00:00 local time (Gregorian) -# Heisei since 1989-01-08 00:00:00 local time (Gregorian) -calendar.japanese.type: LocalGregorianCalendar -calendar.japanese.eras: \ - name=Meiji,abbr=M,since=-3218832000000; \ - name=Taisho,abbr=T,since=-1812153600000; \ - name=Showa,abbr=S,since=-1357603200000; \ - name=Heisei,abbr=H,since=600220800000 - -# -# Taiwanese calendar -# Minguo since 1911-01-01 00:00:00 local time (Gregorian) -calendar.taiwanese.type: LocalGregorianCalendar -calendar.taiwanese.eras: \ - name=MinGuo,since=-1830384000000 - -# -# Thai Buddhist calendar -# Buddhist Era since -542-01-01 00:00:00 local time (Gregorian) -calendar.thai-buddhist.type: LocalGregorianCalendar -calendar.thai-buddhist.eras: \ - name=BuddhistEra,abbr=B.E.,since=-79302585600000 -calendar.thai-buddhist.year-boundary: \ - day1=4-1,since=-79302585600000; \ - day1=1-1,since=-915148800000 - -# # Hijrah calendars # calendar.hijrah.Hijrah-umalqura: hijrah-config-umalqura.properties --- /dev/null 2014-07-17 12:15:34.526867305 +0900 +++ new/test/java/util/Calendar/Bug8048123.java 2014-07-22 14:44:20.946389611 +0900 @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import static java.util.GregorianCalendar.*; +import java.util.Locale; +import java.util.TimeZone; + +/* + * Usage: + * java Bug8048123 + * + * -s prints start time for a test era + * -e prints the English name of the last predefined era + * + * java -Djdk.calendar.japanese.supplemental.era=... Bug8048123 + * -t executes tests with a valid property value + * -b + * executes tests with an invalid property value + * must be the output with -e + */ + +public class Bug8048123 { + private static final Locale WAREKI_LOCALE = Locale.forLanguageTag("ja-JP-u-ca-japanese"); + + private static int errors = 0; + + public static void main(String[] args) { + // args[0] is a flag. + switch (args[0]) { + case "-s": + // print the start time of the new era for testing + Calendar cal = new Calendar.Builder() + .setCalendarType("japanese") + .setTimeZone(TimeZone.getTimeZone("GMT")) + .setDate(200, FEBRUARY, 11) + .build(); + System.out.println(cal.getTimeInMillis()); + break; + + case "-e": + // print the current era name in English + Calendar jcal = new Calendar.Builder() + .setCalendarType("japanese") + .setFields(YEAR, 1, DAY_OF_YEAR, 1) + .build(); + System.out.println(jcal.getDisplayName(ERA, LONG, Locale.US)); + break; + + case "-t": + // test with a valid property value + testProperty(); + break; + + case "-b": + // test with an invalid property value + // args[1] is the current era name given by -e. + testValidation(args[1].replace("\r", "")); // remove any CR for Cygwin + break; + } + if (errors != 0) { + throw new RuntimeException("test failed"); + } + } + + private static void testProperty() { + Calendar jcal = new Calendar.Builder() + .setCalendarType("japanese") + .setFields(YEAR, 1, DAY_OF_YEAR, 1) + .build(); + Date firstDayOfEra = jcal.getTime(); + + jcal.set(ERA, jcal.get(ERA) - 1); // previous era + jcal.set(YEAR, 1); + jcal.set(DAY_OF_YEAR, 1); + Calendar cal = new GregorianCalendar(); + cal.setTimeInMillis(jcal.getTimeInMillis()); + cal.add(YEAR, 199); + int year = cal.get(YEAR); + + SimpleDateFormat sdf; + String s; + + // test long era name + sdf = new SimpleDateFormat("GGGG y-MM-dd", WAREKI_LOCALE); + s = sdf.format(firstDayOfEra); + if (!"NewEra 1-02-11".equals(s)) { + System.err.printf("GGGG y-MM-dd: got=\"%s\", expected=\"NewEra 1-02-11\"%n", s); + errors++; + } + + // test era abbreviation + sdf = new SimpleDateFormat("G y-MM-dd", WAREKI_LOCALE); + s = sdf.format(firstDayOfEra); + if (!"N.E. 1-02-11".equals(s)) { + System.err.printf("GGGG y-MM-dd: got=\"%s\", expected=\"N.E. 1-02-11\"%n", s); + errors++; + } + + // confirm the gregorian year + sdf = new SimpleDateFormat("y", Locale.US); + int y = Integer.parseInt(sdf.format(firstDayOfEra)); + if (y != year) { + System.err.printf("Gregorian year: got=%d, expected=%d%n", y, year); + errors++; + } + } + + private static void testValidation(String eraName) { + Calendar jcal = new Calendar.Builder() + .setCalendarType("japanese") + .setFields(YEAR, 1, DAY_OF_YEAR, 1) + .build(); + if (!jcal.getDisplayName(ERA, LONG, Locale.US).equals(eraName)) { + errors++; + String prop = System.getProperty("jdk.calendar.japanese.supplemental.era"); + System.err.println("Era changed with invalid property: " + prop); + } + } +} --- /dev/null 2014-07-17 12:15:34.526867305 +0900 +++ new/test/java/util/Calendar/Bug8048123.sh 2014-07-22 14:44:21.362892841 +0900 @@ -0,0 +1,73 @@ +# +# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# @test +# @bug 8048123 +# @summary Test for jdk.calendar.japanese.supplemental.era support +# @build Bug8048123 +# @run shell Bug8048123.sh + +PROPERTY=jdk.calendar.japanese.supplemental.era +STATUS=0 + +# get the start time of the fictional next era +SINCE=`${TESTJAVA}/bin/java -cp "${TESTCLASSES}" Bug8048123 -s` + +echo "Tests with valid property values..." +for P in "name=NewEra,abbr=N.E.,since=$SINCE" \ + "name = NewEra, abbr = N.E., since = $SINCE" +do + if ${TESTJAVA}/bin/java ${TESTVMOPTS} -cp "${TESTCLASSES}" \ + -D$PROPERTY="$P" Bug8048123 -t; then + echo "$P: passed" + else + echo "$P: failed" + STATUS=1 + fi +done + +# get the name of the current era to be used to confirm that +# invalid property values are ignored. +ERA=`${TESTJAVA}/bin/java -cp "${TESTCLASSES}" Bug8048123 -e` + +echo "Tests with invalid property values..." +for P in "foo=Bar,name=NewEra,abbr=N.E.,since=$SINCE" \ + "=NewEra,abbr=N.E.,since=$SINCE" \ + "=,abbr=N.E.,since=$SINCE" \ + "abbr=N.E.,since=$SINCE" \ + "name=NewEra,since=$SINCE" \ + "name=,abbr=N.E.,since=$SINCE" \ + "name=NewEra,abbr=,since=$SINCE" \ + "name=NewEra,abbr=N.E." \ + "name=NewEra,abbr=N.E.,since=0" \ + "name=NewEra,abbr=N.E.,since=9223372036854775808" # Long.MAX_VALUE+1 +do + if ${TESTJAVA}/bin/java ${TESTVMOPTS} -cp "${TESTCLASSES}" \ + -D$PROPERTY="$P" Bug8048123 -b "$ERA"; then + echo "$P: passed" + else + echo "$P: failed" + STATUS=1 + fi +done +exit $STATUS