# HG changeset patch # User lana # Date 1283410943 25200 # Node ID 27cfdd5d48d1e4f2e98c21f8f9f18b066ddaf8f1 # Parent 7bbabd9b79e6c2ded15a3482658d183c411cc14c# Parent b0a42d084b26a484ff2a6e50087a2de5ffe386fa Merge diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/make/java/text/base/FILES_java.gmk --- a/jdk/make/java/text/base/FILES_java.gmk Wed Sep 01 16:15:57 2010 -0700 +++ b/jdk/make/java/text/base/FILES_java.gmk Thu Sep 02 00:02:23 2010 -0700 @@ -1,5 +1,5 @@ # -# Copyright (c) 1996, 2007, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1996, 2010, 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 @@ -29,6 +29,7 @@ java/text/AttributedString.java \ java/text/BreakDictionary.java \ java/text/BreakIterator.java \ + java/text/CalendarBuilder.java \ java/text/CharacterIterator.java \ java/text/CharacterIteratorFieldDelegate.java \ java/text/ChoiceFormat.java \ diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/src/share/classes/java/text/CalendarBuilder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/text/CalendarBuilder.java Thu Sep 02 00:02:23 2010 -0700 @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package java.text; + +import java.util.Calendar; +import static java.util.GregorianCalendar.*; + +/** + * {@code CalendarBuilder} keeps field-value pairs for setting + * the calendar fields of the given {@code Calendar}. It has the + * {@link Calendar#FIELD_COUNT FIELD_COUNT}-th field for the week year + * support. Also {@code ISO_DAY_OF_WEEK} is used to specify + * {@code DAY_OF_WEEK} in the ISO day of week numbering. + * + *

