changeset 2847:de0f18fe09e7

4267450: (cal) API: Need public API to calculate, format and parse "year of week" 6549953: (cal) WEEK_OF_YEAR and DAY_OF_YEAR calculation problems around Gregorian cutover Reviewed-by: peytoia
author okutsu
date Wed, 01 Sep 2010 15:19:13 +0900
parents 48070a2633a1
children 513a9ae0bbdd
files make/java/text/base/FILES_java.gmk src/share/classes/java/text/CalendarBuilder.java src/share/classes/java/text/DateFormatSymbols.java src/share/classes/java/text/SimpleDateFormat.java src/share/classes/java/util/Calendar.java src/share/classes/java/util/GregorianCalendar.java test/java/text/Format/DateFormat/WeekDateTest.java test/java/util/Calendar/WeekDateTest.java
diffstat 8 files changed, 1105 insertions(+), 215 deletions(-) [+]
line wrap: on
line diff
--- a/make/java/text/base/FILES_java.gmk	Tue Aug 31 11:27:10 2010 -0700
+++ b/make/java/text/base/FILES_java.gmk	Wed Sep 01 15:19:13 2010 +0900
@@ -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 \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/text/CalendarBuilder.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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.
+ *
+ * <p>{@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;
+    }
+}
--- a/src/share/classes/java/text/DateFormatSymbols.java	Tue Aug 31 11:27:10 2010 -0700
+++ b/src/share/classes/java/text/DateFormatSymbols.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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;
     }
 
     /**
--- a/src/share/classes/java/text/SimpleDateFormat.java	Tue Aug 31 11:27:10 2010 -0700
+++ b/src/share/classes/java/text/SimpleDateFormat.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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.*;
+
 /**
  * <code>SimpleDateFormat</code> is a concrete class for formatting and
  * parsing dates in a locale-sensitive manner. It allows for formatting
@@ -108,40 +110,50 @@
  *         <td><a href="#year">Year</a>
  *         <td><code>1996</code>; <code>96</code>
  *     <tr>
+ *         <td><code>Y</code>
+ *         <td>Week year
+ *         <td><a href="#year">Year</a>
+ *         <td><code>2009</code>; <code>09</code>
+ *     <tr bgcolor="#eeeeff">
  *         <td><code>M</code>
  *         <td>Month in year
  *         <td><a href="#month">Month</a>
  *         <td><code>July</code>; <code>Jul</code>; <code>07</code>
- *     <tr bgcolor="#eeeeff">
+ *     <tr>
  *         <td><code>w</code>
  *         <td>Week in year
  *         <td><a href="#number">Number</a>
  *         <td><code>27</code>
- *     <tr>
+ *     <tr bgcolor="#eeeeff">
  *         <td><code>W</code>
  *         <td>Week in month
  *         <td><a href="#number">Number</a>
  *         <td><code>2</code>
- *     <tr bgcolor="#eeeeff">
+ *     <tr>
  *         <td><code>D</code>
  *         <td>Day in year
  *         <td><a href="#number">Number</a>
  *         <td><code>189</code>
- *     <tr>
+ *     <tr bgcolor="#eeeeff">
  *         <td><code>d</code>
  *         <td>Day in month
  *         <td><a href="#number">Number</a>
  *         <td><code>10</code>
- *     <tr bgcolor="#eeeeff">
+ *     <tr>
  *         <td><code>F</code>
  *         <td>Day of week in month
  *         <td><a href="#number">Number</a>
  *         <td><code>2</code>
- *     <tr>
+ *     <tr bgcolor="#eeeeff">
  *         <td><code>E</code>
- *         <td>Day in week
+ *         <td>Day name in week
  *         <td><a href="#text">Text</a>
  *         <td><code>Tuesday</code>; <code>Tue</code>
+ *     <tr>
+ *         <td><code>u</code>
+ *         <td>Day number of week (1 = Monday, ..., 7 = Sunday)
+ *         <td><a href="#number">Number</a>
+ *         <td><code>1</code>
  *     <tr bgcolor="#eeeeff">
  *         <td><code>a</code>
  *         <td>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.<br><br></li>
  * <li><strong><a name="number">Number:</a></strong>
  *     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.<br><br></li>
  * <li><strong><a name="year">Year:</a></strong>
  *     If the formatter's {@link #getCalendar() Calendar} is the Gregorian
  *     calendar, the following rules are applied.<br>
@@ -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.<br>
+ *     <br>
+ *     If week year {@code 'Y'} is specified and the {@linkplain
+ *     #getCalendar() calendar} doesn't support any <a
+ *     href="../util/GregorianCalendar.html#week_year"> week
+ *     years</a>, 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()}.<br><br></li>
  * <li><strong><a name="month">Month:</a></strong>
  *     If the number of pattern letters is 3 or more, the month is
  *     interpreted as <a href="#text">text</a>; otherwise,
- *     it is interpreted as a <a href="#number">number</a>.
+ *     it is interpreted as a <a href="#number">number</a>.<br><br></li>
  * <li><strong><a name="timezone">General time zone:</a></strong>
  *     Time zones are interpreted as <a href="#text">text</a> 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.
  *     <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
- *     accepted.
+ *     accepted.<br><br></li>
  * <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong>
  *     For formatting, the RFC 822 4-digit time zone format is used:
  *     <pre>
@@ -321,6 +342,9 @@
  *     <tr>
  *         <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code>
  *         <td><code>2001-07-04T12:08:56.235-0700</code>
+ *     <tr bgcolor="#eeeeff">
+ *         <td><code>"YYYY-'W'ww-u"</code>
+ *         <td><code>2001-W27-3</code>
  * </table>
  * </blockquote>
  *
@@ -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<String, Integer> instead of
      * String[].
      */
