# HG changeset patch # User okutsu # Date 1358737495 -32400 # Node ID d9e473e85f3c34c8770ac0290517f12d9066a1c8 # Parent 1821066bde82de0c901d5f14e7a80e40d35ee4e0 4745761: (cal) RFE: Support builder for constructing Calendar Reviewed-by: peytoia diff -r 1821066bde82 -r d9e473e85f3c jdk/src/share/classes/java/util/Calendar.java --- a/jdk/src/share/classes/java/util/Calendar.java Sun Jan 20 09:37:51 2013 +0000 +++ b/jdk/src/share/classes/java/util/Calendar.java Mon Jan 21 12:04:55 2013 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2013, 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 @@ -151,9 +151,9 @@ * calendar field values to determine the date and time in the * following way. * - *

If there is any conflict in calendar field values, + *

If there is any conflict in calendar field values, * Calendar gives priorities to calendar fields that have been set - * more recently. The following are the default combinations of the + * more recently. The following are the default combinations of the * calendar fields. The most recent combination, as determined by the * most recently set single field, will be used. * @@ -1020,6 +1020,556 @@ final static int DST_OFFSET_MASK = (1 << DST_OFFSET); /** + * {@code Calendar.Builder} is used for creating a {@code Calendar} from + * various date-time parameters. + * + *

There are two ways to set a {@code Calendar} to a date-time value. One + * is to set the instant parameter to a millisecond offset from the Epoch. The other is to set individual + * field parameters, such as {@link Calendar#YEAR YEAR}, to their desired + * values. These two ways can't be mixed. Trying to set both the instant and + * individual fields will cause an {@link IllegalStateException} to be + * thrown. However, it is permitted to override previous values of the + * instant or field parameters. + * + *

If no enough field parameters are given for determining date and/or + * time, calendar specific default values are used when building a + * {@code Calendar}. For example, if the {@link Calendar#YEAR YEAR} value + * isn't given for the Gregorian calendar, 1970 will be used. If there are + * any conflicts among field parameters, the resolution rules are applied. + * Therefore, the order of field setting matters. + * + *

In addition to the date-time parameters, + * the {@linkplain #setLocale(Locale) locale}, + * {@linkplain #setTimeZone(TimeZone) time zone}, + * {@linkplain #setWeekDefinition(int, int) week definition}, and + * {@linkplain #setLenient(boolean) leniency mode} parameters can be set. + * + *

Examples + *

The following are sample usages. Sample code assumes that the + * {@code Calendar} constants are statically imported. + * + *

The following code produces a {@code Calendar} with date 2012-12-31 + * (Gregorian) because Monday is the first day of a week with the ISO 8601 + * compatible week parameters. + *

+     *   Calendar cal = new Calendar.Builder().setCalendarType("iso8601")
+     *                        .setWeekDate(2013, 1, MONDAY).build();
+ *

The following code produces a Japanese {@code Calendar} with date + * 1989-01-08 (Gregorian), assuming that the default {@link Calendar#ERA ERA} + * is Heisei that started on that day. + *

+     *   Calendar cal = new Calendar.Builder().setCalendarType("japanese")
+     *                        .setFields(YEAR, 1, DAY_OF_YEAR, 1).build();
+ * + * @since 1.8 + * @see Calendar#getInstance(TimeZone, Locale) + * @see Calendar#fields + */ + public static class Builder { + private static final int NFIELDS = FIELD_COUNT + 1; // +1 for WEEK_YEAR + private static final int WEEK_YEAR = FIELD_COUNT; + + private long instant; + // Calendar.stamp[] (lower half) and Calendar.fields[] (upper half) combined + private int[] fields; + // Pseudo timestamp starting from MINIMUM_USER_STAMP. + // (COMPUTED is used to indicate that the instant has been set.) + private int nextStamp; + // maxFieldIndex keeps the max index of fields which have been set. + // (WEEK_YEAR is never included.) + private int maxFieldIndex; + private String type; + private TimeZone zone; + private boolean lenient = true; + private Locale locale; + private int firstDayOfWeek, minimalDaysInFirstWeek; + + /** + * Constructs a {@code Calendar.Builder}. + */ + public Builder() { + } + + /** + * Sets the instant parameter to the given {@code instant} value that is + * a millisecond offset from the + * Epoch. + * + * @param instant a millisecond offset from the Epoch + * @return this {@code Calendar.Builder} + * @throws IllegalStateException if any of the field parameters have + * already been set + * @see Calendar#setTime(Date) + * @see Calendar#setTimeInMillis(long) + * @see Calendar#time + */ + public Builder setInstant(long instant) { + if (fields != null) { + throw new IllegalStateException(); + } + this.instant = instant; + nextStamp = COMPUTED; + return this; + } + + /** + * Sets the instant parameter to the {@code instant} value given by a + * {@link Date}. This method is equivalent to a call to + * {@link #setInstant(long) setInstant(instant.getTime())}. + * + * @param instant a {@code Date} representing a millisecond offset from + * the Epoch + * @return this {@code Calendar.Builder} + * @throws NullPointerException if {@code instant} is {@code null} + * @throws IllegalStateException if any of the field parameters have + * already been set + * @see Calendar#setTime(Date) + * @see Calendar#setTimeInMillis(long) + * @see Calendar#time + */ + public Builder setInstant(Date instant) { + return setInstant(instant.getTime()); // NPE if instant == null + } + + /** + * Sets the {@code field} parameter to the given {@code value}. + * {@code field} is an index to the {@link Calendar#fields}, such as + * {@link Calendar#DAY_OF_MONTH DAY_OF_MONTH}. Field value validation is + * not performed in this method. Any out of range values are either + * normalized in lenient mode or detected as an invalid value in + * non-lenient mode when building a {@code Calendar}. + * + * @param field an index to the {@code Calendar} fields + * @param value the field value + * @return this {@code Calendar.Builder} + * @throws IllegalArgumentException if {@code field} is invalid + * @throws IllegalStateException if the instant value has already been set, + * or if fields have been set too many + * (approximately {@link Integer#MAX_VALUE}) times. + * @see Calendar#set(int, int) + */ + public Builder set(int field, int value) { + // Note: WEEK_YEAR can't be set with this method. + if (field < 0 || field >= FIELD_COUNT) { + throw new IllegalArgumentException("field is invalid"); + } + if (isInstantSet()) { + throw new IllegalStateException("instant has been set"); + } + allocateFields(); + internalSet(field, value); + return this; + } + + /** + * Sets field parameters to their values given by + * {@code fieldValuePairs} that are pairs of a field and its value. + * For example, + *
+         *   setFeilds(Calendar.YEAR, 2013,
+         *             Calendar.MONTH, Calendar.DECEMBER,
+         *             Calendar.DAY_OF_MONTH, 23);
+ * is equivalent to the sequence of the following + * {@link #set(int, int) set} calls: + *
+         *   set(Calendar.YEAR, 2013)
+         *   .set(Calendar.MONTH, Calendar.DECEMBER)
+         *   .set(Calendar.DAY_OF_MONTH, 23);
+ * + * @param fieldValuePairs field-value pairs + * @return this {@code Calendar.Builder} + * @throws NullPointerException if {@code fieldValuePairs} is {@code null} + * @throws IllegalArgumentException if any of fields are invalid, + * or if {@code fieldValuePairs.length} is an odd number. + * @throws IllegalStateException if the instant value has been set, + * or if fields have been set too many (approximately + * {@link Integer#MAX_VALUE}) times. + */ + public Builder setFields(int... fieldValuePairs) { + int len = fieldValuePairs.length; + if ((len % 2) != 0) { + throw new IllegalArgumentException(); + } + if (isInstantSet()) { + throw new IllegalStateException("instant has been set"); + } + if ((nextStamp + len / 2) < 0) { + throw new IllegalStateException("stamp counter overflow"); + } + allocateFields(); + for (int i = 0; i < len; ) { + int field = fieldValuePairs[i++]; + // Note: WEEK_YEAR can't be set with this method. + if (field < 0 || field >= FIELD_COUNT) { + throw new IllegalArgumentException("field is invalid"); + } + internalSet(field, fieldValuePairs[i++]); + } + return this; + } + + /** + * Sets the date field parameters to the values given by {@code year}, + * {@code month}, and {@code dayOfMonth}. This method is equivalent to + * a call to: + *
+         *   setFields(Calendar.YEAR, year,
+         *             Calendar.MONTH, month,
+         *             Calendar.DAY_OF_MONTH, dayOfMonth);
+ * + * @param year the {@link Calendar#YEAR YEAR} value + * @param month the {@link Calendar#MONTH MONTH} value + * (the month numbering is 0-based). + * @param dayOfMonth the {@link Calendar#DAY_OF_MONTH DAY_OF_MONTH} value + * @return this {@code Calendar.Builder} + */ + public Builder setDate(int year, int month, int dayOfMonth) { + return setFields(YEAR, year, MONTH, month, DAY_OF_MONTH, dayOfMonth); + } + + /** + * Sets the time of day field parameters to the values given by + * {@code hourOfDay}, {@code minute}, and {@code second}. This method is + * equivalent to a call to: + *
+         *   setTimeOfDay(hourOfDay, minute, second, 0);
+ * + * @param hourOfDay the {@link Calendar#HOUR_OF_DAY HOUR_OF_DAY} value + * (24-hour clock) + * @param minute the {@link Calendar#MINUTE MINUTE} value + * @param second the {@link Calendar#SECOND SECOND} value + * @return this {@code Calendar.Builder} + */ + public Builder setTimeOfDay(int hourOfDay, int minute, int second) { + return setTimeOfDay(hourOfDay, minute, second, 0); + } + + /** + * Sets the time of day field parameters to the values given by + * {@code hourOfDay}, {@code minute}, {@code second}, and + * {@code millis}. This method is equivalent to a call to: + *
+         *   setFields(Calendar.HOUR_OF_DAY, hourOfDay,
+         *             Calendar.MINUTE, minute,
+         *             Calendar.SECOND, second,
+         *             Calendar.MILLISECOND, millis);
+ * + * @param hourOfDay the {@link Calendar#HOUR_OF_DAY HOUR_OF_DAY} value + * (24-hour clock) + * @param minute the {@link Calendar#MINUTE MINUTE} value + * @param second the {@link Calendar#SECOND SECOND} value + * @param millis the {@link Calendar#MILLISECOND MILLISECOND} value + * @return this {@code Calendar.Builder} + */ + public Builder setTimeOfDay(int hourOfDay, int minute, int second, int millis) { + return setFields(HOUR_OF_DAY, hourOfDay, MINUTE, minute, + SECOND, second, MILLISECOND, millis); + } + + /** + * Sets the week-based date parameters to the values with the given + * date specifiers - week year, week of year, and day of week. + * + *

If the specified calendar doesn't support week dates, the + * {@link #build() build} method will throw an {@link IllegalArgumentException}. + * + * @param weekYear the week year + * @param weekOfYear the week number based on {@code weekYear} + * @param dayOfWeek the day of week value: one of the constants + * for the {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} field: + * {@link Calendar#SUNDAY SUNDAY}, ..., {@link Calendar#SATURDAY SATURDAY}. + * @return this {@code Calendar.Builder} + * @see Calendar#setWeekDate(int, int, int) + * @see Calendar#isWeekDateSupported() + */ + public Builder setWeekDate(int weekYear, int weekOfYear, int dayOfWeek) { + allocateFields(); + internalSet(WEEK_YEAR, weekYear); + internalSet(WEEK_OF_YEAR, weekOfYear); + internalSet(DAY_OF_WEEK, dayOfWeek); + return this; + } + + /** + * Sets the time zone parameter to the given {@code zone}. If no time + * zone parameter is given to this {@code Caledar.Builder}, the + * {@linkplain TimeZone#getDefault() default + * TimeZone} will be used in the {@link #build() build} + * method. + * + * @param zone the {@link TimeZone} + * @return this {@code Calendar.Builder} + * @throws NullPointerException if {@code zone} is {@code null} + * @see Calendar#setTimeZone(TimeZone) + */ + public Builder setTimeZone(TimeZone zone) { + if (zone == null) { + throw new NullPointerException(); + } + this.zone = zone; + return this; + } + + /** + * Sets the lenient mode parameter to the value given by {@code lenient}. + * If no lenient parameter is given to this {@code Calendar.Builder}, + * lenient mode will be used in the {@link #build() build} method. + * + * @param lenient {@code true} for lenient mode; + * {@code false} for non-lenient mode + * @return this {@code Calendar.Builder} + * @see Calendar#setLenient(boolean) + */ + public Builder setLenient(boolean lenient) { + this.lenient = lenient; + return this; + } + + /** + * Sets the calendar type parameter to the given {@code type}. The + * calendar type given by this method has precedence over any explicit + * or implicit calendar type given by the + * {@linkplain #setLocale(Locale) locale}. + * + *

In addition to the available calendar types returned by the + * {@link Calendar#getAvailableCalendarTypes() Calendar.getAvailableCalendarTypes} + * method, {@code "gregorian"} and {@code "iso8601"} as aliases of + * {@code "gregory"} can be used with this method. + * + * @param type the calendar type + * @return this {@code Calendar.Builder} + * @throws NullPointerException if {@code type} is {@code null} + * @throws IllegalArgumentException if {@code type} is unknown + * @throws IllegalStateException if another calendar type has already been set + * @see Calendar#getCalendarType() + * @see Calendar#getAvailableCalendarTypes() + */ + public Builder setCalendarType(String type) { + if (type.equals("gregorian")) { // NPE if type == null + type = "gregory"; + } + if (!Calendar.getAvailableCalendarTypes().contains(type) + && !type.equals("iso8601")) { + throw new IllegalArgumentException("unknown calendar type: " + type); + } + if (this.type == null) { + this.type = type; + } else { + if (!this.type.equals(type)) { + throw new IllegalStateException("calendar type override"); + } + } + return this; + } + + /** + * Sets the locale parameter to the given {@code locale}. If no locale + * is given to this {@code Calendar.Builder}, the {@linkplain + * Locale#getDefault(Locale.Category) default Locale} + * for {@link Locale.Category#FORMAT} will be used. + * + *

If no calendar type is explicitly given by a call to the + * {@link #setCalendarType(String) setCalendarType} method, + * the {@code Locale} value is used to determine what type of + * {@code Calendar} to be built. + * + *

If no week definition parameters are explicitly given by a call to + * the {@link #setWeekDefinition(int,int) setWeekDefinition} method, the + * {@code Locale}'s default values are used. + * + * @param locale the {@link Locale} + * @throws NullPointerException if {@code locale} is {@code null} + * @return this {@code Calendar.Builder} + * @see Calendar#getInstance(Locale) + */ + public Builder setLocale(Locale locale) { + if (locale == null) { + throw new NullPointerException(); + } + this.locale = locale; + return this; + } + + /** + * Sets the week definition parameters to the values given by + * {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} that are + * used to determine the first + * week of a year. The parameters given by this method have + * precedence over the default values given by the + * {@linkplain #setLocale(Locale) locale}. + * + * @param firstDayOfWeek the first day of a week; one of + * {@link Calendar#SUNDAY} to {@link Calendar#SATURDAY} + * @param minimalDaysInFirstWeek the minimal number of days in the first + * week (1..7) + * @return this {@code Calendar.Builder} + * @throws IllegalArgumentException if {@code firstDayOfWeek} or + * {@code minimalDaysInFirstWeek} is invalid + * @see Calendar#getFirstDayOfWeek() + * @see Calendar#getMinimalDaysInFirstWeek() + */ + public Builder setWeekDefinition(int firstDayOfWeek, int minimalDaysInFirstWeek) { + if (!isValidWeekParameter(firstDayOfWeek) + || !isValidWeekParameter(minimalDaysInFirstWeek)) { + throw new IllegalArgumentException(); + } + this.firstDayOfWeek = firstDayOfWeek; + this.minimalDaysInFirstWeek = minimalDaysInFirstWeek; + return this; + } + + /** + * Returns a {@code Calendar} built from the parameters set by the + * setter methods. The calendar type given by the {@link #setCalendarType(String) + * setCalendarType} method or the {@linkplain #setLocale(Locale) locale} is + * used to determine what {@code Calendar} to be created. If no explicit + * calendar type is given, the locale's default calendar is created. + * + *

If the calendar type is {@code "iso8601"}, the + * {@linkplain GregorianCalendar#setGregorianChange(Date) Gregorian change date} + * of a {@link GregorianCalendar} is set to {@code Date(Long.MIN_VALUE)} + * to be the proleptic Gregorian calendar. Its week definition + * parameters are also set to be compatible + * with the ISO 8601 standard. Note that the + * {@link GregorianCalendar#getCalendarType() getCalendarType} method of + * a {@code GregorianCalendar} created with {@code "iso8601"} returns + * {@code "gregory"}. + * + *

The default values are used for locale and time zone if these + * parameters haven't been given explicitly. + * + *

Any out of range field values are either normalized in lenient + * mode or detected as an invalid value in non-lenient mode. + * + * @return a {@code Calendar} built with parameters of this {@code + * Calendar.Builder} + * @throws IllegalArgumentException if the calendar type is unknown, or + * if any invalid field values are given in non-lenient mode, or + * if a week date is given for the calendar type that doesn't + * support week dates. + * @see Calendar#getInstance(TimeZone, Locale) + * @see Locale#getDefault(Locale.Category) + * @see TimeZone#getDefault() + */ + public Calendar build() { + if (locale == null) { + locale = Locale.getDefault(); + } + if (zone == null) { + zone = TimeZone.getDefault(); + } + Calendar cal; + if (type == null) { + type = locale.getUnicodeLocaleType("ca"); + } + if (type == null) { + if (locale.getCountry() == "TH" + && locale.getLanguage() == "th") { + type = "buddhist"; + } else { + type = "gregory"; + } + } + switch (type) { + case "gregory": + cal = new GregorianCalendar(zone, locale, true); + break; + case "iso8601": + GregorianCalendar gcal = new GregorianCalendar(zone, locale, true); + // make gcal a proleptic Gregorian + gcal.setGregorianChange(new Date(Long.MIN_VALUE)); + // and week definition to be compatible with ISO 8601 + setWeekDefinition(MONDAY, 4); + cal = gcal; + break; + case "buddhist": + cal = new BuddhistCalendar(zone, locale); + cal.clear(); + break; + case "japanese": + cal = new JapaneseImperialCalendar(zone, locale, true); + break; + default: + throw new IllegalArgumentException("unknown calendar type: " + type); + } + cal.setLenient(lenient); + if (firstDayOfWeek != 0) { + cal.setFirstDayOfWeek(firstDayOfWeek); + cal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek); + } + if (isInstantSet()) { + cal.setTimeInMillis(instant); + cal.complete(); + return cal; + } + + if (fields != null) { + boolean weekDate = isSet(WEEK_YEAR) + && fields[WEEK_YEAR] > fields[YEAR]; + if (weekDate && !cal.isWeekDateSupported()) { + throw new IllegalArgumentException("week date is unsupported by " + type); + } + + // Set the fields from the min stamp to the max stamp so that + // the fields resolution works in the Calendar. + for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { + for (int index = 0; index <= maxFieldIndex; index++) { + if (fields[index] == stamp) { + cal.set(index, fields[NFIELDS + index]); + break; + } + } + } + + if (weekDate) { + int weekOfYear = isSet(WEEK_OF_YEAR) ? fields[NFIELDS + WEEK_OF_YEAR] : 1; + int dayOfWeek = isSet(DAY_OF_WEEK) + ? fields[NFIELDS + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); + cal.setWeekDate(fields[NFIELDS + WEEK_YEAR], weekOfYear, dayOfWeek); + } + cal.complete(); + } + + return cal; + } + + private void allocateFields() { + if (fields == null) { + fields = new int[NFIELDS * 2]; + nextStamp = MINIMUM_USER_STAMP; + maxFieldIndex = -1; + } + } + + private void internalSet(int field, int value) { + fields[field] = nextStamp++; + if (nextStamp < 0) { + throw new IllegalStateException("stamp counter overflow"); + } + fields[NFIELDS + field] = value; + if (field > maxFieldIndex && field < WEEK_YEAR) { + maxFieldIndex = field; + } + } + + private boolean isInstantSet() { + return nextStamp == COMPUTED; + } + + private boolean isSet(int index) { + return fields != null && fields[index] > UNSET; + } + + private boolean isValidWeekParameter(int value) { + return value > 0 && value <= 7; + } + } + + /** * Constructs a Calendar with the default time zone * and locale. * @see TimeZone#getDefault @@ -1109,7 +1659,7 @@ Calendar cal = null; if (aLocale.hasExtensions()) { - String caltype = aLocale.getUnicodeLocaleType("ca"); + String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": @@ -2002,6 +2552,38 @@ } /** + * Returns an unmodifiable {@code Set} containing all calendar types + * supported by {@code Calendar} in the runtime environment. The available + * calendar types can be used for the Unicode locale extensions. + * The {@code Set} returned contains at least {@code "gregory"}. The + * calendar types don't include aliases, such as {@code "gregorian"} for + * {@code "gregory"}. + * + * @return an unmodifiable {@code Set} containing all available calendar types + * @since 1.8 + * @see #getCalendarType() + * @see Calendar.Builder#setCalendarType(String) + * @see Locale#getUnicodeLocaleType(String) + */ + public static Set getAvailableCalendarTypes() { + return AvailableCalendarTypes.SET; + } + + private static class AvailableCalendarTypes { + private static final Set SET; + static { + Set set = new HashSet<>(3); + set.add("gregory"); + set.add("buddhist"); + set.add("japanese"); + SET = Collections.unmodifiableSet(set); + } + private AvailableCalendarTypes() { + } + } + + /** * Returns the calendar type of this {@code Calendar}. Calendar types are * defined by the Unicode Locale Data Markup Language (LDML) * specification. diff -r 1821066bde82 -r d9e473e85f3c jdk/src/share/classes/java/util/GregorianCalendar.java --- a/jdk/src/share/classes/java/util/GregorianCalendar.java Sun Jan 20 09:37:51 2013 +0000 +++ b/jdk/src/share/classes/java/util/GregorianCalendar.java Mon Jan 21 12:04:55 2013 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2013, 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 @@ -40,7 +40,6 @@ import java.io.IOException; import java.io.ObjectInputStream; -import sun.util.locale.provider.CalendarDataUtility; import sun.util.calendar.BaseCalendar; import sun.util.calendar.CalendarDate; import sun.util.calendar.CalendarSystem; @@ -722,6 +721,18 @@ this.internalSet(MILLISECOND, millis); } + /** + * Constructs an empty GregorianCalendar. + * + * @param zone the given time zone + * @param aLocale the given locale + * @param flag the flag requesting an empty instance + */ + GregorianCalendar(TimeZone zone, Locale locale, boolean flag) { + super(zone, locale); + gdate = (BaseCalendar.Date) gcal.newCalendarDate(getZone()); + } + ///////////////// // Public methods ///////////////// diff -r 1821066bde82 -r d9e473e85f3c jdk/src/share/classes/java/util/JapaneseImperialCalendar.java --- a/jdk/src/share/classes/java/util/JapaneseImperialCalendar.java Sun Jan 20 09:37:51 2013 +0000 +++ b/jdk/src/share/classes/java/util/JapaneseImperialCalendar.java Mon Jan 21 12:04:55 2013 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2013, 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 @@ -302,6 +302,18 @@ } /** + * Constructs an "empty" {@code JapaneseImperialCalendar}. + * + * @param zone the given time zone + * @param aLocale the given locale + * @param flag the flag requesting an empty instance + */ + JapaneseImperialCalendar(TimeZone zone, Locale aLocale, boolean flag) { + super(zone, aLocale); + jdate = jcal.newCalendarDate(zone); + } + + /** * Returns {@code "japanese"} as the calendar type of this {@code * JapaneseImperialCalendar}. * diff -r 1821066bde82 -r d9e473e85f3c jdk/test/java/util/Calendar/Builder/BuilderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/util/Calendar/Builder/BuilderTest.java Mon Jan 21 12:04:55 2013 +0900 @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2013, 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 4745761 + * @summary Unit test for Calendar.Builder. + */ + +import java.util.*; +import static java.util.Calendar.*; + +public class BuilderTest { + private static final Locale jaJPJP = new Locale("ja", "JP", "JP"); + private static final Locale thTH = new Locale("th", "TH"); + private static final TimeZone LA = TimeZone.getTimeZone("America/Los_Angeles"); + private static final TimeZone TOKYO = TimeZone.getTimeZone("Asia/Tokyo"); + private static int error; + + public static void main(String[] args) { + TimeZone tz = TimeZone.getDefault(); + Locale loc = Locale.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("GMT")); + Locale.setDefault(Locale.US); + Calendar.Builder calb; + Calendar cal, expected; + + // set instant + calb = builder(); + long time = System.currentTimeMillis(); + cal = calb.setInstant(time).build(); + expected = new GregorianCalendar(); + expected.setTimeInMillis(time); + check(cal, expected); + + calb = builder(); + cal = calb.setInstant(new Date(time)).build(); + check(cal, expected); + + // set time zone + calb = builder(); + cal = calb.setTimeZone(LA).setInstant(time).build(); + expected = new GregorianCalendar(LA, Locale.US); + expected.setTimeInMillis(time); + check(cal, expected); + + calb = builder(); + cal = calb.setTimeZone(TOKYO).setInstant(time).build(); + expected = new GregorianCalendar(TOKYO, Locale.US); + expected.setTimeInMillis(time); + check(cal, expected); + + // set vs. setFields + calb = builder(); + cal = calb.set(YEAR, 2013).set(MONTH, JANUARY).set(DAY_OF_MONTH, 31) + .set(HOUR_OF_DAY, 10).set(MINUTE, 20).set(SECOND, 30).set(MILLISECOND, 40).build(); + expected = new GregorianCalendar(2013, JANUARY, 31, 10, 20, 30); + expected.set(MILLISECOND, 40); + check(cal, expected); + + calb = builder(); + cal = calb.setFields(YEAR, 2013, MONTH, JANUARY, DAY_OF_MONTH, 31, + HOUR_OF_DAY, 10, MINUTE, 20, SECOND, 30, MILLISECOND, 40).build(); + check(cal, expected); + + // field resolution + calb = builder(); + cal = calb.setFields(YEAR, 2013, MONTH, DECEMBER, DAY_OF_MONTH, 31, + HOUR_OF_DAY, 10, MINUTE, 20, SECOND, 30, MILLISECOND, 40) + .set(DAY_OF_YEAR, 31).build(); // DAY_OF_YEAR wins. + check(cal, expected); + + // setDate/setTimeOfDay + calb = builder(); + cal = calb.setDate(2013, JANUARY, 31).setTimeOfDay(10, 20, 30, 40).build(); + check(cal, expected); + + // week date (ISO 8601) + calb = builder().setCalendarType("iso8601"); + cal = calb.setWeekDate(2013, 1, MONDAY).setTimeOfDay(10, 20, 30).build(); + expected = getISO8601(); + expected.set(2012, DECEMBER, 31, 10, 20, 30); + check(cal, expected); + + // default YEAR == 1970 + cal = builder().setFields(MONTH, JANUARY, + DAY_OF_MONTH, 9).build(); + check(cal, new GregorianCalendar(1970, JANUARY, 9)); + + // no parameters are given. + calb = builder(); + cal = calb.build(); + expected = new GregorianCalendar(); + expected.clear(); + check(cal, expected); + + // Thai Buddhist calendar + calb = builder(); + cal = calb.setCalendarType("buddhist").setDate(2556, JANUARY, 31).build(); + expected = Calendar.getInstance(thTH); + expected.clear(); + expected.set(2556, JANUARY, 31); + check(cal, expected); + // setLocale + calb = builder(); + cal = calb.setLocale(thTH).setDate(2556, JANUARY, 31).build(); + check(cal, expected); + + // Japanese Imperial calendar + cal = builder().setCalendarType("japanese") + .setFields(YEAR, 1, DAY_OF_YEAR, 1).build(); + expected = Calendar.getInstance(jaJPJP); + expected.clear(); + expected.set(1, JANUARY, 8); + check(cal, expected); + // setLocale + calb = builder(); + cal = calb.setLocale(jaJPJP).setFields(YEAR, 1, DAY_OF_YEAR, 1).build(); + check(cal, expected); + + testExceptions(); + } finally { + // Restore default Locale and TimeZone + Locale.setDefault(loc); + TimeZone.setDefault(tz); + } + if (error > 0) { + throw new RuntimeException("Failed"); + } + } + + private static void testExceptions() { + Calendar.Builder calb; + Calendar cal; + + // NPE + try { + calb = builder().setInstant((Date)null); + noException("setInstant((Date)null)"); + } catch (NullPointerException npe) { + } + try { + calb = builder().setCalendarType(null); + noException("setCalendarType(null)"); + } catch (NullPointerException npe) { + } + try { + calb = builder().setLocale(null); + noException("setLocale(null)"); + } catch (NullPointerException npe) { + } + try { + calb = builder().setTimeZone(null); + noException("setTimeZone(null)"); + } catch (NullPointerException npe) { + } + + // IllegalArgumentException + try { + // invalid field index in set + calb = builder().set(100, 2013); + noException("set(100, 2013)"); + } catch (IllegalArgumentException e) { + } + try { + // invalid field index in setField + calb = builder().setFields(100, 2013); + noException("setFields(100, 2013)"); + } catch (IllegalArgumentException e) { + } + try { + // odd number of arguments + calb = builder().setFields(YEAR, 2013, MONTH); + noException("setFields(YEAR, 2013, MONTH)"); + } catch (IllegalArgumentException e) { + } + try { + // unknown calendar type + calb = builder().setCalendarType("foo"); + noException("setCalendarType(\"foo\")"); + } catch (IllegalArgumentException e) { + } + try { + // invalid week definition parameter + calb = builder().setWeekDefinition(8, 1); + noException("setWeekDefinition(8, 1)"); + } catch (IllegalArgumentException e) { + } + try { + // invalid week definition parameter + calb = builder().setWeekDefinition(SUNDAY, 0); + noException("setWeekDefinition(8, 1)"); + } catch (IllegalArgumentException e) { + } + + try { + // sets both instant and field parameters + calb = builder().setInstant(new Date()).setDate(2013, JANUARY, 1); + noException("setInstant(new Date()).setDate(2013, JANUARY, 1)"); + } catch (IllegalStateException e) { + } + try { + // sets both field parameters and instant + calb = builder().setDate(2013, JANUARY, 1).setInstant(new Date()); + noException("setDate(2013, JANUARY, 1).setInstant(new Date())"); + } catch (IllegalStateException e) { + } + try { + // sets inconsistent calendar types + calb = builder().setCalendarType("iso8601").setCalendarType("japanese"); + noException("setCalendarType(\"iso8601\").setCalendarType(\"japanese\")"); + } catch (IllegalStateException e) { + } + + // IllegalArgumentException in build() + calb = nonLenientBuilder().set(MONTH, 100); + checkException(calb, IllegalArgumentException.class); + calb = nonLenientBuilder().setTimeOfDay(23, 59, 70); + checkException(calb, IllegalArgumentException.class); + calb = builder().setCalendarType("japanese").setWeekDate(2013, 1, MONDAY); + checkException(calb, IllegalArgumentException.class); + } + + private static Calendar.Builder builder() { + return new Calendar.Builder(); + } + + private static Calendar.Builder nonLenientBuilder() { + return builder().setLenient(false); + } + + private static Calendar getISO8601() { + GregorianCalendar cal = new GregorianCalendar(); + cal.setFirstDayOfWeek(MONDAY); + cal.setMinimalDaysInFirstWeek(4); + cal.setGregorianChange(new Date(Long.MIN_VALUE)); + cal.clear(); + return cal; + } + + private static void check(Calendar cal, Calendar expected) { + if (!cal.equals(expected)) { + error++; + System.err.println("FAILED:"); + System.err.println("\t cal = "+cal.getTime()); + System.err.println("\texpected = "+expected.getTime()); + System.err.printf("\tcal = %s%n\texp = %s%n", cal, expected); + } + } + + private static void checkException(Calendar.Builder calb, Class exception) { + try { + Calendar cal = calb.build(); + error++; + System.err.println("expected exception: " + exception); + } catch (Exception e) { + if (!e.getClass().equals(exception)) { + error++; + System.err.println("unexpected exception: " + e.getClass() + ", expected: " + exception); + } + } + } + + private static void noException(String msg) { + error++; + System.err.println("no exception with "+msg); + } +} diff -r 1821066bde82 -r d9e473e85f3c jdk/test/java/util/Calendar/CalendarTypeTest.java --- a/jdk/test/java/util/Calendar/CalendarTypeTest.java Sun Jan 20 09:37:51 2013 +0000 +++ b/jdk/test/java/util/Calendar/CalendarTypeTest.java Mon Jan 21 12:04:55 2013 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2013, 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,7 +23,7 @@ /* * @test - * @bug 7151414 + * @bug 7151414 4745761 * @summary Unit test for calendar types */ @@ -39,20 +39,21 @@ new Locale("ja", "JP", "JP"), Locale.forLanguageTag("en-US-u-ca-japanese"), }; - static String[] types = new String[] { - "gregory", + static final String[] TYPES = new String[] { "gregory", "buddhist", - "buddhist", "japanese", - "japanese", + }; + static final String[] ALIASES = new String[] { + "gregorian", + "iso8601", }; public static void main(String[] args) { for (int i = 0; i < locales.length; i++) { Calendar cal = Calendar.getInstance(locales[i]); String type = cal.getCalendarType(); - checkValue("bad calendar type", type, types[i]); + checkValue("bad calendar type", type, TYPES[i/2]); } GregorianCalendar gcal = new GregorianCalendar(); @@ -63,6 +64,21 @@ Calendar k = new Koyomi(); checkValue("bad class name", k.getCalendarType(), k.getClass().getName()); + + Set types = Calendar.getAvailableCalendarTypes(); + if (types.size() != 3) { + throw new RuntimeException("size != 3"); + } + for (String s : TYPES) { + if (!types.contains(s)) { + throw new RuntimeException(s + " not contained"); + } + } + for (String s : ALIASES) { + if (types.contains(s)) { + throw new RuntimeException("alias " + s + " contained"); + } + } } private static void checkValue(String msg, String got, String expected) {