{@code CalendarBuilder} retains the semantic of the pseudo + * timestamp for fields. {@code CalendarBuilder} uses a single + * int array combining fields[] and stamp[] of {@code Calendar}. + * + * @author Masayoshi Okutsu + */ +class CalendarBuilder { + /* + * Pseudo time stamp constants used in java.util.Calendar + */ + private static final int UNSET = 0; + private static final int COMPUTED = 1; + private static final int MINIMUM_USER_STAMP = 2; + + private static final int MAX_FIELD = FIELD_COUNT + 1; + + public static final int WEEK_YEAR = FIELD_COUNT; + public static final int ISO_DAY_OF_WEEK = 1000; // pseudo field index + + // stamp[] (lower half) and field[] (upper half) combined + private final int[] field; + private int nextStamp; + private int maxFieldIndex; + + CalendarBuilder() { + field = new int[MAX_FIELD * 2]; + nextStamp = MINIMUM_USER_STAMP; + maxFieldIndex = -1; + } + + CalendarBuilder set(int index, int value) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + value = toCalendarDayOfWeek(value); + } + field[index] = nextStamp++; + field[MAX_FIELD + index] = value; + if (index > maxFieldIndex && index < FIELD_COUNT) { + maxFieldIndex = index; + } + return this; + } + + CalendarBuilder addYear(int value) { + field[MAX_FIELD + YEAR] += value; + field[MAX_FIELD + WEEK_YEAR] += value; + return this; + } + + boolean isSet(int index) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + } + return field[index] > UNSET; + } + + Calendar establish(Calendar cal) { + boolean weekDate = isSet(WEEK_YEAR) + && field[WEEK_YEAR] > field[YEAR]; + if (weekDate && !cal.isWeekDateSupported()) { + // Use YEAR instead + if (!isSet(YEAR)) { + set(YEAR, field[MAX_FIELD + WEEK_YEAR]); + } + weekDate = false; + } + + cal.clear(); + // Set the fields from the min stamp to the max stamp so that + // the field resolution works in the Calendar. + for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { + for (int index = 0; index <= maxFieldIndex; index++) { + if (field[index] == stamp) { + cal.set(index, field[MAX_FIELD + index]); + break; + } + } + } + + if (weekDate) { + int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1; + int dayOfWeek = isSet(DAY_OF_WEEK) ? + field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); + if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) { + if (dayOfWeek >= 8) { + dayOfWeek--; + weekOfYear += dayOfWeek / 7; + dayOfWeek = (dayOfWeek % 7) + 1; + } else { + while (dayOfWeek <= 0) { + dayOfWeek += 7; + weekOfYear--; + } + } + dayOfWeek = toCalendarDayOfWeek(dayOfWeek); + } + cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek); + } + return cal; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("CalendarBuilder:["); + for (int i = 0; i < field.length; i++) { + if (isSet(i)) { + sb.append(i).append('=').append(field[MAX_FIELD + i]).append(','); + } + } + int lastIndex = sb.length() - 1; + if (sb.charAt(lastIndex) == ',') { + sb.setLength(lastIndex); + } + sb.append(']'); + return sb.toString(); + } + + static int toISODayOfWeek(int calendarDayOfWeek) { + return calendarDayOfWeek == SUNDAY ? 7 : calendarDayOfWeek - 1; + } + + static int toCalendarDayOfWeek(int isoDayOfWeek) { + if (!isValidDayOfWeek(isoDayOfWeek)) { + // adjust later for lenient mode + return isoDayOfWeek; + } + return isoDayOfWeek == 7 ? SUNDAY : isoDayOfWeek + 1; + } + + static boolean isValidDayOfWeek(int dayOfWeek) { + return dayOfWeek > 0 && dayOfWeek <= 7; + } +} diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/src/share/classes/java/text/DateFormatSymbols.java --- a/jdk/src/share/classes/java/text/DateFormatSymbols.java Wed Sep 01 16:15:57 2010 -0700 +++ b/jdk/src/share/classes/java/text/DateFormatSymbols.java Thu Sep 02 00:02:23 2010 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2010, 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 @@ -226,7 +226,29 @@ * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. * All locales use the same these unlocalized pattern characters. */ - static final String patternChars = "GyMdkHmsSEDFwWahKzZ"; + static final String patternChars = "GyMdkHmsSEDFwWahKzZYu"; + + static final int PATTERN_ERA = 0; // G + static final int PATTERN_YEAR = 1; // y + static final int PATTERN_MONTH = 2; // M + static final int PATTERN_DAY_OF_MONTH = 3; // d + static final int PATTERN_HOUR_OF_DAY1 = 4; // k + static final int PATTERN_HOUR_OF_DAY0 = 5; // H + static final int PATTERN_MINUTE = 6; // m + static final int PATTERN_SECOND = 7; // s + static final int PATTERN_MILLISECOND = 8; // S + static final int PATTERN_DAY_OF_WEEK = 9; // E + static final int PATTERN_DAY_OF_YEAR = 10; // D + static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F + static final int PATTERN_WEEK_OF_YEAR = 12; // w + static final int PATTERN_WEEK_OF_MONTH = 13; // W + static final int PATTERN_AM_PM = 14; // a + static final int PATTERN_HOUR1 = 15; // h + static final int PATTERN_HOUR0 = 16; // K + static final int PATTERN_ZONE_NAME = 17; // z + static final int PATTERN_ZONE_VALUE = 18; // Z + static final int PATTERN_WEEK_YEAR = 19; // Y + static final int PATTERN_ISO_DAY_OF_WEEK = 20; // u /** * Localized date-time pattern characters. For example, a locale may @@ -505,7 +527,7 @@ * @return the localized date-time pattern characters. */ public String getLocalPatternChars() { - return new String(localPatternChars); + return localPatternChars; } /** @@ -514,7 +536,8 @@ * pattern characters. */ public void setLocalPatternChars(String newLocalPatternChars) { - localPatternChars = new String(newLocalPatternChars); + // Call toString() to throw an NPE in case the argument is null + localPatternChars = newLocalPatternChars.toString(); } /** @@ -699,7 +722,7 @@ } else { dst.zoneStrings = null; } - dst.localPatternChars = new String (src.localPatternChars); + dst.localPatternChars = src.localPatternChars; } /** diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/src/share/classes/java/text/SimpleDateFormat.java --- a/jdk/src/share/classes/java/text/SimpleDateFormat.java Wed Sep 01 16:15:57 2010 -0700 +++ b/jdk/src/share/classes/java/text/SimpleDateFormat.java Thu Sep 02 00:02:23 2010 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2010, 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 @@ -55,6 +55,8 @@ import sun.util.calendar.ZoneInfoFile; import sun.util.resources.LocaleData; +import static java.text.DateFormatSymbols.*; + /** * SimpleDateFormat is a concrete class for formatting and * parsing dates in a locale-sensitive manner. It allows for formatting @@ -108,40 +110,50 @@ * Year * 1996; 96 * + * Y + * Week year + * Year + * 2009; 09 + * * M * Month in year * Month * July; Jul; 07 - * + * * w * Week in year * Number * 27 - * + * * W * Week in month * Number * 2 - * + * * D * Day in year * Number * 189 - * + * * d * Day in month * Number * 10 - * + * * F * Day of week in month * Number * 2 - * + * * E - * Day in week + * Day name in week * Text * Tuesday; Tue + * + * u + * Day number of week (1 = Monday, ..., 7 = Sunday) + * Number + * 1 * * a * Am/pm marker @@ -202,12 +214,12 @@ * the full form is used; otherwise a short or abbreviated form * is used if available. * For parsing, both forms are accepted, independent of the number - * of pattern letters. + * of pattern letters.

*

  • Number: * For formatting, the number of pattern letters is the minimum * number of digits, and shorter numbers are zero-padded to this amount. * For parsing, the number of pattern letters is ignored unless - * it's needed to separate two adjacent fields. + * it's needed to separate two adjacent fields.

  • *
  • Year: * If the formatter's {@link #getCalendar() Calendar} is the Gregorian * calendar, the following rules are applied.
    @@ -239,11 +251,20 @@ * letters is 4 or more, a calendar specific {@linkplain * Calendar#LONG long form} is used. Otherwise, a calendar * specific {@linkplain Calendar#SHORT short or abbreviated form} - * is used. + * is used.
    + *
    + * If week year {@code 'Y'} is specified and the {@linkplain + * #getCalendar() calendar} doesn't support any week + * years, the calendar year ({@code 'y'}) is used instead. The + * support of week years can be tested with a call to {@link + * DateFormat#getCalendar() getCalendar()}.{@link + * java.util.Calendar#isWeekDateSupported() + * isWeekDateSupported()}.

  • *
  • Month: * If the number of pattern letters is 3 or more, the month is * interpreted as text; otherwise, - * it is interpreted as a number. + * it is interpreted as a number.

  • *
  • General time zone: * Time zones are interpreted as text if they have * names. For time zones representing a GMT offset value, the @@ -264,7 +285,7 @@ * 00 and 59. The format is locale independent and digits must be taken * from the Basic Latin block of the Unicode standard. *

    For parsing, RFC 822 time zones are also - * accepted. + * accepted.

  • *
  • RFC 822 time zone: * For formatting, the RFC 822 4-digit time zone format is used: *
    @@ -321,6 +342,9 @@
      *     
      *         "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
      *         2001-07-04T12:08:56.235-0700
    + *     
    + *         "YYYY-'W'ww-u"
    + *         2001-W27-3
      * 
      * 
      *
    @@ -877,7 +901,7 @@
          * @param pos the formatting position. On input: an alignment field,
          * if desired. On output: the offsets of the alignment field.
          * @return the formatted date-time string.
    -     * @exception NullPointerException if the given date is null
    +     * @exception NullPointerException if the given {@code date} is {@code null}.
          */
         public StringBuffer format(Date date, StringBuffer toAppendTo,
                                    FieldPosition pos)
    @@ -968,7 +992,10 @@
             Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
             Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
             Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
    -        Calendar.ZONE_OFFSET
    +        Calendar.ZONE_OFFSET,
    +        // Pseudo Calendar fields
    +        CalendarBuilder.WEEK_YEAR,
    +        CalendarBuilder.ISO_DAY_OF_WEEK
         };
     
         // Map index into pattern character string to DateFormat field number
    @@ -982,6 +1009,7 @@
             DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
             DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
             DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD,
    +        DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD
         };
     
         // Maps from DecimalFormatSymbols index to Field constant
    @@ -993,6 +1021,7 @@
             Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH,
             Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE,
             Field.TIME_ZONE,
    +        Field.YEAR, Field.DAY_OF_WEEK
         };
     
         /**
    @@ -1007,9 +1036,24 @@
             int     beginOffset = buffer.length();
     
             int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
    -        int value = calendar.get(field);
    +        int value;
    +        if (field == CalendarBuilder.WEEK_YEAR) {
    +            if (calendar.isWeekDateSupported()) {
    +                value = calendar.getWeekYear();
    +            } else {
    +                // use calendar year 'y' instead
    +                patternCharIndex = PATTERN_YEAR;
    +                field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
    +                value = calendar.get(field);
    +            }
    +        } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
    +            value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
    +        } else {
    +            value = calendar.get(field);
    +        }
    +
             int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
    -        if (!useDateFormatSymbols) {
    +        if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
                 current = calendar.getDisplayName(field, style, locale);
             }
     
    @@ -1018,7 +1062,7 @@
             // zeroPaddingNumber() must be fixed.
     
             switch (patternCharIndex) {
    -        case 0: // 'G' - ERA
    +        case PATTERN_ERA: // 'G'
                 if (useDateFormatSymbols) {
                     String[] eras = formatData.getEras();
                     if (value < eras.length)
    @@ -1028,7 +1072,8 @@
                     current = "";
                 break;
     
    -        case 1: // 'y' - YEAR
    +        case PATTERN_WEEK_YEAR: // 'Y'
    +        case PATTERN_YEAR:      // 'y'
                 if (calendar instanceof GregorianCalendar) {
                     if (count != 2)
                         zeroPaddingNumber(value, count, maxIntCount, buffer);
    @@ -1042,7 +1087,7 @@
                 }
                 break;
     
    -        case 2: // 'M' - MONTH
    +        case PATTERN_MONTH: // 'M'
                 if (useDateFormatSymbols) {
                     String[] months;
                     if (count >= 4) {
    @@ -1062,7 +1107,7 @@
                 }
                 break;
     
    -        case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
    +        case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
                 if (current == null) {
                     if (value == 0)
                         zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
    @@ -1072,7 +1117,7 @@
                 }
                 break;
     
    -        case 9: // 'E' - DAY_OF_WEEK
    +        case PATTERN_DAY_OF_WEEK: // 'E'
                 if (useDateFormatSymbols) {
                     String[] weekdays;
                     if (count >= 4) {
    @@ -1085,14 +1130,14 @@
                 }
                 break;
     
    -        case 14:    // 'a' - AM_PM
    +        case PATTERN_AM_PM:    // 'a'
                 if (useDateFormatSymbols) {
                     String[] ampm = formatData.getAmPmStrings();
                     current = ampm[value];
                 }
                 break;
     
    -        case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
    +        case PATTERN_HOUR1:    // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
                 if (current == null) {
                     if (value == 0)
                         zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1,
    @@ -1102,7 +1147,7 @@
                 }
                 break;
     
    -        case 17: // 'z' - ZONE_OFFSET
    +        case PATTERN_ZONE_NAME: // 'z'
                 if (current == null) {
                     if (formatData.locale == null || formatData.isZoneStringsSet) {
                         int zoneIndex =
    @@ -1129,7 +1174,7 @@
                 }
                 break;
     
    -        case 18: // 'Z' - ZONE_OFFSET ("-/+hhmm" form)
    +        case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
                 value = (calendar.get(Calendar.ZONE_OFFSET) +
                          calendar.get(Calendar.DST_OFFSET)) / 60000;
     
    @@ -1145,16 +1190,17 @@
                 break;
     
             default:
    -            // case 3: // 'd' - DATE
    -            // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
    -            // case 6: // 'm' - MINUTE
    -            // case 7: // 's' - SECOND
    -            // case 8: // 'S' - MILLISECOND
    -            // case 10: // 'D' - DAY_OF_YEAR
    -            // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
    -            // case 12: // 'w' - WEEK_OF_YEAR
    -            // case 13: // 'W' - WEEK_OF_MONTH
    -            // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
    +     // case PATTERN_DAY_OF_MONTH:         // 'd'
    +     // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
    +     // case PATTERN_MINUTE:               // 'm'
    +     // case PATTERN_SECOND:               // 's'
    +     // case PATTERN_MILLISECOND:          // 'S'
    +     // case PATTERN_DAY_OF_YEAR:          // 'D'
    +     // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
    +     // case PATTERN_WEEK_OF_YEAR:         // 'w'
    +     // case PATTERN_WEEK_OF_MONTH:        // 'W'
    +     // case PATTERN_HOUR0:                // 'K' eg, 11PM + 1 hour =>> 0 AM
    +     // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' pseudo field, Monday = 1, ..., Sunday = 7
                 if (current == null) {
                     zeroPaddingNumber(value, count, maxIntCount, buffer);
                 }
    @@ -1264,10 +1310,9 @@
             int oldStart = start;
             int textLength = text.length();
     
    -        calendar.clear(); // Clears all the time fields
    -
             boolean[] ambiguousYear = {false};
     
    +        CalendarBuilder calb = new CalendarBuilder();
     
             for (int i = 0; i < compiledPattern.length; ) {
                 int tag = compiledPattern[i] >>> 8;
    @@ -1340,7 +1385,7 @@
                     }
                     start = subParse(text, start, tag, count, obeyCount,
                                      ambiguousYear, pos,
    -                                 useFollowingMinusSignAsDelimiter);
    +                                 useFollowingMinusSignAsDelimiter, calb);
                     if (start < 0) {
                         pos.index = oldStart;
                         return null;
    @@ -1354,46 +1399,16 @@
     
             pos.index = start;
     
    -        // This part is a problem:  When we call parsedDate.after, we compute the time.
    -        // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
    -        // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
    -        // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
    -        // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
    -        // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
    -        // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
    -        // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
    -        /*
    -        Date parsedDate = calendar.getTime();
    -        if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
    -            calendar.add(Calendar.YEAR, 100);
    -            parsedDate = calendar.getTime();
    -        }
    -        */
    -        // Because of the above condition, save off the fields in case we need to readjust.
    -        // The procedure we use here is not particularly efficient, but there is no other
    -        // way to do this given the API restrictions present in Calendar.  We minimize
    -        // inefficiency by only performing this computation when it might apply, that is,
    -        // when the two-digit year is equal to the start year, and thus might fall at the
    -        // front or the back of the default century.  This only works because we adjust
    -        // the year correctly to start with in other cases -- see subParse().
             Date parsedDate;
             try {
    -            if (ambiguousYear[0]) // If this is true then the two-digit year == the default start year
    -            {
    -                // We need a copy of the fields, and we need to avoid triggering a call to
    -                // complete(), which will recalculate the fields.  Since we can't access
    -                // the fields[] array in Calendar, we clone the entire object.  This will
    -                // stop working if Calendar.clone() is ever rewritten to call complete().
    -                Calendar savedCalendar = (Calendar)calendar.clone();
    -                parsedDate = calendar.getTime();
    -                if (parsedDate.before(defaultCenturyStart))
    -                {
    -                    // We can't use add here because that does a complete() first.
    -                    savedCalendar.set(Calendar.YEAR, defaultCenturyStartYear + 100);
    -                    parsedDate = savedCalendar.getTime();
    +            parsedDate = calb.establish(calendar).getTime();
    +            // If the year value is ambiguous,
    +            // then the two-digit year == the default start year
    +            if (ambiguousYear[0]) {
    +                if (parsedDate.before(defaultCenturyStart)) {
    +                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                     }
                 }
    -            else parsedDate = calendar.getTime();
             }
             // An IllegalArgumentException will be thrown by Calendar.getTime()
             // if any fields are out of range, e.g., MONTH == 17.
    @@ -1415,7 +1430,7 @@
          * @return the new start position if matching succeeded; a negative number
          * indicating matching failure, otherwise.
          */
    -    private int matchString(String text, int start, int field, String[] data)
    +    private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
         {
             int i = 0;
             int count = data.length;
    @@ -1441,7 +1456,7 @@
             }
             if (bestMatch >= 0)
             {
    -            calendar.set(field, bestMatch);
    +            calb.set(field, bestMatch);
                 return start + bestMatchLength;
             }
             return -start;
    @@ -1452,7 +1467,8 @@
          * String[]). This method takes a Map instead of
          * String[].
          */
    -    private int matchString(String text, int start, int field, Map data) {
    +    private int matchString(String text, int start, int field,
    +                            Map data, CalendarBuilder calb) {
             if (data != null) {
                 String bestMatch = null;
     
    @@ -1466,7 +1482,7 @@
                 }
     
                 if (bestMatch != null) {
    -                calendar.set(field, data.get(bestMatch));
    +                calb.set(field, data.get(bestMatch));
                     return start + bestMatch.length();
                 }
             }
    @@ -1486,11 +1502,22 @@
             return -1;
         }
     
    +    private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,
    +                                   String[][] zoneStrings) {
    +        int index = standardIndex + 2;
    +        String zoneName  = zoneStrings[zoneIndex][index];
    +        if (text.regionMatches(true, start,
    +                               zoneName, 0, zoneName.length())) {
    +            return true;
    +        }
    +        return false;
    +    }
    +
         /**
          * find time zone 'text' matched zoneStrings and set to internal
          * calendar.
          */
    -    private int subParseZoneString(String text, int start) {
    +    private int subParseZoneString(String text, int start, CalendarBuilder calb) {
             boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
             TimeZone currentTimeZone = getTimeZone();
     
    @@ -1524,6 +1551,7 @@
                     }
                 }
             }
    +
             if (tz == null) {
                 int len = zoneStrings.length;
                 for (int i = 0; i < len; i++) {
    @@ -1549,8 +1577,8 @@
                 // determine the local time. (6645292)
                 int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;
                 if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {
    -                calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
    -                calendar.set(Calendar.DST_OFFSET, dstAmount);
    +                calb.set(Calendar.ZONE_OFFSET, tz.getRawOffset())
    +                    .set(Calendar.DST_OFFSET, dstAmount);
                 }
                 return (start + zoneNames[nameIndex].length());
             }
    @@ -1577,11 +1605,15 @@
         private int subParse(String text, int start, int patternCharIndex, int count,
                              boolean obeyCount, boolean[] ambiguousYear,
                              ParsePosition origPos,
    -                         boolean useFollowingMinusSignAsDelimiter) {
    +                         boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
             Number number = null;
             int value = 0;
             ParsePosition pos = new ParsePosition(0);
             pos.index = start;
    +        if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
    +            // use calendar year 'y' instead
    +            patternCharIndex = PATTERN_YEAR;
    +        }
             int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
     
             // If there are any spaces here, skip over them.  If we hit the end
    @@ -1602,10 +1634,11 @@
                 // a number value.  We handle further, more generic cases below.  We need
                 // to handle some of them here because some fields require extra processing on
                 // the parsed value.
    -            if (patternCharIndex == 4 /* HOUR_OF_DAY1_FIELD */ ||
    -                patternCharIndex == 15 /* HOUR1_FIELD */ ||
    -                (patternCharIndex == 2 /* MONTH_FIELD */ && count <= 2) ||
    -                patternCharIndex == 1 /* YEAR_FIELD */) {
    +            if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
    +                patternCharIndex == PATTERN_HOUR1 ||
    +                (patternCharIndex == PATTERN_MONTH && count <= 2) ||
    +                patternCharIndex == PATTERN_YEAR ||
    +                patternCharIndex == PATTERN_WEEK_YEAR) {
                     // It would be good to unify this with the obeyCount logic below,
                     // but that's going to be difficult.
                     if (obeyCount) {
    @@ -1617,7 +1650,7 @@
                         number = numberFormat.parse(text, pos);
                     }
                     if (number == null) {
    -                    if (patternCharIndex != 1 || calendar instanceof GregorianCalendar) {
    +                    if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
                             break parsing;
                         }
                     } else {
    @@ -1638,33 +1671,34 @@
     
                 int index;
                 switch (patternCharIndex) {
    -            case 0: // 'G' - ERA
    +            case PATTERN_ERA: // 'G'
                     if (useDateFormatSymbols) {
    -                    if ((index = matchString(text, start, Calendar.ERA, formatData.getEras())) > 0) {
    +                    if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
                             return index;
                         }
                     } else {
                         Map map = calendar.getDisplayNames(field,
                                                                             Calendar.ALL_STYLES,
                                                                             locale);
    -                    if ((index = matchString(text, start, field, map)) > 0) {
    +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
                             return index;
                         }
                     }
                     break parsing;
     
    -            case 1: // 'y' - YEAR
    +            case PATTERN_WEEK_YEAR: // 'Y'
    +            case PATTERN_YEAR:      // 'y'
                     if (!(calendar instanceof GregorianCalendar)) {
                         // calendar might have text representations for year values,
                         // such as "\u5143" in JapaneseImperialCalendar.
                         int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
                         Map map = calendar.getDisplayNames(field, style, locale);
                         if (map != null) {
    -                        if ((index = matchString(text, start, field, map)) > 0) {
    +                        if ((index = matchString(text, start, field, map, calb)) > 0) {
                                 return index;
                             }
                         }
    -                    calendar.set(field, value);
    +                    calb.set(field, value);
                         return pos.index;
                     }
     
    @@ -1676,8 +1710,7 @@
                     // is treated literally:  "2250", "-1", "1", "002".
                     if (count <= 2 && (pos.index - start) == 2
                         && Character.isDigit(text.charAt(start))
    -                    && Character.isDigit(text.charAt(start+1)))
    -                {
    +                    && Character.isDigit(text.charAt(start+1))) {
                         // Assume for example that the defaultCenturyStart is 6/18/1903.
                         // This means that two-digit years will be forced into the range
                         // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
    @@ -1691,16 +1724,16 @@
                         value += (defaultCenturyStartYear/100)*100 +
                             (value < ambiguousTwoDigitYear ? 100 : 0);
                     }
    -                calendar.set(Calendar.YEAR, value);
    +                calb.set(field, value);
                     return pos.index;
     
    -            case 2: // 'M' - MONTH
    +            case PATTERN_MONTH: // 'M'
                     if (count <= 2) // i.e., M or MM.
                     {
                         // Don't want to parse the month if it is a string
                         // while pattern uses numeric style: M or MM.
                         // [We computed 'value' above.]
    -                    calendar.set(Calendar.MONTH, value - 1);
    +                    calb.set(Calendar.MONTH, value - 1);
                         return pos.index;
                     }
     
    @@ -1710,50 +1743,50 @@
                         // Try count == 4 first:
                         int newStart = 0;
                         if ((newStart = matchString(text, start, Calendar.MONTH,
    -                                                formatData.getMonths())) > 0) {
    +                                                formatData.getMonths(), calb)) > 0) {
                             return newStart;
                         }
                         // count == 4 failed, now try count == 3
                         if ((index = matchString(text, start, Calendar.MONTH,
    -                                             formatData.getShortMonths())) > 0) {
    +                                             formatData.getShortMonths(), calb)) > 0) {
                             return index;
                         }
                     } else {
                         Map map = calendar.getDisplayNames(field,
                                                                             Calendar.ALL_STYLES,
                                                                             locale);
    -                    if ((index = matchString(text, start, field, map)) > 0) {
    +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
                             return index;
                         }
                     }
                     break parsing;
     
    -            case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
    +            case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
                     // [We computed 'value' above.]
                     if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
    -                calendar.set(Calendar.HOUR_OF_DAY, value);
    +                calb.set(Calendar.HOUR_OF_DAY, value);
                     return pos.index;
     
    -            case 9:
    -                { // 'E' - DAY_OF_WEEK
    +            case PATTERN_DAY_OF_WEEK:  // 'E'
    +                {
                         if (useDateFormatSymbols) {
                             // Want to be able to parse both short and long forms.
                             // Try count == 4 (DDDD) first:
                             int newStart = 0;
                             if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
    -                                                  formatData.getWeekdays())) > 0) {
    +                                                  formatData.getWeekdays(), calb)) > 0) {
                                 return newStart;
                             }
                             // DDDD failed, now try DDD
                             if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
    -                                                 formatData.getShortWeekdays())) > 0) {
    +                                                 formatData.getShortWeekdays(), calb)) > 0) {
                                 return index;
                             }
                         } else {
                             int[] styles = { Calendar.LONG, Calendar.SHORT };
                             for (int style : styles) {
                                 Map map = calendar.getDisplayNames(field, style, locale);
    -                            if ((index = matchString(text, start, field, map)) > 0) {
    +                            if ((index = matchString(text, start, field, map, calb)) > 0) {
                                     return index;
                                 }
                             }
    @@ -1761,27 +1794,28 @@
                     }
                     break parsing;
     
    -            case 14:    // 'a' - AM_PM
    +            case PATTERN_AM_PM:    // 'a'
                     if (useDateFormatSymbols) {
    -                    if ((index = matchString(text, start, Calendar.AM_PM, formatData.getAmPmStrings())) > 0) {
    +                    if ((index = matchString(text, start, Calendar.AM_PM,
    +                                             formatData.getAmPmStrings(), calb)) > 0) {
                             return index;
                         }
                     } else {
                         Map map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
    -                    if ((index = matchString(text, start, field, map)) > 0) {
    +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
                             return index;
                         }
                     }
                     break parsing;
     
    -            case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
    +            case PATTERN_HOUR1: // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
                     // [We computed 'value' above.]
                     if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) value = 0;
    -                calendar.set(Calendar.HOUR, value);
    +                calb.set(Calendar.HOUR, value);
                     return pos.index;
     
    -            case 17: // 'z' - ZONE_OFFSET
    -            case 18: // 'Z' - ZONE_OFFSET
    +            case PATTERN_ZONE_NAME:  // 'z'
    +            case PATTERN_ZONE_VALUE: // 'Z'
                     // First try to parse generic forms such as GMT-07:00. Do this first
                     // in case localized TimeZoneNames contains the string "GMT"
                     // for a zone; in that case, we don't want to match the first three
    @@ -1797,7 +1831,7 @@
                         if ((text.length() - start) >= GMT.length() &&
                             text.regionMatches(true, start, GMT, 0, GMT.length())) {
                             int num;
    -                        calendar.set(Calendar.DST_OFFSET, 0);
    +                        calb.set(Calendar.DST_OFFSET, 0);
                             pos.index = start + GMT.length();
     
                             try { // try-catch for "GMT" only time zone string
    @@ -1810,8 +1844,8 @@
                             }
                             catch(StringIndexOutOfBoundsException e) {}
     
    -                        if (sign == 0) {        /* "GMT" without offset */
    -                            calendar.set(Calendar.ZONE_OFFSET, 0);
    +                        if (sign == 0) {    /* "GMT" without offset */
    +                            calb.set(Calendar.ZONE_OFFSET, 0);
                                 return pos.index;
                             }
     
    @@ -1875,7 +1909,7 @@
                                     sign = -1;
                                 } else {
                                     // Try parsing the text as a time zone name (abbr).
    -                                int i = subParseZoneString(text, pos.index);
    +                                int i = subParseZoneString(text, pos.index, calb);
                                     if (i != 0) {
                                         return i;
                                     }
    @@ -1933,24 +1967,24 @@
                         // arrive here if the form GMT+/-... or an RFC 822 form was seen.
                         if (sign != 0) {
                             offset *= MILLIS_PER_MINUTE * sign;
    -                        calendar.set(Calendar.ZONE_OFFSET, offset);
    -                        calendar.set(Calendar.DST_OFFSET, 0);
    +                        calb.set(Calendar.ZONE_OFFSET, offset).set(Calendar.DST_OFFSET, 0);
                             return ++pos.index;
                         }
                     }
                     break parsing;
     
                 default:
    -                // case 3: // 'd' - DATE
    -                // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
    -                // case 6: // 'm' - MINUTE
    -                // case 7: // 's' - SECOND
    -                // case 8: // 'S' - MILLISECOND
    -                // case 10: // 'D' - DAY_OF_YEAR
    -                // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
    -                // case 12: // 'w' - WEEK_OF_YEAR
    -                // case 13: // 'W' - WEEK_OF_MONTH
    -                // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
    +         // case PATTERN_DAY_OF_MONTH:         // 'd'
    +         // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
    +         // case PATTERN_MINUTE:               // 'm'
    +         // case PATTERN_SECOND:               // 's'
    +         // case PATTERN_MILLISECOND:          // 'S'
    +         // case PATTERN_DAY_OF_YEAR:          // 'D'
    +         // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
    +         // case PATTERN_WEEK_OF_YEAR:         // 'w'
    +         // case PATTERN_WEEK_OF_MONTH:        // 'W'
    +         // case PATTERN_HOUR0:                // 'K' 0-based.  eg, 11PM + 1 hour =>> 0 AM
    +         // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' (pseudo field);
     
                     // Handle "generic" fields
                     if (obeyCount) {
    @@ -1973,7 +2007,7 @@
                             pos.index--;
                         }
     
    -                    calendar.set(field, value);
    +                    calb.set(field, value);
                         return pos.index;
                     }
                     break parsing;
    @@ -2020,11 +2054,18 @@
                         inQuote = true;
                     else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                         int ci = from.indexOf(c);
    -                    if (ci == -1)
    +                    if (ci >= 0) {
    +                        // patternChars is longer than localPatternChars due
    +                        // to serialization compatibility. The pattern letters
    +                        // unsupported by localPatternChars pass through.
    +                        if (ci < to.length()) {
    +                            c = to.charAt(ci);
    +                        }
    +                    } else {
                             throw new IllegalArgumentException("Illegal pattern " +
                                                                " character '" +
                                                                c + "'");
    -                    c = to.charAt(ci);
    +                    }
                     }
                 }
                 result.append(c);
    diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/src/share/classes/java/util/Calendar.java
    --- a/jdk/src/share/classes/java/util/Calendar.java	Wed Sep 01 16:15:57 2010 -0700
    +++ b/jdk/src/share/classes/java/util/Calendar.java	Thu Sep 02 00:02:23 2010 -0700
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 1996, 2007, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 1996, 2010, 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
    @@ -119,7 +119,7 @@
      * calculating its time or calendar field values if any out-of-range field
      * value has been set.
      *
    - * 

    First Week

    + *

    First Week

    * * Calendar defines a locale-specific seven day week using two * parameters: the first day of the week and the minimal days in first week @@ -2196,6 +2196,101 @@ } /** + * Returns whether this {@code Calendar} supports week dates. + * + *

    The default implementation of this method returns {@code false}. + * + * @return {@code true} if this {@code Calendar} supports week dates; + * {@code false} otherwise. + * @see #getWeekYear() + * @see #setWeekDate(int,int,int) + * @see #getWeeksInWeekYear() + * @since 1.7 + */ + public boolean isWeekDateSupported() { + return false; + } + + /** + * Returns the week year represented by this {@code Calendar}. The + * week year is in sync with the week cycle. The {@linkplain + * #getFirstDayOfWeek() first day of the first week} is the first + * day of the week year. + * + *

    The default implementation of this method throws an + * {@link UnsupportedOperationException}. + * + * @return the week year of this {@code Calendar} + * @exception UnsupportedOperationException + * if any week year numbering isn't supported + * in this {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public int getWeekYear() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the date of this {@code Calendar} with the the given date + * specifiers - week year, week of year, and day of week. + * + *

    Unlike the {@code set} method, all of the calendar fields + * and {@code time} values are calculated upon return. + * + *

    If {@code weekOfYear} is out of the valid week-of-year range + * in {@code weekYear}, the {@code weekYear} and {@code + * weekOfYear} values are adjusted in lenient mode, or an {@code + * IllegalArgumentException} is thrown in non-lenient mode. + * + *

    The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @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 #DAY_OF_WEEK} field: {@link + * #SUNDAY}, ..., {@link #SATURDAY}. + * @exception IllegalArgumentException + * if any of the given date specifiers is invalid + * or any of the calendar fields are inconsistent + * with the given date specifiers in non-lenient mode + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the number of weeks in the week year represented by this + * {@code Calendar}. + * + *

    The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @return the number of weeks in the week year. + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #WEEK_OF_YEAR + * @see #isWeekDateSupported() + * @see #getWeekYear() + * @see #getActualMaximum(int) + * @since 1.7 + */ + public int getWeeksInWeekYear() { + throw new UnsupportedOperationException(); + } + + /** * Returns the minimum value for the given calendar field of this * Calendar instance. The minimum value is defined as * the smallest value returned by the {@link #get(int) get} method diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/src/share/classes/java/util/GregorianCalendar.java --- a/jdk/src/share/classes/java/util/GregorianCalendar.java Wed Sep 01 16:15:57 2010 -0700 +++ b/jdk/src/share/classes/java/util/GregorianCalendar.java Thu Sep 02 00:02:23 2010 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2010, 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 @@ -88,23 +88,49 @@ * adjustment may be made if desired for dates that are prior to the Gregorian * changeover and which fall between January 1 and March 24. * - *

    Values calculated for the WEEK_OF_YEAR field range from 1 to - * 53. Week 1 for a year is the earliest seven day period starting on - * getFirstDayOfWeek() that contains at least - * getMinimalDaysInFirstWeek() days from that year. It thus - * depends on the values of getMinimalDaysInFirstWeek(), - * getFirstDayOfWeek(), and the day of the week of January 1. - * Weeks between week 1 of one year and week 1 of the following year are - * numbered sequentially from 2 to 52 or 53 (as needed). - - *

    For example, January 1, 1998 was a Thursday. If - * getFirstDayOfWeek() is MONDAY and - * getMinimalDaysInFirstWeek() is 4 (these are the values - * reflecting ISO 8601 and many national standards), then week 1 of 1998 starts - * on December 29, 1997, and ends on January 4, 1998. If, however, - * getFirstDayOfWeek() is SUNDAY, then week 1 of 1998 - * starts on January 4, 1998, and ends on January 10, 1998; the first three days - * of 1998 then are part of week 53 of 1997. + *

    Week Of Year and Week Year

    + * + *

    Values calculated for the {@link Calendar#WEEK_OF_YEAR + * WEEK_OF_YEAR} field range from 1 to 53. The first week of a + * calendar year is the earliest seven day period starting on {@link + * Calendar#getFirstDayOfWeek() getFirstDayOfWeek()} that contains at + * least {@link Calendar#getMinimalDaysInFirstWeek() + * getMinimalDaysInFirstWeek()} days from that year. It thus depends + * on the values of {@code getMinimalDaysInFirstWeek()}, {@code + * getFirstDayOfWeek()}, and the day of the week of January 1. Weeks + * between week 1 of one year and week 1 of the following year + * (exclusive) are numbered sequentially from 2 to 52 or 53 (except + * for year(s) involved in the Julian-Gregorian transition). + * + *

    The {@code getFirstDayOfWeek()} and {@code + * getMinimalDaysInFirstWeek()} values are initialized using + * locale-dependent resources when constructing a {@code + * GregorianCalendar}. The week + * determination is compatible with the ISO 8601 standard when {@code + * getFirstDayOfWeek()} is {@code MONDAY} and {@code + * getMinimalDaysInFirstWeek()} is 4, which values are used in locales + * where the standard is preferred. These values can explicitly be set by + * calling {@link Calendar#setFirstDayOfWeek(int) setFirstDayOfWeek()} and + * {@link Calendar#setMinimalDaysInFirstWeek(int) + * setMinimalDaysInFirstWeek()}. + * + *

    A week year is in sync with a + * {@code WEEK_OF_YEAR} cycle. All weeks between the first and last + * weeks (inclusive) have the same week year value. + * Therefore, the first and last days of a week year may have + * different calendar year values. + * + *

    For example, January 1, 1998 is a Thursday. If {@code + * getFirstDayOfWeek()} is {@code MONDAY} and {@code + * getMinimalDaysInFirstWeek()} is 4 (ISO 8601 standard compatible + * setting), then week 1 of 1998 starts on December 29, 1997, and ends + * on January 4, 1998. The week year is 1998 for the last three days + * of calendar year 1997. If, however, {@code getFirstDayOfWeek()} is + * {@code SUNDAY}, then week 1 of 1998 starts on January 4, 1998, and + * ends on January 10, 1998; the first three days of 1998 then are + * part of week 53 of 1997 and their week year is 1997. + * + *

    Week Of Month

    * *

    Values calculated for the WEEK_OF_MONTH field range from 0 * to 6. Week 1 of a month (the days with WEEK_OF_MONTH = @@ -124,7 +150,9 @@ * getMinimalDaysInFirstWeek() is changed to 3, then January 1 * through January 3 have a WEEK_OF_MONTH of 1. * - *

    The clear methods set calendar field(s) + *

    Default Fields Values

    + * + *

    The clear method sets calendar field(s) * undefined. GregorianCalendar uses the following * default value for each calendar field if its value is undefined. * @@ -1625,6 +1653,13 @@ * is 29 because 2004 is a leap year, and if the date of this * instance is February 1, 2005, it's 28. * + *

    This method calculates the maximum value of {@link + * Calendar#WEEK_OF_YEAR WEEK_OF_YEAR} based on the {@link + * Calendar#YEAR YEAR} (calendar year) value, not the week year. Call {@link + * #getWeeksInWeekYear()} to get the maximum value of {@code + * WEEK_OF_YEAR} in the week year of this {@code GregorianCalendar}. + * * @param field the calendar field * @return the maximum of the given field for the time value of * this GregorianCalendar @@ -1742,8 +1777,13 @@ if (gc == this) { gc = (GregorianCalendar) gc.clone(); } - gc.set(DAY_OF_YEAR, getActualMaximum(DAY_OF_YEAR)); + int maxDayOfYear = getActualMaximum(DAY_OF_YEAR); + gc.set(DAY_OF_YEAR, maxDayOfYear); value = gc.get(WEEK_OF_YEAR); + if (internalGet(YEAR) != gc.getWeekYear()) { + gc.set(DAY_OF_YEAR, maxDayOfYear - 7); + value = gc.get(WEEK_OF_YEAR); + } } break; @@ -1934,46 +1974,239 @@ } } -////////////////////// -// Proposed public API -////////////////////// + /** + * Returns {@code true} indicating this {@code GregorianCalendar} + * supports week dates. + * + * @return {@code true} (always) + * @see #getWeekYear() + * @see #setWeekDate(int,int,int) + * @see #getWeeksInWeekYear() + * @since 1.7 + */ + @Override + public final boolean isWeekDateSupported() { + return true; + } /** - * Returns the year that corresponds to the WEEK_OF_YEAR field. - * This may be one year before or after the Gregorian or Julian year stored - * in the YEAR field. For example, January 1, 1999 is considered - * Friday of week 53 of 1998 (if minimal days in first week is - * 2 or less, and the first day of the week is Sunday). Given - * these same settings, the ISO year of January 1, 1999 is - * 1998. + * Returns the week year represented by this + * {@code GregorianCalendar}. The dates in the weeks between 1 and the + * maximum week number of the week year have the same week year value + * that may be one year before or after the {@link Calendar#YEAR YEAR} + * (calendar year) value. * - *

    This method calls {@link Calendar#complete} before - * calculating the week-based year. + *

    This method calls {@link Calendar#complete()} before + * calculating the week year. * - * @return the year corresponding to the WEEK_OF_YEAR field, which - * may be one year before or after the YEAR field. - * @see #YEAR - * @see #WEEK_OF_YEAR + * @return the week year represented by this {@code GregorianCalendar}. + * If the {@link Calendar#ERA ERA} value is {@link #BC}, the year is + * represented by 0 or a negative number: BC 1 is 0, BC 2 + * is -1, BC 3 is -2, and so on. + * @throws IllegalArgumentException + * if any of the calendar fields is invalid in non-lenient mode. + * @see #isWeekDateSupported() + * @see #getWeeksInWeekYear() + * @see Calendar#getFirstDayOfWeek() + * @see Calendar#getMinimalDaysInFirstWeek() + * @since 1.7 */ - /* - public int getWeekBasedYear() { - complete(); - // TODO: Below doesn't work for gregorian cutover... - int weekOfYear = internalGet(WEEK_OF_YEAR); - int year = internalGet(YEAR); - if (internalGet(MONTH) == Calendar.JANUARY) { - if (weekOfYear >= 52) { + @Override + public int getWeekYear() { + int year = get(YEAR); // implicitly calls complete() + if (internalGetEra() == BCE) { + year = 1 - year; + } + + // Fast path for the Gregorian calendar years that are never + // affected by the Julian-Gregorian transition + if (year > gregorianCutoverYear + 1) { + int weekOfYear = internalGet(WEEK_OF_YEAR); + if (internalGet(MONTH) == JANUARY) { + if (weekOfYear >= 52) { + --year; + } + } else { + if (weekOfYear == 1) { + ++year; + } + } + return year; + } + + // General (slow) path + int dayOfYear = internalGet(DAY_OF_YEAR); + int maxDayOfYear = getActualMaximum(DAY_OF_YEAR); + int minimalDays = getMinimalDaysInFirstWeek(); + + // Quickly check the possibility of year adjustments before + // cloning this GregorianCalendar. + if (dayOfYear > minimalDays && dayOfYear < (maxDayOfYear - 6)) { + return year; + } + + // Create a clone to work on the calculation + GregorianCalendar cal = (GregorianCalendar) clone(); + cal.setLenient(true); + // Use GMT so that intermediate date calculations won't + // affect the time of day fields. + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + // Go to the first day of the year, which is usually January 1. + cal.set(DAY_OF_YEAR, 1); + cal.complete(); + + // Get the first day of the first day-of-week in the year. + int delta = getFirstDayOfWeek() - cal.get(DAY_OF_WEEK); + if (delta != 0) { + if (delta < 0) { + delta += 7; + } + cal.add(DAY_OF_YEAR, delta); + } + int minDayOfYear = cal.get(DAY_OF_YEAR); + if (dayOfYear < minDayOfYear) { + if (minDayOfYear <= minimalDays) { --year; } } else { - if (weekOfYear == 1) { - ++year; + cal.set(YEAR, year + 1); + cal.set(DAY_OF_YEAR, 1); + cal.complete(); + int del = getFirstDayOfWeek() - cal.get(DAY_OF_WEEK); + if (del != 0) { + if (del < 0) { + del += 7; + } + cal.add(DAY_OF_YEAR, del); + } + minDayOfYear = cal.get(DAY_OF_YEAR) - 1; + if (minDayOfYear == 0) { + minDayOfYear = 7; + } + if (minDayOfYear >= minimalDays) { + int days = maxDayOfYear - dayOfYear + 1; + if (days <= (7 - minDayOfYear)) { + ++year; + } } } return year; } - */ + + /** + * Sets this {@code GregorianCalendar} to the date given by the + * date specifiers - {@code weekYear}, + * {@code weekOfYear}, and {@code dayOfWeek}. {@code weekOfYear} + * follows the {@code WEEK_OF_YEAR} + * numbering. The {@code dayOfWeek} value must be one of the + * {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} values: {@link + * Calendar#SUNDAY SUNDAY} to {@link Calendar#SATURDAY SATURDAY}. + * + *

    Note that the numeric day-of-week representation differs from + * the ISO 8601 standard, and that the {@code weekOfYear} + * numbering is compatible with the standard when {@code + * getFirstDayOfWeek()} is {@code MONDAY} and {@code + * getMinimalDaysInFirstWeek()} is 4. + * + *

    Unlike the {@code set} method, all of the calendar fields + * and the instant of time value are calculated upon return. + * + *

    If {@code weekOfYear} is out of the valid week-of-year + * range in {@code weekYear}, the {@code weekYear} + * and {@code weekOfYear} values are adjusted in lenient + * mode, or an {@code IllegalArgumentException} is thrown in + * non-lenient mode. + * + * @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 #DAY_OF_WEEK DAY_OF_WEEK} field: + * {@link Calendar#SUNDAY SUNDAY}, ..., + * {@link Calendar#SATURDAY SATURDAY}. + * @exception IllegalArgumentException + * if any of the given date specifiers is invalid, + * or if any of the calendar fields are inconsistent + * with the given date specifiers in non-lenient mode + * @see GregorianCalendar#isWeekDateSupported() + * @see Calendar#getFirstDayOfWeek() + * @see Calendar#getMinimalDaysInFirstWeek() + * @since 1.7 + */ + @Override + public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek) { + if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) { + throw new IllegalArgumentException("invalid dayOfWeek: " + dayOfWeek); + } + // To avoid changing the time of day fields by date + // calculations, use a clone with the GMT time zone. + GregorianCalendar gc = (GregorianCalendar) clone(); + gc.setLenient(true); + int era = gc.get(ERA); + gc.clear(); + gc.setTimeZone(TimeZone.getTimeZone("GMT")); + gc.set(ERA, era); + gc.set(YEAR, weekYear); + gc.set(WEEK_OF_YEAR, 1); + gc.set(DAY_OF_WEEK, getFirstDayOfWeek()); + int days = dayOfWeek - getFirstDayOfWeek(); + if (days < 0) { + days += 7; + } + days += 7 * (weekOfYear - 1); + if (days != 0) { + gc.add(DAY_OF_YEAR, days); + } else { + gc.complete(); + } + + set(ERA, gc.internalGet(ERA)); + set(YEAR, gc.internalGet(YEAR)); + set(MONTH, gc.internalGet(MONTH)); + set(DAY_OF_MONTH, gc.internalGet(DAY_OF_MONTH)); + + // to avoid throwing an IllegalArgumentException in + // non-lenient, set WEEK_OF_YEAR and DAY_OF_WEEK internally + internalSet(WEEK_OF_YEAR, weekOfYear); + internalSet(DAY_OF_WEEK, dayOfWeek); + complete(); + + assert getWeekYear() == weekYear; + assert get(WEEK_OF_YEAR) == weekOfYear; + assert get(DAY_OF_WEEK) == dayOfWeek; + } + + /** + * Returns the number of weeks in the week year + * represented by this {@code GregorianCalendar}. + * + *

    For example, if this {@code GregorianCalendar}'s date is + * December 31, 2008 with the ISO + * 8601 compatible setting, this method will return 53 for the + * period: December 29, 2008 to January 3, 2010 while {@link + * #getActualMaximum(int) getActualMaximum(WEEK_OF_YEAR)} will return + * 52 for the period: December 31, 2007 to December 28, 2008. + * + * @return the number of weeks in the week year. + * @see Calendar#WEEK_OF_YEAR + * @see #getWeekYear() + * @see #getActualMaximum(int) + * @since 1.7 + */ + public int getWeeksInWeekYear() { + GregorianCalendar gc = getNormalizedCalendar(); + int weekYear = gc.getWeekYear(); + if (weekYear == gc.internalGet(YEAR)) { + return gc.getActualMaximum(WEEK_OF_YEAR); + } + + // Use the 2nd week for calculating the max of WEEK_OF_YEAR + if (gc == this) { + gc = (GregorianCalendar) gc.clone(); + } + gc.setWeekDate(weekYear, 2, internalGet(DAY_OF_WEEK)); + return gc.getActualMaximum(WEEK_OF_YEAR); + } ///////////////////////////// // Time => Fields computation @@ -2178,7 +2411,7 @@ // If we are in the cutover year, we need some special handling. if (normalizedYear == cutoverYear) { // Need to take care of the "missing" days. - if (getCutoverCalendarSystem() == jcal) { + if (gregorianCutoverYearJulian <= gregorianCutoverYear) { // We need to find out where we are. The cutover // gap could even be more than one year. (One // year difference in ~48667 years.) @@ -2208,27 +2441,36 @@ // December 31, which is not always true in // GregorianCalendar. long fixedDec31 = fixedDateJan1 - 1; - long prevJan1; + long prevJan1 = fixedDateJan1 - 365; if (normalizedYear > (cutoverYear + 1)) { - prevJan1 = fixedDateJan1 - 365; if (CalendarUtils.isGregorianLeapYear(normalizedYear - 1)) { --prevJan1; } + } else if (normalizedYear <= gregorianCutoverYearJulian) { + if (CalendarUtils.isJulianLeapYear(normalizedYear - 1)) { + --prevJan1; + } } else { BaseCalendar calForJan1 = calsys; - int prevYear = normalizedYear - 1; - if (prevYear == cutoverYear) { + //int prevYear = normalizedYear - 1; + int prevYear = getCalendarDate(fixedDec31).getNormalizedYear(); + if (prevYear == gregorianCutoverYear) { calForJan1 = getCutoverCalendarSystem(); - } - prevJan1 = calForJan1.getFixedDate(prevYear, - BaseCalendar.JANUARY, - 1, - null); - while (prevJan1 > fixedDec31) { - prevJan1 = getJulianCalendarSystem().getFixedDate(--prevYear, - BaseCalendar.JANUARY, - 1, - null); + if (calForJan1 == jcal) { + prevJan1 = calForJan1.getFixedDate(prevYear, + BaseCalendar.JANUARY, + 1, + null); + } else { + prevJan1 = gregorianCutoverDate; + calForJan1 = gcal; + } + } else if (prevYear <= gregorianCutoverYearJulian) { + calForJan1 = getJulianCalendarSystem(); + prevJan1 = calForJan1.getFixedDate(prevYear, + BaseCalendar.JANUARY, + 1, + null); } } weekOfYear = getWeekNumber(prevJan1, fixedDec31); @@ -2260,14 +2502,20 @@ if (nextYear == gregorianCutoverYear) { calForJan1 = getCutoverCalendarSystem(); } - long nextJan1 = calForJan1.getFixedDate(nextYear, - BaseCalendar.JANUARY, - 1, - null); - if (nextJan1 < fixedDate) { + + long nextJan1; + if (nextYear > gregorianCutoverYear + || gregorianCutoverYearJulian == gregorianCutoverYear + || nextYear == gregorianCutoverYearJulian) { + nextJan1 = calForJan1.getFixedDate(nextYear, + BaseCalendar.JANUARY, + 1, + null); + } else { nextJan1 = gregorianCutoverDate; calForJan1 = gcal; } + long nextJan1st = calForJan1.getDayOfWeekDateOnOrBefore(nextJan1 + 6, getFirstDayOfWeek()); int ndays = (int)(nextJan1st - nextJan1); @@ -2409,10 +2657,24 @@ } gfd = jfd; } else { + jfd = fixedDate + getFixedDate(getJulianCalendarSystem(), year, fieldMask); gfd = fixedDate + getFixedDate(gcal, year, fieldMask); - jfd = fixedDate + getFixedDate(getJulianCalendarSystem(), year, fieldMask); } + // Now we have to determine which calendar date it is. + + // If the date is relative from the beginning of the year + // in the Julian calendar, then use jfd; + if (isFieldSet(fieldMask, DAY_OF_YEAR) || isFieldSet(fieldMask, WEEK_OF_YEAR)) { + if (gregorianCutoverYear == gregorianCutoverYearJulian) { + fixedDate = jfd; + break calculateFixedDate; + } else if (year == gregorianCutoverYear) { + fixedDate = gfd; + break calculateFixedDate; + } + } + if (gfd >= gregorianCutoverDate) { if (jfd >= gregorianCutoverDate) { fixedDate = gfd; @@ -2494,9 +2756,10 @@ continue; } if (originalFields[field] != internalGet(field)) { + String s = originalFields[field] + " -> " + internalGet(field); // Restore the original field values System.arraycopy(originalFields, 0, fields, 0, fields.length); - throw new IllegalArgumentException(getFieldName(field)); + throw new IllegalArgumentException(getFieldName(field) + ": " + s); } } } @@ -2669,9 +2932,7 @@ * method returns Gregorian. Otherwise, Julian. */ private BaseCalendar getCutoverCalendarSystem() { - CalendarDate date = getGregorianCutoverDate(); - if (date.getMonth() == BaseCalendar.JANUARY - && date.getDayOfMonth() == 1) { + if (gregorianCutoverYearJulian < gregorianCutoverYear) { return gcal; } return getJulianCalendarSystem(); diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/test/java/text/Format/DateFormat/WeekDateTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/text/Format/DateFormat/WeekDateTest.java Thu Sep 02 00:02:23 2010 -0700 @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2010, 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 4267450 + * @summary Unit test for week date support + */ + +import java.text.*; +import java.util.*; +import static java.util.GregorianCalendar.*; + +public class WeekDateTest { + static SimpleDateFormat ymdFormat = new SimpleDateFormat("yyyy-MM-dd"); + static SimpleDateFormat ywdFormat = new SimpleDateFormat("YYYY-'W'ww-u"); + static { + ymdFormat.setCalendar(newCalendar()); + ywdFormat.setCalendar(newCalendar()); + } + + // Round-trip Data + static final String[][] roundTripData = { + { "2005-01-01", "2004-W53-6" }, + { "2005-01-02", "2004-W53-7" }, + { "2005-12-31", "2005-W52-6" }, + { "2007-01-01", "2007-W01-1" }, + { "2007-12-30", "2007-W52-7" }, + { "2007-12-31", "2008-W01-1" }, + { "2008-01-01", "2008-W01-2" }, + { "2008-12-29", "2009-W01-1" }, + { "2008-12-31", "2009-W01-3" }, + { "2009-01-01", "2009-W01-4" }, + { "2009-12-31", "2009-W53-4" }, + { "2010-01-03", "2009-W53-7" }, + { "2009-12-31", "2009-W53-4" }, + { "2010-01-01", "2009-W53-5" }, + { "2010-01-02", "2009-W53-6" }, + { "2010-01-03", "2009-W53-7" }, + { "2008-12-28", "2008-W52-7" }, + { "2008-12-29", "2009-W01-1" }, + { "2008-12-30", "2009-W01-2" }, + { "2008-12-31", "2009-W01-3" }, + { "2009-01-01", "2009-W01-4" }, + { "2009-01-01", "2009-W01-4" }, + }; + + // Data for leniency test + static final String[][] leniencyData = { + { "2008-12-28", "2009-W01-0" }, + { "2010-01-04", "2009-W53-8" }, + { "2008-12-29", "2008-W53-1" }, + }; + + static final String[] invalidData = { + "2010-W00-1", + "2010-W55-1", + "2010-W03-0", + "2010-W04-8", + "2010-W04-19" + }; + + public static void main(String[] args) throws Exception { + formatTest(roundTripData); + parseTest(roundTripData); + parseTest(leniencyData); + nonLenientTest(invalidData); + noWeekDateSupport(); + } + + private static void formatTest(String[][] data) throws Exception { + for (String[] dates : data) { + String regularDate = dates[0]; + String weekDate = dates[1]; + Date date = null; + date = ymdFormat.parse(regularDate); + String s = ywdFormat.format(date); + if (!s.equals(weekDate)) { + throw new RuntimeException("format: got="+s+", expecetd="+weekDate); + } + } + } + + private static void parseTest(String[][] data) throws Exception { + for (String[] dates : data) { + String regularDate = dates[0]; + String weekDate = dates[1]; + Date date1 = null, date2 = null; + date1 = ymdFormat.parse(regularDate); + date2 = ywdFormat.parse(weekDate); + if (!date1.equals(date2)) { + System.err.println(regularDate + ": date1 = " + date1); + System.err.println(weekDate + ": date2 = " + date2); + throw new RuntimeException("parse: date1 != date2"); + } + } + } + + + // Non-lenient mode test + private static void nonLenientTest(String[] data) { + ywdFormat.setLenient(false); + for (String date : data) { + try { + Date d = ywdFormat.parse(date); + throw new RuntimeException("No ParseException thrown with " + date); + } catch (ParseException e) { + // OK + } + } + ywdFormat.setLenient(true); + } + + + private static void noWeekDateSupport() throws Exception { + // Tests with Japanese Imperial Calendar that doesn't support week dates. + Calendar jcal = Calendar.getInstance(TimeZone.getTimeZone("GMT"), + new Locale("ja", "JP", "JP")); + + jcal.setFirstDayOfWeek(MONDAY); + jcal.setMinimalDaysInFirstWeek(4); + SimpleDateFormat sdf = new SimpleDateFormat("Y-'W'ww-u"); + sdf.setCalendar(jcal); + Date d = sdf.parse("21-W01-3"); // 2008-12-31 == H20-12-31 + GregorianCalendar gcal = newCalendar(); + gcal.setTime(d); + if (gcal.get(YEAR) != 2008 + || gcal.get(MONTH) != DECEMBER + || gcal.get(DAY_OF_MONTH) != 31) { + String s = String.format("noWeekDateSupport: got %04d-%02d-%02d, expected 2008-12-31%n", + gcal.get(YEAR), + gcal.get(MONTH)+1, + gcal.get(DAY_OF_MONTH)); + throw new RuntimeException(s); + } + } + + private static GregorianCalendar newCalendar() { + // Use GMT to avoid any surprises related DST transitions. + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + // Setup the ISO 8601 compatible parameters + cal.setFirstDayOfWeek(MONDAY); + cal.setMinimalDaysInFirstWeek(4); + return cal; + } +} diff -r 7bbabd9b79e6 -r 27cfdd5d48d1 jdk/test/java/util/Calendar/WeekDateTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/util/Calendar/WeekDateTest.java Thu Sep 02 00:02:23 2010 -0700 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010, 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 4267450 + * @summary Unit test for week date support + */ + +import java.text.*; +import java.util.*; +import static java.util.GregorianCalendar.*; + +public class WeekDateTest { + + // Week dates are in the ISO numbering for day-of-week. + static int[][][] data = { + // Calendar year-date, Week year-date + {{ 2005, 01, 01}, {2004, 53, 6}}, + {{ 2005, 01, 02}, {2004, 53, 7}}, + {{ 2005, 12, 31}, {2005, 52, 6}}, + {{ 2007, 01, 01}, {2007, 01, 1}}, + {{ 2007, 12, 30}, {2007, 52, 7}}, + {{ 2007, 12, 31}, {2008, 01, 1}}, + {{ 2008, 01, 01}, {2008, 01, 2}}, + {{ 2008, 12, 29}, {2009, 01, 1}}, + {{ 2008, 12, 31}, {2009, 01, 3}}, + {{ 2009, 01, 01}, {2009, 01, 4}}, + {{ 2009, 12, 31}, {2009, 53, 4}}, + {{ 2010, 01, 03}, {2009, 53, 7}}, + {{ 2009, 12, 31}, {2009, 53, 4}}, + {{ 2010, 01, 01}, {2009, 53, 5}}, + {{ 2010, 01, 02}, {2009, 53, 6}}, + {{ 2010, 01, 03}, {2009, 53, 7}}, + {{ 2008, 12, 28}, {2008, 52, 7}}, + {{ 2008, 12, 29}, {2009, 01, 1}}, + {{ 2008, 12, 30}, {2009, 01, 2}}, + {{ 2008, 12, 31}, {2009, 01, 3}}, + {{ 2009, 01, 01}, {2009, 01, 4}} + }; + + public static void main(String[] args) { + GregorianCalendar cal = newCalendar(); + for (int[][] dates : data) { + int[] expected = dates[0]; + int[] weekDate = dates[1]; + // Convert ISO 8601 day-of-week to Calendar.DAY_OF_WEEK. + int dayOfWeek = weekDate[2] == 7 ? SUNDAY : weekDate[2] + 1; + + cal.clear(); + cal.setWeekDate(weekDate[0], weekDate[1], dayOfWeek); + if (cal.get(YEAR) != expected[0] + || cal.get(MONTH)+1 != expected[1] + || cal.get(DAY_OF_MONTH) != expected[2]) { + String s = String.format("got=%4d-%02d-%02d, expected=%4d-%02d-%02d", + cal.get(YEAR), cal.get(MONTH)+1, cal.get(DAY_OF_MONTH), + expected[0], expected[1], expected[2]); + throw new RuntimeException(s); + } + if (cal.getWeekYear() != weekDate[0] + || cal.get(WEEK_OF_YEAR) != weekDate[1] + || cal.get(DAY_OF_WEEK) != dayOfWeek) { + String s = String.format( + "got=%4d-W%02d-%d, expected=%4d-W%02d-%d (not ISO day-of-week)", + cal.getWeekYear(), cal.get(WEEK_OF_YEAR), cal.get(DAY_OF_WEEK), + weekDate[0], weekDate[1], dayOfWeek); + throw new RuntimeException(s); + } + } + + // Test getWeeksInWeekYear(). + // If we avoid the first week of January and the last week of + // December, getWeeksInWeekYear() and + // getActualMaximum(WEEK_OF_YEAR) values should be the same. + for (int year = 2000; year <= 2100; year++) { + cal.clear(); + cal.set(year, JUNE, 1); + int n = cal.getWeeksInWeekYear(); + if (n != cal.getActualMaximum(WEEK_OF_YEAR)) { + String s = String.format("getWeeksInWeekYear() = %d, " + + "getActualMaximum(WEEK_OF_YEAR) = %d%n", + n, cal.getActualMaximum(WEEK_OF_YEAR)); + throw new RuntimeException(s); + } + cal.setWeekDate(cal.getWeekYear(), 1, MONDAY); + System.out.println(cal.getTime()); + if (cal.getWeeksInWeekYear() != n) { + String s = String.format("first day: got %d, expected %d%n", + cal.getWeeksInWeekYear(), n); + throw new RuntimeException(s); + } + cal.setWeekDate(cal.getWeekYear(), n, SUNDAY); + System.out.println(cal.getTime()); + if (cal.getWeeksInWeekYear() != n) { + String s = String.format("last day: got %d, expected %d%n", + cal.getWeeksInWeekYear(), n); + throw new RuntimeException(s); + } + } + } + + private static GregorianCalendar newCalendar() { + // Use GMT to avoid any surprises related DST transitions. + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + if (!cal.isWeekDateSupported()) { + throw new RuntimeException("Week dates not supported"); + } + // Setup the ISO 8601 compatible parameters + cal.setFirstDayOfWeek(MONDAY); + cal.setMinimalDaysInFirstWeek(4); + return cal; + } +}