-    private int matchString(String text, int start, int field, Map<String,Integer> data) {
+    private int matchString(String text, int start, int field,
+                            Map<String,Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> 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<String,Integer> 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<String,Integer> 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);
--- a/src/share/classes/java/util/Calendar.java	Tue Aug 31 11:27:10 2010 -0700
+++ b/src/share/classes/java/util/Calendar.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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.
  *
- * <h4>First Week</h4>
+ * <h4><a name="first_week">First Week</a></h4>
  *
  * <code>Calendar</code> 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.
+     *
+     * <p>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.
+     *
+     * <p>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.
+     *
+     * <p>Unlike the {@code set} method, all of the calendar fields
+     * and {@code time} values are calculated upon return.
+     *
+     * <p>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.
+     *
+     * <p>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}.
+     *
+     * <p>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
      * <code>Calendar</code> instance. The minimum value is defined as
      * the smallest value returned by the {@link #get(int) get} method
--- a/src/share/classes/java/util/GregorianCalendar.java	Tue Aug 31 11:27:10 2010 -0700
+++ b/src/share/classes/java/util/GregorianCalendar.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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.
  *
- * <p>Values calculated for the <code>WEEK_OF_YEAR</code> field range from 1 to
- * 53.  Week 1 for a year is the earliest seven day period starting on
- * <code>getFirstDayOfWeek()</code> that contains at least
- * <code>getMinimalDaysInFirstWeek()</code> days from that year.  It thus
- * depends on the values of <code>getMinimalDaysInFirstWeek()</code>,
- * <code>getFirstDayOfWeek()</code>, 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).
-
- * <p>For example, January 1, 1998 was a Thursday.  If
- * <code>getFirstDayOfWeek()</code> is <code>MONDAY</code> and
- * <code>getMinimalDaysInFirstWeek()</code> 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,
- * <code>getFirstDayOfWeek()</code> is <code>SUNDAY</code>, 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.
+ * <h4><a name="week_and_year">Week Of Year and Week Year</a></h4>
+ *
+ * <p>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).
+ *
+ * <p>The {@code getFirstDayOfWeek()} and {@code
+ * getMinimalDaysInFirstWeek()} values are initialized using
+ * locale-dependent resources when constructing a {@code
+ * GregorianCalendar}. <a name="iso8601_compatible_setting">The week
+ * determination is compatible</a> 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()}.
+ *
+ * <p>A <a name="week_year"><em>week year</em></a> is in sync with a
+ * {@code WEEK_OF_YEAR} cycle. All weeks between the first and last
+ * weeks (inclusive) have the same <em>week year</em> value.
+ * Therefore, the first and last days of a week year may have
+ * different calendar year values.
+ *
+ * <p>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.
+ *
+ * <h4>Week Of Month</h4>
  *
  * <p>Values calculated for the <code>WEEK_OF_MONTH</code> field range from 0
  * to 6.  Week 1 of a month (the days with <code>WEEK_OF_MONTH =
@@ -124,7 +150,9 @@
  * <code>getMinimalDaysInFirstWeek()</code> is changed to 3, then January 1
  * through January 3 have a <code>WEEK_OF_MONTH</code> of 1.
  *
- * <p>The <code>clear</code> methods set calendar field(s)
+ * <h4>Default Fields Values</h4>
+ *
+ * <p>The <code>clear</code> method sets calendar field(s)
  * undefined. <code>GregorianCalendar</code> 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.
      *
+     * <p>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 <a
+     * href="#week_year">week year</a>. 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 <code>GregorianCalendar</code>
@@ -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 <code>WEEK_OF_YEAR</code> field.
-     * This may be one year before or after the Gregorian or Julian year stored
-     * in the <code>YEAR</code> 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 <a href="#week_year">week year</a> 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.
      *
-     * <p>This method calls {@link Calendar#complete} before
-     * calculating the week-based year.
+     * <p>This method calls {@link Calendar#complete()} before
+     * calculating the week year.
      *
-     * @return the year corresponding to the <code>WEEK_OF_YEAR</code> field, which
-     * may be one year before or after the <code>YEAR</code> 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 - <a href="#week_year">{@code weekYear}</a>,
+     * {@code weekOfYear}, and {@code dayOfWeek}. {@code weekOfYear}
+     * follows the <a href="#week_and_year">{@code WEEK_OF_YEAR}
+     * numbering</a>.  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}.
+     *
+     * <p>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.
+     *
+     * <p>Unlike the {@code set} method, all of the calendar fields
+     * and the instant of time value are calculated upon return.
+     *
+     * <p>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 <a href="#week_year">week year</a>
+     * represented by this {@code GregorianCalendar}.
+     *
+     * <p>For example, if this {@code GregorianCalendar}'s date is
+     * December 31, 2008 with <a href="#iso8601_compatible_setting">the ISO
+     * 8601 compatible setting</a>, 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();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/text/Format/DateFormat/WeekDateTest.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/Calendar/WeekDateTest.java	Wed Sep 01 15:19:13 2010 +0900
@@ -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;
+    }
+}