This commit was manufactured by cvs2svn to create tag 'tz-patch-2-6'.

X-SVN-Rev: 13031
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4d99a35
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,58 @@
+* text=auto !eol
+
+*.c text !eol
+*.cc text !eol
+*.classpath text !eol
+*.cpp text !eol
+*.css text !eol
+*.dsp text !eol
+*.dsw text !eol
+*.filters text !eol
+*.h text !eol
+*.htm text !eol
+*.html text !eol
+*.in text !eol
+*.java text !eol
+*.launch text !eol
+*.mak text !eol
+*.md text !eol
+*.MF text !eol
+*.mk text !eol
+*.pl text !eol
+*.pm text !eol
+*.project text !eol
+*.properties text !eol
+*.py text !eol
+*.rc text !eol
+*.sh text eol=lf
+*.sln text !eol
+*.stub text !eol
+*.txt text !eol
+*.ucm text !eol
+*.vcproj text !eol
+*.vcxproj text !eol
+*.xml text !eol
+*.xsl text !eol
+*.xslt text !eol
+Makefile text !eol
+configure text !eol
+LICENSE text !eol
+README text !eol
+
+*.bin -text
+*.brk -text
+*.cnv -text
+*.icu -text
+*.res -text
+*.nrm -text
+*.spp -text
+*.tri2 -text
+
+# The following file types are stored in Git-LFS.
+*.jar filter=lfs diff=lfs merge=lfs -text
+*.dat filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.gif filter=lfs diff=lfs merge=lfs -text
+
diff --git a/src/com/ibm/icu/dev/demo/calendar/CalendarCalc.java b/src/com/ibm/icu/dev/demo/calendar/CalendarCalc.java
new file mode 100755
index 0000000..95a5dd0
--- /dev/null
+++ b/src/com/ibm/icu/dev/demo/calendar/CalendarCalc.java
@@ -0,0 +1,576 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1997-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/demo/calendar/CalendarCalc.java,v $ 
+ * $Date: 2003/09/04 00:55:18 $ 
+ * $Revision: 1.15 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.demo.calendar;
+
+import java.util.Date;
+import java.awt.*;
+import java.awt.event.*;
+
+//import java.text.DateFormat;
+import com.ibm.icu.text.DateFormat;
+import java.text.ParsePosition;
+
+//import java.util.Calendar;
+import com.ibm.icu.util.Calendar;
+//import java.util.GregorianCalendar;
+import com.ibm.icu.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.Locale;
+
+import com.ibm.icu.util.*;
+import com.ibm.icu.text.*;
+import com.ibm.icu.dev.demo.impl.*;
+
+import javax.swing.*;
+
+/**
+ * CalendarCalc demonstrates how Date/Time formatter works.
+ */
+public class CalendarCalc extends DemoApplet
+{
+    /**
+     * The main function which defines the behavior of the MultiCalendarDemo
+     * applet when an applet is started.
+     */
+    public static void main(String argv[]) {
+        new CalendarCalc().showDemo();
+    }
+
+    /**
+     * This creates a CalendarCalcFrame for the demo applet.
+     */
+    public Frame createDemoFrame(DemoApplet applet) {
+        return new CalendarCalcFrame(applet);
+    }
+}
+
+/**
+ * A Frame is a top-level window with a title. The default layout for a frame
+ * is BorderLayout.  The CalendarCalcFrame class defines the window layout of
+ * MultiCalendarDemo.
+ */
+class CalendarCalcFrame extends Frame implements ActionListener
+{
+    static final Locale[] locales = DemoUtility.getG7Locales();
+
+    private DemoApplet              applet;
+    private long                    time = System.currentTimeMillis();
+
+    private static final RollAddField kRollAddFields[] = {
+        new RollAddField(Calendar.YEAR,                 "Year" ),
+        new RollAddField(Calendar.MONTH,                "Month" ),
+        new RollAddField(Calendar.WEEK_OF_MONTH,        "Week of Month" ),
+        new RollAddField(Calendar.WEEK_OF_YEAR,         "Week of Year" ),
+        new RollAddField(Calendar.DAY_OF_MONTH,         "Day of Month" ),
+        new RollAddField(Calendar.DAY_OF_WEEK,          "Day of Week" ),
+        new RollAddField(Calendar.DAY_OF_WEEK_IN_MONTH, "Day of Week in Month" ),
+        new RollAddField(Calendar.DAY_OF_YEAR,          "Day of Year" ),
+        new RollAddField(Calendar.AM_PM,                "AM/PM" ),
+        new RollAddField(Calendar.HOUR_OF_DAY,          "Hour of day" ),
+        new RollAddField(Calendar.HOUR,                 "Hour" ),
+        new RollAddField(Calendar.MINUTE,               "Minute" ),
+        new RollAddField(Calendar.SECOND,               "Second" ),
+    };
+
+    /**
+     * Constructs a new CalendarCalcFrame that is initially invisible.
+     */
+    public CalendarCalcFrame(DemoApplet applet)
+    {
+        super("Multiple Calendar Demo");
+        this.applet = applet;
+        init();
+        start();
+    }
+
+    /**
+     * Initializes the applet. You never need to call this directly, it
+     * is called automatically by the system once the applet is created.
+     */
+    public void init()
+    {
+        buildGUI();
+
+        patternText.setText( calendars[0].toPattern() );
+
+        // Force an update of the display
+        cityChanged();
+        millisFormat();
+        enableEvents(KeyEvent.KEY_RELEASED);
+        enableEvents(WindowEvent.WINDOW_CLOSING);
+    }
+
+    //------------------------------------------------------------
+    // package private
+    //------------------------------------------------------------
+    void addWithFont(Container container, Component foo, Font font) {
+        if (font != null)
+            foo.setFont(font);
+        container.add(foo);
+    }
+
+    /**
+     * Called to start the applet. You never need to call this method
+     * directly, it is called when the applet's document is visited.
+     */
+    public void start()
+    {
+        // do nothing
+    }
+
+    TextField patternText;
+
+    Choice dateMenu;
+    Choice localeMenu;
+
+    Button up;
+    Button down;
+
+    Checkbox getRoll;
+    Checkbox getAdd;
+
+    public void buildGUI()
+    {
+        setBackground(DemoUtility.bgColor);
+        setLayout(new FlowLayout()); // shouldn't be necessary, but it is.
+
+// TITLE
+        Label label1=new Label("Calendar Converter", Label.CENTER);
+        label1.setFont(DemoUtility.titleFont);
+        add(label1);
+        add(DemoUtility.createSpacer());
+
+// IO Panel
+        Panel topPanel = new Panel();
+        topPanel.setLayout(new FlowLayout());
+
+        CheckboxGroup group1= new CheckboxGroup();
+
+        // Set up the controls for each calendar we're demonstrating
+        for (int i = 0; i < calendars.length; i++)
+        {
+            Label label = new Label(calendars[i].name, Label.RIGHT);
+            label.setFont(DemoUtility.labelFont);
+            topPanel.add(label);
+
+            topPanel.add(calendars[i].text);
+
+            final int j = i;
+            calendars[i].text.addActionListener( new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    textChanged(j);
+                }
+            } );
+
+            calendars[i].rollAdd.setCheckboxGroup(group1);
+            topPanel.add(calendars[i].rollAdd);
+        }
+        calendars[0].rollAdd.setState(true);    // Make the first one selected
+
+        Label label4=new Label("Pattern", Label.RIGHT);
+        label4.setFont(DemoUtility.labelFont);
+        topPanel.add(label4);
+
+        patternText=new TextField(FIELD_COLUMNS);
+        patternText.setFont(DemoUtility.editFont);
+        topPanel.add(patternText);
+        topPanel.add(new Label(""));
+
+        DemoUtility.fixGrid(topPanel,3);
+        add(topPanel);
+        add(DemoUtility.createSpacer());
+
+// ROLL / ADD
+        Panel rollAddPanel=new Panel();
+        {
+            rollAddPanel.setLayout(new FlowLayout());
+
+            Panel rollAddBoxes = new Panel();
+            {
+                rollAddBoxes.setLayout(new GridLayout(2,1));
+                CheckboxGroup group2= new CheckboxGroup();
+                getRoll = new Checkbox("Roll",group2, false);
+                getAdd = new Checkbox("Add",group2, true);
+
+                rollAddBoxes.add(getRoll);
+                rollAddBoxes.add(getAdd);
+            }
+
+            Label dateLabel=new Label("Date Fields");
+            dateLabel.setFont(DemoUtility.labelFont);
+
+            dateMenu= new Choice();
+            dateMenu.setBackground(DemoUtility.choiceColor);
+            for (int i = 0; i < kRollAddFields.length; i++) {
+                dateMenu.addItem(kRollAddFields[i].name);
+                if (kRollAddFields[i].field == Calendar.MONTH) {
+                    dateMenu.select(i);
+                }
+            }
+
+            Panel upDown = new Panel();
+            {
+                upDown.setLayout(new GridLayout(2,1));
+
+                // *** If the images are not found, we use the label.
+                up = new Button("^");
+                down = new Button("v");
+                up.setBackground(DemoUtility.bgColor);
+                down.setBackground(DemoUtility.bgColor);
+                upDown.add(up);
+                upDown.add(down);
+                up.addActionListener(this);
+                down.addActionListener(this);
+            }
+
+            rollAddPanel.add(dateLabel);
+            rollAddPanel.add(dateMenu);
+            rollAddPanel.add(rollAddBoxes);
+            rollAddPanel.add(upDown);
+
+        }
+        Panel localePanel = new Panel();
+        {
+            // Make the locale popup menus
+            localeMenu= new Choice();
+            Locale defaultLocale = Locale.getDefault();
+            int bestMatch = -1, thisMatch = -1;
+            int selectMe = 0;
+            
+            for (int i = 0; i < locales.length; i++) {
+                if (i > 0 && locales[i].getLanguage().equals(locales[i-1].getLanguage()) ||
+                    i < locales.length - 1 &&
+                        locales[i].getLanguage().equals(locales[i+1].getLanguage()))
+                {
+                    localeMenu.addItem( locales[i].getDisplayName() );
+                } else {
+                    localeMenu.addItem( locales[i].getDisplayLanguage());
+                }
+                
+                thisMatch = DemoUtility.compareLocales(locales[i], defaultLocale);
+                
+                if (thisMatch >= bestMatch) {
+                    bestMatch = thisMatch;
+                    selectMe = i;
+                }
+            }
+            
+            localeMenu.setBackground(DemoUtility.choiceColor);
+            localeMenu.select(selectMe);
+
+            Label localeLabel =new Label("Display Locale");
+            localeLabel.setFont(DemoUtility.labelFont);
+
+            localePanel.add(localeLabel);
+            localePanel.add(localeMenu);
+            DemoUtility.fixGrid(localePanel,2);
+
+            localeMenu.addItemListener( new ItemListener() {
+                public void itemStateChanged(ItemEvent e) {
+                    Locale loc = locales[localeMenu.getSelectedIndex()];
+                    System.out.println("Change locale to " + loc.getDisplayName());
+
+                    for (int i = 0; i < calendars.length; i++) {
+                        calendars[i].setLocale(loc);
+                    }
+                    millisFormat();
+                }
+            } );
+        }
+        add(rollAddPanel);
+        add(DemoUtility.createSpacer());
+        add(localePanel);
+        add(DemoUtility.createSpacer());
+
+// COPYRIGHT
+        Panel copyrightPanel = new Panel();
+        addWithFont (copyrightPanel,new Label(DemoUtility.copyright1, Label.LEFT),
+            DemoUtility.creditFont);
+        DemoUtility.fixGrid(copyrightPanel,1);
+        add(copyrightPanel);
+    }
+
+    /**
+     * This function is called when users change the pattern text.
+     */
+    public void setFormatFromPattern() {
+        String timePattern = patternText.getText();
+
+        for (int i = 0; i < calendars.length; i++) {
+            calendars[i].applyPattern(timePattern);
+        }
+
+        millisFormat();
+    }
+
+    /**
+     * This function is called when it is necessary to parse the time
+     * string in one of the formatted date fields
+     */
+    public void textChanged(int index) {
+        String rightString = calendars[index].text.getText();
+
+        ParsePosition status = new ParsePosition(0);
+
+        if (rightString.length() == 0)
+        {
+            errorText("Error: no input to parse!");
+            return;
+        }
+
+        try {
+            Date date = calendars[index].format.parse(rightString, status);
+            time = date.getTime();
+        }
+        catch (Exception e) {
+            for (int i = 0; i < calendars.length; i++) {
+                if (i != index) {
+                    calendars[i].text.setText("ERROR");
+                }
+            }
+            errorText("Exception: " + e.getClass().toString() + " parsing: "+rightString);
+            return;
+        }
+
+        int start = calendars[index].text.getSelectionStart();
+        int end = calendars[index].text.getSelectionEnd();
+
+        millisFormat();
+
+        calendars[index].text.select(start,end);
+    }
+
+    /**
+     * This function is called when it is necessary to format the time
+     * in the "Millis" text field.
+     */
+    public void millisFormat() {
+        String out = "";
+
+        for (int i = 0; i < calendars.length; i++) {
+            try {
+                out = calendars[i].format.format(new Date(time));
+                calendars[i].text.setText(out);
+            }
+            catch (Exception e) {
+                calendars[i].text.setText("ERROR");
+                errorText("Exception: " + e.getClass().toString() + " formatting "
+                            + calendars[i].name + " " + time);
+            }
+        }
+    }
+
+
+    /**
+     * This function is called when users change the pattern text.
+     */
+    public void patternTextChanged() {
+        setFormatFromPattern();
+    }
+
+    /**
+     * This function is called when users select a new representative city.
+     */
+    public void cityChanged() {
+        TimeZone timeZone = TimeZone.getDefault();
+
+        for (int i = 0; i < calendars.length; i++) {
+            calendars[i].format.setTimeZone(timeZone);
+        }
+        millisFormat();
+    }
+
+    /**
+     * This function is called when users select a new time field
+     * to add or roll its value.
+     */
+    public void dateFieldChanged(boolean up) {
+        int field = kRollAddFields[dateMenu.getSelectedIndex()].field;
+
+        for (int i = 0; i < calendars.length; i++)
+        {
+            if (calendars[i].rollAdd.getState())
+            {
+                Calendar c = calendars[i].calendar;
+                c.setTime(new Date(time));
+
+                if (getAdd.getState()) {
+                    c.add(field, up ? 1 : -1);
+                } else {
+                    c.roll(field, up);
+                }
+
+                time = c.getTime().getTime();
+                millisFormat();
+                break;
+            }
+        }
+    }
+
+    /**
+     * Print out the error message while debugging this program.
+     */
+    public void errorText(String s)
+    {
+        if (true) {
+            System.out.println(s);
+        }
+    }
+    
+    /**
+     * Called if an action occurs in the CalendarCalcFrame object.
+     */
+    public void actionPerformed(ActionEvent evt)
+    {
+        // *** Button events are handled here.
+        Object obj = evt.getSource();
+        System.out.println("action " + obj);
+        if (obj instanceof Button) {
+            if (evt.getSource() == up) {
+                dateFieldChanged(false);
+            } else
+                if (evt.getSource() == down) {
+                    dateFieldChanged(true);
+            }
+        }
+    }
+    
+    /**
+     * Handles the event. Returns true if the event is handled and should not
+     * be passed to the parent of this component. The default event handler
+     * calls some helper methods to make life easier on the programmer.
+     */
+    protected void processKeyEvent(KeyEvent evt)
+    {
+        System.out.println("key " + evt);
+        if (evt.getID() == KeyEvent.KEY_RELEASED) { 
+            if (evt.getSource() == patternText) {
+                patternTextChanged();
+            }
+            else {
+                for (int i = 0; i < calendars.length; i++) {
+                    if (evt.getSource() == calendars[i].text) {
+                        textChanged(i);
+                    }
+                }
+            }
+        }
+    }
+    
+    protected void processWindowEvent(WindowEvent evt) 
+    {
+        System.out.println("window " + evt);
+        if (evt.getID() == WindowEvent.WINDOW_CLOSING && 
+            evt.getSource() == this) {
+            this.hide();
+            this.dispose();
+
+            if (applet != null) {
+               applet.demoClosed();
+            } else System.exit(0);
+        }
+    }
+    
+    /*
+    protected void processEvent(AWTEvent evt)
+    {
+        if (evt.getID() == AWTEvent. Event.ACTION_EVENT && evt.target == up) {
+            dateFieldChanged(true);
+            return true;
+        }
+        else if (evt.id == Event.ACTION_EVENT && evt.target == down) {
+            dateFieldChanged(false);
+            return true;
+        }
+    }
+    */
+
+    private static final int        FIELD_COLUMNS = 35;
+
+
+    class CalendarRec {
+        public CalendarRec(String nameStr, Calendar cal)
+        {
+            name = nameStr;
+            calendar = cal;
+            rollAdd = new Checkbox();
+
+            text = new JTextField("",FIELD_COLUMNS);
+            text.setFont(DemoUtility.editFont);
+
+            format = DateFormat.getDateInstance(cal, DateFormat.FULL,
+                                                Locale.getDefault());
+            //format.applyPattern(DEFAULT_FORMAT);
+        }
+
+        public void setLocale(Locale loc) {
+            String pattern = toPattern();
+
+            format = DateFormat.getDateInstance(calendar, DateFormat.FULL,
+                                                loc);
+            applyPattern(pattern);
+        }
+
+        public void applyPattern(String pattern) {
+            if (format instanceof SimpleDateFormat) {
+                ((SimpleDateFormat)format).applyPattern(pattern);
+//hey {al} - 
+//            } else if (format instanceof java.text.SimpleDateFormat) {
+//                ((java.text.SimpleDateFormat)format).applyPattern(pattern);
+            }
+        }
+        
+        private String toPattern() {
+            if (format instanceof SimpleDateFormat) {
+                return ((SimpleDateFormat)format).toPattern();
+//hey {al} - 
+//            } else if (format instanceof java.text.SimpleDateFormat) {
+//                return ((java.text.SimpleDateFormat)format).toPattern();
+            } else {
+                return "";
+            }
+        }
+
+        Calendar  calendar;
+        DateFormat          format;
+        String              name;
+        JTextField           text;
+        Checkbox            rollAdd;
+    };
+
+    private final CalendarRec[] calendars = {
+        new CalendarRec("Gregorian",        new GregorianCalendar()),
+        new CalendarRec("Hebrew",           new HebrewCalendar()),
+        new CalendarRec("Islamic (civil)",  makeIslamic(true)),
+        new CalendarRec("Islamic (true)",   makeIslamic(false)),
+        new CalendarRec("Buddhist",         new BuddhistCalendar()),
+        new CalendarRec("Japanese",         new JapaneseCalendar()),
+//        new CalendarRec("Chinese",          new ChineseCalendar()),
+    };
+
+    static private final Calendar makeIslamic(boolean civil) {
+        IslamicCalendar cal = new IslamicCalendar();
+        cal.setCivil(civil);
+        return cal;
+    };
+};
+
+class RollAddField {
+    RollAddField(int field, String name) {
+        this.field = field;
+        this.name = name;
+    }
+    int field;
+    String name;
+};
diff --git a/src/com/ibm/icu/dev/demo/calendar/CalendarFrame.java b/src/com/ibm/icu/dev/demo/calendar/CalendarFrame.java
new file mode 100755
index 0000000..5e2193a
--- /dev/null
+++ b/src/com/ibm/icu/dev/demo/calendar/CalendarFrame.java
@@ -0,0 +1,417 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1997-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/demo/calendar/CalendarFrame.java,v $ 
+ * $Date: 2003/09/04 00:55:19 $ 
+ * $Revision: 1.13 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.demo.calendar;
+
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.HebrewCalendar;
+import com.ibm.icu.util.BuddhistCalendar;
+import com.ibm.icu.util.JapaneseCalendar;
+import com.ibm.icu.util.IslamicCalendar;
+import java.awt.*;
+import java.awt.event.*;
+import com.ibm.icu.text.DateFormat;
+import java.util.Date;
+import java.util.SimpleTimeZone;
+
+import com.ibm.icu.util.GregorianCalendar;
+import java.util.Locale;
+import com.ibm.icu.dev.demo.impl.*;
+
+/**
+ * A Frame is a top-level window with a title. The default layout for a frame
+ * is BorderLayout.  The CalendarFrame class defines the window layout of
+ * CalendarDemo.
+ */
+class CalendarFrame extends Frame
+{
+    private static final boolean DEBUG = false;
+
+    private DemoApplet applet;
+
+    /**
+     * Constructs a new CalendarFrame that is initially invisible.
+     */
+    public CalendarFrame(DemoApplet myApplet)
+    {
+        super("Calendar Demo");
+        this.applet = myApplet;
+        init();
+
+        // When the window is closed, we want to shut down the applet or application
+        addWindowListener(
+            new WindowAdapter() {
+                public void windowClosing(WindowEvent e) {
+                    setVisible(false);
+                    dispose();
+
+                    if (applet != null) {
+                        applet.demoClosed();
+                    } else System.exit(0);
+                }
+            } );
+    }
+
+    private Choice          displayMenu;
+    private Locale[]        locales = DemoUtility.getG7Locales();
+
+    private Calendar        calendars[]   = new Calendar[2];
+    private Choice          calMenu[]     = new Choice[2];
+    private ColoredLabel    monthLabel[]  = new ColoredLabel[2];
+    private DateFormat      monthFormat[] = new DateFormat[2];
+
+    private Button          prevYear;
+    private Button          prevMonth;
+    private Button          gotoToday;
+    private Button          nextMonth;
+    private Button          nextYear;
+    private CalendarPanel   calendarPanel;
+
+    private static void add(Container container, Component component,
+                            GridBagLayout g, GridBagConstraints c,
+                            int gridwidth, int weightx)
+    {
+        c.gridwidth = gridwidth;
+        c.weightx = weightx;
+        g.setConstraints(component, c);
+        container.add(component);
+    }
+
+    /**
+     * Initializes the applet. You never need to call this directly, it
+     * is called automatically by the system once the applet is created.
+     */
+    public void init() {
+        setBackground(DemoUtility.bgColor);
+        setLayout(new BorderLayout(10,10));
+
+        Panel topPanel = new Panel();
+        GridBagLayout g = new GridBagLayout();
+        topPanel.setLayout(g);
+        GridBagConstraints c = new GridBagConstraints();
+        c.fill = GridBagConstraints.HORIZONTAL;
+
+        // Build the two menus for selecting which calendar is displayed,
+        // plus the month/year label for each calendar
+        for (int i = 0; i < 2; i++) {
+            calMenu[i] = new Choice();
+            for (int j = 0; j < CALENDARS.length; j++) {
+                calMenu[i].addItem(CALENDARS[j].name);
+            }
+            calMenu[i].setBackground(DemoUtility.choiceColor);
+            calMenu[i].select(i);
+            calMenu[i].addItemListener(new CalMenuListener());
+
+            // Label for the current month name
+            monthLabel[i] = new ColoredLabel("", COLORS[i]);
+            monthLabel[i].setFont(DemoUtility.titleFont);
+
+            // And the default calendar to use for this slot
+            calendars[i] = CALENDARS[i].calendar;
+
+            add(topPanel, calMenu[i], g, c, 5, 0);
+            add(topPanel, monthLabel[i], g, c, GridBagConstraints.REMAINDER, 1);
+        }
+
+        // Now add the next/previous year/month buttons:
+        prevYear = new Button("<<");
+        prevYear.addActionListener(new AddAction(Calendar.YEAR, -1));
+
+        prevMonth = new Button("<");
+        prevMonth.addActionListener(new AddAction(Calendar.MONTH, -1));
+
+        gotoToday = new Button("Today");
+        gotoToday.addActionListener( new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e) {
+                calendarPanel.setDate( new Date() );
+                updateMonthName();
+            }
+        } );
+
+        nextMonth = new Button(">");
+        nextMonth.addActionListener(new AddAction(Calendar.MONTH, 1));
+
+        nextYear = new Button(">>");
+        nextYear.addActionListener(new AddAction(Calendar.YEAR, 1));
+
+        c.fill = GridBagConstraints.NONE;
+        add(topPanel, prevYear,  g, c, 1, 0);
+        add(topPanel, prevMonth, g, c, 1, 0);
+        add(topPanel, gotoToday, g, c, 1, 0);
+        add(topPanel, nextMonth, g, c, 1, 0);
+        add(topPanel, nextYear,  g, c, 1, 0);
+
+        // Now add the menu for selecting the display language
+        Panel displayPanel = new Panel();
+        {
+            displayMenu = new Choice();
+            Locale defaultLocale = Locale.getDefault();
+            int bestMatch = -1, thisMatch = -1;
+            int selectMe = 0;
+            
+            for (int i = 0; i < locales.length; i++) {
+                if (i > 0 &&
+                        locales[i].getLanguage().equals(locales[i-1].getLanguage()) ||
+                    i < locales.length - 1 &&
+                        locales[i].getLanguage().equals(locales[i+1].getLanguage()))
+                {
+                    displayMenu.addItem( locales[i].getDisplayName() );
+                } else {
+                    displayMenu.addItem( locales[i].getDisplayLanguage());
+                }
+
+                thisMatch = DemoUtility.compareLocales(locales[i], defaultLocale);
+                
+                if (thisMatch >= bestMatch) {
+                    bestMatch = thisMatch;
+                    selectMe = i;
+                }
+            }
+            
+            displayMenu.setBackground(DemoUtility.choiceColor);
+            displayMenu.select(selectMe);
+
+            displayMenu.addItemListener( new ItemListener()
+            {
+                 public void itemStateChanged(ItemEvent e) {
+                    Locale loc = locales[displayMenu.getSelectedIndex()];
+                    calendarPanel.setLocale( loc );
+                    monthFormat[0] = monthFormat[1] = null;
+                    updateMonthName();
+                    repaint();
+                }
+            } );
+
+            Label l1 = new Label("Display Language:", Label.RIGHT);
+            l1.setFont(DemoUtility.labelFont);
+
+            displayPanel.setLayout(new FlowLayout());
+            displayPanel.add(l1);
+            displayPanel.add(displayMenu);
+
+        }
+        c.fill = GridBagConstraints.NONE;
+        c.anchor = GridBagConstraints.EAST;
+
+        add(topPanel, displayPanel, g, c, GridBagConstraints.REMAINDER, 0);
+
+        // The title, buttons, etc. go in a panel at the top of the window
+        add("North", topPanel);
+
+        // The copyright notice goes at the bottom of the window
+        Label copyright = new Label(DemoUtility.copyright1, Label.LEFT);
+        copyright.setFont(DemoUtility.creditFont);
+        add("South", copyright);
+
+        // Now create the big calendar panel and stick it in the middle
+        calendarPanel = new CalendarPanel( locales[displayMenu.getSelectedIndex()] );
+        add("Center", calendarPanel);
+
+        for (int i = 0; i < 2; i++) {
+            calendarPanel.setCalendar(i, calendars[i]);
+            calendarPanel.setColor(i, COLORS[i]);
+        }
+
+        updateMonthName();
+    };
+
+
+    private void updateMonthName()
+    {
+            for (int i = 0; i < 2; i++) {
+                try {
+                    if (monthFormat[i] == null) {     // TODO: optimize
+                        DateFormat f = DateFormat.getDateTimeInstance(
+                                                calendars[i], DateFormat.MEDIUM, -1,
+                                                locales[displayMenu.getSelectedIndex()]);
+                        if (f instanceof com.ibm.icu.text.SimpleDateFormat) {
+                            com.ibm.icu.text.SimpleDateFormat f1 = (com.ibm.icu.text.SimpleDateFormat) f;
+                            f1.applyPattern("MMMM, yyyy G");
+                            f1.setTimeZone(new SimpleTimeZone(0, "UTC"));
+                        }
+                        monthFormat[i] = f;
+                    }
+                } catch (ClassCastException e) {
+                    //hey {lw} - there's something wrong in this routine that cuases exceptions.
+                    System.out.println(e);
+                }
+
+                monthLabel[i].setText( monthFormat[i].format( calendarPanel.firstOfMonth() ));
+            }
+    }
+
+    /**
+     * CalMenuListener responds to events in the two popup menus that select
+     * the calendar systems to be used in the display.  It figures out which
+     * of the two menus the event occurred in and updates the corresponding
+     * element of the calendars[] array to match the new selection.
+     */
+    private class CalMenuListener implements ItemListener
+    {
+         public void itemStateChanged(ItemEvent e)
+         {
+            for (int i = 0; i < calMenu.length; i++)
+            {
+                if (e.getItemSelectable() == calMenu[i])
+                {
+                    // We found the menu that the event happened in.
+                    // Figure out which new calendar they selected.
+                    Calendar newCal = CALENDARS[ calMenu[i].getSelectedIndex() ].calendar;
+
+                    if (newCal != calendars[i])
+                    {
+                        // If any of the other menus are set to the same new calendar
+                        // we're about to use for this menu, set them to the current
+                        // calendar from *this* menu so we won't have two the same
+                        for (int j = 0; j < calendars.length; j++) {
+                            if (j != i && calendars[j] == newCal) {
+                                calendars[j] = calendars[i];
+                                calendarPanel.setCalendar(j, calendars[j]);
+                                monthFormat[j] = null;
+
+                                for (int k = 0; k < CALENDARS.length; k++) {
+                                    if (calendars[j] == CALENDARS[k].calendar) {
+                                        calMenu[j].select(k);
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        // Now update this menu to use the new calendar the user selected
+                        calendars[i] = newCal;
+                        calendarPanel.setCalendar(i, newCal);
+                        monthFormat[i] = null;
+
+                        updateMonthName();
+                    }
+                    break;
+                }
+            }
+         }
+    };
+
+    /**
+     * AddAction handles the next/previous year/month buttons...
+     */
+    private class AddAction implements ActionListener {
+        AddAction(int field, int amount) {
+            this.field = field;
+            this.amount = amount;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            calendarPanel.add(field, amount);
+            updateMonthName();
+        }
+
+        private int field, amount;
+    }
+
+    /**
+     * ColoredLabel is similar to java.awt.Label, with two differences:
+     *
+     *  - You can set its text color
+     *
+     *  - It draws text using drawString rather than using a host-specific
+     *    "Peer" object like AWT does.  On 1.2, using drawString gives
+     *    us Bidi reordering for free.
+     */
+    static private class ColoredLabel extends Component {
+        public ColoredLabel(String label) {
+            text = label;
+        }
+
+        public ColoredLabel(String label, Color c) {
+            text = label;
+            color = c;
+        }
+
+        public void setText(String label) {
+            text = label;
+            repaint();
+        }
+
+        public void setFont(Font f) {
+            font = f;
+            repaint();
+        }
+
+        public void paint(Graphics g) {
+            FontMetrics fm = g.getFontMetrics(font);
+
+            Rectangle bounds = getBounds();
+
+            g.setColor(color);
+            g.setFont(font);
+            g.drawString(text, fm.stringWidth("\u00a0"),
+                         bounds.height/2 + fm.getHeight()
+                         - fm.getAscent() + fm.getLeading()/2);
+        }
+
+        public Dimension getPreferredSize() {
+            return getMinimumSize();
+        }
+
+        public Dimension getMinimumSize() {
+            FontMetrics fm = getFontMetrics(font);
+
+            return new Dimension( fm.stringWidth(text) + 2*fm.stringWidth("\u00a0"),
+                                  fm.getHeight() + fm.getLeading()*2);
+        }
+
+        String text;
+        Color color = Color.black;
+        Font font = DemoUtility.labelFont;
+    }
+
+    /**
+     * Print out the error message while debugging this program.
+     */
+    public void errorText(String s)
+    {
+        if (DEBUG)
+        {
+            System.out.println(s);
+        }
+    }
+
+    class CalendarRec {
+        public CalendarRec(String nameStr, Calendar cal)
+        {
+            name = nameStr;
+            calendar = cal;
+        }
+
+        Calendar  calendar;
+        String              name;
+    };
+
+    private final CalendarRec[] CALENDARS = {
+        new CalendarRec("Gregorian Calendar",       new GregorianCalendar()),
+        new CalendarRec("Hebrew Calendar",          new HebrewCalendar()),
+        new CalendarRec("Islamic Calendar",         makeIslamic(false)),
+        new CalendarRec("Islamic Civil Calendar ",  makeIslamic(true)),
+        new CalendarRec("Buddhist Calendar",        new BuddhistCalendar()),
+        new CalendarRec("Japanese Calendar",        new JapaneseCalendar()),
+    };
+
+    static private final Calendar makeIslamic(boolean civil) {
+        IslamicCalendar cal = new IslamicCalendar();
+        cal.setCivil(civil);
+        return cal;
+    };
+
+    static final Color[] COLORS = { Color.blue, Color.black };
+}
+
diff --git a/src/com/ibm/icu/dev/demo/calendar/CalendarPanel.java b/src/com/ibm/icu/dev/demo/calendar/CalendarPanel.java
new file mode 100755
index 0000000..85b798d
--- /dev/null
+++ b/src/com/ibm/icu/dev/demo/calendar/CalendarPanel.java
@@ -0,0 +1,362 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1997-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/demo/calendar/CalendarPanel.java,v $ 
+ * $Date: 2003/09/04 00:55:20 $ 
+ * $Revision: 1.12 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.demo.calendar;
+
+import java.awt.*;
+
+import java.util.SimpleTimeZone;
+import java.util.Date;
+import java.util.Locale;
+
+import com.ibm.icu.util.*;
+import com.ibm.icu.text.*;
+import com.ibm.icu.dev.demo.impl.*;
+
+class CalendarPanel extends Canvas {
+
+    public CalendarPanel( Locale locale ) {
+        setLocale(locale);
+    }
+
+    public void setLocale(Locale locale) {
+        if (fDisplayLocale == null || !fDisplayLocale.equals(locale)) {
+            fDisplayLocale = locale;
+            dirty = true;
+
+            for (int i = 0; i < fCalendar.length; i++) {
+                if (fCalendar[i] != null) {
+                    fSymbols[i] = new DateFormatSymbols(fCalendar[i],
+                                                        fDisplayLocale);
+                }
+            }
+            String lang = locale.getLanguage();
+            leftToRight = !(lang.equals("iw") || lang.equals("ar"));
+
+            repaint();
+        }
+    }
+
+    public void setDate(Date date) {
+        fStartOfMonth = date;
+        dirty = true;
+        repaint();
+    }
+
+    public void add(int field, int delta)
+    {
+        synchronized(fCalendar) {
+            fCalendar[0].setTime(fStartOfMonth);
+            fCalendar[0].add(field, delta);
+            fStartOfMonth = fCalendar[0].getTime();
+        }
+        dirty = true;
+        repaint();
+    }
+
+    public void setColor(int index, Color c) {
+        fColor[index] = c;
+        repaint();
+    }
+
+    public void setCalendar(int index, Calendar c) {
+        Date date = (fCalendar[index] == null) ? new Date()
+                                               : fCalendar[index].getTime();
+
+        fCalendar[index] = c;
+        fCalendar[index].setTime(date);
+
+        fSymbols[index] = new DateFormatSymbols(c, fDisplayLocale);
+        dirty = true;
+        repaint();
+    }
+
+    public Calendar getCalendar(int index) {
+        return fCalendar[index];
+    }
+
+    public Locale getDisplayLocale() {
+        return fDisplayLocale;
+    }
+
+    public Date firstOfMonth() {
+        return fStartOfMonth;
+    }
+
+    private Date startOfMonth(Date dateInMonth)
+    {
+        synchronized(fCalendar) {
+            fCalendar[0].setTime(dateInMonth);
+
+            int era = fCalendar[0].get(Calendar.ERA);
+            int year = fCalendar[0].get(Calendar.YEAR);
+            int month = fCalendar[0].get(Calendar.MONTH);
+
+            fCalendar[0].clear();
+            fCalendar[0].set(Calendar.ERA, era);
+            fCalendar[0].set(Calendar.YEAR, year);
+            fCalendar[0].set(Calendar.MONTH, month);
+            fCalendar[0].set(Calendar.DATE, 1);
+
+            return fCalendar[0].getTime();
+        }
+    }
+
+    private void calculate()
+    {
+        //
+        // As a workaround for JDK 1.1.3 and below, where Calendars and time
+        // zones are a bit goofy, always set my calendar's time zone to UTC.
+        // You would think I would want to do this in the "set" function above,
+        // but if I do that, the program hangs when this class is loaded,
+        // perhaps due to some sort of static initialization ordering problem.
+        // So I do it here instead.
+        //
+        fCalendar[0].setTimeZone(new SimpleTimeZone(0, "UTC"));
+
+        Calendar c = (Calendar)fCalendar[0].clone(); // Temporary copy
+
+        fStartOfMonth = startOfMonth(fStartOfMonth);
+
+        // Stash away a few useful constants for this calendar and display
+        minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
+        daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;
+
+        firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();
+
+        // Stash away a Date for the start of this month
+
+        // Find the day of week of the first day in this month
+        c.setTime(fStartOfMonth);
+        firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
+        int firstWeek = c.get(Calendar.WEEK_OF_MONTH);
+
+        // Now find the # of days in the month
+        c.roll(Calendar.DATE, false);
+        daysInMonth = c.get(Calendar.DATE);
+
+        // Finally, find the end of the month, i.e. the start of the next one
+        c.roll(Calendar.DATE, true);
+        c.add(Calendar.MONTH, 1);
+        c.getTime();        // JDK 1.1.2 bug workaround
+        c.add(Calendar.SECOND, -1);
+        Date endOfMonth = c.getTime();
+        if(endOfMonth==null){
+         //do nothing
+        }
+        endOfMonth = null;
+        int lastWeek = c.get(Calendar.WEEK_OF_MONTH);
+        
+        // Calculate the number of full or partial weeks in this month.
+        numWeeks = lastWeek - firstWeek + 1;
+
+        dirty = false;
+    }
+
+    static final int XINSET = 4;
+    static final int YINSET = 2;
+
+    /*
+     * Convert from the day number within a month (1-based)
+     * to the cell coordinates on the calendar (0-based)
+     */
+    private void dateToCell(int date, Point pos)
+    {
+        int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
+        if (firstDayInMonth < firstDayOfWeek) {
+            cell += daysInWeek;
+        }
+
+        pos.x = cell % daysInWeek;
+        pos.y = cell / daysInWeek;
+    }
+    //private Point dateToCell(int date) {
+    //    Point p = new Point(0,0);
+    //    dateToCell(date, p);
+    //    return p;
+    //}
+
+    public void paint(Graphics g) {
+
+        if (dirty) {
+            calculate();
+        }
+
+        Point cellPos = new Point(0,0);     // Temporary variable
+        Dimension d = this.getSize();
+
+        g.setColor(Color.lightGray);
+        g.fillRect(0,0,d.width,d.height);
+
+        // Draw the day names at the top
+        g.setColor(Color.black);
+        g.setFont(DemoUtility.labelFont);
+        FontMetrics fm = g.getFontMetrics();
+        int labelHeight = fm.getHeight() + YINSET * 2;
+
+        int v = fm.getAscent() + YINSET;
+        for (int i = 0; i < daysInWeek; i++) {
+            int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1;
+            String dayName = fSymbols[0].getWeekdays()[dayNum];
+
+
+            double h;
+            if (leftToRight) {
+                h = d.width*(i + 0.5) / daysInWeek;
+            } else {
+                h = d.width*(daysInWeek - i - 0.5) / daysInWeek;
+            }
+            h -= fm.stringWidth(dayName) / 2;
+
+            g.drawString(dayName, (int)h, v);
+        }
+
+        double cellHeight = (d.height - labelHeight - 1) / numWeeks;
+        double cellWidth = (double)(d.width - 1) / daysInWeek;
+
+        // Draw a white background in the part of the calendar
+        // that displays this month.
+        // First figure out how much of the first week should be shaded.
+        {
+            g.setColor(Color.white);
+            dateToCell(1, cellPos);
+            int width = (int)(cellPos.x*cellWidth);  // Width of unshaded area
+
+            if (leftToRight) {
+                g.fillRect((int)(width), labelHeight ,
+                           d.width - width, (int)cellHeight);
+            } else {
+                g.fillRect(0, labelHeight ,
+                           d.width - width, (int)cellHeight);
+            }
+
+            // All of the intermediate weeks get shaded completely
+            g.fillRect(0, (int)(labelHeight + cellHeight),
+                        d.width, (int)(cellHeight * (numWeeks - 2)));
+
+            // Now figure out the last week.
+            dateToCell(daysInMonth, cellPos);
+            width = (int)((cellPos.x+1)*cellWidth);  // Width of shaded area
+
+            if (leftToRight) {
+                g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight),
+                           width, (int)cellHeight);
+            } else {
+                g.fillRect(d.width - width, (int)(labelHeight + (numWeeks-1) * cellHeight),
+                           width, (int)cellHeight);
+            }
+
+        }
+        // Draw the X/Y grid lines
+        g.setColor(Color.black);
+        for (int i = 0; i <= numWeeks; i++) {
+            int y = (int)(labelHeight + i * cellHeight);
+            g.drawLine(0, y, d.width - 1, y);
+        }
+        for (int i = 0; i <= daysInWeek; i++) {
+            int x = (int)(i * cellWidth);
+            g.drawLine(x, labelHeight, x, d.height - 1);
+        }
+
+        // Now loop through all of the days in the month, figure out where
+        // they go in the grid, and draw the day # for each one
+
+        // Figure out the date of the first cell in the calendar display
+        int cell = (1 + firstDayInMonth - firstDayOfWeek - minDay);
+        if (firstDayInMonth < firstDayOfWeek) {
+            cell += daysInWeek;
+        }
+
+        Calendar c = (Calendar)fCalendar[0].clone();
+        c.setTime(fStartOfMonth);
+        c.add(Calendar.DATE, -cell);
+
+        StringBuffer buffer = new StringBuffer();
+
+        for (int row = 0; row < numWeeks; row++) {
+            for (int col = 0; col < daysInWeek; col++) {
+
+                g.setFont(DemoUtility.numberFont);
+                g.setColor(Color.black);
+                fm = g.getFontMetrics();
+
+                int cellx;
+                if (leftToRight) {
+                    cellx = (int)((col) * cellWidth);
+                } else {
+                    cellx = (int)((daysInWeek - col - 1) * cellWidth);
+                }
+
+                int celly = (int)(row * cellHeight + labelHeight);
+
+                for (int i = 0; i < 2; i++) {
+                    fCalendar[i].setTime(c.getTime());
+
+                    int date = fCalendar[i].get(Calendar.DATE);
+                    buffer.setLength(0);
+                    buffer.append(date);
+                    String dayNum = buffer.toString();
+
+                    int x;
+
+                    if (leftToRight) {
+                        x = cellx + (int)cellWidth - XINSET - fm.stringWidth(dayNum);
+                    } else {
+                        x = cellx + XINSET;
+                    }
+                    int y = celly + + fm.getAscent() + YINSET + i * fm.getHeight();
+
+                    if (fColor[i] != null) {
+                        g.setColor(fColor[i]);
+                    }
+                    g.drawString(dayNum, x, y);
+
+                    if (date == 1 || row == 0 && col == 0) {
+                        g.setFont(DemoUtility.numberFont);
+                        String month = fSymbols[i].getMonths()[
+                                            fCalendar[i].get(Calendar.MONTH)];
+
+                        if (leftToRight) {
+                            x = cellx + XINSET;
+                        } else {
+                            x = cellx + (int)cellWidth - XINSET - fm.stringWidth(month);
+                        }
+                        g.drawString(month, x, y);
+                    }
+                }
+
+                c.add(Calendar.DATE, 1);
+            }
+        }
+    }
+
+    // Important state variables
+    private Calendar[]          fCalendar = new Calendar[4];
+    private Color[]             fColor = new Color[4];
+
+    private Locale              fDisplayLocale;
+    private DateFormatSymbols[] fSymbols = new DateFormatSymbols[4];
+
+    private Date                fStartOfMonth = new Date();     // 00:00:00 on first day of month
+
+    // Cached calculations to make drawing faster.
+    private transient int       minDay;           // Minimum legal day #
+    private transient int       daysInWeek;       // # of days in a week
+    private transient int       firstDayOfWeek;   // First day to display in week
+    private transient int       numWeeks;         // # full or partial weeks in month
+    private transient int       daysInMonth;      // # days in this month
+    private transient int       firstDayInMonth;  // Day of week of first day in month
+    private transient boolean   leftToRight;
+
+    private transient boolean dirty = true;
+}
diff --git a/src/com/ibm/icu/dev/demo/holiday/HolidayCalendarDemo.java b/src/com/ibm/icu/dev/demo/holiday/HolidayCalendarDemo.java
new file mode 100755
index 0000000..9012825
--- /dev/null
+++ b/src/com/ibm/icu/dev/demo/holiday/HolidayCalendarDemo.java
@@ -0,0 +1,715 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/demo/holiday/HolidayCalendarDemo.java,v $ 
+ * $Date: 2003/09/04 00:55:20 $ 
+ * $Revision: 1.12 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.demo.holiday;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import com.ibm.icu.text.SimpleDateFormat;
+import java.text.DateFormatSymbols;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.Vector;
+import java.util.Date;
+
+import com.ibm.icu.util.*;
+import com.ibm.icu.dev.demo.impl.*;
+
+/**
+ * CalendarDemo demonstrates how Calendar works.
+ */
+public class HolidayCalendarDemo extends DemoApplet 
+{
+    /**
+     * The main function which defines the behavior of the CalendarDemo
+     * applet when an applet is started.
+     */
+    public static void main(String argv[]) {
+
+        new HolidayCalendarDemo().showDemo();
+    }
+
+    /* This creates a CalendarFrame for the demo applet. */
+    public Frame createDemoFrame(DemoApplet applet) {
+        return new CalendarFrame(applet);
+    }
+
+	/**
+	* A Frame is a top-level window with a title. The default layout for a frame
+	* is BorderLayout.  The CalendarFrame class defines the window layout of
+	* CalendarDemo.
+	*/
+	private static class CalendarFrame extends Frame implements ActionListener,
+                                                                ItemListener
+	{
+    	//private static final String creditString = ""; // unused
+
+    	private static final boolean DEBUG = false;
+
+    	//private Locale curLocale = Locale.US; // unused
+
+    	private DemoApplet applet;
+
+    	private static final Locale[] calendars = {
+        	//new Locale("de","AT"),
+        	Locale.CANADA,
+        	Locale.CANADA_FRENCH,
+        	Locale.FRANCE,
+        	Locale.GERMANY,
+        	new Locale("iw","IL"),
+        	new Locale("el","GR"),
+        	//new Locale("es","MX"),
+        	Locale.UK,
+        	Locale.US,
+    	};
+    	private static final Locale[] displays = {
+        	Locale.CANADA,
+        	Locale.UK,
+        	Locale.US,
+        	Locale.FRANCE,
+        	Locale.CANADA_FRENCH,
+        	//new Locale("de","AT"),
+        	Locale.GERMAN,
+        	new Locale("el","GR"),
+        	//new Locale("iw","IL"),
+        	new Locale("es","MX"),
+    	};
+
+    	/**
+    	* Constructs a new CalendarFrame that is initially invisible.
+    	*/
+    	public CalendarFrame(DemoApplet applet)
+    	{
+        	super("Calendar Demo");
+        	this.applet = applet;
+        	init();
+        	start();
+        	enableEvents(WindowEvent.WINDOW_CLOSING);
+    	}
+
+    	/**
+    	* Initializes the applet. You never need to call this directly, it
+    	* is called automatically by the system once the applet is created.
+    	*/
+    	public void init()
+    	{
+        	// Get G7 locales only for demo purpose. To get all the locales
+        	// supported, switch to calling Calendar.getAvailableLocales().
+        	// commented
+        	locales = displays;
+
+        	buildGUI();
+    	}
+
+    	//------------------------------------------------------------
+    	// package private
+    	//------------------------------------------------------------
+    	void addWithFont(Container container, Component foo, Font font) {
+        	if (font != null)
+            	foo.setFont(font);
+        	container.add(foo);
+    	}
+
+    	/**
+    	* Called to start the applet. You never need to call this method
+    	* directly, it is called when the applet's document is visited.
+    	*/
+    	public void start()
+    	{
+        	// do nothing
+    	}
+
+    	private Choice          localeMenu;
+    	private Choice          displayMenu;
+    	private Locale[]        locales;
+
+    	private Label           monthLabel;
+    	private Button          prevYear;
+    	private Button          prevMonth;
+    	private Button          gotoToday;
+    	private Button          nextMonth;
+    	private Button          nextYear;
+    	private CalendarPanel   calendarPanel;
+
+    	private static final Locale kFirstLocale = Locale.US;
+
+    	private static void add(Container container, Component component,
+                            	GridBagLayout g, GridBagConstraints c)
+    	{
+        	g.setConstraints(component, c);
+        	container.add(component);
+    	}
+
+    	public void buildGUI()
+    	{
+        	setBackground(DemoUtility.bgColor);
+        	setLayout(new BorderLayout(10,10));
+
+        	// Label for the demo's title
+        	Label titleLabel = new Label("Calendar Demo", Label.CENTER);
+        	titleLabel.setFont(DemoUtility.titleFont);
+
+        	// Label for the current month name
+        	monthLabel = new Label("", Label.LEFT);
+        	monthLabel.setFont(new Font(DemoUtility.titleFont.getName(),
+                                    	DemoUtility.titleFont.getStyle(),
+                                    	(DemoUtility.titleFont.getSize() * 3)/2));
+
+        	// Make the locale popup menus
+        	localeMenu= new Choice();
+        	localeMenu.addItemListener(this);
+        	int selectMe = 0;
+        	
+        	for (int i = 0; i < calendars.length; i++) {
+            	if (i > 0 &&
+                    	calendars[i].getCountry().equals(calendars[i-1].getCountry()) ||
+                	i < calendars.length - 1 &&
+                    	calendars[i].getCountry().equals(calendars[i+1].getCountry()))
+            	{
+                	localeMenu.addItem(calendars[i].getDisplayCountry() + " (" +
+                                	calendars[i].getDisplayLanguage() + ")");
+            	} else {
+                	localeMenu.addItem( calendars[i].getDisplayCountry() );
+            	}
+            	
+            	if (calendars[i].equals(kFirstLocale)) {
+                	selectMe = i;
+            	}
+        	}
+        	
+        	localeMenu.setBackground(DemoUtility.choiceColor);
+        	localeMenu.select(selectMe);
+
+        	displayMenu = new Choice();
+        	displayMenu.addItemListener(this);
+        	
+        	selectMe = 0;
+        	for (int i = 0; i < locales.length; i++) {
+            	if (i > 0 &&
+                    	locales[i].getLanguage().equals(locales[i-1].getLanguage()) ||
+                	i < locales.length - 1 &&
+                    	locales[i].getLanguage().equals(locales[i+1].getLanguage()))
+            	{
+                	displayMenu.addItem( locales[i].getDisplayName() );
+            	} else {
+                	displayMenu.addItem( locales[i].getDisplayLanguage());
+            	}
+            	
+            	if (locales[i].equals(kFirstLocale)) {
+            	    selectMe = i;
+            	}
+        	}
+        	
+        	displayMenu.setBackground(DemoUtility.choiceColor);
+        	displayMenu.select(selectMe);
+
+        	// Make all the next/previous/today buttons
+        	prevYear = new Button("<<");
+        	prevYear.addActionListener(this);
+        	prevMonth = new Button("<");
+        	prevMonth.addActionListener(this);
+        	gotoToday = new Button("Today");
+        	gotoToday.addActionListener(this);
+        	nextMonth = new Button(">");
+        	nextMonth.addActionListener(this);
+        	nextYear = new Button(">>");
+        	nextYear.addActionListener(this);
+
+        	// The month name and the control buttons are bunched together
+        	Panel monthPanel = new Panel();
+        	{
+            	GridBagLayout g = new GridBagLayout();
+            	GridBagConstraints c = new GridBagConstraints();
+            	monthPanel.setLayout(g);
+
+            	c.weightx = 1;
+            	c.weighty = 1;
+
+            	c.gridwidth = 1;
+            	c.fill = GridBagConstraints.HORIZONTAL;
+            	c.gridwidth = GridBagConstraints.REMAINDER;
+            	add(monthPanel, monthLabel, g, c);
+
+            	c.gridwidth = 1;
+            	add(monthPanel, prevYear, g, c);
+            	add(monthPanel, prevMonth, g, c);
+            	add(monthPanel, gotoToday, g, c);
+            	add(monthPanel, nextMonth, g, c);
+            	c.gridwidth = GridBagConstraints.REMAINDER;
+            	add(monthPanel, nextYear, g, c);
+        	}
+
+        	// Stick the menu and buttons in a little "control panel"
+        	Panel menuPanel = new Panel();
+        	{
+            	GridBagLayout g = new GridBagLayout();
+            	GridBagConstraints c = new GridBagConstraints();
+            	menuPanel.setLayout(g);
+
+            	c.weightx = 1;
+            	c.weighty = 1;
+
+            	c.fill = GridBagConstraints.HORIZONTAL;
+
+            	c.gridwidth = GridBagConstraints.RELATIVE;
+            	Label l1 = new Label("Holidays");
+            	l1.setFont(DemoUtility.labelFont);
+            	add(menuPanel, l1, g, c);
+
+            	c.gridwidth = GridBagConstraints.REMAINDER;
+            	add(menuPanel, localeMenu, g, c);
+
+            	c.gridwidth = GridBagConstraints.RELATIVE;
+            	Label l2 = new Label("Display:");
+            	l2.setFont(DemoUtility.labelFont);
+            	add(menuPanel, l2, g, c);
+
+            	c.gridwidth = GridBagConstraints.REMAINDER;
+            	add(menuPanel, displayMenu, g, c);
+        	}
+
+        	// The title, buttons, etc. go in a panel at the top of the window
+        	Panel topPanel = new Panel();
+        	{
+            	topPanel.setLayout(new BorderLayout());
+
+            	//topPanel.add("North", titleLabel);
+            	topPanel.add("Center", monthPanel);
+            	topPanel.add("East", menuPanel);
+        	}
+        	add("North", topPanel);
+
+        	// The copyright notice goes at the bottom of the window
+        	Label copyright = new Label(DemoUtility.copyright1, Label.LEFT);
+        	copyright.setFont(DemoUtility.creditFont);
+        	add("South", copyright);
+
+        	// Now create the big calendar panel and stick it in the middle
+        	calendarPanel = new CalendarPanel( kFirstLocale );
+        	add("Center", calendarPanel);
+
+        	updateMonthName();
+    	}
+
+    	private void updateMonthName()
+    	{
+        	SimpleDateFormat f = new SimpleDateFormat("MMMM yyyyy",
+                                                    	calendarPanel.getDisplayLocale());
+        	f.setCalendar(calendarPanel.getCalendar());
+        	f.setTimeZone(new SimpleTimeZone(0, "UTC"));        // JDK 1.1.2 workaround
+        	monthLabel.setText( f.format( calendarPanel.firstOfMonth() ));
+    	}
+    	
+    	/**
+    	* Handles the event. Returns true if the event is handled and should not
+    	* be passed to the parent of this component. The default event handler
+    	* calls some helper methods to make life easier on the programmer.
+    	*/
+    	public void actionPerformed(ActionEvent e)
+    	{
+    	    Object obj = e.getSource();
+    	    
+    	    // *** Button events are handled here.
+        	if (obj instanceof Button) {
+            	if (obj == nextMonth) {
+                	calendarPanel.add(Calendar.MONTH, +1);
+            	}
+            	else
+            	if (obj == prevMonth) {
+                	calendarPanel.add(Calendar.MONTH, -1);
+            	}
+            	else
+            	if (obj == prevYear) {
+                	calendarPanel.add(Calendar.YEAR, -1);
+            	}
+            	else
+            	if (obj == nextYear) {
+                	calendarPanel.add(Calendar.YEAR, +1);
+            	}
+            	else
+            	if (obj == gotoToday) {
+                	calendarPanel.set( new Date() );
+            	}
+            	updateMonthName();
+        	}
+    	}
+    	
+    	public void itemStateChanged(ItemEvent e)
+        {
+            Object obj = e.getSource();
+            if (obj == localeMenu) {
+            	calendarPanel.setCalendarLocale(calendars[localeMenu.getSelectedIndex()]);
+            	updateMonthName();
+        	}
+        	else 
+        	    if (obj == displayMenu) {
+            	    calendarPanel.setDisplayLocale(locales[displayMenu.getSelectedIndex()]);
+            	    updateMonthName();
+            	}
+        }
+        
+        /**
+    	* Print out the error message while debugging this program.
+    	*/
+    	public void errorText(String s)
+    	{
+        	if (DEBUG)
+        	{
+            	System.out.println(s);
+        	}
+    	}
+    	
+    	protected void processWindowEvent(WindowEvent e)
+        {
+            System.out.println("event " + e);
+            if (e.getID() == WindowEvent.WINDOW_CLOSING) {
+            	this.hide();
+            	this.dispose();
+
+            	if (applet != null) {
+            	    applet.demoClosed();
+            	} else {
+                    System.exit(0);
+            	}
+        	}
+        }
+	}
+
+
+	private static class CalendarPanel extends Canvas {
+
+    	public CalendarPanel( Locale locale ) {
+        	set(locale, locale, new Date());
+    	}
+
+    	public void setCalendarLocale(Locale locale) {
+        	set(locale, fDisplayLocale, fCalendar.getTime());
+    	}
+
+    	public void setDisplayLocale(Locale locale) {
+        	set(fCalendarLocale, locale, fCalendar.getTime());
+    	}
+
+    	public void set(Date date) {
+        	set(fCalendarLocale, fDisplayLocale, date);
+    	}
+
+    	public void set(Locale loc, Locale display, Date date)
+    	{
+        	if (fCalendarLocale == null || !loc.equals(fCalendarLocale)) {
+            	fCalendarLocale = loc;
+            	fCalendar = Calendar.getInstance(fCalendarLocale);
+            	fAllHolidays = Holiday.getHolidays(fCalendarLocale);
+        	}
+        	if (fDisplayLocale == null || !display.equals(fDisplayLocale)) {
+            	fDisplayLocale = display;
+            	fSymbols = new DateFormatSymbols(fDisplayLocale);
+        	}
+
+        	fStartOfMonth = date;
+
+        	dirty = true;
+        	repaint();
+    	}
+
+    	public void add(int field, int delta)
+    	{
+        	synchronized(fCalendar) {
+            	fCalendar.setTime(fStartOfMonth);
+            	fCalendar.add(field, delta);
+            	fStartOfMonth = fCalendar.getTime();
+        	}
+        	dirty = true;
+        	repaint();
+    	}
+
+    	public com.ibm.icu.util.Calendar getCalendar() {
+        	return fCalendar;
+    	}
+
+    	public Locale getCalendarLocale() {
+        	return fCalendarLocale;
+    	}
+
+    	public Locale getDisplayLocale() {
+        	return fDisplayLocale;
+    	}
+
+
+    	public Date firstOfMonth() {
+        	return fStartOfMonth;
+    	}
+
+    	private Date startOfMonth(Date dateInMonth)
+    	{
+        	synchronized(fCalendar) {
+            	fCalendar.setTime(dateInMonth);             // TODO: synchronization
+
+            	int era = fCalendar.get(Calendar.ERA);
+            	int year = fCalendar.get(Calendar.YEAR);
+            	int month = fCalendar.get(Calendar.MONTH);
+
+            	fCalendar.clear();
+            	fCalendar.set(Calendar.ERA, era);
+            	fCalendar.set(Calendar.YEAR, year);
+            	fCalendar.set(Calendar.MONTH, month);
+            	fCalendar.set(Calendar.DATE, 1);
+
+            	return fCalendar.getTime();
+        	}
+    	}
+
+    	private void calculate()
+    	{
+        	//
+        	// As a workaround for JDK 1.1.3 and below, where Calendars and time
+        	// zones are a bit goofy, always set my calendar's time zone to UTC.
+        	// You would think I would want to do this in the "set" function above,
+        	// but if I do that, the program hangs when this class is loaded,
+        	// perhaps due to some sort of static initialization ordering problem.
+        	// So I do it here instead.
+        	//
+        	fCalendar.setTimeZone(new SimpleTimeZone(0, "UTC"));
+
+        	Calendar c = (Calendar)fCalendar.clone(); // Temporary copy
+
+        	fStartOfMonth = startOfMonth(fStartOfMonth);
+
+        	// Stash away a few useful constants for this calendar and display
+        	minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
+        	daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;
+
+        	firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();
+
+        	// Stash away a Date for the start of this month
+
+        	// Find the day of week of the first day in this month
+        	c.setTime(fStartOfMonth);
+        	firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
+
+        	// Now find the # of days in the month
+        	c.roll(Calendar.DATE, false);
+        	daysInMonth = c.get(Calendar.DATE);
+
+        	// Finally, find the end of the month, i.e. the start of the next one
+        	c.roll(Calendar.DATE, true);
+        	c.add(Calendar.MONTH, 1);
+        	c.getTime();        // JDK 1.1.2 bug workaround
+        	c.add(Calendar.SECOND, -1);
+        	Date endOfMonth = c.getTime();
+
+        	//
+        	// Calculate the number of full or partial weeks in this month.
+        	// To do this I can just reuse the code that calculates which
+        	// calendar cell contains a given date.
+        	//
+        	numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1;
+
+        	// Remember which holidays fall on which days in this month,
+        	// to save the trouble of having to do it later
+        	fHolidays.setSize(0);
+
+        	for (int h = 0; h < fAllHolidays.length; h++)
+        	{
+            	Date d = fStartOfMonth;
+            	while ( (d = fAllHolidays[h].firstBetween(d, endOfMonth) ) != null)
+            	{
+                	c.setTime(d);
+                	fHolidays.addElement( new HolidayInfo(c.get(Calendar.DATE),
+                                        	fAllHolidays[h],
+                                        	fAllHolidays[h].getDisplayName(fDisplayLocale) ));
+
+                	d.setTime( d.getTime() + 1000 );    // "d++"
+            	}
+        	}
+        	dirty = false;
+    	}
+
+    	static final int INSET = 2;
+
+    	/*
+    	* Convert from the day number within a month (1-based)
+    	* to the cell coordinates on the calendar (0-based)
+    	*/
+    	private void dateToCell(int date, Point pos)
+    	{
+        	int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
+        	if (firstDayInMonth < firstDayOfWeek) {
+            	cell += daysInWeek;
+        	}
+
+        	pos.x = cell % daysInWeek;
+        	pos.y = cell / daysInWeek;
+    	}
+    	private Point dateToCell(int date) {
+        	Point p = new Point(0,0);
+        	dateToCell(date, p);
+        	return p;
+    	}
+
+    	public void paint(Graphics g) {
+
+        	if (dirty) {
+            	calculate();
+        	}
+
+        	Point cellPos = new Point(0,0);     // Temporary variable
+        	Dimension d = getSize();
+
+        	g.setColor(DemoUtility.bgColor);
+        	g.fillRect(0,0,d.width,d.height);
+
+        	// Draw the day names at the top
+        	g.setColor(Color.black);
+        	g.setFont(DemoUtility.labelFont);
+        	FontMetrics fm = g.getFontMetrics();
+        	int labelHeight = fm.getHeight() + INSET * 2;
+
+        	int v = fm.getAscent() + INSET;
+        	for (int i = 0; i < daysInWeek; i++) {
+            	int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1;
+            	String dayName = fSymbols.getWeekdays()[dayNum];
+
+            	int h = (int) (d.width * (i + 0.5)) / daysInWeek;
+            	h -= fm.stringWidth(dayName) / 2;
+
+            	g.drawString(dayName, h, v);
+        	}
+
+        	double cellHeight = (d.height - labelHeight - 1) / numWeeks;
+        	double cellWidth = (double)(d.width - 1) / daysInWeek;
+
+        	// Draw a white background in the part of the calendar
+        	// that displays this month.
+        	// First figure out how much of the first week should be shaded.
+        	{
+            	g.setColor(Color.white);
+            	dateToCell(1, cellPos);
+            	int width = (int)(cellPos.x*cellWidth);  // Width of unshaded area
+
+            	g.fillRect((int)(width), labelHeight ,
+                    	(int)(d.width - width), (int)cellHeight);
+
+            	// All of the intermediate weeks get shaded completely
+            	g.fillRect(0, (int)(labelHeight + cellHeight),
+                        	d.width, (int)(cellHeight * (numWeeks - 2)));
+
+            	// Now figure out the last week.
+            	dateToCell(daysInMonth, cellPos);
+            	width = (int)((cellPos.x+1)*cellWidth);  // Width of shaded area
+
+            	g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight),
+                        	width, (int)(cellHeight));
+
+        	}
+        	// Draw the X/Y grid lines
+        	g.setColor(Color.black);
+        	for (int i = 0; i <= numWeeks; i++) {
+            	int y = (int)(labelHeight + i * cellHeight);
+            	g.drawLine(0, y, d.width - 1, y);
+        	}
+        	for (int i = 0; i <= daysInWeek; i++) {
+            	int x = (int)(i * cellWidth);
+            	g.drawLine(x, labelHeight, x, d.height - 1);
+        	}
+
+        	// Now loop through all of the days in the month, figure out where
+        	// they go in the grid, and draw the day # for each one
+        	Font numberFont = new Font("Helvetica",Font.PLAIN,12);
+        	// not used Font holidayFont = DemoUtility.creditFont;
+
+        	Calendar c = (Calendar)fCalendar.clone();
+        	c.setTime(fStartOfMonth);
+
+        	for (int i = 1, h = 0; i <= daysInMonth; i++) {
+            	g.setFont(numberFont);
+            	g.setColor(Color.black);
+            	fm = g.getFontMetrics();
+
+            	dateToCell(i, cellPos);
+            	int x = (int)((cellPos.x + 1) * cellWidth);
+            	int y = (int)(cellPos.y * cellHeight + labelHeight);
+
+            	StringBuffer buffer = new StringBuffer();
+            	buffer.append(i);
+            	String dayNum = buffer.toString();
+
+            	x = x - INSET - fm.stringWidth(dayNum);
+            	y = y + fm.getAscent() + INSET;
+
+            	g.drawString(dayNum, x, y);
+
+            	// See if any of the holidays land on this day....
+            	HolidayInfo info = null;
+            	int count = 0;
+
+            	// Coordinates of lower-left corner of cell.
+            	x = (int)((cellPos.x) * cellWidth);
+            	y = (int)((cellPos.y+1) * cellHeight) + labelHeight;
+
+            	while (h < fHolidays.size() &&
+                    	(info = (HolidayInfo)fHolidays.elementAt(h)).date <= i)
+            	{
+                	if (info.date == i) {
+                    	// Draw the holiday here.
+                    	g.setFont(numberFont);
+                    	g.setColor(Color.red);
+
+                    	DemoTextBox box = new DemoTextBox(g, info.name, (int)(cellWidth - INSET));
+                    	box.draw(g, x + INSET, y - INSET - box.getHeight());
+
+                    	y -= (box.getHeight() + INSET);
+                    	count++;
+                	}
+                	h++;
+            	}
+        	}
+    	}
+
+    	// Important state variables
+    	private Locale              fCalendarLocale;    // Whose calendar
+    	private Calendar            fCalendar;          // Calendar for calculations
+
+    	private Locale              fDisplayLocale;     // How to display it
+    	private DateFormatSymbols   fSymbols;           // Symbols for drawing
+
+    	private Date                fStartOfMonth;      // 00:00:00 on first day of month
+
+    	// Cached calculations to make drawing faster.
+    	private transient int minDay;           // Minimum legal day #
+    	private transient int daysInWeek;       // # of days in a week
+    	private transient int firstDayOfWeek;   // First day to display in week
+    	private transient int numWeeks;         // # full or partial weeks in month
+    	private transient int daysInMonth;      // # days in this month
+    	private transient int firstDayInMonth;  // Day of week of first day in month
+
+    	private transient Holiday[] fAllHolidays;
+    	private transient Vector    fHolidays = new Vector(5,5);
+
+    	private transient boolean dirty = true;
+	}
+
+	private static class HolidayInfo {
+    	public HolidayInfo(int date, Holiday holiday, String name) {
+        	this.date = date;
+        	this.holiday = holiday;
+        	this.name = name;
+    	}
+
+    	public Holiday holiday;
+    	public int date;
+    	public String name;
+	}
+}
+
diff --git a/src/com/ibm/icu/dev/test/calendar/AstroTest.java b/src/com/ibm/icu/dev/test/calendar/AstroTest.java
new file mode 100755
index 0000000..aaefacc
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/AstroTest.java
@@ -0,0 +1,281 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/AstroTest.java,v $ 
+ * $Date: 2003/09/04 00:57:10 $ 
+ * $Revision: 1.12 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+
+// AstroTest
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+import com.ibm.icu.dev.test.*;
+import com.ibm.icu.util.*;
+import com.ibm.icu.util.CalendarAstronomer.*;
+import com.ibm.icu.text.DateFormat;
+
+// TODO: try finding next new moon after  07/28/1984 16:00 GMT
+
+public class AstroTest extends TestFmwk {
+    public static void main(String[] args) throws Exception {
+        new AstroTest().run(args);
+    }
+
+    static final double PI = Math.PI;
+    
+    static GregorianCalendar gc = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
+    static CalendarAstronomer astro = new CalendarAstronomer();
+    
+    public void TestSolarLongitude() {
+        final double tests[][] = {
+            { 1980, 7, 27, 00, 00, 124.114347 },
+            { 1988, 7, 27, 00, 00, 124.187732 },
+        };
+        logln("");
+        for (int i = 0; i < tests.length; i++) {
+            gc.clear();
+            gc.set((int)tests[i][0], (int)tests[i][1]-1, (int)tests[i][2], (int)tests[i][3], (int) tests[i][4]);
+            
+            astro.setDate(gc.getTime());
+            
+            double longitude = astro.getSunLongitude();
+            longitude = 0;
+            Equatorial result = astro.getSunPosition();
+            result = null;
+        }
+    }
+    
+    public void TestLunarPosition() {
+        final double tests[][] = {
+            { 1979, 2, 26, 16, 00,  0, 0 },
+        };
+        logln("");
+        
+        for (int i = 0; i < tests.length; i++) {
+            gc.clear();
+            gc.set((int)tests[i][0], (int)tests[i][1]-1, (int)tests[i][2], (int)tests[i][3], (int) tests[i][4]);
+            astro.setDate(gc.getTime());
+            
+            Equatorial result = astro.getMoonPosition();
+            result = null;
+        }
+
+    }
+    
+    public void TestCoordinates() {
+        Equatorial result = astro.eclipticToEquatorial(139.686111 * PI/ 180.0, 4.875278* PI / 180.0);
+        logln("result is " + result + ";  " + result.toHmsString());
+    }
+    
+    public void TestCoverage() {
+	GregorianCalendar cal = new GregorianCalendar(1958, Calendar.AUGUST, 15);
+	Date then = cal.getTime();
+	CalendarAstronomer myastro = new CalendarAstronomer(then);
+
+	//Latitude:  34 degrees 05' North  
+	//Longitude:  118 degrees 22' West  
+	double laLat = 34 + 5d/60, laLong = 360 - (118 + 22d/60);
+	CalendarAstronomer myastro2 = new CalendarAstronomer(laLong, laLat);
+
+	double eclLat = laLat * Math.PI / 360;
+	double eclLong = laLong * Math.PI / 360;
+	Ecliptic ecl = new Ecliptic(eclLat, eclLong);
+	logln("ecliptic: " + ecl);
+
+	CalendarAstronomer myastro3 = new CalendarAstronomer();
+	myastro3.setJulianDay((4713 + 2000) * 365.25);
+
+	CalendarAstronomer[] astronomers = {
+	    myastro, myastro2, myastro3, myastro2 // check cache
+	};
+
+	for (int i = 0; i < astronomers.length; ++i) {
+	    CalendarAstronomer astro = astronomers[i];
+
+	    logln("astro: " + astro);
+	    logln("   time: " + astro.getTime());
+	    logln("   date: " + astro.getDate());
+	    logln("   cent: " + astro.getJulianCentury());
+	    logln("   gw sidereal: " + astro.getGreenwichSidereal());
+	    logln("   loc sidereal: " + astro.getLocalSidereal());
+	    logln("   equ ecl: " + astro.eclipticToEquatorial(ecl));
+	    logln("   equ long: " + astro.eclipticToEquatorial(eclLong));
+	    logln("   horiz: " + astro.eclipticToHorizon(eclLong));
+	    logln("   sunrise: " + new Date(astro.getSunRiseSet(true)));
+	    logln("   sunset: " + new Date(astro.getSunRiseSet(false)));
+	    logln("   moon phase: " + astro.getMoonPhase());
+	    logln("   moonrise: " + new Date(astro.getMoonRiseSet(true)));
+	    logln("   moonset: " + new Date(astro.getMoonRiseSet(false)));
+	    logln("   prev summer solstice: " + new Date(astro.getSunTime(astro.SUMMER_SOLSTICE, false)));
+	    logln("   next summer solstice: " + new Date(astro.getSunTime(astro.SUMMER_SOLSTICE, true)));
+	    logln("   prev full moon: " + new Date(astro.getMoonTime(astro.FULL_MOON, false)));
+	    logln("   next full moon: " + new Date(astro.getMoonTime(astro.FULL_MOON, true)));
+	}
+    }
+
+    static final long DAY_MS = 24*60*60*1000L;
+
+    public void TestSunriseTimes() {
+        //        logln("Sunrise/Sunset times for San Jose, California, USA");
+        //        CalendarAstronomer astro = new CalendarAstronomer(-121.55, 37.20);
+        //        TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+
+        // We'll use a table generated by the UNSO website as our reference
+        // From: http://aa.usno.navy.mil/
+        //-Location: W079 25, N43 40
+        //-Rise and Set for the Sun for 2001
+        //-Zone:  4h West of Greenwich
+        int[] USNO = {
+             6,59, 19,45,
+             6,57, 19,46,
+             6,56, 19,47,
+             6,54, 19,48,
+             6,52, 19,49,
+             6,50, 19,51,
+             6,48, 19,52,
+             6,47, 19,53,
+             6,45, 19,54,
+             6,43, 19,55,
+             6,42, 19,57,
+             6,40, 19,58,
+             6,38, 19,59,
+             6,36, 20, 0,
+             6,35, 20, 1,
+             6,33, 20, 3,
+             6,31, 20, 4,
+             6,30, 20, 5,
+             6,28, 20, 6,
+             6,27, 20, 7,
+             6,25, 20, 8,
+             6,23, 20,10,
+             6,22, 20,11,
+             6,20, 20,12,
+             6,19, 20,13,
+             6,17, 20,14,
+             6,16, 20,16,
+             6,14, 20,17,
+             6,13, 20,18,
+             6,11, 20,19,
+        };
+
+        logln("Sunrise/Sunset times for Toronto, Canada");
+        CalendarAstronomer astro = new CalendarAstronomer(-(79+25/60), 43+40/60);
+        TimeZone tz = TimeZone.getTimeZone("America/Montreal");
+
+        GregorianCalendar cal = new GregorianCalendar(tz, Locale.US);
+        GregorianCalendar cal2 = new GregorianCalendar(tz, Locale.US);
+        cal.clear();
+        cal.set(Calendar.YEAR, 2001);
+        cal.set(Calendar.MONTH, Calendar.APRIL);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 12); // must be near local noon for getSunRiseSet to work
+
+        DateFormat df = DateFormat.getTimeInstance(cal, DateFormat.MEDIUM, Locale.US);
+        DateFormat df2 = DateFormat.getDateTimeInstance(cal, DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.US);
+        DateFormat day = DateFormat.getDateInstance(cal, DateFormat.MEDIUM, Locale.US);
+				
+        for (int i=0; i < 30; i++) {
+            astro.setDate(cal.getTime());
+			
+            Date sunrise = new Date(astro.getSunRiseSet(true));
+            Date sunset = new Date(astro.getSunRiseSet(false));
+
+            cal2.setTime(cal.getTime());
+            cal2.set(Calendar.SECOND,      0);
+            cal2.set(Calendar.MILLISECOND, 0);
+
+            cal2.set(Calendar.HOUR_OF_DAY, USNO[4*i+0]);
+            cal2.set(Calendar.MINUTE,      USNO[4*i+1]);
+            Date exprise = cal2.getTime();
+            cal2.set(Calendar.HOUR_OF_DAY, USNO[4*i+2]);
+            cal2.set(Calendar.MINUTE,      USNO[4*i+3]);
+            Date expset = cal2.getTime();
+            // Compute delta of what we got to the USNO data, in seconds
+            int deltarise = Math.abs((int)(sunrise.getTime() - exprise.getTime()) / 1000);
+            int deltaset = Math.abs((int)(sunset.getTime() - expset.getTime()) / 1000);
+
+            // Allow a deviation of 0..MAX_DEV seconds
+            // It would be nice to get down to 60 seconds, but at this
+            // point that appears to be impossible without a redo of the
+            // algorithm using something more advanced than Duffett-Smith.
+            final int MAX_DEV = 180;
+            if (deltarise > MAX_DEV || deltaset > MAX_DEV) {
+                if (deltarise > MAX_DEV) {
+                    errln("FAIL: " + day.format(cal.getTime()) +
+                          ", Sunrise: " + df2.format(sunrise) +
+                          " (USNO " + df.format(exprise) +
+                          " d=" + deltarise + "s)");
+                } else {
+                    logln(day.format(cal.getTime()) +
+                          ", Sunrise: " + df.format(sunrise) +
+                          " (USNO " + df.format(exprise) + ")");
+                }
+                if (deltaset > MAX_DEV) {
+                    errln("FAIL: " + day.format(cal.getTime()) +
+                          ", Sunset: " + df2.format(sunset) +
+                          " (USNO " + df.format(expset) +
+                          " d=" + deltaset + "s)");
+                } else {
+                    logln(day.format(cal.getTime()) +
+                          ", Sunset: " + df.format(sunset) +
+                          " (USNO " + df.format(expset) + ")");
+                }
+            } else {
+                logln(day.format(cal.getTime()) +
+                      ", Sunrise: " + df.format(sunrise) +
+                      " (USNO " + df.format(exprise) + ")" +
+                      ", Sunset: " + df.format(sunset) +
+                      " (USNO " + df.format(expset) + ")");
+            }
+            cal.add(Calendar.DATE, 1);
+        }		
+
+//		CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60);
+//		cal.clear();
+//		cal.set(cal.YEAR, 1986);
+//		cal.set(cal.MONTH, cal.MARCH);
+//		cal.set(cal.DATE, 10);		
+//		cal.set(cal.YEAR, 1988);
+//		cal.set(cal.MONTH, cal.JULY);
+//		cal.set(cal.DATE, 27);		
+//		a.setDate(cal.getTime());
+//		long r = a.getSunRiseSet2(true);		
+    }
+
+    public void TestBasics() {
+        // Check that our JD computation is the same as the book's (p. 88)
+        GregorianCalendar cal3 = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US);
+        DateFormat d3 = DateFormat.getDateTimeInstance(cal3, DateFormat.MEDIUM,DateFormat.MEDIUM,Locale.US);
+        cal3.clear();
+        cal3.set(cal3.YEAR, 1980);
+        cal3.set(cal3.MONTH, Calendar.JULY);
+        cal3.set(cal3.DATE, 27);
+        astro.setDate(cal3.getTime());
+        double jd = astro.getJulianDay() - 2447891.5;
+        double exp = -3444;
+        if (jd == exp) {
+            logln(d3.format(cal3.getTime()) + " => " + jd);
+        } else {
+            errln("FAIL: " + d3.format(cal3.getTime()) + " => " + jd +
+                  ", expected " + exp);
+        }
+
+//        cal3.clear();
+//        cal3.set(cal3.YEAR, 1990);
+//        cal3.set(cal3.MONTH, Calendar.JANUARY);
+//        cal3.set(cal3.DATE, 1);
+//        cal3.add(cal3.DATE, -1);
+//        astro.setDate(cal3.getTime());
+//        astro.foo();
+    }
+}
diff --git a/src/com/ibm/icu/dev/test/calendar/CalendarRegression.java b/src/com/ibm/icu/dev/test/calendar/CalendarRegression.java
new file mode 100755
index 0000000..b6075ed
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/CalendarRegression.java
@@ -0,0 +1,1888 @@
+/**
+ *******************************************************************************
+ * Copyright (C) 2000-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/CalendarRegression.java,v $
+ * $Date: 2003/09/04 00:57:10 $
+ * $Revision: 1.17 $
+ *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+import com.ibm.icu.util.*;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+import com.ibm.icu.text.*;
+
+/**
+ * @test 1.32 99/11/14
+ * @bug 4031502 4035301 4040996 4051765 4059654 4061476 4070502 4071197 4071385
+ * 4073929 4083167 4086724 4092362 4095407 4096231 4096539 4100311 4103271
+ * 4106136 4108764 4114578 4118384 4125881 4125892 4136399 4141665 4142933
+ * 4145158 4145983 4147269 4149677 4162587 4165343 4166109 4167060 4173516
+ * 4174361 4177484 4197699 4209071 4288792
+ */
+public class CalendarRegression extends com.ibm.icu.dev.test.TestFmwk {
+
+    public static void main(String[] args) throws Exception {
+        new CalendarRegression().run(args);
+    }
+
+    static final String[] FIELD_NAME = {
+        "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
+        "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
+        "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
+        "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
+        "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
+        "JULIAN_DAY", "MILLISECONDS_IN_DAY",
+    };
+
+    /*
+      Synopsis: java.sql.Timestamp constructor works wrong on Windows 95
+
+      ==== Here is the test ==== 
+      public static void main (String args[]) { 
+        java.sql.Timestamp t= new java.sql.Timestamp(0,15,5,5,8,13,123456700); 
+        logln("expected=1901-04-05 05:08:13.1234567"); 
+        logln(" result="+t); 
+      } 
+      
+      ==== Here is the output of the test on Solaris or NT ==== 
+      expected=1901-04-05 05:08:13.1234567 
+      result=1901-04-05 05:08:13.1234567 
+      
+      ==== Here is the output of the test on Windows95 ==== 
+      expected=1901-04-05 05:08:13.1234567 
+      result=1901-04-05 06:08:13.1234567 
+      */
+
+    public void Test4031502() {
+        // This bug actually occurs on Windows NT as well, and doesn't
+        // require the host zone to be set; it can be set in Java.
+        String[] ids = TimeZone.getAvailableIDs();
+        boolean bad = false;
+        for (int i=0; i<ids.length; ++i) {
+            TimeZone zone = TimeZone.getTimeZone(ids[i]);
+            GregorianCalendar cal = new GregorianCalendar(zone);
+            cal.clear();
+            cal.set(1900, 15, 5, 5, 8, 13);
+            if (cal.get(Calendar.HOUR) != 5) {
+                logln("Fail: " + zone.getID() + " " +
+                      zone.useDaylightTime() + " " +
+                      cal.get(Calendar.DST_OFFSET) / (60*60*1000) + " " +
+                      zone.getRawOffset() / (60*60*1000) +
+                      ": HOUR = " + cal.get(Calendar.HOUR));
+                bad = true;
+            } else if (false) { // Change to true to debug
+                logln("OK: " + zone.getID() + " " +
+                      zone.useDaylightTime() + " " +
+                      cal.get(Calendar.DST_OFFSET) / (60*60*1000) + " " +
+                      zone.getRawOffset() / (60*60*1000) +
+                      ": HOUR = " + cal.get(Calendar.HOUR));
+            }
+        }
+        if (bad) errln("TimeZone problems with GC");
+    }
+
+    public void Test4035301() {
+        GregorianCalendar c = new GregorianCalendar(98, 8, 7);
+        GregorianCalendar d = new GregorianCalendar(98, 8, 7);
+        if (c.after(d) ||
+            c.after(c) ||
+            c.before(d) ||
+            c.before(c) ||
+            !c.equals(c) ||
+            !c.equals(d))
+            errln("Fail");
+    }
+
+    public void Test4040996() {
+        String[] ids = TimeZone.getAvailableIDs(-8 * 60 * 60 * 1000);
+        SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, ids[0]);
+        pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
+        pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
+        Calendar calendar = new GregorianCalendar(pdt);
+
+        calendar.set(Calendar.MONTH,3);
+        calendar.set(Calendar.DAY_OF_MONTH,18);
+        calendar.set(Calendar.SECOND, 30);
+
+        logln("MONTH: " + calendar.get(Calendar.MONTH));
+        logln("DAY_OF_MONTH: " + 
+                           calendar.get(Calendar.DAY_OF_MONTH));
+        logln("MINUTE: " + calendar.get(Calendar.MINUTE));
+        logln("SECOND: " + calendar.get(Calendar.SECOND));
+
+        calendar.add(Calendar.SECOND,6);
+        //This will print out todays date for MONTH and DAY_OF_MONTH
+        //instead of the date it was set to.
+        //This happens when adding MILLISECOND or MINUTE also
+        logln("MONTH: " + calendar.get(Calendar.MONTH));
+        logln("DAY_OF_MONTH: " + 
+                           calendar.get(Calendar.DAY_OF_MONTH));
+        logln("MINUTE: " + calendar.get(Calendar.MINUTE));
+        logln("SECOND: " + calendar.get(Calendar.SECOND));
+        if (calendar.get(Calendar.MONTH) != 3 ||
+            calendar.get(Calendar.DAY_OF_MONTH) != 18 ||
+            calendar.get(Calendar.SECOND) != 36)
+            errln("Fail: Calendar.add misbehaves");
+    }
+
+    public void Test4051765() {
+        Calendar cal = Calendar.getInstance();
+        cal.setLenient(false);
+        cal.set(Calendar.DAY_OF_WEEK, 0);
+        try {
+            cal.getTime();
+            errln("Fail: DAY_OF_WEEK 0 should be disallowed");
+        }
+        catch (IllegalArgumentException e) {
+            return;
+        }
+    }
+    
+    /* User error - no bug here
+    public void Test4059524() {
+        // Create calendar for April 10, 1997
+        GregorianCalendar calendar  = new GregorianCalendar();
+        // print out a bunch of interesting things
+        logln("ERA: " + calendar.get(calendar.ERA));
+        logln("YEAR: " + calendar.get(calendar.YEAR));
+        logln("MONTH: " + calendar.get(calendar.MONTH));
+        logln("WEEK_OF_YEAR: " + 
+                           calendar.get(calendar.WEEK_OF_YEAR));
+        logln("WEEK_OF_MONTH: " + 
+                           calendar.get(calendar.WEEK_OF_MONTH));
+        logln("DATE: " + calendar.get(calendar.DATE));
+        logln("DAY_OF_MONTH: " + 
+                           calendar.get(calendar.DAY_OF_MONTH));
+        logln("DAY_OF_YEAR: " + calendar.get(calendar.DAY_OF_YEAR));
+        logln("DAY_OF_WEEK: " + calendar.get(calendar.DAY_OF_WEEK));
+        logln("DAY_OF_WEEK_IN_MONTH: " +
+                           calendar.get(calendar.DAY_OF_WEEK_IN_MONTH));
+        logln("AM_PM: " + calendar.get(calendar.AM_PM));
+        logln("HOUR: " + calendar.get(calendar.HOUR));
+        logln("HOUR_OF_DAY: " + calendar.get(calendar.HOUR_OF_DAY));
+        logln("MINUTE: " + calendar.get(calendar.MINUTE));
+        logln("SECOND: " + calendar.get(calendar.SECOND));
+        logln("MILLISECOND: " + calendar.get(calendar.MILLISECOND));
+        logln("ZONE_OFFSET: "
+                           + (calendar.get(calendar.ZONE_OFFSET)/(60*60*1000)));
+        logln("DST_OFFSET: "
+                           + (calendar.get(calendar.DST_OFFSET)/(60*60*1000)));
+        calendar  = new GregorianCalendar(1997,3,10); 
+        calendar.getTime();                        
+        logln("April 10, 1997");
+        logln("ERA: " + calendar.get(calendar.ERA));
+        logln("YEAR: " + calendar.get(calendar.YEAR));
+        logln("MONTH: " + calendar.get(calendar.MONTH));
+        logln("WEEK_OF_YEAR: " + 
+                           calendar.get(calendar.WEEK_OF_YEAR));
+        logln("WEEK_OF_MONTH: " + 
+                           calendar.get(calendar.WEEK_OF_MONTH));
+        logln("DATE: " + calendar.get(calendar.DATE));
+        logln("DAY_OF_MONTH: " + 
+                           calendar.get(calendar.DAY_OF_MONTH));
+        logln("DAY_OF_YEAR: " + calendar.get(calendar.DAY_OF_YEAR));
+        logln("DAY_OF_WEEK: " + calendar.get(calendar.DAY_OF_WEEK));
+        logln("DAY_OF_WEEK_IN_MONTH: " + calendar.get(calendar.DAY_OF_WEEK_IN_MONTH));
+        logln("AM_PM: " + calendar.get(calendar.AM_PM));
+        logln("HOUR: " + calendar.get(calendar.HOUR));
+        logln("HOUR_OF_DAY: " + calendar.get(calendar.HOUR_OF_DAY));
+        logln("MINUTE: " + calendar.get(calendar.MINUTE));
+        logln("SECOND: " + calendar.get(calendar.SECOND));
+        logln("MILLISECOND: " + calendar.get(calendar.MILLISECOND));
+        logln("ZONE_OFFSET: "
+                           + (calendar.get(calendar.ZONE_OFFSET)/(60*60*1000))); // in hours
+        logln("DST_OFFSET: "
+                           + (calendar.get(calendar.DST_OFFSET)/(60*60*1000))); // in hours
+    }
+    */
+
+    public void Test4059654() {
+	// work around bug for jdk1.4 on solaris 2.6, which uses funky timezone names
+	// jdk1.4.1 will drop support for 2.6 so we should be ok when it comes out
+	java.util.TimeZone javazone = java.util.TimeZone.getTimeZone("GMT");
+	TimeZone icuzone = TimeZone.getTimeZone("GMT");
+
+        GregorianCalendar gc = new GregorianCalendar(icuzone);
+        
+        gc.set(1997, 3, 1, 15, 16, 17); // April 1, 1997
+
+        gc.set(Calendar.HOUR, 0);
+        gc.set(Calendar.AM_PM, Calendar.AM);
+        gc.set(Calendar.MINUTE, 0);
+        gc.set(Calendar.SECOND, 0);
+        gc.set(Calendar.MILLISECOND, 0);
+
+        Date cd = gc.getTime();
+        java.util.Calendar cal = java.util.Calendar.getInstance(javazone);
+        cal.clear();
+        cal.set(1997, 3, 1, 0, 0, 0);
+        Date exp = cal.getTime();
+        if (!cd.equals(exp))
+            errln("Fail: Calendar.set broken. Got " + cd + " Want " + exp);
+    }
+
+    public void Test4061476() {
+        SimpleDateFormat fmt = new SimpleDateFormat("ddMMMyy", Locale.UK);
+        Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"), 
+                                                     Locale.UK);
+        fmt.setCalendar(cal);
+        try
+            {
+                Date date = fmt.parse("29MAY97");
+                cal.setTime(date);
+            }
+        catch (Exception e) {
+            System.out.print("");
+        }
+        cal.set(Calendar.HOUR_OF_DAY, 13);
+        logln("Hour: "+cal.get(Calendar.HOUR_OF_DAY));
+        cal.add(Calendar.HOUR_OF_DAY, 6);
+        logln("Hour: "+cal.get(Calendar.HOUR_OF_DAY));
+        if (cal.get(Calendar.HOUR_OF_DAY) != 19)
+            errln("Fail: Want 19 Got " + cal.get(Calendar.HOUR_OF_DAY));
+    }
+
+    public void Test4070502() {
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1998, 0, 30);
+        Date d = getAssociatedDate(tempcal.getTime());
+        Calendar cal = new GregorianCalendar();
+        cal.setTime(d);
+        if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY ||
+            cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
+            errln("Fail: Want weekday Got " + d);
+    }
+
+    /**
+     * Get the associated date starting from a specified date
+     * NOTE: the unnecessary "getTime()'s" below are a work-around for a
+     * bug in jdk 1.1.3 (and probably earlier versions also)
+     * <p>
+     * @param date The date to start from
+     */
+    public static Date getAssociatedDate(Date d) {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setTime(d);
+        //cal.add(field, amount); //<-- PROBLEM SEEN WITH field = DATE,MONTH 
+        // cal.getTime();  // <--- REMOVE THIS TO SEE BUG
+        while (true) {
+            int wd = cal.get(Calendar.DAY_OF_WEEK);
+            if (wd == Calendar.SATURDAY || wd == Calendar.SUNDAY) {
+                cal.add(Calendar.DATE, 1);
+                // cal.getTime();
+            }
+            else
+                break;
+        }
+        return cal.getTime();
+    }
+
+    public void Test4071197() {
+        dowTest(false);
+        dowTest(true);
+    }
+
+    void dowTest(boolean lenient) {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.set(1997, Calendar.AUGUST, 12); // Wednesday
+        // cal.getTime(); // Force update
+        cal.setLenient(lenient);
+        cal.set(1996, Calendar.DECEMBER, 1); // Set the date to be December 1, 1996
+        int dow = cal.get(Calendar.DAY_OF_WEEK);
+        int min = cal.getMinimum(Calendar.DAY_OF_WEEK);
+        int max = cal.getMaximum(Calendar.DAY_OF_WEEK);
+        logln(cal.getTime().toString());
+        if (min != Calendar.SUNDAY || max != Calendar.SATURDAY)
+            errln("FAIL: Min/max bad");
+        if (dow < min || dow > max) 
+            errln("FAIL: Day of week " + dow + " out of range");
+        if (dow != Calendar.SUNDAY) 
+            errln("FAIL: Day of week should be SUNDAY Got " + dow);
+    }
+
+    public void Test4071385() {
+	// work around bug for jdk1.4 on solaris 2.6, which uses funky timezone names
+	// jdk1.4.1 will drop support for 2.6 so we should be ok when it comes out
+	java.util.TimeZone javazone = java.util.TimeZone.getTimeZone("GMT");
+	TimeZone icuzone = TimeZone.getTimeZone("GMT");
+
+        Calendar cal = Calendar.getInstance(icuzone);
+        java.util.Calendar tempcal = java.util.Calendar.getInstance(javazone);
+        tempcal.clear();
+        tempcal.set(1998, Calendar.JUNE, 24);
+        cal.setTime(tempcal.getTime());
+        cal.set(Calendar.MONTH, Calendar.NOVEMBER); // change a field
+        logln(cal.getTime().toString());
+        tempcal.set(1998, Calendar.NOVEMBER, 24);
+        if (!cal.getTime().equals(tempcal.getTime()))
+            errln("Fail");
+    }
+
+    public void Test4073929() {
+        GregorianCalendar foo1 = new GregorianCalendar(1997, 8, 27);
+        foo1.add(Calendar.DAY_OF_MONTH, +1);
+        int testyear = foo1.get(Calendar.YEAR);
+        int testmonth = foo1.get(Calendar.MONTH);
+        int testday = foo1.get(Calendar.DAY_OF_MONTH);
+        if (testyear != 1997 ||
+            testmonth != 8 ||
+            testday != 28)
+            errln("Fail: Calendar not initialized");
+    }
+
+    public void Test4083167() {
+        TimeZone saveZone = TimeZone.getDefault();
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            Date firstDate = new Date();
+            Calendar cal = new GregorianCalendar();
+            cal.setTime(firstDate);
+            long firstMillisInDay = cal.get(Calendar.HOUR_OF_DAY) * 3600000L +
+                cal.get(Calendar.MINUTE) * 60000L +
+                cal.get(Calendar.SECOND) * 1000L +
+                cal.get(Calendar.MILLISECOND);
+            
+            logln("Current time: " + firstDate.toString());
+
+            for (int validity=0; validity<30; validity++) {
+                Date lastDate = new Date(firstDate.getTime() +
+                                         (long)validity*1000*24*60*60);
+                cal.setTime(lastDate);
+                long millisInDay = cal.get(Calendar.HOUR_OF_DAY) * 3600000L +
+                    cal.get(Calendar.MINUTE) * 60000L +
+                    cal.get(Calendar.SECOND) * 1000L +
+                    cal.get(Calendar.MILLISECOND);
+                if (firstMillisInDay != millisInDay) 
+                    errln("Day has shifted " + lastDate);
+            }
+        }
+        finally {
+            TimeZone.setDefault(saveZone);
+        }
+    }
+
+    public void Test4086724() {
+        SimpleDateFormat date;
+        TimeZone saveZone = TimeZone.getDefault();
+        Locale saveLocale = Locale.getDefault();
+        try {
+            Locale.setDefault(Locale.UK); 
+            TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+            date=new SimpleDateFormat("dd MMM yyy (zzzz) 'is in week' ww"); 
+            Calendar cal=Calendar.getInstance(); 
+            cal.set(1997,Calendar.SEPTEMBER,30); 
+            Date now=cal.getTime(); 
+            logln(date.format(now)); 
+            cal.set(1997,Calendar.JANUARY,1); 
+            now=cal.getTime(); 
+            logln(date.format(now)); 
+            cal.set(1997,Calendar.JANUARY,8); 
+            now=cal.getTime(); 
+            logln(date.format(now)); 
+            cal.set(1996,Calendar.DECEMBER,31); 
+            now=cal.getTime(); 
+            logln(date.format(now)); 
+        }
+        finally {
+            Locale.setDefault(saveLocale);
+            TimeZone.setDefault(saveZone);
+        }
+        logln("*** THE RESULTS OF THIS TEST MUST BE VERIFIED MANUALLY ***");
+    }
+
+    public void Test4092362() {
+        GregorianCalendar cal1 = new GregorianCalendar(1997, 10, 11, 10, 20, 40); 
+        /*cal1.set( Calendar.YEAR, 1997 ); 
+        cal1.set( Calendar.MONTH, 10 ); 
+        cal1.set( Calendar.DATE, 11 ); 
+        cal1.set( Calendar.HOUR, 10 ); 
+        cal1.set( Calendar.MINUTE, 20 ); 
+        cal1.set( Calendar.SECOND, 40 ); */
+
+        logln( " Cal1 = " + cal1.getTime().getTime() ); 
+        logln( " Cal1 time in ms = " + cal1.get(Calendar.MILLISECOND) ); 
+        for( int k = 0; k < 100 ; k++ ) {
+            System.out.print(""); 
+        }
+
+        GregorianCalendar cal2 = new GregorianCalendar(1997, 10, 11, 10, 20, 40); 
+        /*cal2.set( Calendar.YEAR, 1997 ); 
+        cal2.set( Calendar.MONTH, 10 ); 
+        cal2.set( Calendar.DATE, 11 ); 
+        cal2.set( Calendar.HOUR, 10 ); 
+        cal2.set( Calendar.MINUTE, 20 ); 
+        cal2.set( Calendar.SECOND, 40 ); */
+
+        logln( " Cal2 = " + cal2.getTime().getTime() ); 
+        logln( " Cal2 time in ms = " + cal2.get(Calendar.MILLISECOND) ); 
+        if( !cal1.equals( cal2 ) ) 
+            errln("Fail: Milliseconds randomized");
+    }
+
+    public void Test4095407() {
+        GregorianCalendar a = new GregorianCalendar(1997,Calendar.NOVEMBER, 13);
+        int dow = a.get(Calendar.DAY_OF_WEEK);
+        if (dow != Calendar.THURSDAY)
+            errln("Fail: Want THURSDAY Got " + dow);
+    }
+
+    public void Test4096231() {
+        TimeZone GMT = TimeZone.getTimeZone("GMT");
+        TimeZone PST = TimeZone.getTimeZone("PST");
+        int sec = 0, min = 0, hr = 0, day = 1, month = 10, year = 1997;
+                            
+        Calendar cal1 = new GregorianCalendar(PST);
+        cal1.setTime(new Date(880698639000L));
+        int p;
+        logln("PST 1 is: " + (p=cal1.get(Calendar.HOUR_OF_DAY)));
+        cal1.setTimeZone(GMT);
+        // Issue 1: Changing the timezone doesn't change the
+        //          represented time.
+        int h1,h2;
+        logln("GMT 1 is: " + (h1=cal1.get(Calendar.HOUR_OF_DAY)));
+        cal1.setTime(new Date(880698639000L));
+        logln("GMT 2 is: " + (h2=cal1.get(Calendar.HOUR_OF_DAY)));
+        // Note: This test had a bug in it.  It wanted h1!=h2, when
+        // what was meant was h1!=p.  Fixed this concurrent with fix
+        // to 4177484.
+        if (p == h1 || h1 != h2)
+            errln("Fail: Hour same in different zones");
+
+        Calendar cal2 = new GregorianCalendar(GMT);
+        Calendar cal3 = new GregorianCalendar(PST);
+        cal2.set(Calendar.MILLISECOND, 0);
+        cal3.set(Calendar.MILLISECOND, 0);
+
+        cal2.set(cal1.get(Calendar.YEAR),
+                 cal1.get(Calendar.MONTH),
+                 cal1.get(Calendar.DAY_OF_MONTH),
+                 cal1.get(Calendar.HOUR_OF_DAY),
+                 cal1.get(Calendar.MINUTE),
+                 cal1.get(Calendar.SECOND));
+
+        long t1,t2,t3,t4;
+        logln("RGMT 1 is: " + (t1=cal2.getTime().getTime()));
+        cal3.set(year, month, day, hr, min, sec);
+        logln("RPST 1 is: " + (t2=cal3.getTime().getTime()));
+        cal3.setTimeZone(GMT);
+        logln("RGMT 2 is: " + (t3=cal3.getTime().getTime()));
+        cal3.set(cal1.get(Calendar.YEAR),
+                 cal1.get(Calendar.MONTH),
+                 cal1.get(Calendar.DAY_OF_MONTH),
+                 cal1.get(Calendar.HOUR_OF_DAY),
+                 cal1.get(Calendar.MINUTE),
+                 cal1.get(Calendar.SECOND));
+        // Issue 2: Calendar continues to use the timezone in its
+        //          constructor for set() conversions, regardless
+        //          of calls to setTimeZone()
+        logln("RGMT 3 is: " + (t4=cal3.getTime().getTime()));
+        if (t1 == t2 ||
+            t1 != t4 ||
+            t2 != t3)
+            errln("Fail: Calendar zone behavior faulty");
+    }
+
+    public void Test4096539() {
+        int[] y = {31,28,31,30,31,30,31,31,30,31,30,31};
+
+        for (int x=0;x<12;x++) {
+            GregorianCalendar gc = new 
+                GregorianCalendar(1997,x,y[x]);
+            int m1,m2;
+            log((m1=gc.get(Calendar.MONTH)+1)+"/"+
+                             gc.get(Calendar.DATE)+"/"+gc.get(Calendar.YEAR)+
+                             " + 1mo = ");
+
+            gc.add(Calendar.MONTH, 1);
+            logln((m2=gc.get(Calendar.MONTH)+1)+"/"+
+                               gc.get(Calendar.DATE)+"/"+gc.get(Calendar.YEAR)
+                               );
+            int m = (m1 % 12) + 1;
+            if (m2 != m)
+                errln("Fail: Want " + m + " Got " + m2);
+        }
+        
+    }
+
+    public void Test4100311() {
+        GregorianCalendar cal = (GregorianCalendar)Calendar.getInstance();
+        cal.set(Calendar.YEAR, 1997);
+        cal.set(Calendar.DAY_OF_YEAR, 1);
+        Date d = cal.getTime();             // Should be Jan 1
+        logln(d.toString());
+        if (cal.get(Calendar.DAY_OF_YEAR) != 1)
+            errln("Fail: DAY_OF_YEAR not set");
+    }
+
+    public void Test4103271() {
+        SimpleDateFormat sdf = new SimpleDateFormat(); 
+        int numYears=40, startYear=1997, numDays=15; 
+        String output, testDesc; 
+        GregorianCalendar testCal = (GregorianCalendar)Calendar.getInstance(); 
+        testCal.clear();
+        sdf.setCalendar(testCal); 
+        sdf.applyPattern("d MMM yyyy"); 
+        boolean fail = false;
+        for (int firstDay=1; firstDay<=2; firstDay++) { 
+            for (int minDays=1; minDays<=7; minDays++) { 
+                testCal.setMinimalDaysInFirstWeek(minDays); 
+                testCal.setFirstDayOfWeek(firstDay); 
+                testDesc = ("Test" + String.valueOf(firstDay) + String.valueOf(minDays)); 
+                logln(testDesc + " => 1st day of week=" +
+                                   String.valueOf(firstDay) +
+                                   ", minimum days in first week=" +
+                                   String.valueOf(minDays)); 
+                for (int j=startYear; j<=startYear+numYears; j++) { 
+                    testCal.set(j,11,25); 
+                    for(int i=0; i<numDays; i++) { 
+                        testCal.add(Calendar.DATE,1); 
+                        String calWOY; 
+                        int actWOY = testCal.get(Calendar.WEEK_OF_YEAR);
+                        if (actWOY < 1 || actWOY > 53) {
+                            Date d = testCal.getTime(); 
+                            calWOY = String.valueOf(actWOY); 
+                            output = testDesc + " - " + sdf.format(d) + "\t"; 
+                            output = output + "\t" + calWOY; 
+                            logln(output); 
+                            fail = true;
+                        }
+                    } 
+                } 
+            } 
+        } 
+
+        int[] DATA = {
+            3, 52, 52, 52, 52, 52, 52, 52,
+                1,  1,  1,  1,  1,  1,  1,
+                2,  2,  2,  2,  2,  2,  2,
+            4, 52, 52, 52, 52, 52, 52, 52,
+               53, 53, 53, 53, 53, 53, 53,
+                1,  1,  1,  1,  1,  1,  1,
+        };
+        testCal.setFirstDayOfWeek(Calendar.SUNDAY);
+        for (int j=0; j<DATA.length; j+=22) {
+            logln("Minimal days in first week = " + DATA[j] +
+                               "  Week starts on Sunday");
+            testCal.setMinimalDaysInFirstWeek(DATA[j]);
+            testCal.set(1997, Calendar.DECEMBER, 21);
+            for (int i=0; i<21; ++i) {
+                int woy = testCal.get(Calendar.WEEK_OF_YEAR);
+                log(testCal.getTime() + " " + woy);
+                if (woy != DATA[j + 1 + i]) {
+                    log(" ERROR");
+                    fail = true;
+                }
+                //logln();
+                
+                // Now compute the time from the fields, and make sure we
+                // get the same answer back.  This is a round-trip test.
+                Date save = testCal.getTime();
+                testCal.clear();
+                testCal.set(Calendar.YEAR, DATA[j+1+i] < 25 ? 1998 : 1997);
+                testCal.set(Calendar.WEEK_OF_YEAR, DATA[j+1+i]);
+                testCal.set(Calendar.DAY_OF_WEEK, (i%7) + Calendar.SUNDAY);
+                if (!testCal.getTime().equals(save)) {
+                    logln("  Parse failed: " + testCal.getTime());
+                    fail= true;
+                }
+
+                testCal.setTime(save);
+                testCal.add(Calendar.DAY_OF_MONTH, 1);
+            }
+        }
+
+        Date d[] = new Date[8];
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1997, Calendar.DECEMBER, 28);
+        d[0] = tempcal.getTime();
+        tempcal.set(1998, Calendar.JANUARY, 10);
+        d[1] = tempcal.getTime();
+        tempcal.set(1998, Calendar.DECEMBER, 31);
+        d[2] = tempcal.getTime();
+        tempcal.set(1999, Calendar.JANUARY, 1);
+        d[3] = tempcal.getTime();
+        // Test field disambiguation with a few special hard-coded cases.
+        // This shouldn't fail if the above cases aren't failing.
+        Object[] DISAM = {
+            new Integer(1998), new Integer(1), new Integer(Calendar.SUNDAY),
+                d[0],
+            new Integer(1998), new Integer(2), new Integer(Calendar.SATURDAY),
+                d[1],
+            new Integer(1998), new Integer(53), new Integer(Calendar.THURSDAY),
+                d[2],
+            new Integer(1998), new Integer(53), new Integer(Calendar.FRIDAY),
+                d[3],
+        };
+        testCal.setMinimalDaysInFirstWeek(3);
+        testCal.setFirstDayOfWeek(Calendar.SUNDAY);
+        for (int i=0; i<DISAM.length; i+=4) {
+            int y = ((Integer)DISAM[i]).intValue();
+            int woy = ((Integer)DISAM[i+1]).intValue();
+            int dow = ((Integer)DISAM[i+2]).intValue();
+            Date exp = (Date)DISAM[i+3];
+            testCal.clear();
+            testCal.set(Calendar.YEAR, y);
+            testCal.set(Calendar.WEEK_OF_YEAR, woy);
+            testCal.set(Calendar.DAY_OF_WEEK, dow);
+            log(y + "-W" + woy +
+                             "-DOW" + dow + " expect:" + exp +
+                             " got:" + testCal.getTime());
+            if (!testCal.getTime().equals(exp)) {
+                log("  FAIL");
+                fail = true;
+            }
+            //logln();
+        }
+
+        // Now try adding and rolling
+        Object ADD = new Object();
+        Object ROLL = new Object();
+        tempcal.set(1998, Calendar.DECEMBER, 25);
+        d[0] = tempcal.getTime();
+        tempcal.set(1999, Calendar.JANUARY, 1);
+        d[1] = tempcal.getTime();
+        tempcal.set(1997, Calendar.DECEMBER, 28);
+        d[2] = tempcal.getTime();
+        tempcal.set(1998, Calendar.JANUARY, 4);
+        d[3] = tempcal.getTime();
+        tempcal.set(1998, Calendar.DECEMBER, 27);
+        d[4] = tempcal.getTime();
+        tempcal.set(1997, Calendar.DECEMBER, 28);
+        d[5] = tempcal.getTime();
+        tempcal.set(1999, Calendar.JANUARY, 2);
+        d[6] = tempcal.getTime();
+        tempcal.set(1998, Calendar.JANUARY, 3);
+        d[7] = tempcal.getTime();
+        
+        Object[] ADDROLL = {
+            ADD, new Integer(1), d[0], d[1],
+            ADD, new Integer(1), d[2], d[3],
+            ROLL, new Integer(1), d[4], d[5],
+            ROLL, new Integer(1), d[6], d[7],
+        };
+        testCal.setMinimalDaysInFirstWeek(3);
+        testCal.setFirstDayOfWeek(Calendar.SUNDAY);
+        for (int i=0; i<ADDROLL.length; i+=4) {
+            int amount = ((Integer)ADDROLL[i+1]).intValue();
+            Date before = (Date)ADDROLL[i+2];
+            Date after = (Date)ADDROLL[i+3];
+
+            testCal.setTime(before);
+            if (ADDROLL[i] == ADD) testCal.add(Calendar.WEEK_OF_YEAR, amount);
+            else testCal.roll(Calendar.WEEK_OF_YEAR, amount);
+            log((ADDROLL[i]==ADD?"add(WOY,":"roll(WOY,") +
+                             amount + ") " + before + " => " +
+                             testCal.getTime());
+            if (!after.equals(testCal.getTime())) {
+                logln("  exp:" + after + "  FAIL");
+                fail = true;
+            }
+            else logln(" ok");
+
+            testCal.setTime(after);
+            if (ADDROLL[i] == ADD) testCal.add(Calendar.WEEK_OF_YEAR, -amount);
+            else testCal.roll(Calendar.WEEK_OF_YEAR, -amount);
+            log((ADDROLL[i]==ADD?"add(WOY,":"roll(WOY,") +
+                             (-amount) + ") " + after + " => " +
+                             testCal.getTime());
+            if (!before.equals(testCal.getTime())) {
+                logln("  exp:" + before + "  FAIL");
+                fail = true;
+            }
+            else logln(" ok");
+        }
+
+        if (fail) errln("Fail: Week of year misbehaving");
+    } 
+
+    public void Test4106136() {
+        Locale saveLocale = Locale.getDefault();
+        String[] names = { "Calendar", "DateFormat", "NumberFormat" };
+        try {
+            Locale[] locales = { Locale.CHINESE, Locale.CHINA };
+            for (int i=0; i<locales.length; ++i) {
+                Locale.setDefault(locales[i]);
+                int[] n = {
+                    Calendar.getAvailableLocales().length,
+                    DateFormat.getAvailableLocales().length,
+                    NumberFormat.getAvailableLocales().length
+                };
+                for (int j=0; j<n.length; ++j) {
+                    if (n[j] == 0)
+                        errln("Fail: " + names[j] + " has no locales for " + locales[i]);
+                }
+            }
+        }
+        finally {
+            Locale.setDefault(saveLocale);
+        }
+    }
+
+    public void Test4108764() {
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1997, Calendar.MARCH, 15, 12, 00, 00);
+        Date d00 = tempcal.getTime();
+        tempcal.set(1997, Calendar.MARCH, 15, 12, 00, 56);
+        Date d01 = tempcal.getTime();
+        tempcal.set(1997, Calendar.MARCH, 15, 12, 34, 00);
+        Date d10 = tempcal.getTime();
+        tempcal.set(1997, Calendar.MARCH, 15, 12, 34, 56);
+        Date d11 = tempcal.getTime();
+        tempcal.set(1997, Calendar.JANUARY, 15, 12, 34, 56);
+        Date dM  = tempcal.getTime();
+        tempcal.clear();
+        tempcal.set(1970, Calendar.JANUARY, 1);
+        Date epoch = tempcal.getTime();
+
+        Calendar cal = Calendar.getInstance(); 
+        cal.setTime(d11);
+
+        cal.clear( Calendar.MINUTE ); 
+        logln(cal.getTime().toString()); 
+        if (!cal.getTime().equals(d01)) {
+            errln("Fail: " + d11 + " clear(MINUTE) => expect " +
+                  d01 + ", got " + cal.getTime());
+        }
+
+        cal.set( Calendar.SECOND, 0 ); 
+        logln(cal.getTime().toString()); 
+        if (!cal.getTime().equals(d00))
+            errln("Fail: set(SECOND, 0) broken");
+
+        cal.setTime(d11);
+        cal.set( Calendar.SECOND, 0 ); 
+        logln(cal.getTime().toString()); 
+        if (!cal.getTime().equals(d10))
+            errln("Fail: set(SECOND, 0) broken #2");
+
+        cal.clear( Calendar.MINUTE ); 
+        logln(cal.getTime().toString()); 
+        if (!cal.getTime().equals(d00))
+            errln("Fail: clear(MINUTE) broken #2");
+
+        cal.clear();
+        logln(cal.getTime().toString());
+        if (!cal.getTime().equals(epoch))
+            errln("Fail: after clear() expect " + epoch + ", got " + cal.getTime());
+
+        cal.setTime(d11);
+        cal.clear( Calendar.MONTH ); 
+        logln(cal.getTime().toString()); 
+        if (!cal.getTime().equals(dM)) {
+            errln("Fail: " + d11 + " clear(MONTH) => expect " +
+                  dM + ", got " + cal.getTime());
+        }
+    }
+
+    public void Test4114578() {
+        int ONE_HOUR = 60*60*1000;
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeZone(TimeZone.getTimeZone("PST"));
+        
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1998, Calendar.APRIL, 5, 1, 0);
+        long onset = tempcal.getTime().getTime() + ONE_HOUR;
+        tempcal.set(1998, Calendar.OCTOBER, 25, 0, 0);
+        long cease = tempcal.getTime().getTime() + 2*ONE_HOUR;
+
+        boolean fail = false;
+        
+        final int ADD = 1;
+        final int ROLL = 2;
+
+        long[] DATA = {
+            // Start            Action   Amt    Expected_change
+            onset - ONE_HOUR,   ADD,      1,     ONE_HOUR,
+            onset,              ADD,     -1,    -ONE_HOUR,
+            onset - ONE_HOUR,   ROLL,     1,     ONE_HOUR,
+            onset,              ROLL,    -1,    -ONE_HOUR,
+            cease - ONE_HOUR,   ADD,      1,     ONE_HOUR,
+            cease,              ADD,     -1,    -ONE_HOUR,
+            cease - ONE_HOUR,   ROLL,     1,     ONE_HOUR,
+            cease,              ROLL,    -1,    -ONE_HOUR,
+        };
+
+        for (int i=0; i<DATA.length; i+=4) {
+            Date date = new Date(DATA[i]);
+            int amt = (int) DATA[i+2];
+            long expectedChange = DATA[i+3];
+            
+            log(date.toString());
+            cal.setTime(date);
+
+            switch ((int) DATA[i+1]) {
+            case ADD:
+                log(" add (HOUR," + (amt<0?"":"+")+amt + ")= ");
+                cal.add(Calendar.HOUR, amt);
+                break;
+            case ROLL:
+                log(" roll(HOUR," + (amt<0?"":"+")+amt + ")= ");
+                cal.roll(Calendar.HOUR, amt);
+                break;
+            }
+
+            log(cal.getTime().toString());
+
+            long change = cal.getTime().getTime() - date.getTime();
+            if (change != expectedChange) {
+                fail = true;
+                logln(" FAIL");
+            }
+            else logln(" OK");
+        }
+
+        if (fail) errln("Fail: roll/add misbehaves around DST onset/cease");
+    }
+
+    /**
+     * Make sure maximum for HOUR field is 11, not 12.
+     */
+    public void Test4118384() {
+        Calendar cal = Calendar.getInstance();
+        if (cal.getMaximum(Calendar.HOUR) != 11 ||
+            cal.getLeastMaximum(Calendar.HOUR) != 11 ||
+            cal.getActualMaximum(Calendar.HOUR) != 11)
+            errln("Fail: maximum of HOUR field should be 11");
+    }
+
+    /**
+     * Check isLeapYear for BC years.
+     */
+    public void Test4125881() {
+        GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
+        DateFormat fmt = new SimpleDateFormat("MMMM d, yyyy G");
+        cal.clear();
+        for (int y=-20; y<=10; ++y) {
+            cal.set(Calendar.ERA, y < 1 ? GregorianCalendar.BC : GregorianCalendar.AD);
+            cal.set(Calendar.YEAR, y < 1 ? 1 - y : y);
+            logln(y + " = " + fmt.format(cal.getTime()) + " " +
+                               cal.isLeapYear(y));
+            if (cal.isLeapYear(y) != ((y+40)%4 == 0))
+                errln("Leap years broken");
+        }
+    }
+
+    // I am disabling this test -- it is currently failing because of a bug
+    // in Sun's latest change to STZ.getOffset().  I have filed a Sun bug
+    // against this problem.
+
+    // Re-enabled after 'porting' TZ and STZ from java.util to com.ibm.icu.util.
+    /**
+     * Prove that GregorianCalendar is proleptic (it used to cut off
+     * at 45 BC, and not have leap years before then).
+     */
+    public void Test4125892() {
+        GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
+        DateFormat fmt = new SimpleDateFormat("MMMM d, yyyy G");
+        fmt = null;
+        cal.clear();
+        cal.set(Calendar.ERA, GregorianCalendar.BC);
+        cal.set(Calendar.YEAR, 81); // 81 BC is a leap year (proleptically)
+        cal.set(Calendar.MONTH, Calendar.FEBRUARY);
+        cal.set(Calendar.DATE, 28);
+        cal.add(Calendar.DATE, 1);
+        if (cal.get(Calendar.DATE) != 29 ||
+            !cal.isLeapYear(-80)) // -80 == 81 BC
+            errln("Calendar not proleptic");
+    }
+
+    /**
+     * Calendar and GregorianCalendar hashCode() methods need improvement.
+     * Calendar needs a good implementation that subclasses can override,
+     * and GregorianCalendar should use that implementation.
+     */
+    public void Test4136399() {
+        /* Note: This test is actually more strict than it has to be.
+         * Technically, there is no requirement that unequal objects have
+         * unequal hashes.  We only require equal objects to have equal hashes.
+         * It is desirable for unequal objects to have distributed hashes, but
+         * there is no hard requirement here.
+         *
+         * In this test we make assumptions about certain attributes of calendar
+         * objects getting represented in the hash, which need not always be the
+         * case (although it does work currently with the given test). */
+        Calendar a = Calendar.getInstance();
+        Calendar b = (Calendar)a.clone();
+        if (a.hashCode() != b.hashCode()) {
+            errln("Calendar hash code unequal for cloned objects");
+        }
+
+        b.setMinimalDaysInFirstWeek(7 - a.getMinimalDaysInFirstWeek());
+        if (a.hashCode() == b.hashCode()) {
+            errln("Calendar hash code ignores minimal days in first week");
+        }
+        b.setMinimalDaysInFirstWeek(a.getMinimalDaysInFirstWeek());
+
+        b.setFirstDayOfWeek((a.getFirstDayOfWeek() % 7) + 1); // Next day
+        if (a.hashCode() == b.hashCode()) {
+            errln("Calendar hash code ignores first day of week");
+        }
+        b.setFirstDayOfWeek(a.getFirstDayOfWeek());
+
+        b.setLenient(!a.isLenient());
+        if (a.hashCode() == b.hashCode()) {
+            errln("Calendar hash code ignores lenient setting");
+        }
+        b.setLenient(a.isLenient());
+        
+        // Assume getTimeZone() returns a reference, not a clone
+        // of a reference -- this is true as of this writing
+        b.getTimeZone().setRawOffset(a.getTimeZone().getRawOffset() + 60*60*1000);
+        if (a.hashCode() == b.hashCode()) {
+            errln("Calendar hash code ignores zone");
+        }
+        b.getTimeZone().setRawOffset(a.getTimeZone().getRawOffset());
+
+        GregorianCalendar c = new GregorianCalendar();
+        GregorianCalendar d = (GregorianCalendar)c.clone();
+        if (c.hashCode() != d.hashCode()) {
+            errln("GregorianCalendar hash code unequal for clones objects");
+        }
+        Date cutover = c.getGregorianChange();
+        d.setGregorianChange(new Date(cutover.getTime() + 24*60*60*1000));
+        if (c.hashCode() == d.hashCode()) {
+            errln("GregorianCalendar hash code ignores cutover");
+        }        
+    }
+
+    /**
+     * GregorianCalendar.equals() ignores cutover date
+     */
+    public void Test4141665() {
+        GregorianCalendar cal = new GregorianCalendar();
+        GregorianCalendar cal2 = (GregorianCalendar)cal.clone();
+        Date cut = cal.getGregorianChange();
+        Date cut2 = new Date(cut.getTime() + 100*24*60*60*1000L); // 100 days later
+        if (!cal.equals(cal2)) {
+            errln("Cloned GregorianCalendars not equal");
+        }
+        cal2.setGregorianChange(cut2);
+        if (cal.equals(cal2)) {
+            errln("GregorianCalendar.equals() ignores cutover");
+        }
+    }
+    
+    /**
+     * Bug states that ArrayIndexOutOfBoundsException is thrown by GregorianCalendar.roll()
+     * when IllegalArgumentException should be.
+     */
+    public void Test4142933() {
+        GregorianCalendar calendar = new GregorianCalendar();
+        try {
+            calendar.roll(-1, true);
+            errln("Test failed, no exception trown");
+        }
+        catch (IllegalArgumentException e) {
+            // OK: Do nothing
+            // logln("Test passed");
+            System.out.print("");
+        }
+        catch (Exception e) {
+            errln("Test failed. Unexpected exception is thrown: " + e);
+            e.printStackTrace();
+        } 
+    }
+
+    /**
+     * GregorianCalendar handling of Dates Long.MIN_VALUE and Long.MAX_VALUE is
+     * confusing; unless the time zone has a raw offset of zero, one or the
+     * other of these will wrap.  We've modified the test given in the bug
+     * report to therefore only check the behavior of a calendar with a zero raw
+     * offset zone.
+     */
+    public void Test4145158() {
+        GregorianCalendar calendar = new GregorianCalendar();
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+        calendar.setTime(new Date(Long.MIN_VALUE));
+        int year1 = calendar.get(Calendar.YEAR);
+        int era1 = calendar.get(Calendar.ERA);
+        
+        calendar.setTime(new Date(Long.MAX_VALUE));
+        int year2 = calendar.get(Calendar.YEAR);
+        int era2 = calendar.get(Calendar.ERA);
+        
+        if (year1 == year2 && era1 == era2) {
+            errln("Fail: Long.MIN_VALUE or Long.MAX_VALUE wrapping around");
+        }
+    }
+
+    /**
+     * Maximum value for YEAR field wrong.
+     */
+    public void Test4145983() {
+        GregorianCalendar calendar = new GregorianCalendar();
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+        Date[] DATES = { new Date(Long.MAX_VALUE), new Date(Long.MIN_VALUE) };
+        for (int i=0; i<DATES.length; ++i) {
+            calendar.setTime(DATES[i]);
+            int year = calendar.get(Calendar.YEAR);
+            int maxYear = calendar.getMaximum(Calendar.YEAR);
+            if (year > maxYear) {
+                errln("Failed for "+DATES[i].getTime()+" ms: year=" +
+                      year + ", maxYear=" + maxYear);
+            }
+        }
+    }
+
+    /**
+     * This is a bug in the validation code of GregorianCalendar.  As reported,
+     * the bug seems worse than it really is, due to a bug in the way the bug
+     * report test was written.  In reality the bug is restricted to the DAY_OF_YEAR
+     * field. - liu 6/29/98
+     */
+    public void Test4147269() {
+        GregorianCalendar calendar = new GregorianCalendar();
+        calendar.setLenient(false);
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1996, Calendar.JANUARY, 3); // Arbitrary date
+        Date date = tempcal.getTime(); 
+        for (int field = 0; field < calendar.getFieldCount(); field++) {
+            calendar.setTime(date);
+            // Note: In the bug report, getActualMaximum() was called instead
+            // of getMaximum() -- this was an error.  The validation code doesn't
+            // use getActualMaximum(), since that's too costly.
+            int max = calendar.getMaximum(field);
+            int value = max+1;
+            calendar.set(field, value); 
+            try {
+                calendar.getTime(); // Force time computation
+                // We expect an exception to be thrown. If we fall through
+                // to the next line, then we have a bug.
+                errln("Test failed with field " + FIELD_NAME[field] +
+                      ", date before: " + date +
+                      ", date after: " + calendar.getTime() +
+                      ", value: " + value + " (max = " + max +")");
+            } catch (IllegalArgumentException e) {
+                System.out.print("");
+            } 
+        }
+    }
+
+    /**
+     * Reported bug is that a GregorianCalendar with a cutover of Date(Long.MAX_VALUE)
+     * doesn't behave as a pure Julian calendar.
+     * CANNOT REPRODUCE THIS BUG
+     */
+    public void Test4149677() {
+        TimeZone[] zones = { TimeZone.getTimeZone("GMT"),
+                             TimeZone.getTimeZone("PST"),
+                             TimeZone.getTimeZone("EAT") };
+        for (int i=0; i<zones.length; ++i) {
+            GregorianCalendar calendar = new GregorianCalendar(zones[i]);
+
+            // Make sure extreme values don't wrap around
+            calendar.setTime(new Date(Long.MIN_VALUE));
+            if (calendar.get(Calendar.ERA) != GregorianCalendar.BC) {
+                errln("Fail: Long.MIN_VALUE ms has an AD year");
+            }
+            calendar.setTime(new Date(Long.MAX_VALUE));
+            if (calendar.get(Calendar.ERA) != GregorianCalendar.AD) {
+                errln("Fail: Long.MAX_VALUE ms has a BC year");
+            }
+
+            calendar.setGregorianChange(new Date(Long.MAX_VALUE));
+            // to obtain a pure Julian calendar
+            
+            boolean is100Leap = calendar.isLeapYear(100);
+            if (!is100Leap) {
+                errln("test failed with zone " + zones[i].getID());
+                errln(" cutover date is Calendar.MAX_DATE");
+                errln(" isLeapYear(100) returns: " + is100Leap);
+            }
+        }
+    }
+
+    /**
+     * Calendar and Date HOUR broken.  If HOUR is out-of-range, Calendar
+     * and Date classes will misbehave.
+     */
+    public void Test4162587() {
+        TimeZone tz = TimeZone.getTimeZone("PST");
+        TimeZone.setDefault(tz);
+        GregorianCalendar cal = new GregorianCalendar(tz);
+        Date d;
+        
+        for (int i=0; i<5; ++i) {
+            if (i>0) logln("---");
+
+            cal.clear();
+            cal.set(1998, Calendar.APRIL, 5, i, 0);
+            d = cal.getTime();
+            String s0 = d.toString();
+            logln("0 " + i + ": " + s0);
+
+            cal.clear();
+            cal.set(1998, Calendar.APRIL, 4, i+24, 0);
+            d = cal.getTime();
+            String sPlus = d.toString();
+            logln("+ " + i + ": " + sPlus);
+
+            cal.clear();
+            cal.set(1998, Calendar.APRIL, 6, i-24, 0);
+            d = cal.getTime();
+            String sMinus = d.toString();
+            logln("- " + i + ": " + sMinus);
+
+            if (!s0.equals(sPlus) || !s0.equals(sMinus)) {
+                errln("Fail: All three lines must match");
+            }
+        }
+    }
+
+    /**
+     * Adding 12 months behaves differently from adding 1 year
+     */
+    public void Test4165343() {
+        GregorianCalendar calendar = new GregorianCalendar(1996, Calendar.FEBRUARY, 29);
+        Date start = calendar.getTime();
+        logln("init date: " + start);
+        calendar.add(Calendar.MONTH, 12); 
+        Date date1 = calendar.getTime();
+        logln("after adding 12 months: " + date1);
+        calendar.setTime(start);
+        calendar.add(Calendar.YEAR, 1);
+        Date date2 = calendar.getTime();
+        logln("after adding one year : " + date2);
+        if (date1.equals(date2)) {
+            logln("Test passed");
+        } else {
+            errln("Test failed");
+        }
+    }
+
+    /**
+     * GregorianCalendar.getActualMaximum() does not account for first day of week.
+     */
+    public void Test4166109() {
+        /* Test month:
+         *
+         *      March 1998
+         * Su Mo Tu We Th Fr Sa
+         *  1  2  3  4  5  6  7
+         *  8  9 10 11 12 13 14
+         * 15 16 17 18 19 20 21
+         * 22 23 24 25 26 27 28
+         * 29 30 31
+         */
+        boolean passed = true;
+        int field = Calendar.WEEK_OF_MONTH;
+
+        GregorianCalendar calendar = new GregorianCalendar(Locale.US);
+        calendar.set(1998, Calendar.MARCH, 1);
+        calendar.setMinimalDaysInFirstWeek(1);
+        logln("Date:  " + calendar.getTime());
+
+        int firstInMonth = calendar.get(Calendar.DAY_OF_MONTH);
+
+        for (int firstInWeek = Calendar.SUNDAY; firstInWeek <= Calendar.SATURDAY; firstInWeek++) {
+            calendar.setFirstDayOfWeek(firstInWeek);
+            int returned = calendar.getActualMaximum(field);
+            int expected = (31 + ((firstInMonth - firstInWeek + 7)% 7) + 6) / 7;
+
+            logln("First day of week = " + firstInWeek +
+                  "  getActualMaximum(WEEK_OF_MONTH) = " + returned +
+                  "  expected = " + expected +
+                  ((returned == expected) ? "  ok" : "  FAIL"));
+
+            if (returned != expected) {
+                passed = false;
+            }
+        }
+        if (!passed) {
+            errln("Test failed");
+        }
+    }
+
+    /**
+     * Calendar.getActualMaximum(YEAR) works wrong.
+     */
+    public void Test4167060() {
+        int field = Calendar.YEAR;
+        DateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy G",
+                                                 Locale.US);
+
+        GregorianCalendar calendars[] = {
+            new GregorianCalendar(100, Calendar.NOVEMBER, 1),
+            new GregorianCalendar(-99 /*100BC*/, Calendar.JANUARY, 1),
+            new GregorianCalendar(1996, Calendar.FEBRUARY, 29),
+        };
+
+        String[] id = { "Hybrid", "Gregorian", "Julian" };
+
+        for (int k=0; k<3; ++k) {
+            logln("--- " + id[k] + " ---");
+
+            for (int j=0; j<calendars.length; ++j) {
+                GregorianCalendar calendar = calendars[j];
+                if (k == 1) {
+                    calendar.setGregorianChange(new Date(Long.MIN_VALUE));
+                } else if (k == 2) {
+                    calendar.setGregorianChange(new Date(Long.MAX_VALUE));
+                }
+
+                format.setCalendar((Calendar)calendar.clone());
+
+                Date dateBefore = calendar.getTime();
+
+                int maxYear = calendar.getActualMaximum(field);
+                logln("maxYear: " + maxYear + " for " + format.format(calendar.getTime()));
+                logln("date before: " + format.format(dateBefore));
+
+                int years[] = {2000, maxYear-1, maxYear, maxYear+1};
+
+                for (int i = 0; i < years.length; i++) {
+                    boolean valid = years[i] <= maxYear;
+                    calendar.set(field, years[i]);
+                    Date dateAfter = calendar.getTime();
+                    int newYear = calendar.get(field);
+                    calendar.setTime(dateBefore); // restore calendar for next use
+
+                    logln(" Year " + years[i] + (valid? " ok " : " bad") +
+                          " => " + format.format(dateAfter));
+                    if (valid && newYear != years[i]) {
+                        errln("  FAIL: " + newYear + " should be valid; date, month and time shouldn't change");
+                    } else if (!valid && newYear == years[i]) {
+                        // We no longer require strict year maxima.  That is, the calendar
+                        // algorithm may work for values > the stated maximum.
+                        //errln("  FAIL: " + newYear + " should be invalid");
+                        logln("  Note: " + newYear + " > maximum, but still valid");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Calendar.roll broken
+     * This bug relies on the TimeZone bug 4173604 to also be fixed.
+     */
+    public void Test4173516() {
+        int fieldsList[][] = {
+            { 1997, Calendar.FEBRUARY,  1, 10, 45, 15, 900 },
+            { 1999, Calendar.DECEMBER, 22, 23, 59, 59, 999 }
+        };
+        int limit = 40;
+        GregorianCalendar cal = new GregorianCalendar();
+
+        cal.setTime(new Date(0));
+        cal.roll(Calendar.HOUR,  0x7F000000);
+        cal.roll(Calendar.HOUR, -0x7F000000);
+        if (cal.getTime().getTime() != 0) {
+            errln("Hour rolling broken");
+        }
+
+        for (int op=0; op<2; ++op) {
+            logln("Testing GregorianCalendar " +
+                  (op==0 ? "add" : "roll"));
+            for (int field=0; field < cal.getFieldCount(); ++field) {
+                if (field != Calendar.ZONE_OFFSET &&
+                    field != Calendar.DST_OFFSET) {
+                    for (int j=0; j<fieldsList.length; ++j) {
+                        int fields[] = fieldsList[j];
+                        cal.clear();
+                        cal.set(fields[0], fields[1], fields[2],
+                                fields[3], fields[4], fields[5]);
+                        cal.set(Calendar.MILLISECOND, fields[6]);
+                        for (int i = 0; i < 2*limit; i++) {
+                            if (op == 0) {
+                                cal.add(field, i < limit ? 1 : -1);
+                            } else {
+                                cal.roll(field, i < limit ? 1 : -1);
+                            }
+                        }
+                        if (cal.get(Calendar.YEAR) != fields[0] ||
+                            cal.get(Calendar.MONTH) != fields[1] ||
+                            cal.get(Calendar.DATE) != fields[2] ||
+                            cal.get(Calendar.HOUR_OF_DAY) != fields[3] ||
+                            cal.get(Calendar.MINUTE) != fields[4] ||
+                            cal.get(Calendar.SECOND) != fields[5] ||
+                            cal.get(Calendar.MILLISECOND) != fields[6]) {
+                            errln("Field " + field +
+                                  " (" + FIELD_NAME[field] +
+                                  ") FAIL, expected " +
+                                  fields[0] +
+                                  "/" + (fields[1] + 1) +
+                                  "/" + fields[2] +
+                                  " " + fields[3] +
+                                  ":" + fields[4] +
+                                  ":" + fields[5] +
+                                  "." + fields[6] +
+                                  ", got " + cal.get(Calendar.YEAR) +
+                                  "/" + (cal.get(Calendar.MONTH) + 1) +
+                                  "/" + cal.get(Calendar.DATE) +
+                                  " " + cal.get(Calendar.HOUR_OF_DAY) +
+                                  ":" + cal.get(Calendar.MINUTE) +
+                                  ":" + cal.get(Calendar.SECOND) +
+                                  "." + cal.get(Calendar.MILLISECOND));
+                            cal.clear();
+                            cal.set(fields[0], fields[1], fields[2],
+                                    fields[3], fields[4], fields[5]);
+                            cal.set(Calendar.MILLISECOND, fields[6]);
+                            logln("Start date: " + cal.get(Calendar.YEAR) +
+                                  "/" + (cal.get(Calendar.MONTH) + 1) +
+                                  "/" + cal.get(Calendar.DATE) +
+                                  " " + cal.get(Calendar.HOUR_OF_DAY) +
+                                  ":" + cal.get(Calendar.MINUTE) +
+                                  ":" + cal.get(Calendar.SECOND) +
+                                  "." + cal.get(Calendar.MILLISECOND));
+                            long prev = cal.getTime().getTime();
+                            for (int i = 0; i < 2*limit; i++) {
+                                if (op == 0) {
+                                    cal.add(field, i < limit ? 1 : -1);
+                                } else {
+                                    cal.roll(field, i < limit ? 1 : -1);
+                                }
+                                long t = cal.getTime().getTime();
+                                long delta = t - prev;
+                                prev = t;
+                                logln((op == 0 ? "add(" : "roll(") + FIELD_NAME[field] +
+                                      (i < limit ? ", +1) => " : ", -1) => ") +
+                                      cal.get(Calendar.YEAR) +
+                                      "/" + (cal.get(Calendar.MONTH) + 1) +
+                                      "/" + cal.get(Calendar.DATE) +
+                                      " " + cal.get(Calendar.HOUR_OF_DAY) +
+                                      ":" + cal.get(Calendar.MINUTE) +
+                                      ":" + cal.get(Calendar.SECOND) +
+                                      "." + cal.get(Calendar.MILLISECOND) +
+                                      " delta=" + delta + " ms");
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void Test4174361() {
+        GregorianCalendar calendar = new GregorianCalendar(1996, 1, 29);
+
+        calendar.add(Calendar.MONTH, 10); 
+        Date date1 = calendar.getTime();
+        date1 = null;
+        int d1 = calendar.get(Calendar.DAY_OF_MONTH);
+
+        calendar = new GregorianCalendar(1996, 1, 29);
+        calendar.add(Calendar.MONTH, 11); 
+        Date date2 = calendar.getTime();
+        date2 = null;
+        int d2 = calendar.get(Calendar.DAY_OF_MONTH);
+
+        if (d1 != d2) {
+            errln("adding months to Feb 29 broken");
+        }
+    }
+
+    /**
+     * Calendar does not update field values when setTimeZone is called.
+     */
+    public void Test4177484() {
+        TimeZone PST = TimeZone.getTimeZone("PST");
+        TimeZone EST = TimeZone.getTimeZone("EST");
+
+        Calendar cal = Calendar.getInstance(PST, Locale.US);
+        cal.clear();
+        cal.set(1999, 3, 21, 15, 5, 0); // Arbitrary
+        int h1 = cal.get(Calendar.HOUR_OF_DAY);
+        cal.setTimeZone(EST);
+        int h2 = cal.get(Calendar.HOUR_OF_DAY);
+        if (h1 == h2) {
+            errln("FAIL: Fields not updated after setTimeZone");
+        }
+
+        // getTime() must NOT change when time zone is changed.
+        // getTime() returns zone-independent time in ms.
+        cal.clear();
+        cal.setTimeZone(PST);
+        cal.set(Calendar.HOUR_OF_DAY, 10);
+        Date pst10 = cal.getTime();
+        cal.setTimeZone(EST);
+        Date est10 = cal.getTime();
+        if (!pst10.equals(est10)) {
+            errln("FAIL: setTimeZone changed time");
+        }
+    }
+
+    /**
+     * Week of year is wrong at the start and end of the year.
+     */
+    public void Test4197699() {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setFirstDayOfWeek(Calendar.MONDAY);
+        cal.setMinimalDaysInFirstWeek(4);
+        DateFormat fmt = new SimpleDateFormat("E dd MMM yyyy  'DOY='D 'WOY='w");
+        fmt.setCalendar(cal);
+
+        int[] DATA = {
+            2000,  Calendar.JANUARY,   1,   52,
+            2001,  Calendar.DECEMBER,  31,  1,
+        };
+
+        for (int i=0; i<DATA.length; ) {
+            cal.set(DATA[i++], DATA[i++], DATA[i++]);
+            int expWOY = DATA[i++];
+            int actWOY = cal.get(Calendar.WEEK_OF_YEAR);
+            if (expWOY == actWOY) {
+                logln("Ok: " + fmt.format(cal.getTime()));
+            } else {
+                errln("FAIL: " + fmt.format(cal.getTime())
+                      + ", expected WOY=" + expWOY);
+                cal.add(Calendar.DATE, -8);
+                for (int j=0; j<14; ++j) {
+                    cal.add(Calendar.DATE, 1);
+                    logln(fmt.format(cal.getTime()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Calendar DAY_OF_WEEK_IN_MONTH fields->time broken.  The problem
+     * is in the field disambiguation code in GregorianCalendar.  This
+     * code is supposed to choose the most recent set of fields
+     * among the following:
+     *
+     *   MONTH + DAY_OF_MONTH
+     *   MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
+     *   MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
+     *   DAY_OF_YEAR
+     *   WEEK_OF_YEAR + DAY_OF_WEEK
+     */
+    public void Test4209071() {
+        Calendar cal = Calendar.getInstance(Locale.US);
+
+        // General field setting test
+        int Y = 1995;
+        
+        Date d[] = new Date[13];
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(Y, Calendar.JANUARY, 1);
+        d[0] = tempcal.getTime();
+        tempcal.set(Y, Calendar.MARCH, 1);
+        d[1] = tempcal.getTime();
+        tempcal.set(Y, Calendar.JANUARY, 4);
+        d[2] = tempcal.getTime();
+        tempcal.set(Y, Calendar.JANUARY, 18);
+        d[3] = tempcal.getTime();
+        tempcal.set(Y, Calendar.JANUARY, 18);
+        d[4] = tempcal.getTime();
+        tempcal.set(Y-1, Calendar.DECEMBER, 22);
+        d[5] = tempcal.getTime();
+        tempcal.set(Y, Calendar.JANUARY, 26);
+        d[6] = tempcal.getTime();
+        tempcal.set(Y, Calendar.JANUARY, 26);
+        d[7] = tempcal.getTime();
+        tempcal.set(Y, Calendar.MARCH, 1);
+        d[8] = tempcal.getTime();
+        tempcal.set(Y, Calendar.OCTOBER, 6);
+        d[9] = tempcal.getTime();
+        tempcal.set(Y, Calendar.OCTOBER, 13);
+        d[10] = tempcal.getTime();
+        tempcal.set(Y, Calendar.AUGUST, 10);
+        d[11] = tempcal.getTime();
+        tempcal.set(Y, Calendar.DECEMBER, 7);
+        d[12] = tempcal.getTime();
+
+        Object[] FIELD_DATA = {
+            // Add new test cases as needed.
+
+            // 0
+            new int[] {}, d[0],
+            // 1
+            new int[] { Calendar.MONTH, Calendar.MARCH }, d[1],
+            // 2
+            new int[] { Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY }, d[2],
+            // 3
+            new int[] { Calendar.DAY_OF_WEEK, Calendar.THURSDAY,
+                        Calendar.DAY_OF_MONTH, 18, }, d[3],
+            // 4
+            new int[] { Calendar.DAY_OF_MONTH, 18,
+                        Calendar.DAY_OF_WEEK, Calendar.THURSDAY, }, d[4],
+            // 5  (WOM -1 is in previous month)
+            new int[] { Calendar.DAY_OF_MONTH, 18,
+                        Calendar.WEEK_OF_MONTH, -1,
+                        Calendar.DAY_OF_WEEK, Calendar.THURSDAY, }, d[5],
+            // 6
+            new int[] { Calendar.DAY_OF_MONTH, 18,
+                        Calendar.WEEK_OF_MONTH, 4,
+                        Calendar.DAY_OF_WEEK, Calendar.THURSDAY, }, d[6],
+            // 7  (DIM -1 is in same month)
+            new int[] { Calendar.DAY_OF_MONTH, 18,
+                        Calendar.DAY_OF_WEEK_IN_MONTH, -1,
+                        Calendar.DAY_OF_WEEK, Calendar.THURSDAY, }, d[7],
+            // 8
+            new int[] { Calendar.WEEK_OF_YEAR, 9,
+                        Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY, }, d[8],
+            // 9
+            new int[] { Calendar.MONTH, Calendar.OCTOBER,
+                        Calendar.DAY_OF_WEEK_IN_MONTH, 1,
+                        Calendar.DAY_OF_WEEK, Calendar.FRIDAY, }, d[9],
+            // 10
+            new int[] { Calendar.MONTH, Calendar.OCTOBER,
+                        Calendar.WEEK_OF_MONTH, 2,
+                        Calendar.DAY_OF_WEEK, Calendar.FRIDAY, }, d[10],
+            // 11
+            new int[] { Calendar.MONTH, Calendar.OCTOBER,
+                        Calendar.DAY_OF_MONTH, 15,
+                        Calendar.DAY_OF_YEAR, 222, }, d[11],
+            // 12
+            new int[] { Calendar.DAY_OF_WEEK, Calendar.THURSDAY,
+                        Calendar.MONTH, Calendar.DECEMBER, }, d[12],
+        };
+
+        for (int i=0; i<FIELD_DATA.length; i+=2) {
+            int[] fields = (int[]) FIELD_DATA[i];
+            Date exp = (Date) FIELD_DATA[i+1];
+            
+            cal.clear();
+            cal.set(Calendar.YEAR, Y);
+            for (int j=0; j<fields.length; j+=2) {
+                cal.set(fields[j], fields[j+1]);
+            }
+            
+            Date act = cal.getTime();
+            if (!act.equals(exp)) {
+                errln("FAIL: Test " + (i/2) + " got " + act +
+                      ", want " + exp +
+                      " (see test/java/util/Calendar/CalendarRegression.java");
+            }
+        }
+
+        tempcal.set(1997, Calendar.JANUARY, 5);
+        d[0] = tempcal.getTime();
+        tempcal.set(1997, Calendar.JANUARY, 26);
+        d[1] = tempcal.getTime();
+        tempcal.set(1997, Calendar.FEBRUARY, 23);
+        d[2] = tempcal.getTime();
+        tempcal.set(1997, Calendar.JANUARY, 26);
+        d[3] = tempcal.getTime();
+        tempcal.set(1997, Calendar.JANUARY, 5);
+        d[4] = tempcal.getTime();
+        tempcal.set(1996, Calendar.DECEMBER, 8);
+        d[5] = tempcal.getTime();
+        // Test specific failure reported in bug
+        Object[] DATA = { 
+            new Integer(1), d[0], new Integer(4), d[1],
+            new Integer(8), d[2], new Integer(-1), d[3],
+            new Integer(-4), d[4], new Integer(-8), d[5],
+        };
+        for (int i=0; i<DATA.length; i+=2) {
+            cal.clear();
+            cal.set(Calendar.DAY_OF_WEEK_IN_MONTH,
+                    ((Number) DATA[i]).intValue());
+            cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
+            cal.set(Calendar.MONTH, Calendar.JANUARY);
+            cal.set(Calendar.YEAR, 1997);
+            Date actual = cal.getTime();
+            if (!actual.equals(DATA[i+1])) {
+                errln("FAIL: Sunday " + DATA[i] +
+                      " of Jan 1997 -> " + actual +
+                      ", want " + DATA[i+1]);
+            }
+        }
+    }
+
+    /**
+     * WEEK_OF_YEAR computed incorrectly.  A failure of this test can indicate
+     * a problem in several different places in the 
+     */
+    public void Test4288792() throws Exception 
+    {
+	TimeZone savedTZ = TimeZone.getDefault();
+	TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+	GregorianCalendar cal = new GregorianCalendar();
+        
+	for (int i = 1900; i < 2100; i++) {
+	    for (int j1 = 1; j1 <= 7; j1++) {
+		// Loop for MinimalDaysInFirstWeek: 1..7
+		for (int j = Calendar.SUNDAY; j <= Calendar.SATURDAY; j++) {
+		    // Loop for FirstDayOfWeek: SUNDAY..SATURDAY
+		    cal.clear();
+		    cal.setMinimalDaysInFirstWeek(j1);
+		    cal.setFirstDayOfWeek(j);
+                    // Set the calendar to the first day of the last week
+                    // of the year.  This may overlap some of the start of
+                    // the next year; that is, the last week of 1999 may
+                    // include some of January 2000.  Use the add() method
+                    // to advance through the week.  For each day, call
+                    // get(WEEK_OF_YEAR).  The result should be the same
+                    // for the whole week.  Note that a bug in
+                    // getActualMaximum() will break this test.
+		    cal.set(Calendar.YEAR, i);
+		    int maxWeek = cal.getActualMaximum(Calendar.WEEK_OF_YEAR);
+		    cal.set(Calendar.WEEK_OF_YEAR, maxWeek);
+		    cal.set(Calendar.DAY_OF_WEEK, j);
+		    for (int k = 1; k < 7; k++) {
+			cal.add(Calendar.DATE, 1);
+			int WOY = cal.get(Calendar.WEEK_OF_YEAR);
+			if (WOY != maxWeek) {
+			    errln(cal.getTime() + ",got=" + WOY
+				  + ",expected=" + maxWeek 
+				  + ",min=" + j1 + ",first=" + j);
+			}
+		    }
+                    // Now advance the calendar one more day.  This should
+                    // put it at the first day of week 1 of the next year.
+		    cal.add(Calendar.DATE, 1);
+		    int WOY = cal.get(Calendar.WEEK_OF_YEAR);
+		    if (WOY != 1) {
+			errln(cal.getTime() + ",got=" + WOY 
+			      + ",expected=1,min=" + j1 + ",first" + j);
+		    }
+		}
+	    }
+	}
+	TimeZone.setDefault(savedTZ);
+    }
+
+    /**
+     * Test fieldDifference().
+     */
+    public void TestJ438() throws Exception {
+        int DATA[] = {
+            2000, Calendar.JANUARY, 20,   2010, Calendar.JUNE, 15,
+            2010, Calendar.JUNE, 15,      2000, Calendar.JANUARY, 20,
+            1964, Calendar.SEPTEMBER, 7,  1999, Calendar.JUNE, 4,
+            1999, Calendar.JUNE, 4,       1964, Calendar.SEPTEMBER, 7,
+        };
+        Calendar cal = Calendar.getInstance(Locale.US);
+        for (int i=0; i<DATA.length; i+=6) {
+            int y1 = DATA[i];
+            int m1 = DATA[i+1];
+            int d1 = DATA[i+2];
+            int y2 = DATA[i+3];
+            int m2 = DATA[i+4];
+            int d2 = DATA[i+5];
+
+            cal.clear();
+            cal.set(y1, m1, d1);
+            Date date1 = cal.getTime();
+            cal.set(y2, m2, d2);
+            Date date2 = cal.getTime();
+
+            cal.setTime(date1);
+            int dy = cal.fieldDifference(date2, Calendar.YEAR);
+            int dm = cal.fieldDifference(date2, Calendar.MONTH);
+            int dd = cal.fieldDifference(date2, Calendar.DATE);
+
+            logln("" + date2 + " - " + date1 + " = " +
+                  dy + "y " + dm + "m " + dd + "d");
+
+            cal.setTime(date1);
+            cal.add(Calendar.YEAR, dy);
+            cal.add(Calendar.MONTH, dm);
+            cal.add(Calendar.DATE, dd);
+            Date date22 = cal.getTime();
+            if (!date2.equals(date22)) {
+                errln("FAIL: " + date1 + " + " +
+                      dy + "y " + dm + "m " + dd + "d = " +
+                      date22 + ", exp " + date2);
+            } else {
+                logln("Ok: " + date1 + " + " +
+                      dy + "y " + dm + "m " + dd + "d = " +
+                      date22);
+            }
+        }
+    }
+
+    /**
+     * Set behavior of DST_OFFSET field.  ICU4J Jitterbug 9.
+     */
+    public void TestJ9() {
+        int HOURS = 60*60*1000;
+        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("PST"),
+                                             Locale.US);
+
+        final int END_FIELDS = 0x1234;
+
+        int[] DATA = {
+            // With no explicit ZONE/DST expect 12:00 am
+            Calendar.MONTH, Calendar.JUNE,
+            END_FIELDS,
+            0, 0, // expected hour, min
+
+            // Normal ZONE/DST for June 1 Pacific is 8:00/1:00
+            Calendar.MONTH, Calendar.JUNE,
+            Calendar.ZONE_OFFSET, -8*HOURS,
+            Calendar.DST_OFFSET, HOURS,
+            END_FIELDS,
+            0, 0, // expected hour, min
+
+            // With ZONE/DST of 8:00/0:30 expect time of 12:30 am
+            Calendar.MONTH, Calendar.JUNE,
+            Calendar.ZONE_OFFSET, -8*HOURS,
+            Calendar.DST_OFFSET, HOURS/2,
+            END_FIELDS,
+            0, 30, // expected hour, min
+
+            // With ZONE/DST of 8:00/UNSET expect time of 1:00 am
+            Calendar.MONTH, Calendar.JUNE,
+            Calendar.ZONE_OFFSET, -8*HOURS,
+            END_FIELDS,
+            1, 0, // expected hour, min
+
+            // With ZONE/DST of UNSET/0:30 expect 4:30 pm (day before)
+            Calendar.MONTH, Calendar.JUNE,
+            Calendar.DST_OFFSET, HOURS/2,
+            END_FIELDS,
+            16, 30, // expected hour, min
+        };
+
+        for (int i=0; i<DATA.length; ) {
+            int start = i;
+            cal.clear();
+
+            // Set fields
+            while (DATA[i] != END_FIELDS) {
+                cal.set(DATA[i++], DATA[i++]);
+            }
+            ++i; // skip over END_FIELDS
+
+            // Get hour/minute
+            int h = cal.get(Calendar.HOUR_OF_DAY);
+            int m = cal.get(Calendar.MINUTE);
+
+            // Check
+            if (h != DATA[i] || m != DATA[i+1]) {
+                errln("Fail: expected " + DATA[i] + ":" + DATA[i+1] +
+                      ", got " + h + ":" + m + " after:");
+                while (DATA[start] != END_FIELDS) {
+                    logln("set(" + FIELD_NAME[DATA[start++]] +
+                          ", " + DATA[start++] + ");");
+                }
+            }
+
+            i += 2; // skip over expected hour, min
+        }
+    }
+
+    /**
+     * DateFormat class mistakes date style and time style as follows:
+     * - DateFormat.getDateTimeInstance takes date style as time
+     * style, and time style as date style
+     * - If a Calendar is passed to
+     * DateFormat.getDateInstance, it returns time instance
+     * - If a Calendar
+     * is passed to DateFormat.getTimeInstance, it returns date instance
+     */
+    public void TestDateFormatFactoryJ26() {
+        TimeZone zone = TimeZone.getDefault();
+        try {
+            Locale loc = Locale.US;
+            TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+            java.util.Calendar tempcal = java.util.Calendar.getInstance();
+            tempcal.set(2001 + 1900, Calendar.APRIL, 5, 17, 43, 53);
+            Date date = tempcal.getTime();
+            Calendar cal = Calendar.getInstance(loc);
+            Object[] DATA = {
+                DateFormat.getDateInstance(DateFormat.SHORT, loc),
+                "DateFormat.getDateInstance(DateFormat.SHORT, loc)",
+                "4/5/01",
+
+                DateFormat.getTimeInstance(DateFormat.SHORT, loc),
+                "DateFormat.getTimeInstance(DateFormat.SHORT, loc)",
+                "5:43 PM",
+
+                DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT, loc),
+                "DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT, loc)",
+                "Friday, April 5, 3901 5:43 PM",
+
+                DateFormat.getDateInstance(cal, DateFormat.SHORT, loc),
+                "DateFormat.getDateInstance(cal, DateFormat.SHORT, loc)",
+                "4/5/01",
+
+                DateFormat.getTimeInstance(cal, DateFormat.SHORT, loc),
+                "DateFormat.getTimeInstance(cal, DateFormat.SHORT, loc)",
+                "5:43 PM",
+
+                DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.SHORT, loc),
+                "DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.SHORT, loc)",
+                "Friday, April 5, 3901 5:43 PM",
+            
+                cal.getDateTimeFormat(DateFormat.SHORT, DateFormat.FULL, loc),
+                "cal.getDateTimeFormat(DateFormat.SHORT, DateFormat.FULL, loc)",
+                "4/5/01 5:43:53 PM PST",
+
+                cal.getDateTimeFormat(DateFormat.FULL, DateFormat.SHORT, loc),
+                "cal.getDateTimeFormat(DateFormat.FULL, DateFormat.SHORT, loc)",
+                "Friday, April 5, 3901 5:43 PM",
+            };
+            for (int i=0; i<DATA.length; i+=3) {
+                DateFormat df = (DateFormat) DATA[i];
+                String desc = (String) DATA[i+1];
+                String exp = (String) DATA[i+2];
+                String got = df.format(date);
+                if (got.equals(exp)) {
+                    logln("Ok: " + desc + " => " + got);
+                } else {
+                    errln("FAIL: " + desc + " => " + got + ", expected " + exp);
+                }
+            }
+        } finally {
+            TimeZone.setDefault(zone);
+        }
+    }
+
+    public void TestRegistration() {
+        /*
+        Set names = Calendar.getCalendarFactoryNames();
+
+        TimeZone tz = TimeZone.getDefault();
+        Locale loc = Locale.getDefault();
+        Iterator iter = names.iterator();
+        while (iter.hasNext()) {
+            String name = (String)iter.next();
+            logln("Testing factory: " + name);
+
+            Calendar cal = Calendar.getInstance(tz, loc, name);
+            logln("Calendar class: " + cal.getClass());
+
+            DateFormat fmt = cal.getDateTimeFormat(DateFormat.LONG, DateFormat.LONG, loc);
+            
+            logln("Date: " + fmt.format(cal.getTime()));
+        }
+
+        // register new default for our locale
+        logln("\nTesting registration");
+        loc = new Locale("en", "US");
+        Object key = Calendar.register(JapaneseCalendar.factory(), loc, true);
+
+        loc = new Locale("en", "US", "TEST");
+        Calendar cal = Calendar.getInstance(loc);
+        logln("Calendar class: " + cal.getClass());
+        DateFormat fmt = cal.getDateTimeFormat(DateFormat.LONG, DateFormat.LONG, loc);
+        logln("Date: " + fmt.format(cal.getTime()));
+
+        // force to use other default anyway
+        logln("\nOverride registration");
+        cal = Calendar.getInstance(tz, loc, "Gregorian");
+        fmt = cal.getDateTimeFormat(DateFormat.LONG, DateFormat.LONG, loc);
+        logln("Date: " + fmt.format(cal.getTime()));
+
+        // unregister default
+        logln("\nUnregistration");
+        logln("Unregister returned: " + Calendar.unregister(key));
+        cal = Calendar.getInstance(tz, loc, "Gregorian");
+        fmt = cal.getDateTimeFormat(DateFormat.LONG, DateFormat.LONG, loc);
+        logln("Date: " + fmt.format(cal.getTime()));
+        */
+    }
+}
+
+//eof
diff --git a/src/com/ibm/icu/dev/test/calendar/CalendarTest.java b/src/com/ibm/icu/dev/test/calendar/CalendarTest.java
new file mode 100755
index 0000000..228d796
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/CalendarTest.java
@@ -0,0 +1,374 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/CalendarTest.java,v $ 
+ * $Date: 2003/09/04 00:57:11 $ 
+ * $Revision: 1.15 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.test.calendar;
+
+import com.ibm.icu.dev.test.*;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.SimpleTimeZone;
+
+import com.ibm.icu.util.*;
+
+/**
+ * A base class for classes that test individual Calendar subclasses.
+ * Defines various useful utility methods and constants
+ */
+public class CalendarTest extends TestFmwk {
+    
+    // Constants for use by subclasses, solely to save typing
+    public final static int SUN = Calendar.SUNDAY;
+    public final static int MON = Calendar.MONDAY;
+    public final static int TUE = Calendar.TUESDAY;
+    public final static int WED = Calendar.WEDNESDAY;
+    public final static int THU = Calendar.THURSDAY;
+    public final static int FRI = Calendar.FRIDAY;
+    public final static int SAT = Calendar.SATURDAY;
+
+    public final static int ERA     = Calendar.ERA;
+    public final static int YEAR    = Calendar.YEAR;
+    public final static int MONTH   = Calendar.MONTH;
+    public final static int DATE    = Calendar.DATE;
+    public final static int HOUR    = Calendar.HOUR;
+    public final static int MINUTE  = Calendar.MINUTE;
+    public final static int SECOND  = Calendar.SECOND;
+    public final static int DOY     = Calendar.DAY_OF_YEAR;
+    public final static int WOY     = Calendar.WEEK_OF_YEAR;
+    public final static int WOM     = Calendar.WEEK_OF_MONTH;
+    public final static int DOW     = Calendar.DAY_OF_WEEK;
+    public final static int DOWM    = Calendar.DAY_OF_WEEK_IN_MONTH;
+    
+    public final static SimpleTimeZone UTC = new SimpleTimeZone(0, "GMT");
+
+    private static final String[] FIELD_NAME = {
+        "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
+        "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
+        "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
+        "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
+        "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
+        "JULIAN_DAY", "MILLISECONDS_IN_DAY",
+        "IS_LEAP_MONTH" // (ChineseCalendar only)
+    };
+
+    public static final String fieldName(int f) {
+        return (f>=0 && f<FIELD_NAME.length) ?
+            FIELD_NAME[f] : ("<Field " + f + ">");
+    }
+
+    /**
+     * Iterates through a list of calendar <code>TestCase</code> objects and
+     * makes sure that the time-to-fields and fields-to-time calculations work
+     * correnctly for the values in each test case.
+     */
+    public void doTestCases(TestCase[] cases, Calendar cal)
+    {
+        cal.setTimeZone(UTC);
+        
+        // Get a format to use for printing dates in the calendar system we're testing
+        DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.SHORT, -1, Locale.getDefault());
+
+        final String pattern = (cal instanceof ChineseCalendar) ?
+            "E MMl/dd/y G HH:mm:ss.S z" :
+            "E, MM/dd/yyyy G HH:mm:ss.S z";
+    
+        ((SimpleDateFormat)format).applyPattern(pattern);
+
+        // This format is used for printing Gregorian dates.
+        DateFormat gregFormat = new SimpleDateFormat(pattern);
+        gregFormat.setTimeZone(UTC);
+
+        GregorianCalendar pureGreg = new GregorianCalendar(UTC);
+        pureGreg.setGregorianChange(new Date(Long.MIN_VALUE));
+        DateFormat pureGregFmt = new SimpleDateFormat("E M/d/yyyy G");
+        pureGregFmt.setCalendar(pureGreg);
+        
+        // Now iterate through the test cases and see what happens
+        for (int i = 0; i < cases.length; i++)
+        {
+            TestCase test = cases[i];
+            
+            //
+            // First we want to make sure that the millis -> fields calculation works
+            // test.applyTime will call setTime() on the calendar object, and
+            // test.fieldsEqual will retrieve all of the field values and make sure
+            // that they're the same as the ones in the testcase
+            //
+            test.applyTime(cal);
+            if (!test.fieldsEqual(cal, this)) {
+                errln("Fail: (millis=>fields) " +
+                      gregFormat.format(test.getTime()) + " => " +
+                      format.format(cal.getTime()) +
+                      ", expected " + test);
+            }
+
+            //
+            // If that was OK, check the fields -> millis calculation
+            // test.applyFields will set all of the calendar's fields to 
+            // match those in the test case.
+            //
+            cal.clear();
+            test.applyFields(cal);
+            if (!test.equals(cal)) {
+                errln("Fail: (fields=>millis) " + test + " => " +
+                      pureGregFmt.format(cal.getTime()) +
+                      ", expected " + pureGregFmt.format(test.getTime()));
+            }
+        }
+    }
+    
+    static public final boolean ROLL = true;
+    static public final boolean ADD = false;
+    
+    /**
+     * Process test cases for <code>add</code> and <code>roll</code> methods.
+     * Each test case is an array of integers, as follows:
+     * <ul>
+     *  <li>0: input year
+     *  <li>1:       month  (zero-based)
+     *  <li>2:       day
+     *  <li>3: field to roll or add to
+     *  <li>4: amount to roll or add
+     *  <li>5: result year
+     *  <li>6:        month (zero-based)
+     *  <li>7:        day
+     * </ul>
+     * For example:
+     * <pre>
+     *   //       input                add by          output
+     *   //  year  month     day     field amount    year  month     day
+     *   {   5759, HESHVAN,   2,     MONTH,   1,     5759, KISLEV,    2 },
+     * </pre>
+     *
+     * @param roll  <code>true</code> or <code>ROLL</code> to test the <code>roll</code> method;
+     *              <code>false</code> or <code>ADD</code> to test the <code>add</code method
+     */
+    public void doRollAdd(boolean roll, Calendar cal, int[][] tests)
+    {
+        String name = roll ? "rolling" : "adding";
+        
+        for (int i = 0; i < tests.length; i++) {
+            int[] test = tests[i];
+
+            cal.clear();
+            if (cal instanceof ChineseCalendar) {
+                cal.set(Calendar.EXTENDED_YEAR, test[0]);
+                cal.set(Calendar.MONTH, test[1]);
+                cal.set(Calendar.DAY_OF_MONTH, test[2]);
+            } else {
+                cal.set(test[0], test[1], test[2]);
+            }
+            double day0 = getJulianDay(cal);
+            if (roll) {
+                cal.roll(test[3], test[4]);
+            } else {
+                cal.add(test[3], test[4]);
+            }
+            int y = cal.get(cal instanceof ChineseCalendar ?
+                            Calendar.EXTENDED_YEAR : YEAR);
+            if (y != test[5] || cal.get(MONTH) != test[6]
+                    || cal.get(DATE) != test[7])
+            {
+                errln("Fail: " + name + " "+ ymdToString(test[0], test[1], test[2])
+                    + " (" + day0 + ")"
+                    + " " + FIELD_NAME[test[3]] + " by " + test[4]
+                    + ": expected " + ymdToString(test[5], test[6], test[7])
+                    + ", got " + ymdToString(cal));
+            } else if (isVerbose()) {
+                logln("OK: " + name + " "+ ymdToString(test[0], test[1], test[2])
+                    + " (" + day0 + ")"
+                    + " " + FIELD_NAME[test[3]] + " by " + test[4]
+                    + ": got " + ymdToString(cal));
+            }
+        }
+    }
+
+    /**
+     * Test the functions getXxxMinimum() and getXxxMaximum() by marching a
+     * test calendar 'cal' through 'numberOfDays' sequential days starting
+     * with 'startDate'.  For each date, read a field value along with its
+     * reported actual minimum and actual maximum.  These values are
+     * checked against one another as well as against getMinimum(),
+     * getGreatestMinimum(), getLeastMaximum(), and getMaximum().  We
+     * expect to see:
+     *
+     * 1. minimum <= actualMinimum <= greatestMinimum <=
+     *    leastMaximum <= actualMaximum <= maximum
+     *
+     * 2. actualMinimum <= value <= actualMaximum
+     *
+     * Note: In addition to outright failures, this test reports some
+     * results as warnings.  These are not generally of concern, but they
+     * should be evaluated by a human.  To see these, run this test in
+     * verbose mode.
+     * @param cal the calendar to be tested
+     * @param fieldsToTest an array of field values to be tested, e.g., new
+     * int[] { Calendar.MONTH, Calendar.DAY_OF_MONTH }.  It only makes
+     * sense to test the day fields; the time fields are not tested by this
+     * method.  If null, then test all standard fields.
+     * @param startDate the first date to test
+     * @param testDuration if positive, the number of days to be tested.
+     * If negative, the number of seconds to run the test.
+     */
+    protected void doLimitsTest(Calendar cal, int[] fieldsToTest,
+                                Date startDate, int testDuration) {
+        GregorianCalendar greg = new GregorianCalendar();
+        greg.setTime(startDate);
+        logln("Start: " + startDate);
+
+        if (fieldsToTest == null) {
+            fieldsToTest = new int[] {
+                Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
+                Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
+                Calendar.DAY_OF_MONTH, Calendar.DAY_OF_YEAR,
+                Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.YEAR_WOY,
+                Calendar.EXTENDED_YEAR
+            };
+        }
+
+        // Keep a record of minima and maxima that we actually see.
+        // These are kept in an array of arrays of hashes.
+        Hashtable[][] limits = new Hashtable[fieldsToTest.length][2];
+        Object nub = new Object(); // Meaningless placeholder
+
+        // This test can run for a long time; show progress.
+        long millis = System.currentTimeMillis();
+        long mark = millis + 5000; // 5 sec
+        millis -= testDuration * 1000; // stop time if testDuration<0
+
+        for (int i=0;
+             testDuration>0 ? i<testDuration
+                            : System.currentTimeMillis()<millis;
+             ++i) {
+            if (System.currentTimeMillis() >= mark) {
+                logln("(" + i + " days)");
+                mark += 5000; // 5 sec
+            }
+            cal.setTimeInMillis(greg.getTimeInMillis());
+            for (int j=0; j<fieldsToTest.length; ++j) {
+                int f = fieldsToTest[j];
+                int v = cal.get(f);
+                int minActual = cal.getActualMinimum(f);
+                int maxActual = cal.getActualMaximum(f);
+                int minLow = cal.getMinimum(f);
+                int minHigh = cal.getGreatestMinimum(f);
+                int maxLow = cal.getLeastMaximum(f);
+                int maxHigh = cal.getMaximum(f);
+
+                // Fetch the hash for this field and keep track of the
+                // minima and maxima.
+                Hashtable[] h = limits[j];
+                if (h[0] == null) {
+                    h[0] = new Hashtable();
+                    h[1] = new Hashtable();
+                }
+                h[0].put(new Integer(minActual), nub);
+                h[1].put(new Integer(maxActual), nub);
+
+                if (minActual < minLow || minActual > minHigh) {
+                    errln("Fail: " + ymdToString(cal) +
+                          " Range for min of " + FIELD_NAME[f] +
+                          "=" + minLow + ".." + minHigh +
+                          ", actual_min=" + minActual);
+                }
+                if (maxActual < maxLow || maxActual > maxHigh) {
+                    errln("Fail: " + ymdToString(cal) +
+                          " Range for max of " + FIELD_NAME[f] +
+                          "=" + maxLow + ".." + maxHigh +
+                          ", actual_max=" + maxActual);
+                }
+                if (v < minActual || v > maxActual) {
+                    errln("Fail: " + ymdToString(cal) +
+                          " " + FIELD_NAME[f] + "=" + v +
+                          ", actual range=" + minActual + ".." + maxActual +
+                          ", allowed=(" + minLow + ".." + minHigh + ")..(" +
+                          maxLow + ".." + maxHigh + ")");
+                }
+            }
+            greg.add(Calendar.DAY_OF_YEAR, 1);
+        }
+
+        // Check actual maxima and minima seen against ranges returned
+        // by API.
+        StringBuffer buf = new StringBuffer();
+        for (int j=0; j<fieldsToTest.length; ++j) {
+            int f = fieldsToTest[j];
+            buf.setLength(0);
+            buf.append(FIELD_NAME[f]);
+            Hashtable[] h = limits[j];
+            boolean fullRangeSeen = true;
+            for (int k=0; k<2; ++k) {
+                int rangeLow = (k==0) ?
+                    cal.getMinimum(f) : cal.getLeastMaximum(f);
+                int rangeHigh = (k==0) ?
+                    cal.getGreatestMinimum(f) : cal.getMaximum(f);
+                // If either the top of the range or the bottom was never
+                // seen, then there may be a problem.
+                if (h[k].get(new Integer(rangeLow)) == null ||
+                    h[k].get(new Integer(rangeHigh)) == null) {
+                    fullRangeSeen = false;
+                }
+                buf.append(k==0 ? " minima seen=(" : "; maxima seen=(");
+                for (Enumeration e=h[k].keys(); e.hasMoreElements(); ) {
+                    int v = ((Integer) e.nextElement()).intValue();
+                    buf.append(" " + v);
+                }
+                buf.append(") range=" + rangeLow + ".." + rangeHigh);
+            }
+            if (fullRangeSeen) {
+                logln("OK: " + buf.toString());
+            } else {
+                // This may or may not be an error -- if the range of dates
+                // we scan over doesn't happen to contain a minimum or
+                // maximum, it doesn't mean some other range won't.
+                logln("Warning: " + buf.toString());
+            }
+        }
+
+        logln("End: " + greg.getTime());
+    }
+
+    /**
+     * Convert year,month,day values to the form "year/month/day".
+     * On input the month value is zero-based, but in the result string it is one-based.
+     */
+    static public String ymdToString(int year, int month, int day) {
+        return "" + year + "/" + (month+1) + "/" + day;
+    }
+
+    /**
+     * Convert year,month,day values to the form "year/month/day".
+     */
+    static public String ymdToString(Calendar cal) {
+        double day = getJulianDay(cal);
+        if (cal instanceof ChineseCalendar) {
+            return "" + cal.get(Calendar.EXTENDED_YEAR) + "/" +
+                (cal.get(Calendar.MONTH)+1) +
+                (cal.get(ChineseCalendar.IS_LEAP_MONTH)==1?"(leap)":"") + "/" +
+                cal.get(Calendar.DATE) + " (" + day + ")";
+        }
+        return ymdToString(cal.get(Calendar.EXTENDED_YEAR),
+                            cal.get(MONTH), cal.get(DATE)) +
+                            " (" + day + ")";
+    }
+
+    static double getJulianDay(Calendar cal) {
+        return (cal.getTime().getTime() - JULIAN_EPOCH) / DAY_MS;
+    }
+
+    static final double DAY_MS = 24*60*60*1000.0;
+    static final long JULIAN_EPOCH = -210866760000000L;   // 1/1/4713 BC 12:00
+}
diff --git a/src/com/ibm/icu/dev/test/calendar/CompatibilityTest.java b/src/com/ibm/icu/dev/test/calendar/CompatibilityTest.java
new file mode 100755
index 0000000..3adf54a
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/CompatibilityTest.java
@@ -0,0 +1,1055 @@
+/**
+ *******************************************************************************
+ * Copyright (C) 2000-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/CompatibilityTest.java,v $
+ * $Date: 2003/09/04 00:57:12 $
+ * $Revision: 1.12 $
+ *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+import com.ibm.icu.util.*;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+import java.io.*;
+
+public class CompatibilityTest extends com.ibm.icu.dev.test.TestFmwk {
+
+    public static void main(String argv[]) throws Exception {
+        new CompatibilityTest().run(argv);
+    }
+
+    static final String[] FIELD_NAME = {
+        "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
+        "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
+        "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
+        "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
+        "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
+        "JULIAN_DAY", "MILLISECONDS_IN_DAY",
+    };
+
+    /**
+     * Test the behavior of the GregorianCalendar around the changeover.
+     */
+    public void TestGregorianChangeover() {
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1582, Calendar.OCTOBER, 15);
+        Date co = tempcal.getTime();
+        final int ONE_DAY = 24*60*60*1000;
+        GregorianCalendar cal = new GregorianCalendar();
+        /*
+          Changeover -7 days: 1582/9/28 dow=6
+          Changeover -6 days: 1582/9/29 dow=7
+          Changeover -5 days: 1582/9/30 dow=1
+          Changeover -4 days: 1582/10/1 dow=2
+          Changeover -3 days: 1582/10/2 dow=3
+          Changeover -2 days: 1582/10/3 dow=4
+          Changeover -1 days: 1582/10/4 dow=5
+          Changeover +0 days: 1582/10/15 dow=6
+          Changeover +1 days: 1582/10/16 dow=7
+          Changeover +2 days: 1582/10/17 dow=1
+          Changeover +3 days: 1582/10/18 dow=2
+          Changeover +4 days: 1582/10/19 dow=3
+          Changeover +5 days: 1582/10/20 dow=4
+          Changeover +6 days: 1582/10/21 dow=5
+          Changeover +7 days: 1582/10/22 dow=6
+          */
+        int MON[] = {  9,  9,  9,10,10,10,10, 10, 10, 10, 10, 10, 10, 10, 10 };
+        int DOM[] = { 28, 29, 30, 1, 2, 3, 4, 15, 16, 17, 18, 19, 20, 21, 22 };
+        int DOW[] = {  6,  7,  1, 2, 3, 4, 5,  6,  7,  1,  2,  3,  4,  5,  6 };
+        //                                     ^ <-Changeover Fri Oct 15 1582
+        int j=0;
+        for (int i=-7; i<=7; ++i, ++j) {
+            Date d = new Date(co.getTime() + i*ONE_DAY);
+            cal.setTime(d);
+            int y = cal.get(Calendar.YEAR), mon = cal.get(Calendar.MONTH)+1-Calendar.JANUARY,
+                dom = cal.get(Calendar.DATE), dow = cal.get(Calendar.DAY_OF_WEEK);
+            logln("Changeover " + (i>=0?"+":"") +
+                  i + " days: " + y + "/" + mon + "/" + dom + " dow=" + dow);
+            if (y != 1582 || mon != MON[j] || dom != DOM[j] || dow != DOW[j])
+                errln(" Fail: Above line is wrong");
+        }
+    }
+
+    /**
+     * Test the mapping between millis and fields.  For the purposes
+     * of this test, we don't care about timezones and week data
+     * (first day of week, minimal days in first week).
+     */
+    public void TestMapping() {
+        if (false) {
+            Date PURE_GREGORIAN = new Date(Long.MIN_VALUE);
+            Date PURE_JULIAN = new Date(Long.MAX_VALUE);
+            GregorianCalendar cal =
+                new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+            final int EPOCH_JULIAN = 2440588;
+            final long ONE_DAY = 24*60*60*1000L;
+            com.ibm.icu.text.SimpleDateFormat fmt =
+                new com.ibm.icu.text.SimpleDateFormat("EEE MMM dd yyyy G");
+                /*HH:mm:ss.SSS z*/
+
+            for (int type=0; type<2; ++type) {
+                System.out.println(type==0 ? "Gregorian" : "Julian");
+                cal.setGregorianChange(type==0 ? PURE_GREGORIAN : PURE_JULIAN);
+                fmt.setCalendar(cal);
+                int[] J = {
+                    0x7FFFFFFF,
+                    0x7FFFFFF0,
+                    0x7F000000,
+                    0x78000000,
+                    0x70000000,
+                    0x60000000,
+                    0x50000000,
+                    0x40000000,
+                    0x30000000,
+                    0x20000000,
+                    0x10000000,
+                };
+                for (int i=0; i<J.length; ++i) {
+                    String[] lim = new String[2];
+                    long[] ms = new long[2];
+                    int jd = J[i];
+                    for (int sign=0; sign<2; ++sign) {
+                        int julian = jd;
+                        if (sign==0) julian = -julian;
+                        long millis = ((long)julian - EPOCH_JULIAN) * ONE_DAY;
+                        ms[sign] = millis;
+                        cal.setTime(new Date(millis));
+                        lim[sign] = fmt.format(cal.getTime());
+                    }
+                    System.out.println("JD +/-" +
+                                       Long.toString(jd, 16) +
+                                       ": " + ms[0] + ".." + ms[1] +
+                                       ": " + lim[0] + ".." + lim[1]);
+                }
+            }
+        }
+
+        TimeZone saveZone = TimeZone.getDefault();
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            //NEWCAL
+            Date PURE_GREGORIAN = new Date(Long.MIN_VALUE);
+            Date PURE_JULIAN = new Date(Long.MAX_VALUE);
+            GregorianCalendar cal = new GregorianCalendar();
+            final int EPOCH_JULIAN = 2440588;
+            final long ONE_DAY = 24*60*60*1000L;
+            int[] DATA = {
+                // Julian#   Year  Month               DOM   JULIAN:Year, Month,       DOM
+                2440588,     1970, Calendar.JANUARY,   1,    1969, Calendar.DECEMBER,  19, 
+                2415080,     1900, Calendar.MARCH,     1,    1900, Calendar.FEBRUARY,  17,
+                2451604,     2000, Calendar.FEBRUARY,  29,   2000, Calendar.FEBRUARY,  16,
+                2452269,     2001, Calendar.DECEMBER,  25,   2001, Calendar.DECEMBER,  12,
+                2416526,     1904, Calendar.FEBRUARY,  15,   1904, Calendar.FEBRUARY,  2,
+                2416656,     1904, Calendar.JUNE,      24,   1904, Calendar.JUNE,      11,
+                1721426,        1, Calendar.JANUARY,   1,       1, Calendar.JANUARY,   3,
+                2000000,      763, Calendar.SEPTEMBER, 18,    763, Calendar.SEPTEMBER, 14,
+                4000000,     6239, Calendar.JULY,      12,   6239, Calendar.MAY,       28,
+                8000000,    17191, Calendar.FEBRUARY,  26,  17190, Calendar.OCTOBER,   22,
+                10000000,   22666, Calendar.DECEMBER,  20,  22666, Calendar.JULY,      5,
+            };
+            for (int i=0; i<DATA.length; i+=7) {
+                int julian = DATA[i];
+                int year = DATA[i+1];
+                int month = DATA[i+2];
+                int dom = DATA[i+3];
+                int year2, month2, dom2;
+                long millis = (julian - EPOCH_JULIAN) * ONE_DAY;
+                String s;
+
+                // Test Gregorian computation
+                cal.setGregorianChange(PURE_GREGORIAN);
+                cal.clear();
+                cal.set(year, month, dom);
+                long calMillis = cal.getTime().getTime();
+                long delta = calMillis - millis;
+                cal.setTime(new Date(millis));
+                year2 = cal.get(Calendar.YEAR);
+                month2 = cal.get(Calendar.MONTH);
+                dom2 = cal.get(Calendar.DAY_OF_MONTH);
+                s = "G " + year + "-" + (month+1-Calendar.JANUARY) + "-" + dom +
+                    " => " + calMillis +
+                    " (" + ((float)delta/ONE_DAY) + " day delta) => " +
+                    year2 + "-" + (month2+1-Calendar.JANUARY) + "-" + dom2;
+                if (delta != 0 || year != year2 || month != month2 ||
+                    dom != dom2) errln(s + " FAIL");
+                else logln(s);
+                
+                // Test Julian computation
+                year = DATA[i+4];
+                month = DATA[i+5];
+                dom = DATA[i+6];
+                cal.setGregorianChange(PURE_JULIAN);
+                cal.clear();
+                cal.set(year, month, dom);
+                calMillis = cal.getTime().getTime();
+                delta = calMillis - millis;
+                cal.setTime(new Date(millis));
+                year2 = cal.get(Calendar.YEAR);
+                month2 = cal.get(Calendar.MONTH);
+                dom2 = cal.get(Calendar.DAY_OF_MONTH);
+                s = "J " + year + "-" + (month+1-Calendar.JANUARY) + "-" + dom +
+                    " => " + calMillis +
+                    " (" + ((float)delta/ONE_DAY) + " day delta) => " +
+                    year2 + "-" + (month2+1-Calendar.JANUARY) + "-" + dom2;
+                if (delta != 0 || year != year2 || month != month2 ||
+                    dom != dom2) errln(s + " FAIL");
+                else logln(s);
+            }
+
+            java.util.Calendar tempcal = java.util.Calendar.getInstance();
+            tempcal.clear();
+            tempcal.set(1582, Calendar.OCTOBER, 15);
+            cal.setGregorianChange(tempcal.getTime());
+            auxMapping(cal, 1582, Calendar.OCTOBER, 4);
+            auxMapping(cal, 1582, Calendar.OCTOBER, 15);
+            auxMapping(cal, 1582, Calendar.OCTOBER, 16);
+            for (int y=800; y<3000; y+=1+(int)100*Math.random()) {
+                for (int m=Calendar.JANUARY; m<=Calendar.DECEMBER; ++m) {
+                    auxMapping(cal, y, m, 15);
+                }
+            }
+        }
+        finally {
+            TimeZone.setDefault(saveZone);
+        }
+    }
+    private void auxMapping(Calendar cal, int y, int m, int d) {
+        cal.clear();
+        cal.set(y, m, d);
+        long millis = cal.getTime().getTime();
+        cal.setTime(new Date(millis));
+        int year2 = cal.get(Calendar.YEAR);
+        int month2 = cal.get(Calendar.MONTH);
+        int dom2 = cal.get(Calendar.DAY_OF_MONTH);
+        if (y != year2 || m != month2 || dom2 != d)
+            errln("Round-trip failure: " + y + "-" + (m+1) + "-"+d+" =>ms=> " +
+                  year2 + "-" + (month2+1) + "-" + dom2);
+    }
+
+    public void TestGenericAPI() {
+        // not used String str;
+
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1990, Calendar.APRIL, 15);
+        Date when = tempcal.getTime();
+
+        String tzid = "TestZone";
+        int tzoffset = 123400;
+
+        SimpleTimeZone zone = new SimpleTimeZone(tzoffset, tzid);
+        Calendar cal = (Calendar)Calendar.getInstance((SimpleTimeZone)zone.clone());
+
+        if (!zone.equals(cal.getTimeZone())) errln("FAIL: Calendar.getTimeZone failed");
+
+        Calendar cal2 = Calendar.getInstance(cal.getTimeZone());
+
+        cal.setTime(when);
+        cal2.setTime(when);
+
+        if (!(cal.equals(cal2))) errln("FAIL: Calendar.operator== failed");
+        // if ((*cal != *cal2))  errln("FAIL: Calendar.operator!= failed");
+        if (!cal.equals(cal2) ||
+            cal.before(cal2) ||
+            cal.after(cal2)) errln("FAIL: equals/before/after failed");
+
+        cal2.setTime(new Date(when.getTime() + 1000));
+        if (cal.equals(cal2) ||
+            cal2.before(cal) ||
+            cal.after(cal2)) errln("FAIL: equals/before/after failed");
+
+        cal.roll(Calendar.SECOND, true);
+        if (!cal.equals(cal2) ||
+            cal.before(cal2) ||
+            cal.after(cal2)) errln("FAIL: equals/before/after failed");
+
+        // Roll back to January
+        cal.roll(Calendar.MONTH, (int)(1 + Calendar.DECEMBER - cal.get(Calendar.MONTH)));
+        if (cal.equals(cal2) ||
+            cal2.before(cal) ||
+            cal.after(cal2)) errln("FAIL: equals/before/after failed");
+
+        // C++ only
+        /* TimeZone z = cal.orphanTimeZone();
+           if (z.getID(str) != tzid ||
+           z.getRawOffset() != tzoffset)
+           errln("FAIL: orphanTimeZone failed");
+           */
+
+        for (int i=0; i<2; ++i) {
+            boolean lenient = ( i > 0 );
+            cal.setLenient(lenient);
+            if (lenient != cal.isLenient()) errln("FAIL: setLenient/isLenient failed");
+            // Later: Check for lenient behavior
+        }
+
+        int i;
+        for (i=Calendar.SUNDAY; i<=Calendar.SATURDAY; ++i) {
+            cal.setFirstDayOfWeek(i);
+            if (cal.getFirstDayOfWeek() != i) errln("FAIL: set/getFirstDayOfWeek failed");
+        }
+
+        for (i=1; i<=7; ++i) {
+            cal.setMinimalDaysInFirstWeek(i);
+            if (cal.getMinimalDaysInFirstWeek() != i) errln("FAIL: set/getFirstDayOfWeek failed");
+        }
+
+        for (i=0; i<cal.getFieldCount(); ++i) {
+            if (cal.getMinimum(i) != cal.getGreatestMinimum(i))
+                errln("FAIL: getMinimum doesn't match getGreatestMinimum for field " + i);
+            if (cal.getLeastMaximum(i) > cal.getMaximum(i))
+                errln("FAIL: getLeastMaximum larger than getMaximum for field " + i);
+            if (cal.getMinimum(i) >= cal.getMaximum(i))
+                errln("FAIL: getMinimum not less than getMaximum for field " + i);
+        }
+
+        cal.setTimeZone(TimeZone.getDefault());
+        cal.clear();
+        cal.set(1984, 5, 24);
+        tempcal.clear();
+        tempcal.set(1984, 5, 24);
+        if (cal.getTime().getTime() != tempcal.getTime().getTime()) {
+            errln("FAIL: Calendar.set(3 args) failed");
+            logln(" Got: " + cal.getTime() + "  Expected: " + tempcal.getTime());
+        }
+
+        cal.clear();
+        cal.set(1985, 3, 2, 11, 49);
+        tempcal.clear();
+        tempcal.set(1985, 3, 2, 11, 49);
+        if (cal.getTime().getTime() != tempcal.getTime().getTime()) {
+            errln("FAIL: Calendar.set(5 args) failed");
+            logln(" Got: " + cal.getTime() + "  Expected: " + tempcal.getTime());
+        }
+
+        cal.clear();
+        cal.set(1995, 9, 12, 1, 39, 55);
+        tempcal.clear();
+        tempcal.set(1995, 9, 12, 1, 39, 55);
+        if (cal.getTime().getTime() != tempcal.getTime().getTime()) {
+            errln("FAIL: Calendar.set(6 args) failed");
+            logln(" Got: " + cal.getTime() + "  Expected: " + tempcal.getTime());
+        }
+
+        cal.getTime();
+        // This test is strange -- why do we expect certain fields to be set, and
+        // others not to be?  Revisit the appropriateness of this.  - Alan NEWCAL
+        for (i=0; i<cal.getFieldCount(); ++i) {
+            switch(i) {
+            case Calendar.YEAR: case Calendar.MONTH: case Calendar.DATE:
+            case Calendar.HOUR_OF_DAY: case Calendar.MINUTE: case Calendar.SECOND:
+            case Calendar.EXTENDED_YEAR:
+                if (!cal.isSet(i)) errln("FAIL: " + FIELD_NAME[i] + " is not set");
+                break;
+            default:
+                if (cal.isSet(i)) errln("FAIL: " + FIELD_NAME[i] + " is set");
+            }
+            cal.clear(i);
+            if (cal.isSet(i)) errln("FAIL: Calendar.clear/isSet failed");
+        }
+
+        // delete cal;
+        // delete cal2;
+
+        Locale[] loc = Calendar.getAvailableLocales();
+        long count = loc.length;
+        if (count < 1 || loc == null) {
+            errln("FAIL: getAvailableLocales failed");
+        }
+        else {
+            for (i=0; i<count; ++i) {
+                cal = Calendar.getInstance(loc[i]);
+                // delete cal;
+            }
+        }
+
+        cal = Calendar.getInstance(TimeZone.getDefault(), Locale.ENGLISH);
+        // delete cal;
+
+        cal = Calendar.getInstance(zone, Locale.ENGLISH);
+        // delete cal;
+
+        GregorianCalendar gc = new GregorianCalendar(zone);
+        // delete gc;
+
+        gc = new GregorianCalendar(Locale.ENGLISH);
+        // delete gc;
+
+        gc = new GregorianCalendar(Locale.ENGLISH);
+        // delete gc;
+
+        gc = new GregorianCalendar(zone, Locale.ENGLISH);
+        // delete gc;
+
+        gc = new GregorianCalendar(zone);
+        // delete gc;
+
+        gc = new GregorianCalendar(1998, 10, 14, 21, 43);
+        tempcal.clear();
+        tempcal.set(1998, 10, 14, 21, 43);
+        if (gc.getTime().getTime() != tempcal.getTime().getTime())
+            errln("FAIL: new GregorianCalendar(ymdhm) failed");
+        // delete gc;
+
+        gc = new GregorianCalendar(1998, 10, 14, 21, 43, 55);
+        tempcal.clear();
+        tempcal.set(1998, 10, 14, 21, 43, 55);
+        if (gc.getTime().getTime() != tempcal.getTime().getTime())
+            errln("FAIL: new GregorianCalendar(ymdhms) failed");
+
+        // C++ only:
+        // GregorianCalendar gc2 = new GregorianCalendar(Locale.ENGLISH);
+        // gc2 = gc;
+        // if (gc2 != gc || !(gc2 == gc)) errln("FAIL: GregorianCalendar assignment/operator==/operator!= failed");
+        // delete gc;
+        // delete z;
+    }
+
+    // Verify Roger Webster's bug
+    public void TestRog() {
+        GregorianCalendar gc = new GregorianCalendar();
+
+        int year = 1997, month = Calendar.APRIL, date = 1;
+        gc.set(year, month, date); // April 1, 1997
+
+        gc.set(Calendar.HOUR_OF_DAY, 23);
+        gc.set(Calendar.MINUTE, 0);
+        gc.set(Calendar.SECOND, 0);
+        gc.set(Calendar.MILLISECOND, 0);
+
+        for (int i = 0; i < 9; i++, gc.add(Calendar.DATE, 1)) {
+            if (gc.get(Calendar.YEAR) != year ||
+                gc.get(Calendar.MONTH) != month ||
+                gc.get(Calendar.DATE) != (date + i))
+                errln("FAIL: Date " + gc.getTime() + " wrong");
+        }
+    }
+
+    // Verify DAY_OF_WEEK
+    public void TestDOW943() {
+        dowTest(false);
+        dowTest(true);
+    }
+
+    void dowTest(boolean lenient) {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.set(1997, Calendar.AUGUST, 12); // Wednesday
+        cal.getTime(); // Force update
+        cal.setLenient(lenient);
+        cal.set(1996, Calendar.DECEMBER, 1); // Set the date to be December 1, 1996
+        int dow = cal.get(Calendar.DAY_OF_WEEK);
+        int min = cal.getMinimum(Calendar.DAY_OF_WEEK);
+        int max = cal.getMaximum(Calendar.DAY_OF_WEEK);
+        if (dow < min || dow > max) errln("FAIL: Day of week " + dow + " out of range");
+        if (dow != Calendar.SUNDAY) {
+            errln("FAIL2: Day of week should be SUNDAY; is " + dow + ": " + cal.getTime());
+        }
+        if (min != Calendar.SUNDAY || max != Calendar.SATURDAY) errln("FAIL: Min/max bad");
+    }
+
+    // Verify that the clone method produces distinct objects with no
+    // unintentionally shared fields.
+    public void TestClonesUnique908() {
+        Calendar c = Calendar.getInstance();
+        Calendar d = (Calendar)c.clone();
+        c.set(Calendar.MILLISECOND, 123);
+        d.set(Calendar.MILLISECOND, 456);
+        if (c.get(Calendar.MILLISECOND) != 123 ||
+            d.get(Calendar.MILLISECOND) != 456) {
+            errln("FAIL: Clones share fields");
+        }
+    }
+
+    // Verify effect of Gregorian cutoff value
+    public void TestGregorianChange768() {
+        boolean b;
+        GregorianCalendar c = new GregorianCalendar();
+        logln("With cutoff " + c.getGregorianChange());
+        logln(" isLeapYear(1800) = " + (b=c.isLeapYear(1800)));
+        logln(" (should be FALSE)");
+        if (b != false) errln("FAIL");
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1900, 0, 1);
+        c.setGregorianChange(tempcal.getTime()); // Jan 1 1900
+        logln("With cutoff " + c.getGregorianChange());
+        logln(" isLeapYear(1800) = " + (b=c.isLeapYear(1800)));
+        logln(" (should be TRUE)");
+        if (b != true) errln("FAIL");
+    }
+
+    // Test the correct behavior of the disambiguation algorithm.
+    public void TestDisambiguation765() throws Exception {
+        Calendar c = Calendar.getInstance();
+        c.setLenient(false);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.MONTH, Calendar.JUNE);
+        c.set(Calendar.DATE, 3);
+
+        verify765("1997 third day of June = ", c, 1997, Calendar.JUNE, 3);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.MONTH, Calendar.JUNE);
+        c.set(Calendar.DAY_OF_WEEK_IN_MONTH, 1);
+        verify765("1997 first Tuesday in June = ", c, 1997, Calendar.JUNE, 3);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.MONTH, Calendar.JUNE);
+        c.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);
+        verify765("1997 last Tuesday in June = ", c, 1997, Calendar.JUNE, 24);
+
+        IllegalArgumentException e = null;
+        try {
+            c.clear();
+            c.set(Calendar.YEAR, 1997);
+            c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+            c.set(Calendar.MONTH, Calendar.JUNE);
+            c.set(Calendar.DAY_OF_WEEK_IN_MONTH, 0);
+            c.getTime();
+        }
+        catch (IllegalArgumentException ex) {
+            e = ex;
+        }
+        verify765("1997 zero-th Tuesday in June = ", e, c);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.MONTH, Calendar.JUNE);
+        c.set(Calendar.WEEK_OF_MONTH, 1);
+        verify765("1997 Tuesday in week 1 of June = ", c, 1997, Calendar.JUNE, 3);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.MONTH, Calendar.JUNE);
+        c.set(Calendar.WEEK_OF_MONTH, 5);
+        verify765("1997 Tuesday in week 5 of June = ", c, 1997, Calendar.JULY, 1);
+
+        try {
+            c.clear();
+            c.set(Calendar.YEAR, 1997);
+            c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+            c.set(Calendar.MONTH, Calendar.JUNE);
+            c.set(Calendar.WEEK_OF_MONTH, 0);
+            verify765("1997 Tuesday in week 0 of June = ", c, 1997, Calendar.MAY, 27);
+        }
+        catch (IllegalArgumentException ex) {
+            errln("FAIL: Exception seen:");
+            // ex.printStackTrace(log);
+        }
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.WEEK_OF_YEAR, 1);
+        verify765("1997 Tuesday in week 1 of year = ", c, 1996, Calendar.DECEMBER, 31);
+
+        c.clear();
+        c.set(Calendar.YEAR, 1997);
+        c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+        c.set(Calendar.WEEK_OF_YEAR, 10);
+        verify765("1997 Tuesday in week 10 of year = ", c, 1997, Calendar.MARCH, 4);
+
+        try {
+            c.clear();
+            c.set(Calendar.YEAR, 1997);
+            c.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
+            c.set(Calendar.WEEK_OF_YEAR, 0);
+            verify765("1997 Tuesday in week 0 of year = ", c, 1996, Calendar.DECEMBER, 24);
+            throw new Exception("Fail: WEEK_OF_YEAR 0 should be illegal");
+        }
+        catch (IllegalArgumentException ex) {
+            System.out.print("");
+        }
+    }
+    void verify765(String msg, Calendar c, int year, int month, int day) {
+        int cy = c.get(Calendar.YEAR); // NEWCAL
+        int cm = c.get(Calendar.MONTH);
+        int cd = c.get(Calendar.DATE);
+        if (cy == year &&
+            cm == month &&
+            cd == day) {
+            logln("PASS: " + msg + c.getTime());
+        }
+        else {
+            errln("FAIL: " + msg + cy + "/" + (cm+1) + "/" + cd +
+                  "=" + c.getTime() +
+                  "; expected " +
+                  year + "/" + (month+1) + "/" + day);
+        }
+    }
+    // Called when e expected to be non-null
+    void verify765(String msg, IllegalArgumentException e, Calendar c) {
+        if (e == null) errln("FAIL: No IllegalArgumentException for " + msg +
+                             c.getTime());
+        else logln("PASS: " + msg + "IllegalArgument as expected");
+    }
+
+    // Test the behavior of GMT vs. local time
+    public void TestGMTvsLocal4064654() {
+        // Sample output 1:
+        // % /usr/local/java/jdk1.1.3/solaris/bin/java test 1997 1 1 12 0 0
+        // date = Wed Jan 01 04:00:00 PST 1997
+        // offset for Wed Jan 01 04:00:00 PST 1997= -8hr
+        aux4064654(1997, 1, 1, 12, 0, 0);
+
+        // Sample output 2:
+        // % /usr/local/java/jdk1.1.3/solaris/bin/java test 1997 4 16 18 30 0
+        // date = Wed Apr 16 10:30:00 PDT 1997
+        // offset for Wed Apr 16 10:30:00 PDT 1997= -7hr
+
+        // Note that in sample output 2 according to the offset, the gmt time
+        // of the result would be 1997 4 16 17 30 0 which is different from the
+        // input of 1997 4 16 18 30 0.
+        aux4064654(1997, 4, 16, 18, 30, 0);
+    }
+    void aux4064654(int yr, int mo, int dt, int hr, int mn, int sc) {
+        Date date;
+        Calendar gmtcal = Calendar.getInstance();
+        gmtcal.setTimeZone(TimeZone.getTimeZone("Africa/Casablanca"));
+        gmtcal.set(yr, mo-1, dt, hr, mn, sc);
+        gmtcal.set(Calendar.MILLISECOND, 0);
+
+        date = gmtcal.getTime();
+        logln("date = "+date);
+
+        Calendar cal = Calendar.getInstance();
+	cal.setTimeZone(TimeZone.getTimeZone("America/Los_Angels"));
+        cal.setTime(date);
+
+        int offset = cal.getTimeZone().getOffset(cal.get(Calendar.ERA),
+                                                 cal.get(Calendar.YEAR),
+                                                 cal.get(Calendar.MONTH),
+                                                 cal.get(Calendar.DATE),
+                                                 cal.get(Calendar.DAY_OF_WEEK),
+                                                 cal.get(Calendar.MILLISECOND));
+
+        logln("offset for "+date+"= "+(offset/1000/60/60.0) + "hr");
+
+        int utc = ((cal.get(Calendar.HOUR_OF_DAY) * 60 +
+                    cal.get(Calendar.MINUTE)) * 60 +
+                   cal.get(Calendar.SECOND)) * 1000 +
+            cal.get(Calendar.MILLISECOND) - offset;
+
+        int expected = ((hr * 60 + mn) * 60 + sc) * 1000;
+
+        if (utc != expected)
+            errln("FAIL: Discrepancy of " +
+                  (utc - expected) + " millis = " +
+                  ((utc-expected)/1000/60/60.0) + " hr");
+    }
+
+    // Verify that add and set work regardless of the order in which
+    // they are called.
+    public void TestAddSetOrder621() {
+        java.util.Calendar tempcal = java.util.Calendar.getInstance();
+        tempcal.clear();
+        tempcal.set(1997, 4, 14, 13, 23, 45);
+        Date d = tempcal.getTime();
+
+        Calendar cal = Calendar.getInstance ();
+        cal.setTime (d);
+        cal.add (Calendar.DATE, -5);
+        cal.set (Calendar.HOUR_OF_DAY, 0);
+        cal.set (Calendar.MINUTE, 0);
+        cal.set (Calendar.SECOND, 0);
+        // ma feb 03 00:00:00 GMT+00:00 1997
+        String s = cal.getTime ().toString ();
+
+        cal = Calendar.getInstance ();
+        cal.setTime (d);
+        cal.set (Calendar.HOUR_OF_DAY, 0);
+        cal.set (Calendar.MINUTE, 0);
+        cal.set (Calendar.SECOND, 0);
+        cal.add (Calendar.DATE, -5);
+        // ma feb 03 13:11:06 GMT+00:00 1997
+        String s2 = cal.getTime ().toString ();
+
+        if (s.equals(s2))
+            logln("Pass: " + s + " == " + s2);
+        else
+            errln("FAIL: " + s + " != " + s2);
+    }
+
+    // Verify that add works.
+    public void TestAdd520() {
+        int y = 1997, m = Calendar.FEBRUARY, d = 1;
+        GregorianCalendar temp = new GregorianCalendar( y, m, d );
+        check520(temp, y, m, d);
+
+        temp.add( Calendar.YEAR, 1 );
+        y++;
+        check520(temp, y, m, d);
+
+        temp.add( Calendar.MONTH, 1 );
+        m++;
+        check520(temp, y, m, d);
+
+        temp.add( Calendar.DATE, 1 );
+        d++;
+        check520(temp, y, m, d);
+
+        temp.add( Calendar.DATE, 2 );
+        d += 2;
+        check520(temp, y, m, d);
+
+        temp.add( Calendar.DATE, 28 );
+        d = 1; ++m;
+        check520(temp, y, m, d);
+    }
+    void check520(Calendar c, int y, int m, int d) {
+        if (c.get(Calendar.YEAR) != y ||
+            c.get(Calendar.MONTH) != m ||
+            c.get(Calendar.DATE) != d) {
+            errln("FAILURE: Expected YEAR/MONTH/DATE of " +
+                  y + "/" + (m+1) + "/" + d +
+                  "; got " +
+                  c.get(Calendar.YEAR) + "/" +
+                  (c.get(Calendar.MONTH)+1) + "/" +
+                  c.get(Calendar.DATE));
+        }
+        else logln("Confirmed: " +
+                   y + "/" + (m+1) + "/" + d);
+    }
+
+    // Verify that setting fields works.  This test fails when an exception is thrown.
+    public void TestFieldSet4781() {
+        try {
+            GregorianCalendar g = new GregorianCalendar();
+            GregorianCalendar g2 = new GregorianCalendar();
+            // At this point UTC value is set, various fields are not.
+            // Now set to noon.
+            g2.set(Calendar.HOUR, 12);
+            g2.set(Calendar.MINUTE, 0);
+            g2.set(Calendar.SECOND, 0);
+            // At this point the object thinks UTC is NOT set, but fields are set.
+            // The following line will result in IllegalArgumentException because
+            // it thinks the YEAR is set and it is NOT.
+            if (g2.equals(g))
+                logln("Same");
+            else
+                logln("Different");
+        }
+        catch (IllegalArgumentException e) {
+            errln("Unexpected exception seen: " + e);
+        }
+    }
+
+    // Test serialization of a Calendar object
+    public void TestSerialize337() {
+        Calendar cal = Calendar.getInstance();
+
+        boolean ok = false;
+
+        try {
+            FileOutputStream f = new FileOutputStream(FILENAME);
+            ObjectOutput s = new ObjectOutputStream(f);
+            s.writeObject(PREFIX);
+            s.writeObject(cal);
+            s.writeObject(POSTFIX);
+            f.close();
+
+            FileInputStream in = new FileInputStream(FILENAME);
+            ObjectInputStream t = new ObjectInputStream(in);
+            String pre = (String)t.readObject();
+            Calendar c = (Calendar)t.readObject();
+            String post = (String)t.readObject();
+            in.close();
+
+            ok = pre.equals(PREFIX) &&
+                post.equals(POSTFIX) &&
+                cal.equals(c);
+
+            File fl = new File(FILENAME);
+            fl.delete();
+        }
+        catch (IOException e) {
+            errln("FAIL: Exception received:");
+            // e.printStackTrace(log);
+        }
+        catch (ClassNotFoundException e) {
+            errln("FAIL: Exception received:");
+            // e.printStackTrace(log);
+        }
+
+        if (!ok) errln("Serialization of Calendar object failed.");
+    }
+    static final String PREFIX = "abc";
+    static final String POSTFIX = "def";
+    static final String FILENAME = "tmp337.bin";
+
+    // Try to zero out the seconds field
+    public void TestSecondsZero121() {
+        Calendar        cal = new GregorianCalendar();
+        // Initialize with current date/time
+        cal.setTime(new Date());
+        // Round down to minute
+        cal.set(Calendar.SECOND, 0);
+        Date    d = cal.getTime();
+        String s = d.toString();
+        if (s.indexOf(":00 ") < 0) errln("Expected to see :00 in " + s);
+    }
+
+    // Try various sequences of add, set, and get method calls.
+    public void TestAddSetGet0610() {
+        //
+        // Error case 1:
+        // - Upon initialization calendar fields, millis = System.currentTime
+        // - After set is called fields are initialized, time is not
+        // - Addition uses millis which are still *now*
+        //
+        {
+            Calendar calendar = new GregorianCalendar( ) ;
+            calendar.set( 1993, Calendar.JANUARY, 4 ) ;
+            logln( "1A) " + value( calendar ) ) ;
+            calendar.add( Calendar.DATE, 1 ) ;
+            String v = value(calendar);
+            logln( "1B) " + v );
+            logln( "--) 1993/0/5" ) ;
+            if (!v.equals(EXPECTED_0610)) errln("Expected " + EXPECTED_0610 +
+                                                "; saw " + v);
+        }
+
+        //
+        // Error case 2:
+        // - Upon initialization calendar fields set, millis = 0
+        // - Addition uses millis which are still 1970, 0, 1
+        //
+
+        {
+            Calendar calendar = new GregorianCalendar( 1993, Calendar.JANUARY, 4 ) ;
+            logln( "2A) " + value( calendar ) ) ;
+            calendar.add( Calendar.DATE, 1 ) ;
+            String v = value(calendar);
+            logln( "2B) " + v );
+            logln( "--) 1993/0/5" ) ;
+            if (!v.equals(EXPECTED_0610)) errln("Expected " + EXPECTED_0610 +
+                                                "; saw " + v);
+        }
+
+        //
+        // Error case 3:
+        // - Upon initialization calendar fields, millis = 0
+        // - getTime( ) is called which forces the millis to be set
+        // - Addition uses millis which are correct
+        //
+
+        {
+            Calendar calendar = new GregorianCalendar( 1993, Calendar.JANUARY, 4 ) ;
+            logln( "3A) " + value( calendar ) ) ;
+            calendar.getTime( ) ;
+            calendar.add( Calendar.DATE, 1 ) ;
+            String v = value(calendar);
+            logln( "3B) " + v ) ;
+            logln( "--) 1993/0/5" ) ;
+            if (!v.equals(EXPECTED_0610)) errln("Expected " + EXPECTED_0610 +
+                                                "; saw " + v);
+        }
+    }
+    static String value( Calendar calendar ) {
+        return( calendar.get( Calendar.YEAR )  + "/" +
+                calendar.get( Calendar.MONTH ) + "/" +
+                calendar.get( Calendar.DATE ) ) ;
+    }
+    static String EXPECTED_0610 = "1993/0/5";
+
+    // Test that certain fields on a certain date are as expected.
+    public void TestFields060() {
+        int year = 1997;
+        int month = java.util.Calendar.OCTOBER;  //october
+        int dDate = 22;   //DAYOFWEEK should return 3 for Wednesday
+        GregorianCalendar calendar = null;
+
+        calendar = new GregorianCalendar( year, month, dDate);
+        for (int i=0; i<EXPECTED_FIELDS.length; ) {
+            int field = EXPECTED_FIELDS[i++];
+            int expected = EXPECTED_FIELDS[i++];
+            if (calendar.get(field) != expected) {
+                errln("Expected field " + field + " to have value " + expected +
+                      "; received " + calendar.get(field) + " instead");
+            }
+        }
+    }
+    static int EXPECTED_FIELDS[] = {
+        Calendar.YEAR, 1997,
+        Calendar.MONTH, Calendar.OCTOBER,
+        Calendar.DAY_OF_MONTH, 22,
+        Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY,
+        Calendar.DAY_OF_WEEK_IN_MONTH, 4,
+        Calendar.DAY_OF_YEAR, 295
+    };
+
+    // Verify that the fields are as expected (mostly zero) at the epoch start.
+    // Note that we adjust for the default timezone to get most things to zero.
+    public void TestEpochStartFields() {
+        TimeZone z = TimeZone.getDefault();
+        Calendar c = Calendar.getInstance();
+        Date d = new Date(-z.getRawOffset());
+        if (z.inDaylightTime(d)) {
+            logln("Warning: Skipping test because " + d +
+                  " is in DST.");
+        }
+        else {
+            c.setTime(d);
+            for (int i=0; i<Calendar.ZONE_OFFSET; ++i) {
+                if (c.get(i) != EPOCH_FIELDS[i])
+                    errln("Expected field " + i + " to have value " + EPOCH_FIELDS[i] +
+                          "; saw " + c.get(i) + " instead");
+            }
+            if (c.get(Calendar.ZONE_OFFSET) != z.getRawOffset())
+                errln("Expected field ZONE_OFFSET to have value " + z.getRawOffset() +
+                      "; saw " + c.get(Calendar.ZONE_OFFSET) + " instead");
+            if (c.get(Calendar.DST_OFFSET) != 0)
+                errln("Expected field DST_OFFSET to have value 0" +
+                      "; saw " + c.get(Calendar.DST_OFFSET) + " instead");
+        }
+    }
+    // These are the fields at the epoch start
+    static int EPOCH_FIELDS[] = { 1, 1970, 0, 1, 1, 1, 1, 5, 1, 0, 0, 0, 0, 0, 0, -28800000, 0 };
+
+    // Verify that as you add days to the calendar (e.g., 24 day periods),
+    // the day of the week shifts in the expected pattern.
+    public void TestDOWProgression() {
+        Calendar cal =
+            new GregorianCalendar(1972, Calendar.OCTOBER, 26);
+        marchByDelta(cal, 24); // Last parameter must be != 0 modulo 7
+    }
+
+    // Supply a delta which is not a multiple of 7.
+    void marchByDelta(Calendar cal, int delta) {
+        Calendar cur = (Calendar)cal.clone();
+        int initialDOW = cur.get(Calendar.DAY_OF_WEEK);
+        int DOW, newDOW = initialDOW;
+        do {
+            DOW = newDOW;
+            logln("DOW = " + DOW + "  " + cur.getTime());
+
+            cur.add(Calendar.DAY_OF_WEEK, delta);
+            newDOW = cur.get(Calendar.DAY_OF_WEEK);
+            int expectedDOW = 1 + (DOW + delta - 1) % 7;
+            if (newDOW != expectedDOW) {
+                errln("Day of week should be " + expectedDOW +
+                      " instead of " + newDOW + " on " + cur.getTime());
+                return;
+            }
+        }
+        while (newDOW != initialDOW);
+    }
+
+    public void TestActualMinMax() {
+        Calendar cal = new GregorianCalendar(1967, Calendar.MARCH, 10);
+        cal.setFirstDayOfWeek(Calendar.SUNDAY);
+        cal.setMinimalDaysInFirstWeek(3);
+
+        if (cal.getActualMinimum(Calendar.DAY_OF_MONTH) != 1)
+            errln("Actual minimum date for 3/10/1967 should have been 1; got " +
+                  cal.getActualMinimum(Calendar.DAY_OF_MONTH));
+        if (cal.getActualMaximum(Calendar.DAY_OF_MONTH) != 31)
+            errln("Actual maximum date for 3/10/1967 should have been 31; got " +
+                  cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+
+        cal.set(Calendar.MONTH, Calendar.FEBRUARY);
+        if (cal.getActualMaximum(Calendar.DAY_OF_MONTH) != 28)
+            errln("Actual maximum date for 2/10/1967 should have been 28; got " +
+                  cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        if (cal.getActualMaximum(Calendar.DAY_OF_YEAR) != 365)
+            errln("Number of days in 1967 should have been 365; got " +
+                  cal.getActualMaximum(Calendar.DAY_OF_YEAR));
+
+        cal.set(Calendar.YEAR, 1968);
+        if (cal.getActualMaximum(Calendar.DAY_OF_MONTH) != 29)
+            errln("Actual maximum date for 2/10/1968 should have been 29; got " +
+                  cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        if (cal.getActualMaximum(Calendar.DAY_OF_YEAR) != 366)
+            errln("Number of days in 1968 should have been 366; got " +
+                  cal.getActualMaximum(Calendar.DAY_OF_YEAR));
+        // Using week settings of SUNDAY/3 (see above)
+        if (cal.getActualMaximum(Calendar.WEEK_OF_YEAR) != 52)
+            errln("Number of weeks in 1968 should have been 52; got " +
+                  cal.getActualMaximum(Calendar.WEEK_OF_YEAR));
+
+        cal.set(Calendar.YEAR, 1976);
+        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek()); // Added - Liu 11/6/00
+        // Using week settings of SUNDAY/3 (see above)
+        if (cal.getActualMaximum(Calendar.WEEK_OF_YEAR) != 53)
+            errln("Number of weeks in 1976 should have been 53; got " +
+                  cal.getActualMaximum(Calendar.WEEK_OF_YEAR));
+    }
+
+    public void TestRoll() {
+        Calendar cal = new GregorianCalendar(1997, Calendar.JANUARY, 31);
+
+        int[] dayValues = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 };
+        for (int i = 0; i < dayValues.length; i++) {
+            Calendar cal2 = (Calendar)cal.clone();
+            cal2.roll(Calendar.MONTH, i);
+            if (cal2.get(Calendar.DAY_OF_MONTH) != dayValues[i])
+                errln("Rolling the month in 1/31/1997 up by " + i + " should have yielded "
+                      + ((i + 1) % 12) + "/" + dayValues[i] + "/1997, but actually yielded "
+                      + ((i + 1) % 12) + "/" + cal2.get(Calendar.DAY_OF_MONTH) + "/1997.");
+        }
+
+        cal.set(1996, Calendar.FEBRUARY, 29);
+
+        //int[] monthValues = { 1, 2, 2, 2, 1, 2, 2, 2, 1, 2 };
+        //int[] dayValues2 = { 29, 1, 1, 1, 29, 1, 1, 1, 29, 1 };
+
+        // I've revised the expected values to make more sense -- rolling
+        // the year should pin the DAY_OF_MONTH. - Liu 11/6/00
+        int[] monthValues = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
+        int[] dayValues2 = { 29, 28, 28, 28, 29, 28, 28, 28, 29, 28 };
+
+        for (int i = 0; i < dayValues2.length; i++) {
+            Calendar cal2 = (Calendar)cal.clone();
+            cal2.roll(Calendar.YEAR, i);
+            if (cal2.get(Calendar.DAY_OF_MONTH) != dayValues2[i] || cal2.get(Calendar.MONTH)
+                != monthValues[i])
+                errln("Roll 2/29/1996 by " + i + " year: expected "
+                      + (monthValues[i] + 1) + "/" + dayValues2[i] + "/"
+                      + (1996 + i) + ", got "
+                      + (cal2.get(Calendar.MONTH) + 1) + "/" +
+                      cal2.get(Calendar.DAY_OF_MONTH) + "/" + cal2.get(Calendar.YEAR));
+        }
+
+        // Test rolling hour of day
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.roll(Calendar.HOUR_OF_DAY, -2);
+        int f = cal.get(Calendar.HOUR_OF_DAY);
+        if (f != 22) errln("Rolling HOUR_OF_DAY=0 delta=-2 gave " + f + " Wanted 22");
+        cal.roll(Calendar.HOUR_OF_DAY, 5);
+        f = cal.get(Calendar.HOUR_OF_DAY);
+        if (f != 3) errln("Rolling HOUR_OF_DAY=22 delta=5 gave " + f + " Wanted 3");
+        cal.roll(Calendar.HOUR_OF_DAY, 21);
+        f = cal.get(Calendar.HOUR_OF_DAY);
+        if (f != 0) errln("Rolling HOUR_OF_DAY=3 delta=21 gave " + f + " Wanted 0");
+
+        // Test rolling hour
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.roll(Calendar.HOUR, -2);
+        f = cal.get(Calendar.HOUR);
+        if (f != 10) errln("Rolling HOUR=0 delta=-2 gave " + f + " Wanted 10");
+        cal.roll(Calendar.HOUR, 5);
+        f = cal.get(Calendar.HOUR);
+        if (f != 3) errln("Rolling HOUR=10 delta=5 gave " + f + " Wanted 3");
+        cal.roll(Calendar.HOUR, 9);
+        f = cal.get(Calendar.HOUR);
+        if (f != 0) errln("Rolling HOUR=3 delta=9 gave " + f + " Wanted 0");
+    }
+}
+
+//eof
diff --git a/src/com/ibm/icu/dev/test/calendar/HebrewTest.java b/src/com/ibm/icu/dev/test/calendar/HebrewTest.java
new file mode 100755
index 0000000..ca2b444
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/HebrewTest.java
@@ -0,0 +1,421 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/HebrewTest.java,v $ 
+ * $Date: 2003/09/04 00:57:13 $ 
+ * $Revision: 1.10 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.dev.test.calendar;
+
+//import com.ibm.icu.dev.test.*;
+//import com.ibm.icu.util.*;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import com.ibm.icu.impl.LocaleUtility;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.HebrewCalendar;
+
+/**
+ * Tests for the <code>HebrewCalendar</code> class.
+ */
+public class HebrewTest extends CalendarTest {
+    public static void main(String args[]) throws Exception {
+        new HebrewTest().run(args);
+    }
+
+    // Constants to save typing.
+    public static final int TISHRI  = HebrewCalendar.TISHRI;
+    public static final int HESHVAN = HebrewCalendar.HESHVAN;
+    public static final int KISLEV  = HebrewCalendar.KISLEV;
+    public static final int TEVET   = HebrewCalendar.TEVET;
+    public static final int SHEVAT  = HebrewCalendar.SHEVAT;
+    public static final int ADAR_1  = HebrewCalendar.ADAR_1;
+    public static final int ADAR    = HebrewCalendar.ADAR;
+    public static final int NISAN   = HebrewCalendar.NISAN;
+    public static final int IYAR    = HebrewCalendar.IYAR;
+    public static final int SIVAN   = HebrewCalendar.SIVAN;
+    public static final int TAMUZ   = HebrewCalendar.TAMUZ;
+    public static final int AV      = HebrewCalendar.AV;
+    public static final int ELUL    = HebrewCalendar.ELUL;
+
+    /**
+     * Test the behavior of HebrewCalendar.roll
+     * The only real nastiness with roll is the MONTH field, since a year can
+     * have a variable number of months.
+     */
+    public void TestRoll() {
+        int[][] tests = new int[][] {
+            //       input                roll by          output
+            //  year  month     day     field amount    year  month     day
+    
+            {   5759, HESHVAN,   2,     MONTH,   1,     5759, KISLEV,    2 },   // non-leap years
+            {   5759, SHEVAT,    2,     MONTH,   1,     5759, ADAR,      2 },
+            {   5759, SHEVAT,    2,     MONTH,   2,     5759, NISAN,     2 },
+            {   5759, SHEVAT,    2,     MONTH,  12,     5759, SHEVAT,    2 },
+            {   5759, AV,        1,     MONTH,  12,     5759, AV,        1 }, // Alan
+
+            {   5757, HESHVAN,   2,     MONTH,   1,     5757, KISLEV,    2 },   // leap years
+            {   5757, SHEVAT,    2,     MONTH,   1,     5757, ADAR_1,    2 },
+            {   5757, SHEVAT,    2,     MONTH,   2,     5757, ADAR,      2 },
+            {   5757, SHEVAT,    2,     MONTH,   3,     5757, NISAN,     2 },
+            {   5757, SHEVAT,    2,     MONTH,  12,     5757, TEVET,     2 },
+            {   5757, SHEVAT,    2,     MONTH,  13,     5757, SHEVAT,    2 },
+            {   5757, AV,        1,     MONTH,  12,     5757, TAMUZ,     1 }, // Alan
+            
+            {   5757, KISLEV,    1,     DATE,   30,     5757, KISLEV,    2 },   // 29-day month
+            {   5758, KISLEV,    1,     DATE,   31,     5758, KISLEV,    2 },   // 30-day month
+            
+            // Try some other fields too
+            {   5757, TISHRI,    1,     YEAR,    1,     5758, TISHRI,    1 },
+   
+
+            // Try some rolls that require other fields to be adjusted
+            {   5757, TISHRI,   30,     MONTH,   1,     5757, HESHVAN,  29 },
+            {   5758, KISLEV,   30,     YEAR,   -1,     5757, KISLEV,   29 },
+        };
+       
+        HebrewCalendar cal = new HebrewCalendar(UTC, Locale.getDefault());
+
+        doRollAdd(ROLL, cal, tests);
+    }
+    
+    /**
+     * Test the behavior of HebrewCalendar.roll
+     * The only real nastiness with roll is the MONTH field, since a year can
+     * have a variable number of months.
+     */
+    public void TestAdd() {
+        int[][] tests = new int[][] {
+            //       input                add by          output
+            //  year  month     day     field amount    year  month     day
+            {   5759, HESHVAN,   2,     MONTH,   1,     5759, KISLEV,    2 },   // non-leap years
+            {   5759, SHEVAT,    2,     MONTH,   1,     5759, ADAR,      2 },
+            {   5759, SHEVAT,    2,     MONTH,   2,     5759, NISAN,     2 },
+            {   5759, SHEVAT,    2,     MONTH,  12,     5760, SHEVAT,    2 },
+
+            {   5757, HESHVAN,   2,     MONTH,   1,     5757, KISLEV,    2 },   // leap years
+            {   5757, SHEVAT,    2,     MONTH,   1,     5757, ADAR_1,    2 },
+            {   5757, SHEVAT,    2,     MONTH,   2,     5757, ADAR,      2 },
+            {   5757, SHEVAT,    2,     MONTH,   3,     5757, NISAN,     2 },
+            {   5757, SHEVAT,    2,     MONTH,  12,     5758, TEVET,     2 },
+            {   5757, SHEVAT,    2,     MONTH,  13,     5758, SHEVAT,    2 },
+            
+            {   5762, AV,        1,     MONTH,   1,     5762, ELUL,      1 },   // JB#2327
+            {   5762, AV,       30,     DATE,    1,     5762, ELUL,      1 },   // JB#2327
+            {   5762, ELUL,      1,     DATE,   -1,     5762, AV,       30 },   // JB#2327
+            {   5762, ELUL,      1,     MONTH,  -1,     5762, AV,        1 },   // JB#2327
+            
+            {   5757, KISLEV,    1,     DATE,   30,     5757, TEVET,     2 },   // 29-day month
+            {   5758, KISLEV,    1,     DATE,   31,     5758, TEVET,     2 },   // 30-day month
+        };
+       
+        HebrewCalendar cal = new HebrewCalendar(UTC, Locale.getDefault());
+
+        doRollAdd(ADD, cal, tests);
+    }
+
+    /**
+     * A huge list of test cases to make sure that computeTime and computeFields
+     * work properly for a wide range of data.
+     */
+    public void TestCases() {
+        doTestCases(testCases, new HebrewCalendar());
+    }
+
+    static final TestCase[] testCases = {
+        //
+        // Most of these test cases were taken from the back of
+        // "Calendrical Calculations", with some extras added to help
+        // debug a few of the problems that cropped up in development.
+        //
+        // The months in this table are 1-based rather than 0-based,
+        // because it's easier to edit that way.
+        //
+        //         Julian Day  Era  Year  Month Day  WkDay Hour Min Sec
+        new TestCase(1507231.5,  0,  3174,   12,  10,  SUN,   0,  0,  0),
+        new TestCase(1660037.5,  0,  3593,    3,  25,  WED,   0,  0,  0),
+        new TestCase(1746893.5,  0,  3831,    1,   3,  WED,   0,  0,  0),
+        new TestCase(1770641.5,  0,  3896,    1,   9,  SUN,   0,  0,  0),
+        new TestCase(1892731.5,  0,  4230,    4,  18,  WED,   0,  0,  0),
+        new TestCase(1931579.5,  0,  4336,   10,   4,  MON,   0,  0,  0),
+        new TestCase(1974851.5,  0,  4455,    2,  13,  SAT,   0,  0,  0),
+        new TestCase(2091164.5,  0,  4773,    9,   6,  SUN,   0,  0,  0),
+        new TestCase(2121509.5,  0,  4856,    9,  23,  SUN,   0,  0,  0),
+        new TestCase(2155779.5,  0,  4950,    8,   7,  FRI,   0,  0,  0),
+        new TestCase(2174029.5,  0,  5000,    7,   8,  SAT,   0,  0,  0),
+        new TestCase(2191584.5,  0,  5048,    8,  21,  FRI,   0,  0,  0),
+        new TestCase(2195261.5,  0,  5058,    9,   7,  SUN,   0,  0,  0),
+        new TestCase(2229274.5,  0,  5151,   11,   1,  SUN,   0,  0,  0),
+        new TestCase(2245580.5,  0,  5196,    5,   7,  WED,   0,  0,  0),
+        new TestCase(2266100.5,  0,  5252,    8,   3,  SAT,   0,  0,  0),
+        new TestCase(2288542.5,  0,  5314,    1,   1,  SAT,   0,  0,  0),
+        new TestCase(2290901.5,  0,  5320,    6,  27,  SAT,   0,  0,  0),
+        new TestCase(2323140.5,  0,  5408,   10,  20,  WED,   0,  0,  0),
+        new TestCase(2334551.5,  0,  5440,    1,   1,  THU,   0,  0,  0),
+        new TestCase(2334581.5,  0,  5440,    2,   1,  SAT,   0,  0,  0),
+        new TestCase(2334610.5,  0,  5440,    3,   1,  SUN,   0,  0,  0),
+        new TestCase(2334639.5,  0,  5440,    4,   1,  MON,   0,  0,  0),
+        new TestCase(2334668.5,  0,  5440,    5,   1,  TUE,   0,  0,  0),
+        new TestCase(2334698.5,  0,  5440,    6,   1,  THU,   0,  0,  0),
+        new TestCase(2334728.5,  0,  5440,    7,   1,  SAT,   0,  0,  0),
+        new TestCase(2334757.5,  0,  5440,    8,   1,  SUN,   0,  0,  0),
+        new TestCase(2334787.5,  0,  5440,    9,   1,  TUE,   0,  0,  0),
+        new TestCase(2334816.5,  0,  5440,   10,   1,  WED,   0,  0,  0),
+        new TestCase(2334846.5,  0,  5440,   11,   1,  FRI,   0,  0,  0),
+        new TestCase(2334848.5,  0,  5440,   11,   3,  SUN,   0,  0,  0),
+        new TestCase(2334934.5,  0,  5441,    1,   1,  TUE,   0,  0,  0),
+        new TestCase(2348020.5,  0,  5476,   12,   5,  FRI,   0,  0,  0),
+        new TestCase(2366978.5,  0,  5528,   11,   4,  SUN,   0,  0,  0),
+        new TestCase(2385648.5,  0,  5579,   12,  11,  MON,   0,  0,  0),
+        new TestCase(2392825.5,  0,  5599,    8,  12,  WED,   0,  0,  0),
+        new TestCase(2416223.5,  0,  5663,    8,  22,  SUN,   0,  0,  0),
+        new TestCase(2425848.5,  0,  5689,   12,  19,  SUN,   0,  0,  0),
+        new TestCase(2430266.5,  0,  5702,    1,   8,  MON,   0,  0,  0),
+        new TestCase(2430833.5,  0,  5703,    8,  14,  MON,   0,  0,  0),
+        new TestCase(2431004.5,  0,  5704,    1,   8,  THU,   0,  0,  0),
+        new TestCase(2448698.5,  0,  5752,    7,  12,  TUE,   0,  0,  0),
+        new TestCase(2450138.5,  0,  5756,    7,   5,  SUN,   0,  0,  0),
+        new TestCase(2465737.5,  0,  5799,    2,  12,  WED,   0,  0,  0),
+        new TestCase(2486076.5,  0,  5854,   12,   5,  SUN,   0,  0,  0),
+
+        // Additional test cases for bugs found during development
+        //           G.YY/MM/DD  Era  Year  Month Day  WkDay Hour Min Sec
+        new TestCase(1013, 9, 8, 0,  4774,    1,   1,  TUE,   0,  0,  0),
+        new TestCase(1239, 9, 1, 0,  5000,    1,   1,  THU,   0,  0,  0),
+        new TestCase(1240, 9,18, 0,  5001,    1,   1,  TUE,   0,  0,  0),
+
+        // Test cases taken from a table of 14 "year types" in the Help file
+        // of the application "Hebrew Calendar"
+        new TestCase(2456187.5,  0,  5773,    1,   1,  MON,   0,  0,  0),
+        new TestCase(2459111.5,  0,  5781,    1,   1,  SAT,   0,  0,  0),
+        new TestCase(2453647.5,  0,  5766,    1,   1,  TUE,   0,  0,  0),
+        new TestCase(2462035.5,  0,  5789,    1,   1,  THU,   0,  0,  0),
+        new TestCase(2458756.5,  0,  5780,    1,   1,  MON,   0,  0,  0),
+        new TestCase(2460586.5,  0,  5785,    1,   1,  THU,   0,  0,  0),
+        new TestCase(2463864.5,  0,  5794,    1,   1,  SAT,   0,  0,  0),
+        new TestCase(2463481.5,  0,  5793,    1,   1,  MON,   0,  0,  0),
+        new TestCase(2470421.5,  0,  5812,    1,   1,  THU,   0,  0,  0),
+        new TestCase(2460203.5,  0,  5784,    1,   1,  SAT,   0,  0,  0),
+        new TestCase(2459464.5,  0,  5782,    1,   1,  TUE,   0,  0,  0),
+        new TestCase(2467142.5,  0,  5803,    1,   1,  MON,   0,  0,  0),
+        new TestCase(2455448.5,  0,  5771,    1,   1,  THU,   0,  0,  0),
+    
+        // Test cases for JB#2327        
+        // http://www.fourmilab.com/documents/calendar/
+        // http://www.calendarhome.com/converter/
+//      2452465.5, 2002, JULY, 10, 5762, AV, 1,
+//      2452494.5, 2002, AUGUST, 8, 5762, AV, 30,
+//      2452495.5, 2002, AUGUST, 9, 5762, ELUL, 1,
+//      2452523.5, 2002, SEPTEMBER, 6, 5762, ELUL, 29,
+//      2452524.5, 2002, SEPTEMBER, 7, 5763, TISHRI, 1,
+        //         Julian Day  Era  Year  Month Day  WkDay Hour Min Sec
+        new TestCase(2452465.5,  0,  5762,    AV+1,  1,  WED,   0,  0,  0),
+        new TestCase(2452494.5,  0,  5762,    AV+1, 30,  THU,   0,  0,  0),
+        new TestCase(2452495.5,  0,  5762,  ELUL+1,  1,  FRI,   0,  0,  0),
+        new TestCase(2452523.5,  0,  5762,  ELUL+1, 29,  FRI,   0,  0,  0),
+        new TestCase(2452524.5,  0,  5763,TISHRI+1,  1,  SAT,   0,  0,  0),
+    };
+    
+    /**
+     * Problem reported by Armand Bendanan in which setting of the MONTH
+     * field in a Hebrew calendar causes the time fields to go negative.
+     */
+    public void TestTimeFields() {
+        HebrewCalendar calendar = new HebrewCalendar(5761, 0, 11, 12, 28, 15);
+		calendar.set(Calendar.YEAR, 5717);
+		calendar.set(Calendar.MONTH, 2);
+		calendar.set(Calendar.DAY_OF_MONTH, 23);
+        if (calendar.get(Calendar.HOUR_OF_DAY) != 12) {
+            errln("Fail: HebrewCalendar HOUR_OF_DAY = " + calendar.get(Calendar.HOUR_OF_DAY));
+        }
+    }
+
+    /**
+     * Problem reported by Armand Bendanan (armand.bendanan@free.fr)
+     * in which setting of the MONTH field in a Hebrew calendar to
+     * ELUL on non leap years causes the date to be set on TISHRI next year.
+     */
+    public void TestElulMonth() {
+        HebrewCalendar cal = new HebrewCalendar();
+        // Leap years are:
+        // 3 6 8 11 14 17 19 (and so on - 19-year cycle)
+        for (int year=1; year<50; year++) {
+            // I hope that year = 0 does not exists
+            // because the test fails for it !
+            cal.clear();
+            
+            cal.set(Calendar.YEAR, year);
+            cal.set(Calendar.MONTH, ELUL);
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+            
+            int yact = cal.get(Calendar.YEAR);
+            int mact = cal.get(Calendar.MONTH);
+            
+            if (year != yact || ELUL != mact) {
+                errln("Fail: " + ELUL + "/" + year +
+                      " -> " +
+                      mact + "/" + yact);
+            }
+        }
+    }
+    
+    /**
+     * Test of the behavior of the month field.  This requires special
+     * handling in the Hebrew calendar because of the pattern of leap
+     * years.
+     */
+    public void TestMonthMovement() {
+        HebrewCalendar cal = new HebrewCalendar();
+        // Leap years are:
+        // 3 6 8 11 14 17 19 (and so on - 19-year cycle)
+        // We can't test complete() on some lines below because of ADAR_1 -- if
+        // the calendar is set to ADAR_1 on a non-leap year, the result is undefined.
+        int[] DATA = {
+            // m1/y1 - month/year before (month is 1-based) 
+            // delta - amount to add to month field
+            // m2/y2 - month/year after add(MONTH, delta)
+            // m3/y3 - month/year after set(MONTH, m1+delta)
+          //m1  y1 delta  m2  y2  m3  y3
+            10,  2,  +24,  9,  4,  9,  4,
+            10,  2,  +60,  8,  7,  8,  7,
+            1 ,  2,  +12,  1,  3, 13,  2, //*set != add; also see '*' below
+            3 , 18,  -24,  4, 16,  4, 16,
+            1 ,  6,  -24,  1,  4,  1,  4,
+            4 ,  3,   +2,  6,  3,  6,  3, // Leap year - no skip 4,5,6,7,8
+            8 ,  3,   -2,  6,  3,  6,  3, // Leap year - no skip
+            4 ,  2,   +2,  7,  2,  7,  2, // Skip leap month 4,5,(6),7,8
+            8 ,  2,   -2,  5,  2,  7,  2, //*Skip leap month going backward
+        };
+        for (int i=0; i<DATA.length; ) {
+            int m = DATA[i++], y = DATA[i++];
+            int monthDelta = DATA[i++];
+            int m2 = DATA[i++], y2 = DATA[i++];
+            int m3 = DATA[i++], y3 = DATA[i++];
+            int mact, yact;
+
+            cal.clear();
+            cal.set(Calendar.YEAR, y);
+            cal.set(Calendar.MONTH, m-1);
+            cal.add(Calendar.MONTH, monthDelta);
+            yact = cal.get(Calendar.YEAR); mact = cal.get(Calendar.MONTH) + 1;
+            if (y2 != yact || m2 != mact) {
+                errln("Fail: " + m + "/" + y +
+                      " -> add(MONTH, " + monthDelta + ") -> " +
+                      mact + "/" + yact + ", expected " +
+                      m2 + "/" + y2);
+                cal.clear();
+                cal.set(Calendar.YEAR, y);
+                cal.set(Calendar.MONTH, m-1);
+                logln("Start: " + m + "/" + y);
+                int delta = monthDelta > 0 ? 1 : -1;
+                for (int c=0; c!=monthDelta; c+=delta) {
+                    cal.add(Calendar.MONTH, delta);
+                    logln("+ " + delta + " MONTH -> " +
+                          (cal.get(Calendar.MONTH) + 1) + "/" + cal.get(Calendar.YEAR));
+                }
+            }
+            
+            cal.clear();
+            cal.set(Calendar.YEAR, y);
+            cal.set(Calendar.MONTH, m + monthDelta - 1);
+            yact = cal.get(Calendar.YEAR); mact = cal.get(Calendar.MONTH) + 1;
+            if (y3 != yact || m3 != mact) {
+                errln("Fail: " + (m+monthDelta) + "/" + y +
+                      " -> complete() -> " +
+                      mact + "/" + yact + ", expected " +
+                      m3 + "/" + y3);
+            }
+        }
+    }
+
+    /**
+     * Test handling of ADAR_1.
+     */
+    /*
+    public void TestAdar1() {
+        HebrewCalendar cal = new HebrewCalendar();
+        cal.clear();
+        cal.set(Calendar.YEAR, 1903); // leap
+        cal.set(Calendar.MONTH, HebrewCalendar.ADAR_1);
+        logln("1903(leap)/ADAR_1 => " +
+              cal.get(Calendar.YEAR) + "/" + (cal.get(Calendar.MONTH)+1));
+
+        cal.clear();
+        cal.set(Calendar.YEAR, 1904); // non-leap
+        cal.set(Calendar.MONTH, HebrewCalendar.ADAR_1);
+        logln("1904(non-leap)/ADAR_1 => " +
+              cal.get(Calendar.YEAR) + "/" + (cal.get(Calendar.MONTH)+1));
+    }
+    */
+
+    /**
+     * With no fields set, the calendar should use default values.
+     */
+    public void TestDefaultFieldValues() {
+        HebrewCalendar cal = new HebrewCalendar();
+        cal.clear();
+        logln("cal.clear() -> " + cal.getTime());
+    }
+    
+    public void TestCoverage() {
+    	{
+    	    // new HebrewCalendar(TimeZone)
+    	    HebrewCalendar cal = new HebrewCalendar(TimeZone.getDefault());
+    	    if(cal == null){
+                errln("could not create HebrewCalendar with TimeZone");
+    	    }
+        }
+    
+    	{
+    	    // new HebrewCalendar(Locale)
+    	    HebrewCalendar cal = new HebrewCalendar(Locale.getDefault());
+            if(cal == null){
+                errln("could not create HebrewCalendar with locale");
+            }
+        }
+    
+    	{
+    	    // new HebrewCalendar(Date)
+    	    HebrewCalendar cal = new HebrewCalendar(new Date());
+            if(cal == null){
+                errln("could not create HebrewCalendar with date");
+            }
+    	}
+    
+    	{
+    	    // data
+    	    HebrewCalendar cal = new HebrewCalendar(2800, HebrewCalendar.SHEVAT, 1);
+    	    Date time = cal.getTime();
+    
+    	    String[] calendarLocales = {
+    		"iw_IL"
+    	    };
+    
+    	    String[] formatLocales = {
+    		"en", "fi", "fr", "hu", "iw", "nl"
+    	    };
+    	    for (int i = 0; i < calendarLocales.length; ++i) {
+        		String calLocName = calendarLocales[i];
+        		Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
+        		cal = new HebrewCalendar(calLocale);
+        
+        		for (int j = 0; j < formatLocales.length; ++j) {
+        		    String locName = formatLocales[j];
+        		    Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
+        		    DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
+        		    logln(calLocName + "/" + locName + " --> " + format.format(time));
+        		}
+    	    }
+    	}
+    }
+};
diff --git a/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java b/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java
new file mode 100755
index 0000000..4d13e2e
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java
@@ -0,0 +1,720 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2000-2003, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java,v $ 
+ * $Date: 2003/09/04 23:07:33 $ 
+ * $Revision: 1.23 $
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.text.ParseException;
+
+import com.ibm.icu.impl.LocaleUtility;
+import com.ibm.icu.impl.ZoneMeta;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.SimpleDateFormat;
+import com.ibm.icu.util.*;
+
+/**
+ * @test
+ * @summary Tests of new functionality in IBMCalendar
+ */
+public class IBMCalendarTest extends CalendarTest {
+
+    public static void main(String[] args) throws Exception {
+        new IBMCalendarTest().run(args);
+    }
+
+    /**
+     * Test weekend support in IBMCalendar.
+     *
+     * NOTE: This test will have to be updated when the isWeekend() etc.
+     *       API is finalized later.
+     *
+     *       In particular, the test will have to be rewritten to instantiate
+     *       a Calendar in the given locale (using getInstance()) and call
+     *       that Calendar's isWeekend() etc. methods.
+     */
+    public void TestWeekend() {
+        SimpleDateFormat fmt = new SimpleDateFormat("EEE MMM dd yyyy G HH:mm:ss.SSS");
+        
+        // NOTE
+        // This test tests for specific locale data.  This is probably okay
+        // as far as US data is concerned, but if the Arabic/Bahrain data
+        // changes, this test will have to be updated.
+
+        // Test specific days
+        Object[] DATA1 = {
+            Locale.US, new int[] { // Saturday:Sunday
+                2000, Calendar.MARCH, 17, 23,  0, 0, // Fri 23:00
+                2000, Calendar.MARCH, 18,  0, -1, 0, // Fri 23:59:59.999
+                2000, Calendar.MARCH, 18,  0,  0, 1, // Sat 00:00
+                2000, Calendar.MARCH, 18, 15,  0, 1, // Sat 15:00
+                2000, Calendar.MARCH, 19, 23,  0, 1, // Sun 23:00
+                2000, Calendar.MARCH, 20,  0, -1, 1, // Sun 23:59:59.999
+                2000, Calendar.MARCH, 20,  0,  0, 0, // Mon 00:00
+                2000, Calendar.MARCH, 20,  8,  0, 0, // Mon 08:00
+            },
+            new Locale("ar", "BH"), new int[] { // Thursday:Friday
+                2000, Calendar.MARCH, 15, 23,  0, 0, // Wed 23:00
+                2000, Calendar.MARCH, 16,  0, -1, 0, // Wed 23:59:59.999
+                2000, Calendar.MARCH, 16,  0,  0, 1, // Thu 00:00
+                2000, Calendar.MARCH, 16, 15,  0, 1, // Thu 15:00
+                2000, Calendar.MARCH, 17, 23,  0, 1, // Fri 23:00
+                2000, Calendar.MARCH, 18,  0, -1, 1, // Fri 23:59:59.999
+                2000, Calendar.MARCH, 18,  0,  0, 0, // Sat 00:00
+                2000, Calendar.MARCH, 18,  8,  0, 0, // Sat 08:00
+            },
+        };
+
+        // Test days of the week
+        Object[] DATA2 = {
+            Locale.US, new int[] {
+                Calendar.MONDAY,   Calendar.WEEKDAY,
+                Calendar.FRIDAY,   Calendar.WEEKDAY,
+                Calendar.SATURDAY, Calendar.WEEKEND,
+                Calendar.SUNDAY,   Calendar.WEEKEND,
+            },
+            new Locale("ar", "BH"), new int[] { // Thursday:Friday
+                Calendar.WEDNESDAY,Calendar.WEEKDAY,
+                Calendar.SATURDAY, Calendar.WEEKDAY,
+                Calendar.THURSDAY, Calendar.WEEKEND,
+                Calendar.FRIDAY,   Calendar.WEEKEND,
+            },
+        };
+
+        // We only test the getDayOfWeekType() and isWeekend() APIs.
+        // The getWeekendTransition() API is tested indirectly via the
+        // isWeekend() API, which calls it.
+
+        for (int i1=0; i1<DATA1.length; i1+=2) {
+            Locale loc = (Locale)DATA1[i1];
+            int[] data = (int[]) DATA1[i1+1];
+            Calendar cal = Calendar.getInstance(loc);
+            logln("Locale: " + loc);
+            for (int i=0; i<data.length; i+=6) {
+                cal.clear();
+                cal.set(data[i], data[i+1], data[i+2], data[i+3], 0, 0);
+                if (data[i+4] != 0) {
+                    cal.setTime(new Date(cal.getTime().getTime() + data[i+4]));
+                }
+                boolean isWeekend = cal.isWeekend();
+                boolean ok = isWeekend == (data[i+5] != 0);
+                if (ok) {
+                    logln("Ok:   " + fmt.format(cal.getTime()) + " isWeekend=" + isWeekend);
+                } else {
+                    errln("FAIL: " + fmt.format(cal.getTime()) + " isWeekend=" + isWeekend +
+                          ", expected=" + (!isWeekend));
+                }
+            }
+        }
+
+        for (int i2=0; i2<DATA2.length; i2+=2) {
+            Locale loc = (Locale)DATA2[i2];
+            int[] data = (int[]) DATA2[i2+1];
+            logln("Locale: " + loc);
+            Calendar cal = Calendar.getInstance(loc);
+            for (int i=0; i<data.length; i+=2) {
+                int type = cal.getDayOfWeekType(data[i]);
+                int exp  = data[i+1];
+                if (type == exp) {
+                    logln("Ok:   DOW " + data[i] + " type=" + type);
+                } else {
+                    errln("FAIL: DOW " + data[i] + " type=" + type +
+                          ", expected=" + exp);
+                }
+            }
+        }
+    }
+
+    /**
+     * Run a test of a quasi-Gregorian calendar.  This is a calendar
+     * that behaves like a Gregorian but has different year/era mappings.
+     * The int[] data array should have the format:
+     * 
+     * { era, year, gregorianYear, month, dayOfMonth, ... }
+     */
+    void quasiGregorianTest(Calendar cal, int[] data) {
+        // As of JDK 1.4.1_01, using the Sun JDK GregorianCalendar as
+        // a reference throws us off by one hour.  This is most likely
+        // due to the JDK 1.4 incorporation of historical time zones.
+        //java.util.Calendar grego = java.util.Calendar.getInstance();
+        Calendar grego = Calendar.getInstance();
+        for (int i=0; i<data.length; ) {
+            int era = data[i++];
+            int year = data[i++];
+            int gregorianYear = data[i++];
+            int month = data[i++];
+            int dayOfMonth = data[i++];
+
+            grego.clear();
+            grego.set(gregorianYear, month, dayOfMonth);
+            Date D = grego.getTime();
+
+            cal.clear();
+            cal.set(Calendar.ERA, era);
+            cal.set(year, month, dayOfMonth);
+            Date d = cal.getTime();
+            if (d.equals(D)) {
+                logln("OK: " + era + ":" + year + "/" + (month+1) + "/" + dayOfMonth +
+                      " => " + d);
+            } else {
+                errln("Fail: " + era + ":" + year + "/" + (month+1) + "/" + dayOfMonth +
+                      " => " + d + ", expected " + D);
+            }
+
+            cal.clear();
+            cal.setTime(D);
+            int e = cal.get(Calendar.ERA);
+            int y = cal.get(Calendar.YEAR);
+            if (y == year && e == era) {
+                logln("OK: " + D + " => " + cal.get(Calendar.ERA) + ":" +
+                      cal.get(Calendar.YEAR) + "/" +
+                      (cal.get(Calendar.MONTH)+1) + "/" + cal.get(Calendar.DATE));
+            } else {
+                logln("Fail: " + D + " => " + cal.get(Calendar.ERA) + ":" +
+                      cal.get(Calendar.YEAR) + "/" +
+                      (cal.get(Calendar.MONTH)+1) + "/" + cal.get(Calendar.DATE) +
+                      ", expected " + era + ":" + year + "/" + (month+1) + "/" +
+                      dayOfMonth);
+            }
+        }
+    }
+
+    /**
+     * Verify that BuddhistCalendar shifts years to Buddhist Era but otherwise
+     * behaves like GregorianCalendar.
+     */
+    public void TestBuddhist() {
+        quasiGregorianTest(new BuddhistCalendar(),
+                           new int[] {
+                               // BE 2542 == 1999 CE
+                               0, 2542, 1999, Calendar.JUNE, 4
+                           });
+    }
+
+    public void TestBuddhistCoverage() {
+	{
+	    // new BuddhistCalendar(TimeZone)
+	    BuddhistCalendar cal = new BuddhistCalendar(TimeZone.getDefault());
+        if(cal == null){
+            errln("could not create BuddhistCalendar with TimeZone");
+        }
+    }
+	
+	{
+	    // new BuddhistCalendar(Locale)
+	    BuddhistCalendar cal = new BuddhistCalendar(Locale.getDefault());
+        if(cal == null){
+            errln("could not create BuddhistCalendar with Locale");
+        }
+    }
+
+	{
+	    // new BuddhistCalendar(TimeZone, Locale)
+	    BuddhistCalendar cal = new BuddhistCalendar(TimeZone.getDefault(), Locale.getDefault());
+        if(cal == null){
+            errln("could not create BuddhistCalendar with TimeZone and Locale");
+        }
+    }
+
+	{
+	    // new BuddhistCalendar(Date)
+	    BuddhistCalendar cal = new BuddhistCalendar(new Date());
+        if(cal == null){
+            errln("could not create BuddhistCalendar with Date");
+        }
+	}
+
+	{
+	    // new BuddhistCalendar(int year, int month, int date)
+	    BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22);
+        if(cal == null){
+            errln("could not create BuddhistCalendar with year,month,data");
+        }
+    }
+
+	{
+	    // new BuddhistCalendar(int year, int month, int date, int hour, int minute, int second)
+	    BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22, 1, 1, 1);
+        if(cal == null){
+            errln("could not create BuddhistCalendar with year,month,date,hour,minute,second");
+        }
+    }
+
+	{
+	    // data
+	    BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22);
+	    Date time = cal.getTime();
+
+	    String[] calendarLocales = {
+		"th_TH"
+	    };
+
+	    String[] formatLocales = {
+		"en", "ar", "hu", "th"
+	    };
+
+	    for (int i = 0; i < calendarLocales.length; ++i) {
+		String calLocName = calendarLocales[i];
+		Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
+		cal = new BuddhistCalendar(calLocale);
+
+		for (int j = 0; j < formatLocales.length; ++j) {
+		    String locName = formatLocales[j];
+		    Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
+		    DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
+		    logln(calLocName + "/" + locName + " --> " + format.format(time));
+		}
+	    }
+	}
+    }
+
+    /**
+     * Verify that JapaneseCalendar shifts years to Buddhist Era but otherwise
+     * behaves like GregorianCalendar.
+     */
+    public void TestJapanese() {
+        // First make sure this test works for GregorianCalendar
+        int[] control = {
+            GregorianCalendar.AD, 1868, 1868, Calendar.SEPTEMBER, 8,
+            GregorianCalendar.AD, 1868, 1868, Calendar.SEPTEMBER, 9,
+            GregorianCalendar.AD, 1869, 1869, Calendar.JUNE, 4,
+            GregorianCalendar.AD, 1912, 1912, Calendar.JULY, 29,
+            GregorianCalendar.AD, 1912, 1912, Calendar.JULY, 30,
+            GregorianCalendar.AD, 1912, 1912, Calendar.AUGUST, 1,
+        };
+        quasiGregorianTest(new GregorianCalendar(), control);
+
+        int[] data = {
+            JapaneseCalendar.MEIJI, 1, 1868, Calendar.SEPTEMBER, 8,
+            JapaneseCalendar.MEIJI, 1, 1868, Calendar.SEPTEMBER, 9,
+            JapaneseCalendar.MEIJI, 2, 1869, Calendar.JUNE, 4,
+            JapaneseCalendar.MEIJI, 45, 1912, Calendar.JULY, 29,
+            JapaneseCalendar.TAISHO, 1, 1912, Calendar.JULY, 30,
+            JapaneseCalendar.TAISHO, 1, 1912, Calendar.AUGUST, 1,
+        };
+        quasiGregorianTest(new JapaneseCalendar(), data);
+    }
+
+    /**
+     * Test limits of the Gregorian calendar.
+     */
+    public void TestGregorianLimits() {
+        // Final parameter is either number of days, if > 0, or test
+        // duration in seconds, if < 0.
+        Calendar cal = Calendar.getInstance();
+        cal.set(2004, Calendar.JANUARY, 1);
+        doLimitsTest(new GregorianCalendar(), null, cal.getTime(), -10);
+    }
+    
+    /**
+     * Test behavior of fieldDifference around leap years.  Also test a large
+     * field difference to check binary search.
+     */
+    public void TestLeapFieldDifference() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(2004, Calendar.FEBRUARY, 29);
+        Date date2004 = cal.getTime();
+        cal.set(2000, Calendar.FEBRUARY, 29);
+        Date date2000 = cal.getTime();
+        int y = cal.fieldDifference(date2004, Calendar.YEAR);
+        int d = cal.fieldDifference(date2004, Calendar.DAY_OF_YEAR);
+        if (d == 0) {
+            logln("Ok: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
+        } else {
+            errln("FAIL: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
+        }
+        cal.setTime(date2004);
+        y = cal.fieldDifference(date2000, Calendar.YEAR);
+        d = cal.fieldDifference(date2000, Calendar.DAY_OF_YEAR);
+        if (d == 0) {
+            logln("Ok: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
+        } else {
+            errln("FAIL: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
+        }
+        // Test large difference
+        cal.set(2001, Calendar.APRIL, 5); // 2452005
+        Date ayl = cal.getTime();
+        cal.set(1964, Calendar.SEPTEMBER, 7); // 2438646
+        Date asl = cal.getTime();
+        d = cal.fieldDifference(ayl, Calendar.DAY_OF_MONTH);
+        cal.setTime(ayl);
+        int d2 = cal.fieldDifference(asl, Calendar.DAY_OF_MONTH);
+        if (d == -d2 && d == 13359) {
+            logln("Ok: large field difference symmetrical " + d);
+        } else {
+            logln("FAIL: large field difference incorrect " + d + ", " + d2 +
+                  ", expect +/- 13359");
+        }
+    }
+
+    /**
+     * Test ms_MY "Malay (Malaysia)" locale.  Bug 1543.
+     */
+    public void TestMalaysianInstance() {
+        Locale loc = new Locale("ms", "MY");  // Malay (Malaysia)
+        Calendar cal = Calendar.getInstance(loc);
+        if(cal == null){
+            errln("could not create Malaysian instance");
+        }
+    }
+
+    /**
+     * setFirstDayOfWeek and setMinimalDaysInFirstWeek may change the
+     * field <=> time mapping, since they affect the interpretation of
+     * the WEEK_OF_MONTH or WEEK_OF_YEAR fields.
+     */
+    public void TestWeekShift() {
+        Calendar cal = new GregorianCalendar(
+                             TimeZone.getTimeZone("America/Los_Angeles"),
+                             new Locale("en", "US"));
+        cal.setTime(new Date(997257600000L)); // Wed Aug 08 01:00:00 PDT 2001
+        // In pass one, change the first day of week so that the weeks
+        // shift in August 2001.  In pass two, change the minimal days
+        // in the first week so that the weeks shift in August 2001.
+        //     August 2001     
+        // Su Mo Tu We Th Fr Sa
+        //           1  2  3  4
+        //  5  6  7  8  9 10 11
+        // 12 13 14 15 16 17 18
+        // 19 20 21 22 23 24 25
+        // 26 27 28 29 30 31   
+        for (int pass=0; pass<2; ++pass) {
+            if (pass==0) {
+                cal.setFirstDayOfWeek(Calendar.WEDNESDAY);
+                cal.setMinimalDaysInFirstWeek(4);
+            } else {
+                cal.setFirstDayOfWeek(Calendar.SUNDAY);
+                cal.setMinimalDaysInFirstWeek(4);
+            }
+            cal.add(Calendar.DATE, 1); // Force recalc
+            cal.add(Calendar.DATE, -1);
+
+            Date time1 = cal.getTime(); // Get time -- should not change
+
+            // Now change a week parameter and then force a recalc.
+            // The bug is that the recalc should not be necessary --
+            // calendar should do so automatically.
+            if (pass==0) {
+                cal.setFirstDayOfWeek(Calendar.THURSDAY);
+            } else {
+                cal.setMinimalDaysInFirstWeek(5);
+            }
+
+            int woy1 = cal.get(Calendar.WEEK_OF_YEAR);
+            int wom1 = cal.get(Calendar.WEEK_OF_MONTH);
+
+            cal.add(Calendar.DATE, 1); // Force recalc
+            cal.add(Calendar.DATE, -1);
+
+            int woy2 = cal.get(Calendar.WEEK_OF_YEAR);
+            int wom2 = cal.get(Calendar.WEEK_OF_MONTH);
+
+            Date time2 = cal.getTime();
+
+            if (!time1.equals(time2)) {
+                errln("FAIL: shifting week should not alter time");
+            } else {
+                logln(time1.toString());
+            }
+            if (woy1 == woy2 && wom1 == wom2) {
+                logln("Ok: WEEK_OF_YEAR: " + woy1 +
+                      ", WEEK_OF_MONTH: " + wom1);
+            } else {
+                errln("FAIL: WEEK_OF_YEAR: " + woy1 + " => " + woy2 +
+                      ", WEEK_OF_MONTH: " + wom1 + " => " + wom2 +
+                      " after week shift");
+            }
+        }
+    }
+
+    /**
+     * Make sure that when adding a day, we actually wind up in a
+     * different day.  The DST adjustments we use to keep the hour
+     * constant across DST changes can backfire and change the day.
+     */
+    public void TestTimeZoneTransitionAdd() {
+        Locale locale = Locale.US; // could also be CHINA
+        SimpleDateFormat dateFormat =
+            new SimpleDateFormat("MM/dd/yyyy HH:mm z", locale);
+
+        String tz[] = TimeZone.getAvailableIDs();
+
+        for (int z=0; z<tz.length; ++z) {
+            TimeZone t = TimeZone.getTimeZone(tz[z]);
+            dateFormat.setTimeZone(t);
+
+            Calendar cal = Calendar.getInstance(t, locale);
+            cal.clear();
+            // Scan the year 2003, overlapping the edges of the year
+            cal.set(Calendar.YEAR, 2002);
+            cal.set(Calendar.MONTH, Calendar.DECEMBER);
+            cal.set(Calendar.DAY_OF_MONTH, 25);
+
+            for (int i=0; i<365+10; ++i) {
+                Date yesterday = cal.getTime();
+                int yesterday_day = cal.get(Calendar.DAY_OF_MONTH);
+                cal.add(Calendar.DAY_OF_MONTH, 1);
+                if (yesterday_day == cal.get(Calendar.DAY_OF_MONTH)) {
+                    errln(tz[z] + " " +
+                          dateFormat.format(yesterday) + " +1d= " +
+                          dateFormat.format(cal.getTime()));
+                }
+            }
+        }
+    }
+
+    public void TestJB1684() {
+        class TestData {
+            int year;
+            int month;
+            int date;
+            int womyear;
+            int wommon;
+            int wom;
+            int dow;
+            String data;
+            String normalized;
+
+            public TestData(int year, int month, int date,
+                            int womyear, int wommon, int wom, int dow,
+                            String data, String normalized) {
+                this.year = year;
+                this.month = month-1;
+                this.date = date;
+                this.womyear = womyear;
+                this.wommon = wommon-1;
+                this.wom = wom;
+                this.dow = dow;
+                this.data = data; // year, month, week of month, day
+                this.normalized = data;
+                if (normalized != null) this.normalized = normalized;
+            }
+        };
+
+        //      July 2001            August 2001           January 2002    
+        // Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
+        //  1  2  3  4  5  6  7            1  2  3  4         1  2  3  4  5
+        //  8  9 10 11 12 13 14   5  6  7  8  9 10 11   6  7  8  9 10 11 12
+        // 15 16 17 18 19 20 21  12 13 14 15 16 17 18  13 14 15 16 17 18 19
+        // 22 23 24 25 26 27 28  19 20 21 22 23 24 25  20 21 22 23 24 25 26
+        // 29 30 31              26 27 28 29 30 31     27 28 29 30 31      
+        TestData[] tests = {
+            new TestData(2001, 8,  6,  2001,8,2,Calendar.MONDAY,    "2001 08 02 Mon", null),
+            new TestData(2001, 8,  7,  2001,8,2,Calendar.TUESDAY,   "2001 08 02 Tue", null),
+            new TestData(2001, 8,  5,/*12,*/ 2001,8,2,Calendar.SUNDAY,    "2001 08 02 Sun", null),
+            new TestData(2001, 8,6, /*7,  30,*/ 2001,7,6,Calendar.MONDAY,    "2001 07 06 Mon", "2001 08 02 Mon"),
+            new TestData(2001, 8,7, /*7,  31,*/ 2001,7,6,Calendar.TUESDAY,   "2001 07 06 Tue", "2001 08 02 Tue"),
+            new TestData(2001, 8,  5,  2001,7,6,Calendar.SUNDAY,    "2001 07 06 Sun", "2001 08 02 Sun"),
+            new TestData(2001, 7,  30, 2001,8,1,Calendar.MONDAY,    "2001 08 01 Mon", "2001 07 05 Mon"),
+            new TestData(2001, 7,  31, 2001,8,1,Calendar.TUESDAY,   "2001 08 01 Tue", "2001 07 05 Tue"),
+            new TestData(2001, 7,29, /*8,  5,*/  2001,8,1,Calendar.SUNDAY,    "2001 08 01 Sun", "2001 07 05 Sun"),
+            new TestData(2001, 12, 31, 2001,12,6,Calendar.MONDAY,   "2001 12 06 Mon", null),
+            new TestData(2002, 1,  1,  2002,1,1,Calendar.TUESDAY,   "2002 01 01 Tue", null),
+            new TestData(2002, 1,  2,  2002,1,1,Calendar.WEDNESDAY, "2002 01 01 Wed", null),
+            new TestData(2002, 1,  3,  2002,1,1,Calendar.THURSDAY,  "2002 01 01 Thu", null),
+            new TestData(2002, 1,  4,  2002,1,1,Calendar.FRIDAY,    "2002 01 01 Fri", null),
+            new TestData(2002, 1,  5,  2002,1,1,Calendar.SATURDAY,  "2002 01 01 Sat", null),
+            new TestData(2001,12,30, /*2002, 1,  6,*/  2002,1,1,Calendar.SUNDAY,    "2002 01 01 Sun", "2001 12 06 Sun"),
+        };
+
+        int pass = 0, error = 0, warning = 0;
+
+        final String pattern = "yyyy MM WW EEE";
+        GregorianCalendar cal = new GregorianCalendar();
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+        sdf.setCalendar(cal);
+
+        cal.setFirstDayOfWeek(Calendar.SUNDAY);
+        cal.setMinimalDaysInFirstWeek(1);
+
+        for (int i = 0; i < tests.length; ++i) {
+            TestData test = tests[i];
+            log("\n-----\nTesting round trip of " + test.year +
+                  " " + (test.month + 1) +
+                  " " + test.date +
+                  " (written as) " + test.data);
+
+            cal.clear();
+            cal.set(test.year, test.month, test.date);
+            Date ms = cal.getTime();
+
+            cal.clear();
+            cal.set(Calendar.YEAR, test.womyear);
+            cal.set(Calendar.MONTH, test.wommon);
+            cal.set(Calendar.WEEK_OF_MONTH, test.wom);
+            cal.set(Calendar.DAY_OF_WEEK, test.dow);
+            Date ms2 = cal.getTime();
+
+            if (!ms2.equals(ms)) {
+                log("\nError: GregorianCalendar.DOM gave " + ms +
+                    "\n       GregorianCalendar.WOM gave " + ms2);
+                error++;
+            } else {
+                pass++;
+            }
+
+            ms2 = null;
+            try {
+                ms2 = sdf.parse(test.data);
+            }
+            catch (ParseException e) {
+                errln("parse exception: " + e);
+            }
+
+            if (!ms2.equals(ms)) {
+                log("\nError: GregorianCalendar gave      " + ms +
+                    "\n       SimpleDateFormat.parse gave " + ms2);
+                error++;
+            } else {
+                pass++;
+            }
+
+            String result = sdf.format(ms);
+            if (!result.equals(test.normalized)) {
+                log("\nWarning: format of '" + test.data + "' gave" +
+                    "\n                   '" + result + "'" +
+                    "\n          expected '" + test.normalized + "'");
+                warning++;
+            } else {
+                pass++;
+            }
+
+            Date ms3 = null;
+            try {
+                ms3 = sdf.parse(result);
+            }
+            catch (ParseException e) {
+                errln("parse exception 2: " + e);
+            }
+
+            if (!ms3.equals(ms)) {
+                error++;
+                log("\nError: Re-parse of '" + result + "' gave time of " +
+                    "\n        " + ms3 +
+                    "\n    not " + ms);
+            } else {
+                pass++;
+            }
+        }
+        String info = "\nPassed: " + pass + ", Warnings: " + warning + ", Errors: " + error;
+        if (error > 0) {
+            errln(info);
+        } else {
+            logln(info);
+        }
+    }
+
+    /**
+     * Test the ZoneMeta API.
+     */
+    public void TestZoneMeta() {
+        // Test index by country API
+
+        // Format: {country, zone1, zone2, ..., zoneN}
+        String COUNTRY[][] = { {"", "GMT", "UTC"},
+                               {"US", "America/Los_Angeles", "PST"} };
+        StringBuffer buf = new StringBuffer();
+        for (int i=0; i<COUNTRY.length; ++i) {
+            String[] a = ZoneMeta.getAvailableIDs(COUNTRY[i][0]);
+            buf.setLength(0);
+            buf.append("Country \"" + COUNTRY[i][0] + "\": [");
+            // Use bitmask to track which of the expected zones we see
+            int mask = 0;
+            for (int j=0; j<a.length; ++j) {
+                if (j!=0) buf.append(", ");
+                buf.append(a[j]);
+                for (int k=1; k<COUNTRY[i].length; ++k) {
+                    if ((mask & (1<<k)) == 0 &&
+                        a[j] == COUNTRY[i][k]) {
+                        mask |= (1<<k);
+                    }
+                }
+            }
+            buf.append("]");
+            mask >>= 1;
+            // Check bitmask to see if we saw all expected zones
+            if (mask == (1 << (COUNTRY[i].length-1))-1) {
+                logln(buf.toString());
+            } else {
+                errln(buf.toString());
+            }
+        }
+
+        // Test equivalent IDs API
+
+        int n = ZoneMeta.countEquivalentIDs("PST");
+        boolean ok = false;
+        buf.setLength(0);
+        buf.append("Equivalent to PST: ");
+        for (int i=0; i<n; ++i) {
+            String id = ZoneMeta.getEquivalentID("PST", i);
+            if (id == "America/Los_Angeles") {
+                ok = true;
+            }
+            if (i!=0) buf.append(", ");
+            buf.append(id);
+        }
+        if (ok) {
+            logln(buf.toString());
+        } else {
+            errln(buf.toString());
+        }
+    }
+
+    /**
+     * Miscellaneous tests to increase coverage.
+     */
+    public void TestCoverage() {
+        // BuddhistCalendar
+        BuddhistCalendar bcal = new BuddhistCalendar();
+        /*int i =*/ bcal.getMinimum(Calendar.ERA);
+        bcal.add(Calendar.YEAR, 1);
+        bcal.add(Calendar.MONTH, 1);
+        /*Date d = */bcal.getTime();
+
+        // CalendarAstronomer
+        // (This class should probably be made package-private.)
+        CalendarAstronomer astro = new CalendarAstronomer();
+        String s = astro.local(0);
+
+        // ChineseCalendar
+        ChineseCalendar ccal = new ChineseCalendar(TimeZone.getDefault(),
+                                                   Locale.getDefault());
+        ccal.add(Calendar.MONTH, 1);
+        ccal.add(Calendar.YEAR, 1);
+        ccal.roll(Calendar.MONTH, 1);
+        ccal.roll(Calendar.YEAR, 1);
+        ccal.getTime();
+
+        // ICU 2.6
+        Calendar cal = Calendar.getInstance(Locale.US);
+        logln(cal.toString());
+        logln(cal.getDisplayName(Locale.US));
+        int weekendOnset=-1;
+        int weekendCease=-1;
+        for (int i=Calendar.SUNDAY; i<=Calendar.SATURDAY; ++i) {
+            if (cal.getDayOfWeekType(i) == Calendar.WEEKEND_ONSET) {
+                weekendOnset = i;
+            }
+            if (cal.getDayOfWeekType(i) == Calendar.WEEKEND_CEASE) {
+                weekendCease = i;
+            }
+        }
+        // can't call this unless we get a transition day (unusual),
+        // but make the call anyway for coverage reasons
+        try {
+            /*int x=*/ cal.getWeekendTransition(weekendOnset);
+            /*int x=*/ cal.getWeekendTransition(weekendCease);
+        } catch (IllegalArgumentException e) {}
+        /*int x=*/ cal.isWeekend(new Date());
+
+    }
+}
diff --git a/src/com/ibm/icu/dev/test/calendar/IslamicTest.java b/src/com/ibm/icu/dev/test/calendar/IslamicTest.java
new file mode 100755
index 0000000..5a965bb
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/IslamicTest.java
@@ -0,0 +1,243 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/IslamicTest.java,v $ 
+ * $Date: 2003/09/04 00:57:14 $ 
+ * $Revision: 1.8 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import com.ibm.icu.impl.LocaleUtility;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.IslamicCalendar;
+
+/**
+ * Tests for the <code>IslamicCalendar</code> class.
+ */
+public class IslamicTest extends CalendarTest {
+    public static void main(String args[]) throws Exception {
+        new IslamicTest().run(args);
+    }
+
+    /** Constants to save typing. */
+    public static final int MUHARRAM = IslamicCalendar.MUHARRAM;
+    public static final int SAFAR =  IslamicCalendar.SAFAR;
+    public static final int RABI_1 =  IslamicCalendar.RABI_1;
+    public static final int RABI_2 =  IslamicCalendar.RABI_2;
+    public static final int JUMADA_1 =  IslamicCalendar.JUMADA_1;
+    public static final int JUMADA_2 =  IslamicCalendar.JUMADA_2;
+    public static final int RAJAB =  IslamicCalendar.RAJAB;
+    public static final int SHABAN =  IslamicCalendar.SHABAN;
+    public static final int RAMADAN =  IslamicCalendar.RAMADAN;
+    public static final int SHAWWAL =  IslamicCalendar.SHAWWAL;
+    public static final int QIDAH =  IslamicCalendar.DHU_AL_QIDAH;
+    public static final int HIJJAH =  IslamicCalendar.DHU_AL_HIJJAH;
+
+    public void TestRoll() {
+        int[][] tests = new int[][] {
+            //       input                roll by          output
+            //  year  month     day     field amount    year  month     day
+    
+            {   0001, QIDAH,     2,     MONTH,   1,     0001, HIJJAH,    2 },   // non-leap years
+            {   0001, QIDAH,     2,     MONTH,   2,     0001, MUHARRAM,  2 },
+            {   0001, QIDAH,     2,     MONTH,  -1,     0001, SHAWWAL,   2 },
+            {   0001, MUHARRAM,  2,     MONTH,  12,     0001, MUHARRAM,  2 },
+            {   0001, MUHARRAM,  2,     MONTH,  13,     0001, SAFAR,     2 },
+
+            {   0001, HIJJAH,    1,     DATE,   30,     0001, HIJJAH,    2 },   // 29-day month
+            {   0002, HIJJAH,    1,     DATE,   31,     0002, HIJJAH,    2 },   // 30-day month
+
+            // Try some rolls that require other fields to be adjusted
+            {   0001, MUHARRAM, 30,     MONTH,   1,     0001, SAFAR,    29 },
+            {   0002, HIJJAH,   30,     YEAR,   -1,     0001, HIJJAH,   29 },
+        };
+       
+        IslamicCalendar cal = newCivil();
+
+        doRollAdd(ROLL, cal, tests);
+    }
+
+    /**
+     * A huge list of test cases to make sure that computeTime and computeFields
+     * work properly for a wide range of data in the civil calendar.
+     */
+    public void TestCivilCases()
+    {
+        final TestCase[] tests = {
+            //
+            // Most of these test cases were taken from the back of
+            // "Calendrical Calculations", with some extras added to help
+            // debug a few of the problems that cropped up in development.
+            //
+            // The months in this table are 1-based rather than 0-based,
+            // because it's easier to edit that way.
+            //                       Islamic
+            //          Julian Day  Era  Year  Month Day  WkDay Hour Min Sec
+            new TestCase(1507231.5,  0, -1245,   12,   9,  SUN,   0,  0,  0),
+            new TestCase(1660037.5,  0,  -813,    2,  23,  WED,   0,  0,  0),
+            new TestCase(1746893.5,  0,  -568,    4,   1,  WED,   0,  0,  0),
+            new TestCase(1770641.5,  0,  -501,    4,   6,  SUN,   0,  0,  0),
+            new TestCase(1892731.5,  0,  -157,   10,  17,  WED,   0,  0,  0),
+            new TestCase(1931579.5,  0,   -47,    6,   3,  MON,   0,  0,  0),
+            new TestCase(1974851.5,  0,    75,    7,  13,  SAT,   0,  0,  0),
+            new TestCase(2091164.5,  0,   403,   10,   5,  SUN,   0,  0,  0),
+            new TestCase(2121509.5,  0,   489,    5,  22,  SUN,   0,  0,  0),
+            new TestCase(2155779.5,  0,   586,    2,   7,  FRI,   0,  0,  0),
+            new TestCase(2174029.5,  0,   637,    8,   7,  SAT,   0,  0,  0),
+            new TestCase(2191584.5,  0,   687,    2,  20,  FRI,   0,  0,  0),
+            new TestCase(2195261.5,  0,   697,    7,   7,  SUN,   0,  0,  0),
+            new TestCase(2229274.5,  0,   793,    7,   1,  SUN,   0,  0,  0),
+            new TestCase(2245580.5,  0,   839,    7,   6,  WED,   0,  0,  0),
+            new TestCase(2266100.5,  0,   897,    6,   1,  SAT,   0,  0,  0),
+            new TestCase(2288542.5,  0,   960,    9,  30,  SAT,   0,  0,  0),
+            new TestCase(2290901.5,  0,   967,    5,  27,  SAT,   0,  0,  0),
+            new TestCase(2323140.5,  0,  1058,    5,  18,  WED,   0,  0,  0),
+            new TestCase(2334848.5,  0,  1091,    6,   2,  SUN,   0,  0,  0),
+            new TestCase(2348020.5,  0,  1128,    8,   4,  FRI,   0,  0,  0),
+            new TestCase(2366978.5,  0,  1182,    2,   3,  SUN,   0,  0,  0),
+            new TestCase(2385648.5,  0,  1234,   10,  10,  MON,   0,  0,  0),
+            new TestCase(2392825.5,  0,  1255,    1,  11,  WED,   0,  0,  0),
+            new TestCase(2416223.5,  0,  1321,    1,  21,  SUN,   0,  0,  0),
+            new TestCase(2425848.5,  0,  1348,    3,  19,  SUN,   0,  0,  0),
+            new TestCase(2430266.5,  0,  1360,    9,   8,  MON,   0,  0,  0),
+            new TestCase(2430833.5,  0,  1362,    4,  13,  MON,   0,  0,  0),
+            new TestCase(2431004.5,  0,  1362,   10,   7,  THU,   0,  0,  0),
+            new TestCase(2448698.5,  0,  1412,    9,  13,  TUE,   0,  0,  0),
+            new TestCase(2450138.5,  0,  1416,   10,   5,  SUN,   0,  0,  0),
+            new TestCase(2465737.5,  0,  1460,   10,  12,  WED,   0,  0,  0),
+            new TestCase(2486076.5,  0,  1518,    3,   5,  SUN,   0,  0,  0),
+        };
+        
+        IslamicCalendar civilCalendar = newCivil();
+        civilCalendar.setLenient(true);
+        doTestCases(tests, civilCalendar);
+    }
+
+    public void TestBasic() {
+        IslamicCalendar cal = newCivil();
+        cal.clear();
+        cal.set(1000, 0, 30);
+        logln("1000/0/30 -> " +
+              cal.get(YEAR) + "/" +
+              cal.get(MONTH) + "/" + 
+              cal.get(DATE));
+        cal.clear();
+        cal.set(1, 0, 30);
+        logln("1/0/30 -> " +
+              cal.get(YEAR) + "/" +
+              cal.get(MONTH) + "/" + 
+              cal.get(DATE));
+    }
+
+    public void TestCoverage() {
+	{
+	    // new IslamicCalendar(TimeZone)
+	    IslamicCalendar cal = new IslamicCalendar(TimeZone.getDefault());
+        if(cal == null){
+            errln("could not create IslamicCalendar with TimeZone");
+        }
+    }
+	
+	{
+	    // new IslamicCalendar(Locale)
+	    IslamicCalendar cal = new IslamicCalendar(Locale.getDefault());
+        if(cal == null){
+            errln("could not create IslamicCalendar with Locale");
+        }
+    }
+
+	{
+	    // new IslamicCalendar(Date)
+	    IslamicCalendar cal = new IslamicCalendar(new Date());
+        if(cal == null){
+            errln("could not create IslamicCalendar with Date");
+        }
+	}
+
+	{
+	    // new IslamicCalendar(int year, int month, int date)
+	    IslamicCalendar cal = new IslamicCalendar(800, IslamicCalendar.RAMADAN, 1);
+        if(cal == null){
+            errln("could not create IslamicCalendar with year,month,date");
+        }
+    }
+
+	{
+	    // new IslamicCalendar(int year, int month, int date, int hour, int minute, int second)
+	    IslamicCalendar cal = new IslamicCalendar(800, IslamicCalendar.RAMADAN, 1, 1, 1, 1);
+        if(cal == null){
+            errln("could not create IslamicCalendar with year,month,date,hour,minute,second");
+        }
+    }
+
+	{
+	    // setCivil/isCivil
+	    // operations on non-civil calendar
+	    IslamicCalendar cal = new IslamicCalendar(800, IslamicCalendar.RAMADAN, 1, 1, 1, 1);
+	    cal.setCivil(false);
+	    if (cal.isCivil()) {
+		errln("islamic calendar is civil");
+	    }
+
+	    Date now = new Date();
+	    cal.setTime(now);
+
+	    Date then = cal.getTime();
+	    if (!now.equals(then)) {
+		errln("get/set time failed with non-civil islamic calendar");
+	    }
+
+	    logln(then.toString());
+
+	    cal.add(Calendar.MONTH, 1);
+	    cal.add(Calendar.DAY_OF_MONTH, 1);
+	    cal.add(Calendar.YEAR, 1);
+
+	    logln(cal.getTime().toString());
+	}
+	
+	{
+	    // data
+	    IslamicCalendar cal = new IslamicCalendar(800, IslamicCalendar.RAMADAN, 1);
+	    Date time = cal.getTime();
+
+	    String[] calendarLocales = {
+		"ar_AE", "ar_BH", "ar_DZ", "ar_EG", "ar_JO", "ar_KW", "ar_OM", 
+		"ar_QA", "ar_SA", "ar_SY", "ar_YE", "ms_MY"
+	    };
+
+	    String[] formatLocales = {
+		"en", "ar", "fi", "fr", "hu", "iw", "nl"
+	    };
+	    for (int i = 0; i < calendarLocales.length; ++i) {
+		String calLocName = calendarLocales[i];
+		Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
+		cal = new IslamicCalendar(calLocale);
+
+		for (int j = 0; j < formatLocales.length; ++j) {
+		    String locName = formatLocales[j];
+		    Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
+		    DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
+		    logln(calLocName + "/" + locName + " --> " + format.format(time));
+		}
+	    }
+	}
+    }
+
+    private static IslamicCalendar newCivil() {
+        IslamicCalendar civilCalendar = new IslamicCalendar();
+        civilCalendar.setCivil(true);
+        return civilCalendar;
+    }
+    
+};
diff --git a/src/com/ibm/icu/dev/test/calendar/JapaneseTest.java b/src/com/ibm/icu/dev/test/calendar/JapaneseTest.java
new file mode 100644
index 0000000..085799df
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/JapaneseTest.java
@@ -0,0 +1,131 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2002-2003, International Business Machines Corporation and         *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/JapaneseTest.java,v $
+ * $Date: 2003/09/04 00:57:14 $
+ * $Revision: 1.4 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import com.ibm.icu.impl.LocaleUtility;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.JapaneseCalendar;
+
+/**
+ * Tests for the <code>IslamicCalendar</code> class.
+ */
+public class JapaneseTest extends CalendarTest {
+    public static void main(String args[]) throws Exception {
+        new JapaneseTest().run(args);
+    }
+
+    public void TestCoverage() {
+	{
+	    // new JapaneseCalendar(TimeZone)
+	    JapaneseCalendar cal = new JapaneseCalendar(TimeZone.getDefault());
+        if(cal == null){
+            errln("could not create JapaneseCalendar with TimeZone");
+        }
+    }
+	
+	{
+	    // new JapaneseCalendar(Locale)
+	    JapaneseCalendar cal = new JapaneseCalendar(Locale.getDefault());
+        if(cal == null){
+            errln("could not create JapaneseCalendar with Locale");
+        }
+	}
+
+	{
+	    // new JapaneseCalendar(TimeZone, Locale)
+	    JapaneseCalendar cal = new JapaneseCalendar(TimeZone.getDefault(), Locale.getDefault());
+        if(cal == null){
+            errln("could not create JapaneseCalendar with TimeZone Locale");
+        }
+	}
+
+	{
+	    // new JapaneseCalendar(Date)
+	    JapaneseCalendar cal = new JapaneseCalendar(new Date());
+        if(cal == null){
+            errln("could not create JapaneseCalendar with Date");
+        }
+    }
+
+	{
+	    // new JapaneseCalendar(int year, int month, int date)
+	    JapaneseCalendar cal = new JapaneseCalendar(1868, Calendar.JANUARY, 1);
+        if(cal == null){
+            errln("could not create JapaneseCalendar with year,month,date");
+        }
+	}
+
+	{
+	    // new JapaneseCalendar(int era, int year, int month, int date)
+	    JapaneseCalendar cal = new JapaneseCalendar(JapaneseCalendar.MEIJI, 43, Calendar.JANUARY, 1);
+        if(cal == null){
+            errln("could not create JapaneseCalendar with era,year,month,date");
+        }
+    }
+
+	{
+	    // new JapaneseCalendar(int year, int month, int date, int hour, int minute, int second)
+	    JapaneseCalendar cal = new JapaneseCalendar(1868, Calendar.JANUARY, 1, 1, 1, 1);
+        if(cal == null){
+            errln("could not create JapaneseCalendar with year,month,date,hour,min,second");
+        }
+    }
+
+	{
+	    // limits
+	    JapaneseCalendar cal = new JapaneseCalendar();
+	    DateFormat fmt = cal.getDateTimeFormat(DateFormat.FULL, DateFormat.FULL, Locale.ENGLISH);
+
+	    cal.set(Calendar.ERA, JapaneseCalendar.MEIJI);
+	    logln("date: " + cal.getTime());
+	    logln("min era: " + cal.getMinimum(Calendar.ERA));
+	    logln("min year: " + cal.getMinimum(Calendar.YEAR));
+	    cal.set(Calendar.YEAR, cal.getActualMaximum(Calendar.YEAR));
+	    logln("date: " + fmt.format(cal.getTime()));
+	    cal.add(Calendar.YEAR, 1);
+	    logln("date: " + fmt.format(cal.getTime()));
+	}
+	
+	{
+	    // data
+	    JapaneseCalendar cal = new JapaneseCalendar(1868, Calendar.JANUARY, 1);
+	    Date time = cal.getTime();
+
+	    String[] calendarLocales = {
+		"en", "ja_JP"
+	    };
+
+	    String[] formatLocales = {
+		"en", "ja"
+	    };
+	    for (int i = 0; i < calendarLocales.length; ++i) {
+		String calLocName = calendarLocales[i];
+		Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
+		cal = new JapaneseCalendar(calLocale);
+
+		for (int j = 0; j < formatLocales.length; ++j) {
+		    String locName = formatLocales[j];
+		    Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
+		    DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
+		    logln(calLocName + "/" + locName + " --> " + format.format(time));
+		}
+	    }
+	}
+    }
+}
+
diff --git a/src/com/ibm/icu/dev/test/calendar/TestCase.java b/src/com/ibm/icu/dev/test/calendar/TestCase.java
new file mode 100755
index 0000000..ebc7dfb
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/calendar/TestCase.java
@@ -0,0 +1,245 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/calendar/TestCase.java,v $ 
+ * $Date: 2003/09/04 00:57:14 $ 
+ * $Revision: 1.10 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.dev.test.calendar;
+
+import com.ibm.icu.dev.test.*;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.GregorianCalendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+
+/**
+ * A pseudo <code>Calendar</code> that is useful for testing
+ * new calendars.  A <code>TestCase</code> object is used to hold the
+ * field and millisecond values that the calendar should have at one
+ * particular instant in time.  The applyFields and applyTime
+ * methods are used to apply these settings to the calendar object being
+ * tested, and the equals and fieldsEqual methods are used to ensure
+ * that the calendar has ended up in the right state.
+ */
+public class TestCase {
+
+    //------------------------------------------------------------------
+    // Pseudo-Calendar fields and methods
+    //------------------------------------------------------------------
+
+    protected int[] fields = new int[32];
+    protected boolean[] isSet = new boolean[32];
+    protected long time;
+
+    protected void set(int field, int value) {
+        fields[field] = value;
+        isSet[field] = true;
+    }
+
+    protected int get(int field) {
+        return fields[field];
+    }
+
+    protected boolean isSet(int field) {
+        return isSet[field];
+    }
+
+    protected void setTime(Date d) {
+        time = d.getTime();
+    }
+
+    public Date getTime() {
+        return new Date(time);
+    }
+
+    /**
+     * Return a String representation of this test case's time.
+     */
+    public String toString() {
+        return dowToString(get(Calendar.DAY_OF_WEEK)) + " " +
+            get(Calendar.YEAR) + "/" + (get(Calendar.MONTH)+1) + "/" +
+            get(Calendar.DATE);
+    }
+
+    private static final String[] DOW_NAMES = {
+        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+    };
+
+    public static String dowToString(int dow) {
+        --dow;
+        return (dow < 0 || dow > 6) ?
+            ("<DOW " + dow + ">") : DOW_NAMES[dow];
+    }
+
+    /**
+     * Initialize a TestCase object using a julian day number and
+     * the corresponding fields for the calendar being tested.
+     *
+     * @param era       The ERA field of tested calendar on the given julian day
+     * @param year      The YEAR field of tested calendar on the given julian day
+     * @param month     The MONTH (1-based) field of tested calendar on the given julian day
+     * @param day       The DAY_OF_MONTH field of tested calendar on the given julian day
+     * @param dayOfWeek The DAY_OF_WEEK field of tested calendar on the given julian day
+     * @param hour      The HOUR field of tested calendar on the given julian day
+     * @param min       The MINUTE field of tested calendar on the given julian day
+     * @param sec       The SECOND field of tested calendar on the given julian day
+     */
+    public TestCase(double julian,
+                    int era, int year, int month, int day,
+                    int dayOfWeek,
+                    int hour, int min, int sec)
+    {
+        setTime(new Date(JULIAN_EPOCH + (long)(ONE_DAY * julian)));
+        
+        set(Calendar.ERA, era);
+        set(Calendar.YEAR, year);
+        set(Calendar.MONTH, month - 1);
+        set(Calendar.DATE, day);
+        set(Calendar.DAY_OF_WEEK, dayOfWeek);
+        set(Calendar.HOUR, hour);
+        set(Calendar.MINUTE, min);
+        set(Calendar.SECOND, sec);
+    }
+
+    /**
+     * Initialize a TestCase object using a Gregorian year/month/day and
+     * the corresponding fields for the calendar being tested.
+     *
+     * @param gregYear  The Gregorian year of the date to be tested
+     * @param gregMonth The Gregorian month of the date to be tested
+     * @param gregDay   The Gregorian day of the month of the date to be tested
+     *
+     * @param era       The ERA field of tested calendar on the given gregorian date
+     * @param year      The YEAR field of tested calendar on the given gregorian date
+     * @param month     The MONTH (0-based) field of tested calendar on the given gregorian date
+     * @param day       The DAY_OF_MONTH field of tested calendar on the given gregorian date
+     * @param dayOfWeek The DAY_OF_WEEK field of tested calendar on the given gregorian date
+     * @param hour      The HOUR field of tested calendar on the given gregorian date
+     * @param min       The MINUTE field of tested calendar on the given gregorian date
+     * @param sec       The SECOND field of tested calendar on the given gregorian date
+     */
+    public TestCase(int gregYear, int gregMonth, int gregDay,
+                    int era, int year, int month, int day,
+                    int dayOfWeek,
+                    int hour, int min, int sec)
+    {
+        GregorianCalendar greg = new GregorianCalendar(UTC, Locale.getDefault());
+        greg.clear();
+        greg.set(gregYear, gregMonth-1, gregDay);
+        setTime(greg.getTime());
+        
+        set(Calendar.ERA, era);
+        set(Calendar.YEAR, year);
+        set(Calendar.MONTH, month - 1);
+        set(Calendar.DATE, day);
+        set(Calendar.DAY_OF_WEEK, dayOfWeek);
+        set(Calendar.HOUR, hour);
+        set(Calendar.MINUTE, min);
+        set(Calendar.SECOND, sec);
+    }
+    
+    /**
+     * For subclasses.
+     */
+    protected TestCase() {}
+
+    /**
+     * Apply this test case's field values to another calendar
+     * by calling its set method for each field.  This is useful in combination
+     * with the equal method.
+     *
+     * @see #equal
+     */
+    public void applyFields(Calendar c) {
+        for (int i=0; i < c.getFieldCount(); i++) {
+            if (isSet(i)) {
+                c.set(i, get(i));
+            }
+        }
+    }
+    
+    /**
+     * Apply this test case's time in milliseconds to another calendar
+     * by calling its setTime method.  This is useful in combination
+     * with fieldsEqual
+     *
+     * @see #fieldsEqual
+     */
+    public void applyTime(Calendar c) {
+        c.setTime(new Date(time));
+    }
+
+    /**
+     * Determine whether the fields of this calendar
+     * are the same as that of the other calendar.  This method is useful
+     * for determining whether the other calendar's computeFields method
+     * works properly.  For example:
+     * <pre>
+     *    Calendar testCalendar = ...
+     *    TestCase case = ...
+     *    case.applyTime(testCalendar);
+     *    if (!case.fieldsEqual(testCalendar)) {
+     *        // Error!
+     *    }
+     * </pre>
+     * 
+     * @see #applyTime
+     */
+    public boolean fieldsEqual(Calendar c, TestLog log) {
+        for (int i=0; i < c.getFieldCount(); i++) {
+            if (isSet(i) && get(i) != c.get(i)) {
+                log.errln("Fail: " + CalendarTest.fieldName(i) + " = " + c.get(i) +
+                          ", expected " + get(i));
+                for (int j=0; j<c.getFieldCount(); ++j) {
+                    if (isSet(j)) {
+                        if (get(j) == c.get(j)) {
+                            log.errln(" OK: " + CalendarTest.fieldName(j) + " = " +
+                                      c.get(j));
+                        } else {
+                            log.errln(" Fail: " + CalendarTest.fieldName(j) + " = " +
+                                      c.get(j) + ", expected " + get(j));
+                        }
+                    }
+                }
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Determine whether time in milliseconds of this calendar
+     * is the same as that of the other calendar.  This method is useful
+     * for determining whether the other calendar's computeTime method
+     * works properly.  For example:
+     * <pre>
+     *    Calendar testCalendar = ...
+     *    TestCase case = ...
+     *    case.applyFields(testCalendar);
+     *    if (!case.equals(testCalendar)) {
+     *        // Error!
+     *    }
+     * </pre>
+     * 
+     * @see #applyFields
+     */
+    public boolean equals(Object obj) {
+        return time == ((Calendar)obj).getTime().getTime();
+    }
+    
+    protected static final int  ONE_SECOND = 1000;
+    protected static final int  ONE_MINUTE = 60*ONE_SECOND;
+    protected static final int  ONE_HOUR   = 60*ONE_MINUTE;
+    protected static final long ONE_DAY    = 24*ONE_HOUR;
+    protected static final long JULIAN_EPOCH = -210866760000000L;   // 1/1/4713 BC 12:00
+
+    public final static SimpleTimeZone UTC = new SimpleTimeZone(0, "GMT");
+}
diff --git a/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java b/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java
new file mode 100755
index 0000000..621cbc0
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java
@@ -0,0 +1,884 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2001-2003, International Business Machines Corporation and         *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java,v $ 
+ * $Date: 2003/09/04 00:58:15 $ 
+ * $Revision: 1.8 $
+ *
+ *****************************************************************************************
+ */
+
+/** 
+ * Port From:   ICU4C v1.8.1 : format : DateFormatRegressionTest
+ * Source File: $ICU4CRoot/source/test/intltest/dtfmrgts.cpp
+ **/
+
+package com.ibm.icu.dev.test.format;
+
+import com.ibm.icu.text.*;
+import com.ibm.icu.util.*;
+
+import java.io.*;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+/** 
+ * Performs regression test for DateFormat
+ **/
+public class DateFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
+
+    public static void main(String[] args) throws Exception{
+        new DateFormatRegressionTest().run(args);
+    }
+    
+    /**
+     * @bug 4029195
+     */
+    public void Test4029195() {
+        Calendar cal = Calendar.getInstance();
+        Date today = cal.getTime();
+        logln("today: " + today);
+        SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance();
+        String pat = sdf.toPattern();
+        logln("pattern: " + pat);
+        StringBuffer fmtd = new StringBuffer("");
+        FieldPosition pos = new FieldPosition(0);
+        fmtd = sdf.format(today, fmtd, pos);
+        logln("today: " + fmtd);
+    
+        sdf.applyPattern("G yyyy DDD");
+        StringBuffer todayS = new StringBuffer("");
+        todayS = sdf.format(today, todayS, pos);
+        logln("today: " + todayS);
+        try {
+            today = sdf.parse(todayS.toString());
+            logln("today date: " + today);
+        } catch (Exception e) {
+            errln("Error reparsing date: " + e.getMessage());
+        }
+    
+        try {
+            StringBuffer rt = new StringBuffer("");
+            rt = sdf.format(sdf.parse(todayS.toString()), rt, pos);
+            logln("round trip: " + rt);
+            if (!rt.toString().equals(todayS.toString()))
+                errln("Fail: Want " + todayS + " Got " + rt);
+        } catch (ParseException e) {
+            errln("Fail: " + e);
+            e.printStackTrace();
+        }
+    }
+    
+    /**
+     * @bug 4052408
+     */
+    public void Test4052408() {
+    
+        DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US); 
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(97 + 1900, Calendar.MAY, 3, 8, 55);
+        Date dt = cal.getTime();
+        String str = fmt.format(dt);
+        logln(str);
+        
+        if (!str.equals("5/3/97 8:55 AM"))
+            errln("Fail: Test broken; Want 5/3/97 8:55 AM Got " + str);
+    
+        String expected[] = {
+            "", //"ERA_FIELD",
+            "97", //"YEAR_FIELD",
+            "5", //"MONTH_FIELD",
+            "3", //"DATE_FIELD",
+            "", //"HOUR_OF_DAY1_FIELD",
+            "", //"HOUR_OF_DAY0_FIELD",
+            "55", //"MINUTE_FIELD",
+            "", //"SECOND_FIELD",
+            "", //"MILLISECOND_FIELD",
+            "", //"DAY_OF_WEEK_FIELD",
+            "", //"DAY_OF_YEAR_FIELD",
+            "", //"DAY_OF_WEEK_IN_MONTH_FIELD",
+            "", //"WEEK_OF_YEAR_FIELD",
+            "", //"WEEK_OF_MONTH_FIELD",
+            "AM", //"AM_PM_FIELD",
+            "8", //"HOUR1_FIELD",
+            "", //"HOUR0_FIELD",
+            "" //"TIMEZONE_FIELD"
+            };        
+        String fieldNames[] = {
+                "ERA_FIELD", 
+                "YEAR_FIELD", 
+                "MONTH_FIELD", 
+                "DATE_FIELD", 
+                "HOUR_OF_DAY1_FIELD", 
+                "HOUR_OF_DAY0_FIELD", 
+                "MINUTE_FIELD", 
+                "SECOND_FIELD", 
+                "MILLISECOND_FIELD", 
+                "DAY_OF_WEEK_FIELD", 
+                "DAY_OF_YEAR_FIELD", 
+                "DAY_OF_WEEK_IN_MONTH_FIELD", 
+                "WEEK_OF_YEAR_FIELD", 
+                "WEEK_OF_MONTH_FIELD", 
+                "AM_PM_FIELD", 
+                "HOUR1_FIELD", 
+                "HOUR0_FIELD", 
+                "TIMEZONE_FIELD"}; 
+    
+        boolean pass = true;
+        for (int i = 0; i <= 17; ++i) {
+            FieldPosition pos = new FieldPosition(i);
+            StringBuffer buf = new StringBuffer("");
+            fmt.format(dt, buf, pos);
+            //char[] dst = new char[pos.getEndIndex() - pos.getBeginIndex()];
+            String dst = buf.substring(pos.getBeginIndex(), pos.getEndIndex());
+            str = dst;
+            log(i + ": " + fieldNames[i] + ", \"" + str + "\", "
+                    + pos.getBeginIndex() + ", " + pos.getEndIndex()); 
+            String exp = expected[i];
+            if ((exp.length() == 0 && str.length() == 0) || str.equals(exp))
+                logln(" ok");
+            else {
+                logln(" expected " + exp);
+                pass = false;
+            }
+        }
+        if (!pass)
+            errln("Fail: FieldPosition not set right by DateFormat");
+    }
+    
+    /**
+     * @bug 4056591
+     * Verify the function of the [s|g]et2DigitYearStart() API.
+     */
+    public void Test4056591() {
+    
+        try {
+            SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd", Locale.US);
+            Calendar cal = Calendar.getInstance();
+            cal.clear();
+            cal.set(1809, Calendar.DECEMBER, 25);
+            Date start = cal.getTime();
+            fmt.set2DigitYearStart(start);
+            if ((fmt.get2DigitYearStart() != start))
+                errln("get2DigitYearStart broken");
+            cal.clear();
+            cal.set(1809, Calendar.DECEMBER, 25);
+            Date d1 = cal.getTime();
+            cal.clear();
+            cal.set(1909, Calendar.DECEMBER, 24);
+            Date d2 = cal.getTime();
+            cal.clear();
+            cal.set(1809, Calendar.DECEMBER, 26);
+            Date d3 = cal.getTime();
+            cal.clear();
+            cal.set(1861, Calendar.DECEMBER, 25);
+            Date d4 = cal.getTime();
+            
+            Date dates[] = {d1, d2, d3, d4};
+    
+            String strings[] = {"091225", "091224", "091226", "611225"};            
+    
+            for (int i = 0; i < 4; i++) {
+                String s = strings[i];
+                Date exp = dates[i];
+                Date got = fmt.parse(s);
+                logln(s + " . " + got + "; exp " + exp);
+                if (got.getTime() != exp.getTime())
+                    errln("set2DigitYearStart broken");
+            }
+        } catch (ParseException e) {
+            errln("Fail: " + e);
+            e.printStackTrace();
+        }
+    }
+    
+    /**
+     * @bug 4059917
+     */
+    public void Test4059917() {        
+        SimpleDateFormat fmt;
+        String myDate;
+        fmt = new SimpleDateFormat("yyyy/MM/dd");
+        myDate = "1997/01/01";
+        aux917( fmt, myDate );        
+        fmt = new SimpleDateFormat("yyyyMMdd");
+        myDate = "19970101";
+        aux917( fmt, myDate );
+    }
+    
+    public void aux917(SimpleDateFormat fmt, String str) {
+    
+        String pat = fmt.toPattern();
+        logln("==================");
+        logln("testIt: pattern=" + pat + " string=" + str);
+        ParsePosition pos = new ParsePosition(0);
+        Object o = fmt.parseObject(str, pos);
+        //logln( UnicodeString("Parsed object: ") + o );
+    
+        StringBuffer formatted = new StringBuffer("");
+        FieldPosition poss = new FieldPosition(0);
+        formatted = fmt.format(o, formatted, poss);
+    
+        logln("Formatted string: " + formatted);
+        if (!formatted.toString().equals(str))
+            errln("Fail: Want " + str + " Got " + formatted);
+    }
+    
+    /**
+     * @bug 4060212
+     */
+    public void Test4060212() {
+        String dateString = "1995-040.05:01:29";
+        logln("dateString= " + dateString);
+        logln("Using yyyy-DDD.hh:mm:ss");
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-DDD.hh:mm:ss");
+        ParsePosition pos = new ParsePosition(0);
+        Date myDate = formatter.parse(dateString, pos);
+        DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.LONG); 
+        String myString = fmt.format(myDate);
+        logln(myString);
+        Calendar cal = new GregorianCalendar();
+        cal.setTime(myDate);
+        if ((cal.get(Calendar.DAY_OF_YEAR) != 40))
+            errln("Fail: Got " + cal.get(Calendar.DAY_OF_YEAR) + " Want 40");
+    
+        logln("Using yyyy-ddd.hh:mm:ss");
+        formatter = new SimpleDateFormat("yyyy-ddd.hh:mm:ss");
+        pos.setIndex(0);
+        myDate = formatter.parse(dateString, pos);
+        myString = fmt.format(myDate);
+        logln(myString);
+        cal.setTime(myDate);
+        if ((cal.get(Calendar.DAY_OF_YEAR) != 40))
+            errln("Fail: Got " + cal.get(Calendar.DAY_OF_YEAR) + " Want 40");
+    }
+    /**
+     * @bug 4061287
+     */
+    public void Test4061287() {
+    
+        SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
+        try {
+            logln(df.parse("35/01/1971").toString());
+        } catch (ParseException e) {
+            errln("Fail: " + e);
+            e.printStackTrace();
+        }
+        df.setLenient(false);
+        boolean ok = false;
+        try {
+            logln(df.parse("35/01/1971").toString());
+        } catch (ParseException e) {
+            ok = true;
+        }
+        if (!ok)
+            errln("Fail: Lenient not working");
+    }
+    
+    /**
+     * @bug 4065240
+     */
+    public void Test4065240() {
+        Date curDate;
+        DateFormat shortdate, fulldate;
+        String strShortDate, strFullDate;
+        Locale saveLocale = Locale.getDefault();
+        TimeZone saveZone = TimeZone.getDefault();
+    
+        try {
+            Locale curLocale = new Locale("de", "DE");
+            Locale.setDefault(curLocale);
+            // {sfb} adoptDefault instead of setDefault
+            //TimeZone.setDefault(TimeZone.createTimeZone("EST"));
+            TimeZone.setDefault(TimeZone.getTimeZone("EST"));
+            Calendar cal = Calendar.getInstance();
+            cal.clear();
+            cal.set(98 + 1900, 0, 1);
+            curDate = cal.getTime();
+            shortdate = DateFormat.getDateInstance(DateFormat.SHORT);
+            fulldate = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
+            strShortDate = "The current date (short form) is ";
+            String temp;
+            temp = shortdate.format(curDate);
+            strShortDate += temp;
+            strFullDate = "The current date (long form) is ";
+            String temp2 = fulldate.format(curDate);
+            strFullDate += temp2;
+    
+            logln(strShortDate);
+            logln(strFullDate);
+    
+            // {sfb} What to do with resource bundle stuff?????
+    
+            // Check to see if the resource is present; if not, we can't test
+            //ResourceBundle bundle = //The variable is never used
+            //    ICULocaleData.getBundle("DateFormatZoneData", curLocale); 
+    
+            // {sfb} API change to ResourceBundle -- add getLocale()
+            /*if (bundle.getLocale().getLanguage().equals("de")) {
+                // UPDATE THIS AS ZONE NAME RESOURCE FOR <EST> in de_DE is updated
+                if (!strFullDate.endsWith("GMT-05:00"))
+                    errln("Fail: Want GMT-05:00");
+            } else {
+                logln("*** TEST COULD NOT BE COMPLETED BECAUSE DateFormatZoneData ***");
+                logln("*** FOR LOCALE de OR de_DE IS MISSING ***");
+            }*/
+        } catch (Exception e) {
+            logln(e.getMessage());
+        } finally {
+            Locale.setDefault(saveLocale);
+            TimeZone.setDefault(saveZone);
+        }
+    
+    }
+    
+    /*
+      DateFormat.equals is too narrowly defined.  As a result, MessageFormat
+      does not work correctly.  DateFormat.equals needs to be written so
+      that the Calendar sub-object is not compared using Calendar.equals,
+      but rather compared for equivalency.  This may necessitate adding a
+      (package private) method to Calendar to test for equivalency.
+      
+      Currently this bug breaks MessageFormat.toPattern
+      */
+    /**
+     * @bug 4071441
+     */
+    public void Test4071441() {
+        DateFormat fmtA = DateFormat.getInstance();
+        DateFormat fmtB = DateFormat.getInstance();
+    
+        // {sfb} Is it OK to cast away const here?
+        Calendar calA = fmtA.getCalendar();
+        Calendar calB = fmtB.getCalendar();
+        calA.clear();
+        calA.set(1900, 0 ,0);
+        calB.clear();
+        calB.set(1900, 0, 0);
+        if (!calA.equals(calB))
+            errln("Fail: Can't complete test; Calendar instances unequal");
+        if (!fmtA.equals(fmtB))
+            errln("Fail: DateFormat unequal when Calendars equal");
+        calB.clear();
+        calB.set(1961, Calendar.DECEMBER, 25);
+        if (calA.equals(calB))
+            errln("Fail: Can't complete test; Calendar instances equal");
+        if (!fmtA.equals(fmtB))
+            errln("Fail: DateFormat unequal when Calendars equivalent");
+        logln("DateFormat.equals ok");
+    }
+        
+    /* The java.text.DateFormat.parse(String) method expects for the
+      US locale a string formatted according to mm/dd/yy and parses it
+      correctly.
+    
+      When given a string mm/dd/yyyy it only parses up to the first
+      two y's, typically resulting in a date in the year 1919.
+      
+      Please extend the parsing method(s) to handle strings with
+      four-digit year values (probably also applicable to various
+      other locales.  */
+    /**
+     * @bug 4073003
+     */
+    public void Test4073003() {
+        try {
+            DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+            String tests[] = {"12/25/61", "12/25/1961", "4/3/2010", "4/3/10"};
+            for (int i = 0; i < 4; i += 2) {
+                Date d = fmt.parse(tests[i]);
+                Date dd = fmt.parse(tests[i + 1]);
+                String s;
+                s = fmt.format(d);
+                String ss;
+                ss = fmt.format(dd);
+                if (d.getTime() != dd.getTime())
+                    errln("Fail: " + d + " != " + dd);
+                if (!s.equals(ss))
+                    errln("Fail: " + s + " != " + ss);
+                logln("Ok: " + s + " " + d);
+            }
+        } catch (ParseException e) {
+            errln("Fail: " + e);
+            e.printStackTrace();
+        }    
+    }
+    
+    /**
+     * @bug 4089106
+     */
+    public void Test4089106() {
+        TimeZone def = TimeZone.getDefault();
+        try {
+            TimeZone z = new SimpleTimeZone((int) (1.25 * 3600000), "FAKEZONE");
+            TimeZone.setDefault(z);
+            SimpleDateFormat f = new SimpleDateFormat();
+            if (!f.getTimeZone().equals(z))
+                errln("Fail: SimpleTimeZone should use TimeZone.getDefault()");
+        } finally {
+            TimeZone.setDefault(def);
+        }
+    }
+    
+    /**
+     * @bug 4100302
+     */
+    public void Test4100302() {
+        
+        Locale locales[] = {
+            Locale.CANADA, Locale.CANADA_FRENCH, Locale.CHINA, 
+            Locale.CHINESE, Locale.ENGLISH, Locale.FRANCE, Locale.FRENCH, 
+            Locale.GERMAN, Locale.GERMANY, Locale.ITALIAN, Locale.ITALY, 
+            Locale.JAPAN, Locale.JAPANESE, Locale.KOREA, Locale.KOREAN, 
+            Locale.PRC, Locale.SIMPLIFIED_CHINESE, Locale.TAIWAN, 
+            Locale.TRADITIONAL_CHINESE, Locale.UK, Locale.US}; 
+        try {
+            boolean pass = true;
+            for (int i = 0; i < 21; i++) {
+                Format format = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locales[i]); 
+                byte[] bytes;
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                ObjectOutputStream oos = new ObjectOutputStream(baos);
+                oos.writeObject(format);
+                oos.flush();
+                baos.close();
+                bytes = baos.toByteArray();
+                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+                Object o = ois.readObject();
+                if (!format.equals(o)) {
+                    pass = false;
+                    logln("DateFormat instance for locale " + locales[i] + " is incorrectly serialized/deserialized."); 
+                } else {
+                    logln("DateFormat instance for locale " + locales[i] + " is OKAY.");
+                }
+            }
+            if (!pass)
+                errln("Fail: DateFormat serialization/equality bug");
+        } catch (OptionalDataException e) {
+            errln("Fail: " + e);
+        } catch (IOException e) {
+            errln("Fail: " + e);
+        } catch (ClassNotFoundException e) {
+            errln("Fail: " + e);
+        } catch (Exception e) {
+            errln("Fail: " + e);
+        }
+    
+    }
+    
+    /**
+     * @bug 4101483
+     */
+    public void Test4101483() {
+        SimpleDateFormat sdf = new SimpleDateFormat("z", Locale.US);
+        FieldPosition fp = new FieldPosition(DateFormat.TIMEZONE_FIELD);
+        Date d = new Date(9234567890L);
+        StringBuffer buf = new StringBuffer("");
+        sdf.format(d, buf, fp);
+        logln(sdf.format(d, buf, fp).toString());
+        logln("beginIndex = " + fp.getBeginIndex());
+        logln("endIndex = " + fp.getEndIndex());
+        if (fp.getBeginIndex() == fp.getEndIndex())
+            errln("Fail: Empty field");
+    }
+    
+    /**
+     * @bug 4103340
+     * @bug 4138203
+     * This bug really only works in Locale.US, since that's what the locale
+     * used for Date.toString() is.  Bug 4138203 reports that it fails on Korean
+     * NT; it would actually have failed on any non-US locale.  Now it should
+     * work on all locales.
+     */
+    public void Test4103340() {
+    
+        // choose a date that is the FIRST of some month 
+        // and some arbitrary time
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1997, 3, 1, 1, 1, 1);
+        Date d = cal.getTime(); 
+        SimpleDateFormat df = new SimpleDateFormat("MMMM", Locale.US);
+        String s = d.toString();
+        StringBuffer s2 = new StringBuffer("");
+        FieldPosition pos = new FieldPosition(0);
+        s2 = df.format(d, s2, pos);
+        logln("Date=" + s); 
+        logln("DF=" + s2);
+        String substr = s2.substring(0,2);
+        if (s.indexOf(substr) == -1)
+          errln("Months should match");
+    }
+    
+    /**
+     * @bug 4103341
+     */
+    public void Test4103341() {
+        TimeZone saveZone = TimeZone.getDefault();
+        try {
+            // {sfb} changed from adoptDefault to setDefault
+            TimeZone.setDefault(TimeZone.getTimeZone("CST"));
+            SimpleDateFormat simple = new SimpleDateFormat("MM/dd/yyyy HH:mm");
+            TimeZone temp = TimeZone.getDefault();
+            if (!simple.getTimeZone().equals(temp))
+                errln("Fail: SimpleDateFormat not using default zone");
+        } finally {
+            TimeZone.setDefault(saveZone);
+        }
+    }
+    
+    /**
+     * @bug 4104136
+     */
+    public void Test4104136() {
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        String pattern = "'time' hh:mm";
+        sdf.applyPattern(pattern);
+        logln("pattern: \"" + pattern + "\"");
+        String strings[] = {"time 10:30", "time 10:x", "time 10x"};
+        ParsePosition ppos[] = {new ParsePosition(10), new ParsePosition(0), new ParsePosition(0)};
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1970, Calendar.JANUARY, 1, 10, 30);
+        Date dates[] = {cal.getTime(), new Date(-1), new Date(-1)};
+        for (int i = 0; i < 3; i++) {
+            String text = strings[i];
+            ParsePosition finish = ppos[i];
+            Date exp = dates[i];
+            ParsePosition pos = new ParsePosition(0);
+            Date d = sdf.parse(text, pos);
+            logln(" text: \"" + text + "\"");
+            logln(" index: %d" + pos.getIndex());
+            logln(" result: " + d);
+            if (pos.getIndex() != finish.getIndex())
+                errln("Fail: Expected pos " + finish.getIndex());
+            if (!((d == null && exp.equals(new Date(-1))) || (d.equals(exp))))
+                errln( "Fail: Expected result " + exp);
+        }
+    }
+    
+    /**
+     * @bug 4104522
+     * CANNOT REPRODUCE
+     * According to the bug report, this test should throw a
+     * StringIndexOutOfBoundsException during the second parse.  However,
+     * this is not seen.
+     */
+    public void Test4104522() {
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        String pattern = "'time' hh:mm";
+        sdf.applyPattern(pattern);
+        logln("pattern: \"" + pattern + "\"");
+        // works correctly
+        ParsePosition pp = new ParsePosition(0);
+        String text = "time ";
+        Date dt = sdf.parse(text, pp);
+        logln(" text: \"" + text + "\"" + " date: " + dt);
+        // works wrong
+        pp.setIndex(0);
+        text = "time";
+        dt = sdf.parse(text, pp);
+        logln(" text: \"" + text + "\"" + " date: " + dt);    
+    }
+    
+    /**
+     * @bug 4106807
+     */
+    public void Test4106807() {
+        Date dt;
+        DateFormat df = DateFormat.getDateTimeInstance();
+    
+        SimpleDateFormat sdfs[] = {
+                new SimpleDateFormat("yyyyMMddHHmmss"), 
+                new SimpleDateFormat("yyyyMMddHHmmss'Z'"), 
+                new SimpleDateFormat("yyyyMMddHHmmss''"), 
+                new SimpleDateFormat("yyyyMMddHHmmss'a''a'"), 
+                new SimpleDateFormat("yyyyMMddHHmmss %")}; 
+        String strings[] = {
+                "19980211140000", 
+                "19980211140000", 
+                "19980211140000", 
+                "19980211140000a", 
+                "19980211140000 "}; 
+        GregorianCalendar gc = new GregorianCalendar();
+        TimeZone timeZone = TimeZone.getDefault();
+        TimeZone gmt = (TimeZone) timeZone.clone();
+        gmt.setRawOffset(0);
+        for (int i = 0; i < 5; i++) {
+            SimpleDateFormat format = sdfs[i];
+            String dateString = strings[i];
+            try {
+                format.setTimeZone(gmt);
+                dt = format.parse(dateString);
+                // {sfb} some of these parses will fail purposely
+    
+                StringBuffer fmtd = new StringBuffer("");
+                FieldPosition pos = new FieldPosition(0);
+                fmtd = df.format(dt, fmtd, pos);
+                logln(fmtd.toString());
+                //logln(df.format(dt)); 
+                gc.setTime(dt);
+                logln("" + gc.get(Calendar.ZONE_OFFSET));
+                StringBuffer s = new StringBuffer("");
+                s = format.format(dt, s, pos);
+                logln(s.toString());
+            } catch (ParseException e) {
+                logln("No way Jose");
+            }
+        }
+    }
+    
+    /*
+      Synopsis: Chinese time zone CTT is not recogonized correctly.
+      Description: Platform Chinese Windows 95 - ** Time zone set to CST ** 
+      */
+    /**
+     * @bug 4108407
+     */
+    
+    // {sfb} what to do with this one ?? 
+    public void Test4108407() {
+        /*
+        long l = System.currentTimeMillis(); 
+        logln("user.timezone = " + System.getProperty("user.timezone", "?"));
+        logln("Time Zone :" + 
+                           DateFormat.getDateInstance().getTimeZone().getID()); 
+        logln("Default format :" + 
+                           DateFormat.getDateInstance().format(new Date(l))); 
+        logln("Full format :" + 
+                           DateFormat.getDateInstance(DateFormat.FULL).format(new 
+                                                                              Date(l))); 
+        logln("*** Set host TZ to CST ***");
+        logln("*** THE RESULTS OF THIS TEST MUST BE VERIFIED MANUALLY ***");
+        */
+    }
+    
+    /**
+     * @bug 4134203
+     * SimpleDateFormat won't parse "GMT"
+     */
+    public void Test4134203() {
+        String dateFormat = "MM/dd/yy HH:mm:ss zzz";
+        SimpleDateFormat fmt = new SimpleDateFormat(dateFormat);
+    
+        ParsePosition p0 = new ParsePosition(0);
+        Date d = fmt.parse("01/22/92 04:52:00 GMT", p0);
+        logln(d.toString());
+        if(p0.equals(new ParsePosition(0)))
+            errln("Fail: failed to parse 'GMT'");
+        // In the failure case an exception is thrown by parse();
+        // if no exception is thrown, the test passes.
+    }
+    
+    /**
+     * @bug 4151631
+     * SimpleDateFormat incorrect handling of 2 single quotes in format()
+     */
+    public void Test4151631() {
+        String pattern = 
+            "'TO_DATE('''dd'-'MM'-'yyyy HH:mm:ss''' , ''DD-MM-YYYY HH:MI:SS'')'"; 
+        logln("pattern=" + pattern);
+        SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.US);
+        StringBuffer result = new StringBuffer("");
+        FieldPosition pos = new FieldPosition(0);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1998, Calendar.JUNE, 30, 13, 30, 0);
+        Date d = cal.getTime();
+        result = format.format(d, result, pos); 
+        if (!result.toString().equals("TO_DATE('30-06-1998 13:30:00' , 'DD-MM-YYYY HH:MI:SS')")) {
+            errln("Fail: result=" + result);
+        } else {
+            logln("Pass: result=" + result);
+        }
+    }
+    
+    /**
+     * @bug 4151706
+     * 'z' at end of date format throws index exception in SimpleDateFormat
+     * CANNOT REPRODUCE THIS BUG ON 1.2FCS
+     */
+    public void Test4151706() {
+        String dateString = "Thursday, 31-Dec-98 23:00:00 GMT";
+        SimpleDateFormat fmt = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss z", Locale.US);
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);
+        cal.clear();
+        cal.set(1998, Calendar.DECEMBER, 31, 23, 0, 0);
+        Date d = new Date();
+        try {
+            d = fmt.parse(dateString);
+            // {sfb} what about next two lines?
+            if (d.getTime() != cal.getTime().getTime())
+                errln("Incorrect value: " + d);
+        } catch (Exception e) {
+            errln("Fail: " + e);
+        }
+        StringBuffer temp = new StringBuffer("");
+        FieldPosition pos = new FieldPosition(0);
+        logln(dateString + " . " + fmt.format(d, temp, pos));
+    }
+    
+    /**
+     * @bug 4162071
+     * Cannot reproduce this bug under 1.2 FCS -- it may be a convoluted duplicate
+     * of some other bug that has been fixed.
+     */
+    public void Test4162071() {
+        String dateString = "Thu, 30-Jul-1999 11:51:14 GMT";
+        String format = "EEE', 'dd-MMM-yyyy HH:mm:ss z"; // RFC 822/1123
+        SimpleDateFormat df = new SimpleDateFormat(format, Locale.US);
+        try {
+            Date x = df.parse(dateString);
+            StringBuffer temp = new StringBuffer("");
+            FieldPosition pos = new FieldPosition(0);
+            logln(dateString + " -> " + df.format(x, temp, pos));
+        } catch (Exception e) {
+            errln("Parse format \"" + format + "\" failed.");
+        }
+    }
+    
+    /**
+     * DateFormat shouldn't parse year "-1" as a two-digit year (e.g., "-1" . 1999).
+     */
+    public void Test4182066() {
+        SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yy", Locale.US);
+        SimpleDateFormat dispFmt = new SimpleDateFormat("MMM dd yyyy HH:mm:ss GG", Locale.US);
+        /* We expect 2-digit year formats to put 2-digit years in the right
+         * window.  Out of range years, that is, anything less than "00" or
+         * greater than "99", are treated as literal years.  So "1/2/3456"
+         * becomes 3456 AD.  Likewise, "1/2/-3" becomes -3 AD == 2 BC.
+         */
+        final String STRINGS[] = 
+            {"02/29/00", "01/23/01", "04/05/-1", "01/23/-9", "11/12/1314", "10/31/1", "09/12/+1", "09/12/001",}; 
+        int STRINGS_COUNT = STRINGS.length;
+                
+        Calendar cal = Calendar.getInstance();
+        Date FAIL_DATE = cal.getTime();
+        cal.clear();
+        cal.set(2000, Calendar.FEBRUARY, 29);
+        Date d0 = cal.getTime();
+        cal.clear();
+        cal.set(2001, Calendar.JANUARY, 23);
+        Date d1 = cal.getTime();
+        cal.clear();
+        cal.set(-1, Calendar.APRIL, 5);
+        Date d2 = cal.getTime();
+        cal.clear();
+        cal.set(-9, Calendar.JANUARY, 23);
+        Date d3 = cal.getTime();
+        cal.clear();
+        cal.set(1314, Calendar.NOVEMBER, 12);
+        Date d4 = cal.getTime();
+        cal.clear();
+        cal.set(1, Calendar.OCTOBER, 31);
+        Date d5 = cal.getTime();
+        cal.clear();        
+        cal.set(1, Calendar.SEPTEMBER, 12);
+        Date d7 = cal.getTime();
+        Date DATES[] = {d0, d1, d2, d3, d4, d5, FAIL_DATE, d7};
+    
+        String out = "";
+        boolean pass = true;
+        for (int i = 0; i < STRINGS_COUNT; ++i) {
+            String str = STRINGS[i];
+            Date expected = DATES[i];            
+            Date actual = null;
+            try {
+                actual = fmt.parse(str);
+            } catch (ParseException e) {
+                actual = FAIL_DATE;
+            }
+            String actStr = "";
+            if ((actual.getTime()) == FAIL_DATE.getTime()) {
+                actStr += "null";
+            } else {
+                // Yuck: See j25
+                actStr = ((DateFormat) dispFmt).format(actual);
+            }
+                               
+            if (expected.getTime() == (actual.getTime())) {
+                out += str + " => " + actStr + "\n";
+            } else {
+                String expStr = "";
+                if (expected.getTime() == FAIL_DATE.getTime()) {
+                    expStr += "null";
+                } else {
+                    // Yuck: See j25
+                    expStr = ((DateFormat) dispFmt).format(expected);
+                }
+                out += "FAIL: " + str + " => " + actStr + ", expected " + expStr + "\n";
+                pass = false;
+            }
+        }
+        if (pass) {
+            log(out);
+        } else {
+            err(out);
+        }
+    }
+    
+    /**
+     * j32 {JDK Bug 4210209 4209272}
+     * DateFormat cannot parse Feb 29 2000 when setLenient(false)
+     */
+    public void Test4210209() {
+    
+        String pattern = "MMM d, yyyy";
+        DateFormat fmt = new SimpleDateFormat(pattern, Locale.US);
+        DateFormat disp = new SimpleDateFormat("MMM dd yyyy GG", Locale.US);
+    
+        Calendar calx = fmt.getCalendar();
+        calx.setLenient(false);
+        Calendar calendar = Calendar.getInstance();
+        calendar.clear();
+        calendar.set(2000, Calendar.FEBRUARY, 29);
+        Date d = calendar.getTime();
+        String s = fmt.format(d);
+        logln(disp.format(d) + " f> " + pattern + " => \"" + s + "\"");
+        ParsePosition pos = new ParsePosition(0);
+        d = fmt.parse(s, pos);
+        logln("\"" + s + "\" p> " + pattern + " => " +
+              (d!=null?disp.format(d):"null"));
+        logln("Parse pos = " + pos.getIndex() + ", error pos = " + pos.getErrorIndex());
+        if (pos.getErrorIndex() != -1) {
+            errln("FAIL: Error index should be -1");
+        }
+    
+        // The underlying bug is in GregorianCalendar.  If the following lines
+        // succeed, the bug is fixed.  If the bug isn't fixed, they will throw
+        // an exception.
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.clear();
+        cal.setLenient(false);
+        cal.set(2000, Calendar.FEBRUARY, 29); // This should work!
+        d = cal.getTime();
+        logln("Attempt to set Calendar to Feb 29 2000: " + disp.format(d));
+    }
+    
+    public void Test714() {
+        //TimeZone Offset
+        TimeZone defaultTZ = TimeZone.getDefault();
+        TimeZone PST = TimeZone.getTimeZone("PST");
+        int defaultOffset = defaultTZ.getRawOffset();
+        int PSTOffset = PST.getRawOffset();
+        Date d = new Date(978103543000l - (defaultOffset - PSTOffset));
+        d = new Date(d.getTime() - (defaultTZ.inDaylightTime(d) ? 3600000 : 0));
+        DateFormat fmt = DateFormat.getDateTimeInstance(-1, DateFormat.MEDIUM, Locale.US);
+        String tests = "7:25:43 AM";
+        String s = fmt.format(d);
+        if (!s.equals(tests)) {
+            errln("Fail: " + s + " != " + tests);
+        } else {
+            logln("OK: " + s + " == " + tests);
+        }
+    }
+}
diff --git a/src/com/ibm/icu/dev/test/format/DateFormatRegressionTestJ.java b/src/com/ibm/icu/dev/test/format/DateFormatRegressionTestJ.java
new file mode 100755
index 0000000..1222b3e
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/format/DateFormatRegressionTestJ.java
@@ -0,0 +1,289 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2001-2003, International Business Machines Corporation and         *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/DateFormatRegressionTestJ.java,v $ 
+ * $Date: 2003/09/04 00:58:15 $ 
+ * $Revision: 1.7 $
+ *
+ *****************************************************************************************
+ */
+
+/*
+ * New added, 2001-10-17 [Jing/GCL]
+ */
+
+package com.ibm.icu.dev.test.format;
+
+import com.ibm.icu.text.*;
+import com.ibm.icu.util.*;
+import java.util.Date;
+import java.util.TimeZone;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+public class DateFormatRegressionTestJ extends com.ibm.icu.dev.test.TestFmwk {
+    
+    static final String TIME_STRING = "2000/11/17 08:01:00";
+    static final long UTC_LONG = 974476860000L;
+    static SimpleDateFormat sdf_ = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+    
+    public static void main(String[] args) throws Exception {
+        new DateFormatRegressionTestJ().run(args);
+    }
+    
+    //Return value of getAmPmStrings
+    public void Test4103926() {
+        String act_Ampms[];
+        String exp_Ampms[]={"AM","PM"};
+        Locale.setDefault(Locale.US);
+        
+        DateFormatSymbols dfs = new DateFormatSymbols();
+        act_Ampms = dfs.getAmPmStrings();
+        if(act_Ampms.length != exp_Ampms.length) {
+            errln("The result is not expected!");
+        } else {
+            for(int i =0; i<act_Ampms.length; i++) {
+                if(!act_Ampms[i].equals(exp_Ampms[i]))
+                    errln("The result is not expected!");
+            }
+        }
+    }
+    
+    //Missing digit in millisecone format in SimpleDateFormat 
+    public void Test4148168() {
+            Date d = new Date(1002705212906l); 
+            String[] ISOPattern = {
+                "''yyyy-MM-dd-hh.mm.ss.S''", "''yyyy-MM-dd-hh.mm.ss.SS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSS''", "''yyyy-MM-dd-hh.mm.ss.SSSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSSS''", "''yyyy-MM-dd-hh.mm.ss.SSSSSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSSSSS''", "''yyyy-MM-dd-hh.mm.ss.SSS000''"};
+            SimpleDateFormat aSimpleDF = (SimpleDateFormat)DateFormat.getDateTimeInstance();
+    
+            for(int i = 0; i<ISOPattern.length; i++) {
+                aSimpleDF.applyPattern( ISOPattern[i] );
+                logln( "Pattern = " + aSimpleDF.toPattern());
+                logln( "Format = " + aSimpleDF.format(d));
+            }
+    }
+    
+    //DateFormat getDateTimeInstance(int, int), invalid styles no exception
+    public void Test4213086() {
+        Date someDate = new Date();
+        String d=null;
+        try {
+            DateFormat df2 = DateFormat.getDateTimeInstance(2, -2);
+            d = df2.format(someDate);
+            errln("we should catch an exception here");
+        } catch(Exception e){
+            logln("dateStyle = 2" + "\t timeStyle = -2");
+            logln("Exception caught!");
+        }            
+        
+        try {
+            DateFormat df3 = DateFormat.getDateTimeInstance(4, 2);
+            d = df3.format(someDate);
+            errln("we should catch an exception here");
+        } catch(Exception e){
+            logln("dateStyle = 4" + "\t timeStyle = 2");
+            logln("Exception caught!");
+            logln("********************************************");
+        }
+    
+        try {
+            DateFormat df4 = DateFormat.getDateTimeInstance(-12, -12);
+            d = df4.format(someDate);
+            errln("we should catch an exception here");
+        } catch(Exception e){
+            logln("dateStyle = -12" + "\t timeStyle = -12");
+            logln("Exception caught!");
+            logln("********************************************");
+        }
+    
+        try{
+            DateFormat df5 = DateFormat.getDateTimeInstance(2, 123);
+            d = df5.format(someDate);    
+            errln("we should catch an exception here");
+        } catch(Exception e){
+            logln("dateStyle = 2" + "\t timeStyle = 123");
+            logln("Exception caught!");
+            logln("********************************************");
+        }
+        //read the value in d to get rid of the warning
+        if(d!=null){
+            logln("The value of d: " + d);
+        }
+    }
+    
+    //DateFormat.format works wrongly?
+    public void Test4250359() {
+        Locale.setDefault(Locale.US);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(101 + 1900, 9, 9, 17, 53);
+        Date d = cal.getTime();
+        DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
+        String act_result = tf.format(d);
+        String exp_result = "5:53 PM";
+        
+        if(!act_result.equals(exp_result)){
+            errln("The result is not expected");
+        }
+    }
+    
+    //pattern "s.S, parse '1ms'"
+    public void Test4253490() {
+        Date d = new Date(1002705212231l);
+    
+        String[] ISOPattern = {
+                "''yyyy-MM-dd-hh.mm.ss.S''", 
+                "''yyyy-MM-dd-hh.mm.ss.SS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSSSS''", 
+                "''yyyy-MM-dd-hh.mm.ss.SSSSSSS''"}; 
+    
+        SimpleDateFormat aSimpleDF = (SimpleDateFormat) DateFormat.getDateTimeInstance();
+        for (int i = 0; i < ISOPattern.length; i++) {
+            aSimpleDF.applyPattern(ISOPattern[i]);
+            logln("Pattern = " + aSimpleDF.toPattern());
+            logln("Format = " + aSimpleDF.format(d));
+        }
+    }
+    
+    //about regression test
+    public void Test4266432() {
+        Locale.setDefault(Locale.JAPAN);
+        Locale loc = Locale.getDefault();
+        String dateFormat = "MM/dd/yy HH:mm:ss zzz";
+        SimpleDateFormat fmt = new SimpleDateFormat(dateFormat);
+    
+        ParsePosition p0 = new ParsePosition(0);
+        logln("Under  " + loc +"  locale");
+        Date d = fmt.parse("01/22/92 04:52:00 GMT", p0);
+        logln(d.toString());
+    }
+    
+    //SimpleDateFormat inconsistent for number of digits for years
+    public void Test4358730() {
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2001,11,10);
+        Date today = cal.getTime();
+    
+        sdf.applyPattern("MM d y");
+        logln(sdf.format(today));
+        sdf.applyPattern("MM d yy");
+        logln(sdf.format(today));
+    
+        sdf.applyPattern("MM d yyy");
+        logln(sdf.format(today));
+    
+        sdf.applyPattern("MM d yyyy");
+        logln(sdf.format(today));
+    
+        sdf.applyPattern("MM d yyyyy");
+        logln(sdf.format(today));
+    }
+    
+    //Parse invalid string
+    public void Test4375399() {
+        final String pattern = new String("yyyy.MM.dd G 'at' hh:mm:ss z");
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.JAPAN);
+        try{
+            Date currentTime = sdf.parse("vggf 20  01.0 9.29 ap. J.-C. at 05:26:33 GMT+08:00",
+                                new ParsePosition(0));
+            if(currentTime ==null)
+                logln("parse right");
+        } catch(Exception e){
+            errln("Error");
+        }
+    }
+    /*
+    public void Test4407042() {
+        DateParseThread d1 = new DateParseThread();
+        DateFormatThread d2 = new DateFormatThread();
+        d1.start();
+        d2.start();
+        try {
+            logln("test");
+            Thread.sleep(1000);
+        } catch (Exception e) {}
+    }*/
+    
+    public void Test4468663() {
+        Date d =new Date(-93716671115767l);
+        String origin_d = d.toString();
+        String str;
+        final String pattern = new String("EEEE, MMMM d, yyyy");
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+
+        if (sdf.getTimeZone().useDaylightTime()) {
+            logln("original date: " + origin_d.toString());
+            str = sdf.format(d);
+            logln(" after format----->" + str);
+            
+            d = sdf.parse(str, new ParsePosition(0));
+            logln(" after parse----->" + d.toString());
+    
+            str = sdf.format(d);
+            logln(" after format----->" + str);
+    
+            d = sdf.parse(str, new ParsePosition(0));
+            logln(" after parse----->" + d.toString());
+    
+            str = sdf.format(d);
+            logln(" after format----->" + str);        
+        }
+    }
+    
+    //Class used by Test4407042
+    class DateParseThread extends Thread {
+        public void run() {
+            SimpleDateFormat sdf = (SimpleDateFormat) sdf_.clone();
+            TimeZone defaultTZ = TimeZone.getDefault();
+            TimeZone PST = TimeZone.getTimeZone("PST");
+            int defaultOffset = defaultTZ.getRawOffset();
+            int PSTOffset = PST.getRawOffset();
+            int offset = defaultOffset - PSTOffset;
+            long ms = UTC_LONG - offset;
+            try {
+                int i = 0;
+                while (i < 10000) {
+                    Date date = sdf.parse(TIME_STRING);
+                    long t = date.getTime();
+                    i++;
+                    if (t != ms) {
+                        throw new ParseException("Parse Error: " + i + " (" + sdf.format(date) 
+                                  + ") " + t + " != " + ms, 0);
+                    }
+                }
+            } catch (Exception e) {
+                errln("parse error: " + e.getMessage());
+            }
+        }
+    }
+    
+    //Class used by Test4407042
+    class DateFormatThread extends Thread {
+        public void run() {            
+            SimpleDateFormat sdf = (SimpleDateFormat) sdf_.clone();
+            TimeZone tz = TimeZone.getTimeZone("PST");
+            sdf.setTimeZone(tz);
+            int i = 0;
+            while (i < 10000) {
+                i++;
+                String s = sdf.format(new Date(UTC_LONG));
+                if (!s.equals(TIME_STRING)) {
+                    errln("Format Error: " + i + " " + s + " != " 
+                                    + TIME_STRING);
+                }
+            }
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/src/com/ibm/icu/dev/test/format/DateFormatRoundTripTest.java b/src/com/ibm/icu/dev/test/format/DateFormatRoundTripTest.java
new file mode 100755
index 0000000..298428e
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/format/DateFormatRoundTripTest.java
@@ -0,0 +1,274 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2001-2003, International Business Machines Corporation and         *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/DateFormatRoundTripTest.java,v $ 
+ * $Date: 2003/09/04 00:58:15 $ 
+ * $Revision: 1.7 $
+ *
+ *****************************************************************************************
+ */
+
+/** 
+ * Port From:   ICU4C v1.8.1 : format : DateFormatRoundTripTest
+ * Source File: $ICU4CRoot/source/test/intltest/dtfmtrtts.cpp
+ **/
+
+package com.ibm.icu.dev.test.format;
+
+import com.ibm.icu.text.*;
+import com.ibm.icu.util.*;
+import java.util.Locale;
+import java.util.Date;
+import java.util.Random;
+import java.util.TimeZone;
+import java.text.FieldPosition;
+import java.text.ParseException;
+
+/** 
+ * Performs round-trip tests for DateFormat
+ **/
+public class DateFormatRoundTripTest extends com.ibm.icu.dev.test.TestFmwk {
+    public boolean INFINITE = false;
+    public boolean quick = true;
+    private SimpleDateFormat dateFormat;
+    private Calendar getFieldCal;
+    private int SPARSENESS = 18;
+    private int TRIALS = 4;
+    private int DEPTH = 5;
+    private Random ran;
+
+    public static void main(String[] args) throws Exception {
+        new DateFormatRoundTripTest().run(args);
+    }
+    
+    public void TestDateFormatRoundTrip() {
+        dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G");
+        getFieldCal = Calendar.getInstance();
+	ran = createRandom(); // use test framework's random seed
+
+        final Locale[] avail = DateFormat.getAvailableLocales();
+        int locCount = avail.length;
+        logln("DateFormat available locales: " + locCount);
+        if (quick) {
+            if (locCount > 5)
+                locCount = 5;
+            logln("Quick mode: only testing first 5 Locales");
+        }
+        TimeZone tz = TimeZone.getDefault();
+        logln("Default TimeZone:             " + tz.getID());
+    
+        if (INFINITE) {
+            // Special infinite loop test mode for finding hard to reproduce errors
+            Locale loc = Locale.getDefault();
+            logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName());
+            for (;;) {
+                _test(loc);
+            }
+        } else {        
+            _test(Locale.getDefault());
+            for (int i = 0; i < locCount; ++i) {
+                _test(avail[i]);
+            }
+        }
+    }
+    
+    public String styleName(int s) {
+        switch (s) {
+            case DateFormat.SHORT :
+                return "SHORT";
+            case DateFormat.MEDIUM :
+                return "MEDIUM";
+            case DateFormat.LONG :
+                return "LONG";
+            case DateFormat.FULL :
+                return "FULL";
+            default :
+                return "Unknown";
+        }
+    }
+    
+    public void _test(Locale loc) {
+        if (!INFINITE) {
+            logln("Locale: " + loc.getDisplayName());
+        }
+        // Total possibilities = 24
+        //  4 date
+        //  4 time
+        //  16 date-time
+        boolean[] TEST_TABLE = new boolean[24];
+        int i = 0;
+        for (i = 0; i < 24; ++i)
+            TEST_TABLE[i] = true;
+    
+        // If we have some sparseness, implement it here.  Sparseness decreases
+        // test time by eliminating some tests, up to 23.
+        for (i = 0; i < SPARSENESS; i++) {
+            int random = (int) (ran.nextDouble() * 24);
+            if (random >= 0 && random < 24 && TEST_TABLE[i]) {
+                TEST_TABLE[random] = false;
+            }
+        }    
+        
+        int itable = 0;
+        int style = 0;
+        for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
+            if (TEST_TABLE[itable++]) {
+                logln("Testing style " + styleName(style)); 
+                DateFormat df = DateFormat.getDateInstance(style, loc); 
+                _test(df, false);
+            }
+        }
+    
+        for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
+            if (TEST_TABLE[itable++]) {
+                logln("Testing style " + styleName(style));
+                DateFormat  df = DateFormat.getTimeInstance(style, loc); 
+                _test(df, true);
+            }
+        }
+    
+        for (int dstyle = DateFormat.FULL; dstyle <= DateFormat.SHORT; ++dstyle) {
+            for (int tstyle = DateFormat.FULL; tstyle <= DateFormat.SHORT; ++tstyle) {
+                if (TEST_TABLE[itable++]) {
+                    logln("Testing dstyle " + styleName(dstyle) + ", tstyle " + styleName(tstyle)); 
+                    DateFormat df = DateFormat.getDateTimeInstance(dstyle, tstyle, loc); 
+                    _test(df, false);
+                }
+            }
+        }
+    }
+    
+    public void _test(DateFormat fmt, boolean timeOnly) {
+    
+        if (!(fmt instanceof SimpleDateFormat)) {
+            errln("DateFormat wasn't a SimpleDateFormat");
+            return;
+        }
+    
+        String pat = ((SimpleDateFormat) fmt).toPattern();
+        logln(pat);
+    
+        // NOTE TO MAINTAINER
+        // This indexOf check into the pattern needs to be refined to ignore
+        // quoted characters.  Currently, this isn't a problem with the locale
+        // patterns we have, but it may be a problem later.
+    
+        boolean hasEra = (pat.indexOf("G") != -1);
+        boolean hasZone = (pat.indexOf("z") != -1);
+    
+        // Because patterns contain incomplete data representing the Date,
+        // we must be careful of how we do the roundtrip.  We start with
+        // a randomly generated Date because they're easier to generate.
+        // From this we get a string.  The string is our real starting point,
+        // because this string should parse the same way all the time.  Note
+        // that it will not necessarily parse back to the original date because
+        // of incompleteness in patterns.  For example, a time-only pattern won't
+        // parse back to the same date.
+    
+        try {
+            for (int i = 0; i < TRIALS; ++i) {
+                Date[] d = new Date[DEPTH];
+                String[] s = new String[DEPTH];
+    
+                d[0] = generateDate();
+                
+                // We go through this loop until we achieve a match or until
+                // the maximum loop count is reached.  We record the points at
+                // which the date and the string starts to match.  Once matching
+                // starts, it should continue.
+                int loop;
+                int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime()
+                int smatch = 0; // s[smatch].equals(s[smatch-1])
+                for (loop = 0; loop < DEPTH; ++loop) {
+                    if (loop > 0) {
+                        d[loop] = fmt.parse(s[loop - 1]);
+                    }
+    
+                    s[loop] = fmt.format(d[loop]);
+    
+                    if (loop > 0) {
+                        if (smatch == 0) {
+                            boolean match = s[loop].equals(s[loop - 1]);
+                            if (smatch == 0) {
+                                if (match)
+                                    smatch = loop;
+                            } else
+                                if (!match)
+                                    errln("FAIL: String mismatch after match");
+                        }
+    
+                        if (dmatch == 0) {
+                            // {sfb} watch out here, this might not work
+                            boolean match = d[loop].getTime() == d[loop - 1].getTime();
+                            if (dmatch == 0) {
+                                if (match)
+                                    dmatch = loop;
+                            } else
+                                if (!match)
+                                    errln("FAIL: Date mismatch after match");
+                        }
+    
+                        if (smatch != 0 && dmatch != 0)
+                            break;
+                    }
+                }
+                // At this point loop == DEPTH if we've failed, otherwise loop is the
+                // max(smatch, dmatch), that is, the index at which we have string and
+                // date matching.
+    
+                // Date usually matches in 2.  Exceptions handled below.
+                int maxDmatch = 2;
+                int maxSmatch = 1;
+                if (dmatch > maxDmatch || smatch > maxSmatch) {
+                    //If the Date is BC
+                    if (!timeOnly && !hasEra && getField(d[0], Calendar.ERA) == GregorianCalendar.BC) {
+                        maxDmatch = 3;
+                        maxSmatch = 2;
+                    }
+                    if (hasZone && (fmt.getTimeZone().inDaylightTime(d[0]) || fmt.getTimeZone().inDaylightTime(d[1]) )) {
+                        maxSmatch = 2;
+                        if (timeOnly) {
+                            maxDmatch = 3;
+                        }
+                    }
+                }
+                    
+                if (dmatch > maxDmatch || smatch > maxSmatch) {
+                    SimpleDateFormat sdf = new SimpleDateFormat("EEEE, MMMM d, yyyy HH:mm:ss, z G", Locale.US);                    
+                    logln("Date = " + sdf.format(d[0]) + "; ms = " + d[0].getTime());
+                    logln("Dmatch: " + dmatch + " maxD: " + maxDmatch + " Smatch:" + smatch + " maxS:" + maxSmatch);
+                    errln("Pattern: " + pat + " failed to match" + "; ms = " + d[0].getTime());
+                    for (int j = 0; j <= loop && j < DEPTH; ++j) {
+                        StringBuffer temp = new StringBuffer("");
+                        FieldPosition pos = new FieldPosition(0);
+                        logln((j > 0 ? " P> " : "    ") + dateFormat.format(d[j], temp, pos) 
+                            + " F> " + s[j] + (j > 0 && d[j].getTime() == d[j - 1].getTime() ? " d==" : "")
+                            + (j > 0 && s[j].equals(s[j - 1]) ? " s==" : ""));
+                    }
+                }
+            }
+        } catch (ParseException e) {
+            errln("Exception: " + e.getMessage());
+            logln(e.toString());
+        }
+    }
+    
+    public int getField(Date d, int f) {
+        getFieldCal.setTime(d);
+        int ret = getFieldCal.get(f);    
+        return ret;
+    }
+    
+    public Date generateDate() {
+        double a = ran.nextDouble();
+        // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years
+        a *= 8000;
+        // Range from (4000-1970) BC to (8000-1970) AD
+        a -= 4000;
+        // Now scale up to ms
+        a *= 365.25 * 24 * 60 * 60 * 1000;
+        return new Date((long)a);
+    }
+}
diff --git a/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/src/com/ibm/icu/dev/test/format/DateFormatTest.java
new file mode 100755
index 0000000..7bdb05c
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/format/DateFormatTest.java
@@ -0,0 +1,985 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2001-2003, International Business Machines Corporation and         *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/DateFormatTest.java,v $ 
+ * $Date: 2003/09/04 00:58:15 $ 
+ * $Revision: 1.18 $
+ *
+ *****************************************************************************************
+ */
+
+/** 
+ * Port From:   ICU4C v1.8.1 : format : DateFormatTest
+ * Source File: $ICU4CRoot/source/test/intltest/dtfmttst.cpp
+ **/
+
+package com.ibm.icu.dev.test.format;
+
+import com.ibm.icu.text.*;
+import com.ibm.icu.util.*;
+import com.ibm.icu.impl.*;
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.util.TimeZone;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+import java.text.FieldPosition;
+
+public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
+    
+    public static void main(String[] args) throws Exception {
+        new DateFormatTest().run(args);
+    }
+    
+    // Test written by Wally Wedel and emailed to me.
+    public void TestWallyWedel() {
+        /*
+         * Instantiate a TimeZone so we can get the ids.
+         */
+        //TimeZone tz = new SimpleTimeZone(7, ""); //The variable is never used
+        /*
+         * Computational variables.
+         */
+        int offset, hours, minutes;
+        /*
+         * Instantiate a SimpleDateFormat set up to produce a full time
+         zone name.
+         */
+        SimpleDateFormat sdf = new SimpleDateFormat("zzzz");
+        /*
+         * A String array for the time zone ids.
+         */
+    
+        final String[] ids = TimeZone.getAvailableIDs();
+        int ids_length = ids.length; //when fixed the bug should comment it out
+    
+        /*
+         * How many ids do we have?
+         */
+        logln("Time Zone IDs size:" + ids_length);
+        /*
+         * Column headings (sort of)
+         */
+        logln("Ordinal ID offset(h:m) name");
+        /*
+         * Loop through the tzs.
+         */
+        Date today = new Date();
+        Calendar cal = Calendar.getInstance();
+        for (int i = 0; i < ids_length; i++) {
+            logln(i + " " + ids[i]);
+            TimeZone ttz = TimeZone.getTimeZone(ids[i]);
+            // offset = ttz.getRawOffset();
+            cal.setTimeZone(ttz);
+            cal.setTime(today);
+            offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
+            // logln(i + " " + ids[i] + " offset " + offset);
+            String sign = "+";
+            if (offset < 0) {
+                sign = "-";
+                offset = -offset;
+            }
+            hours = offset / 3600000;
+            minutes = (offset % 3600000) / 60000;
+            String dstOffset = sign + (hours < 10 ? "0" : "") + hours
+                    + ":" + (minutes < 10 ? "0" : "") + minutes; 
+            /*
+             * Instantiate a date so we can display the time zone name.
+             */
+            sdf.setTimeZone(ttz);
+            /*
+             * Format the output.
+             */
+            StringBuffer fmtOffset = new StringBuffer("");
+            FieldPosition pos = new FieldPosition(0);
+            
+            try {
+                fmtOffset = sdf.format(today, fmtOffset, pos);
+            } catch (Exception e) {            
+                logln("Exception:" + e);
+                continue;
+            }
+            // UnicodeString fmtOffset = tzS.toString();
+            String fmtDstOffset = null;
+            if (fmtOffset.toString().startsWith("GMT")) {
+                //fmtDstOffset = fmtOffset.substring(3);
+                fmtDstOffset = fmtOffset.substring(3, fmtOffset.length());
+            }
+            /*
+             * Show our result.
+             */
+    
+            boolean ok = fmtDstOffset == null || fmtDstOffset.equals("") || fmtDstOffset.equals(dstOffset);
+            if (ok) {
+                logln(i + " " + ids[i] + " " + dstOffset + " "
+                      + fmtOffset + (fmtDstOffset != null ? " ok" : " ?")); 
+            } else {
+                errln(i + " " + ids[i] + " " + dstOffset + " " + fmtOffset + " *** FAIL ***");
+            }
+        
+        }
+    }
+    
+    public void TestEquals() {
+        DateFormat fmtA = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); 
+        DateFormat fmtB = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); 
+        if (!fmtA.equals(fmtB))
+            errln("FAIL");    
+    }
+    
+    /**
+     * Test the parsing of 2-digit years.
+     */
+    public void TestTwoDigitYearDSTParse() {
+    
+        SimpleDateFormat fullFmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); 
+        SimpleDateFormat fmt = new SimpleDateFormat("dd-MMM-yy h:mm:ss 'o''clock' a z", Locale.ENGLISH); 
+        String s = "03-Apr-04 2:20:47 o'clock AM PST";
+    
+        /*
+         * SimpleDateFormat(pattern, locale) Construct a SimpleDateDateFormat using
+         * the givening pattern, the locale and using the TimeZone.getDefault();
+         * So it need to add the timezone offset on hour field. 
+         * ps. the Method Calendar.getTime() used by SimpleDateFormat.parse() always 
+         * return Date vaule with TimeZone.getDefault() [Richard/GCL]
+         */
+        
+        TimeZone defaultTZ = TimeZone.getDefault();
+        TimeZone PST = TimeZone.getTimeZone("PST");
+        int defaultOffset = defaultTZ.getRawOffset();
+        int PSTOffset = PST.getRawOffset();
+        int hour = 2 + (defaultOffset - PSTOffset) / (60*60*1000);
+        hour = (hour < 0) ? hour + 24 : hour;
+        try {
+            Date d = fmt.parse(s);
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(d);
+            //DSTOffset
+            hour += defaultTZ.inDaylightTime(d) ? 1 : 0;
+            
+            logln(s + " P> " + ((DateFormat) fullFmt).format(d));
+            int hr = cal.get(Calendar.HOUR_OF_DAY);
+            if (hr != hour)
+                errln("FAIL: Should parse to hour " + hour);
+        } catch (ParseException e) {
+            errln("Parse Error:" + e.getMessage());
+        }
+    
+    }
+    
+    /**
+     * Verify that returned field position indices are correct.
+     */
+    public void TestFieldPosition() {
+        DateFormat[] dateFormats = new DateFormat[4];
+        int dateFormats_length = dateFormats.length;
+        String fieldNames[] = {
+                "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH",   "DAY_OF_YEAR",
+                "DAY_OF_WEEK",   "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR","HOUR_OF_DAY","MINUTE",
+                "SECOND", "MILLISECOND", "ZONE_OFFSET" };
+        /* {sfb} This test was coded incorrectly.
+        / FieldPosition uses the fields in the class you are formatting with
+        / So, for example, to get the DATE field from a DateFormat use
+        / DateFormat.DATE_FIELD, __not__ Calendar.DATE
+        / The ordering of the expected values used previously was wrong.
+        / instead of re-ordering this mess of strings, just transform the index values */
+    
+        /* field values, in Calendar order */
+        final String[] expected =
+            { "", "1997", "August", "", "", "13", "", "Wednesday", "", "PM", "2", "", "34", "12", "", "PDT",
+            /* Following two added by weiv for two new fields */"", "1997", "#", 
+            /* # is a marker for "ao\xfbt" == "aou^t" */
+            "", "", "13", "", "mercredi", "", "", "", "14", "34", "", "", "PDT",
+            /* Following two added by weiv for two new fields */
+            "AD", "1997", "8", "33", "3", "13", "225", "Wed", "2", "PM", "2", "14", "34", "12", "513", "PDT",
+            /* Following two added by weiv for two new fields */
+            "AD",  "1997",  "August",  "0033", "0003", "0013", "0225", "Wednesday", "0002",
+             "PM", "0002",  "0014", "0034", "0012",   "0513", "Pacific Daylight Time",
+            /* Following two added by weiv for two new fields */ "1997", "0004", "" };
+    
+        Date someDate = new Date((long) 871508052513.0);
+        int j, exp;
+    
+        dateFormats[0] = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.US);
+        dateFormats[1] = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.FRANCE);
+        dateFormats[2] = new SimpleDateFormat("G, y, M, d, k, H, m, s, S, E, D, F, w, W, a, h, K, z, y, E", Locale.US);
+        dateFormats[3] = new SimpleDateFormat("GGGG, yyyy, MMMM, dddd, kkkk, HHHH, mmmm, ssss, SSSS, EEEE, DDDD, FFFF, wwww, WWWW, aaaa, hhhh, KKKK, zzzz, yyyy, EEEE", Locale.US);
+        //fix the jdk resources differences between jdk 1.2 and jdk 1.3
+        // String javaVersion = System.getProperty("java.version");
+        
+        for (j = 0, exp = 0; j < dateFormats_length; ++j) {
+          //  String str;
+            DateFormat df = dateFormats[j];
+            TimeZone tz = TimeZone.getTimeZone("PST");
+            df.setTimeZone(tz);
+            logln(" Pattern = " + ((SimpleDateFormat) df).toPattern());
+            // str = "";
+            try {
+                logln("  Result = " + df.format(someDate));
+            } catch (Exception e) {
+                System.out.println(e);
+            }
+            for (int i = 0; i < fieldNames.length; ++i) {
+                String field = getFieldText(df, i, someDate);
+                String expStr = "";
+                if (!expected[exp].substring(0).equals("#")) {
+                    expStr = expected[exp];
+                } else {
+                    // we cannot have latin-1 characters in source code, therefore we fix up the string for "aou^t" 
+                    expStr = expStr + "\u0061" + "\u006f" + "\u00fb" + "\u0074";
+                }
+                if (/*javaVersion.startsWith("1.2") &&*/ (exp==31)) {
+                    expStr = "GMT-07:00";
+                }
+                if (!field.equals(expStr))
+                    errln("FAIL: field #" + i + " " + fieldNames[i] + " = \"" + field + "\", expected \"" + expStr + "\"");
+                ++exp;
+            }
+        }
+    }
+    
+    // internal utility function
+    public String getFieldText(DateFormat df, int field, Date date) {
+        final int[] fgCalendarToDateFormatField ={
+            DateFormat.ERA_FIELD, 
+            DateFormat.YEAR_FIELD, 
+            DateFormat.MONTH_FIELD,
+            DateFormat.WEEK_OF_YEAR_FIELD, 
+            DateFormat.WEEK_OF_MONTH_FIELD,
+            DateFormat.DATE_FIELD, 
+            DateFormat.DAY_OF_YEAR_FIELD, 
+            DateFormat.DAY_OF_WEEK_FIELD,     
+            DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, 
+            DateFormat.AM_PM_FIELD,
+            DateFormat.HOUR1_FIELD, 
+            DateFormat.HOUR_OF_DAY0_FIELD, 
+            DateFormat.MINUTE_FIELD, 
+            DateFormat.SECOND_FIELD, 
+            DateFormat.MILLISECOND_FIELD,
+            DateFormat.TIMEZONE_FIELD
+            };
+        StringBuffer formatResult = new StringBuffer("");
+        // {sfb} added to convert Calendar Fields to DateFormat fields
+        FieldPosition pos = new FieldPosition(fgCalendarToDateFormatField[field]);
+        formatResult = df.format(date, formatResult, pos);    
+        return formatResult.substring(pos.getBeginIndex(), pos.getEndIndex());
+    }
+    
+    /**
+     * Verify that strings which contain incomplete specifications are parsed
+     * correctly.  In some instances, this means not being parsed at all, and
+     * returning an appropriate error.
+     */
+    public void TestPartialParse994() {
+    
+        SimpleDateFormat f = new SimpleDateFormat();
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1997, 1 - 1, 17, 10, 11, 42);
+        Date date = null;
+        tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17 10:11:42", cal.getTime());
+        tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17 10:", date);
+        tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17 10", date);
+        tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17 ", date);
+        tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17", date);
+    }
+    
+    // internal test subroutine, used by TestPartialParse994
+    public void tryPat994(SimpleDateFormat format, String pat, String str, Date expected) {
+        Date Null = null;
+        logln("Pattern \"" + pat + "\"   String \"" + str + "\"");
+        try {
+            format.applyPattern(pat);
+            Date date = format.parse(str);    
+            String f = ((DateFormat) format).format(date);
+            logln(" parse(" + str + ") -> " + date);
+            logln(" format -> " + f);
+            if (expected.equals(Null) || !date.equals(expected))
+                errln("FAIL: Expected null"); //" + expected);
+            if (!f.equals(str))
+                errln("FAIL: Expected " + str);
+        } catch (ParseException e) {
+            logln("ParseException: " + e.getMessage());
+            if (!(expected ==Null))
+                errln("FAIL: Expected " + expected);
+        } catch (Exception e) {
+            errln("*** Exception:");
+            e.printStackTrace();
+        }
+    }
+    
+    /**
+     * Verify the behavior of patterns in which digits for different fields run together
+     * without intervening separators.
+     */
+    public void TestRunTogetherPattern985() {
+        String format = "yyyyMMddHHmmssSSS";
+        String now, then;
+        //UBool flag;
+        SimpleDateFormat formatter = new SimpleDateFormat(format);
+        Date date1 = new Date();
+        now = ((DateFormat) formatter).format(date1);
+        logln(now);
+        ParsePosition pos = new ParsePosition(0);
+        Date date2 = formatter.parse(now, pos);
+        if (date2 == null)
+            then = "Parse stopped at " + pos.getIndex();
+        else
+            then = ((DateFormat) formatter).format(date2);
+        logln(then);
+        if (date2 == null || !date2.equals(date1))
+            errln("FAIL");
+    }
+
+    /**
+     * Verify the behavior of patterns in which digits for different fields run together
+     * without intervening separators.
+     */
+    public void TestRunTogetherPattern917() {
+        SimpleDateFormat fmt;
+        String myDate;
+        fmt = new SimpleDateFormat("yyyy/MM/dd");
+        myDate = "1997/02/03";
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1997, 2 - 1, 3);
+        _testIt917(fmt, myDate, cal.getTime());
+        fmt = new SimpleDateFormat("yyyyMMdd");
+        myDate = "19970304";
+        cal.clear();
+        cal.set(1997, 3 - 1, 4);
+        _testIt917(fmt, myDate, cal.getTime());
+    
+    }
+    
+    // internal test subroutine, used by TestRunTogetherPattern917
+    public void _testIt917(SimpleDateFormat fmt, String str, Date expected) {
+        logln("pattern=" + fmt.toPattern() + "   string=" + str);
+        Date o = new Date();
+        o = (Date) ((DateFormat) fmt).parseObject(str, new ParsePosition(0));
+        logln("Parsed object: " + o);
+        if (o == null || !o.equals(expected))
+            errln("FAIL: Expected " + expected);
+        String formatted = o==null? "null" : ((DateFormat) fmt).format(o);
+        logln( "Formatted string: " + formatted);
+        if (!formatted.equals(str))
+            errln( "FAIL: Expected " + str);
+    }
+    
+    /**
+     * Verify the handling of Czech June and July, which have the unique attribute that
+     * one is a proper prefix substring of the other.
+     */
+    public void TestCzechMonths459() {
+        DateFormat fmt = DateFormat.getDateInstance(DateFormat.FULL, new Locale("cs", "", "")); 
+        logln("Pattern " + ((SimpleDateFormat) fmt).toPattern());
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1997, Calendar.JUNE, 15);
+        Date june = cal.getTime();
+        cal.clear();
+        cal.set(1997, Calendar.JULY, 15);
+        Date july = cal.getTime();
+        String juneStr = fmt.format(june);
+        String julyStr = fmt.format(july);
+        try {
+            logln("format(June 15 1997) = " + juneStr);
+            Date d = fmt.parse(juneStr);
+            String s = fmt.format(d);
+            int month, yr, day, hr, min, sec;
+            cal.setTime(d);
+            yr = cal.get(Calendar.YEAR) - 1900;
+            month = cal.get(Calendar.MONTH);
+            day = cal.get(Calendar.DAY_OF_WEEK);
+            hr = cal.get(Calendar.HOUR_OF_DAY);
+            min = cal.get(Calendar.MINUTE);
+            sec = cal.get(Calendar.SECOND);
+            logln("  . parse . " + s + " (month = " + month + ")");
+            if (month != Calendar.JUNE)
+                errln("FAIL: Month should be June");
+            logln("format(July 15 1997) = " + julyStr);
+            d = fmt.parse(julyStr);
+            s = fmt.format(d);
+            cal.setTime(d);
+            yr = cal.get(Calendar.YEAR) - 1900;
+            month = cal.get(Calendar.MONTH);
+            day = cal.get(Calendar.DAY_OF_WEEK);
+            hr = cal.get(Calendar.HOUR_OF_DAY);
+            min = cal.get(Calendar.MINUTE);
+            sec = cal.get(Calendar.SECOND);
+            logln("  . parse . " + s + " (month = " + month + ")");
+            if (month != Calendar.JULY)
+                errln("FAIL: Month should be July");
+        } catch (ParseException e) {
+            errln(e.getMessage());
+        }
+    }
+    
+    /**
+     * Test the handling of 'D' in patterns.
+     */
+    public void TestLetterDPattern212() {
+        String dateString = "1995-040.05:01:29";
+        String bigD = "yyyy-DDD.hh:mm:ss";
+        String littleD = "yyyy-ddd.hh:mm:ss";
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1995, 0, 1, 5, 1, 29);
+        Date expLittleD = cal.getTime();
+        Date expBigD = new Date((long) (expLittleD.getTime() + 39 * 24 * 3600000.0));
+        expLittleD = expBigD; // Expect the same, with default lenient parsing
+        logln("dateString= " + dateString);
+        SimpleDateFormat formatter = new SimpleDateFormat(bigD);
+        ParsePosition pos = new ParsePosition(0);
+        Date myDate = formatter.parse(dateString, pos);
+        logln("Using " + bigD + " . " + myDate);
+        if (!myDate.equals(expBigD))
+            errln("FAIL: Expected " + expBigD);
+        formatter = new SimpleDateFormat(littleD);
+        pos = new ParsePosition(0);
+        myDate = formatter.parse(dateString, pos);
+        logln("Using " + littleD + " . " + myDate);
+        if (!myDate.equals(expLittleD))
+            errln("FAIL: Expected " + expLittleD);
+    }
+    
+    /**
+     * Test the day of year pattern.
+     */
+    public void TestDayOfYearPattern195() {
+        Calendar cal = Calendar.getInstance();
+        Date today = cal.getTime();
+        int year,month,day; 
+        year = cal.get(Calendar.YEAR);
+        month = cal.get(Calendar.MONTH);
+        day = cal.get(Calendar.DAY_OF_MONTH);
+        cal.clear();
+        cal.set(year, month, day);
+        Date expected = cal.getTime();
+        logln("Test Date: " + today);
+        SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
+        tryPattern(sdf, today, null, expected);
+        tryPattern(sdf, today, "G yyyy DDD", expected);
+    }
+    
+    // interl test subroutine, used by TestDayOfYearPattern195
+    public void tryPattern(SimpleDateFormat sdf, Date d, String pattern, Date expected) {
+        if (pattern != null)
+            sdf.applyPattern(pattern);
+        logln("pattern: " + sdf.toPattern());
+        String formatResult = ((DateFormat) sdf).format(d);
+        logln(" format -> " + formatResult);
+        try {
+            Date d2 = sdf.parse(formatResult);
+            logln(" parse(" + formatResult + ") -> " + d2);
+            if (!d2.equals(expected))
+                errln("FAIL: Expected " + expected);
+            String format2 = ((DateFormat) sdf).format(d2);
+            logln(" format -> " + format2);
+            if (!formatResult.equals(format2))
+                errln("FAIL: Round trip drift");
+        } catch (Exception e) {
+            errln(e.getMessage());
+        }
+    }
+    
+    /**
+     * Test the handling of single quotes in patterns.
+     */
+    public void TestQuotePattern161() {
+        SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy 'at' hh:mm:ss a zzz", Locale.US); 
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1997, Calendar.AUGUST, 13, 10, 42, 28);
+        Date currentTime_1 = cal.getTime();
+        String dateString = ((DateFormat) formatter).format(currentTime_1);
+        String exp = "08/13/1997 at 10:42:28 AM ";
+        logln("format(" + currentTime_1 + ") = " + dateString);
+        if (!dateString.substring(0, exp.length()).equals(exp))
+            errln("FAIL: Expected " + exp);
+    
+    }
+        
+    /**
+     * Verify the correct behavior when handling invalid input strings.
+     */
+    public void TestBadInput135() {
+        int looks[] = {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}; 
+        int looks_length = looks.length;
+        final String[] strings = {"Mar 15", "Mar 15 1997", "asdf", "3/1/97 1:23:", "3/1/00 1:23:45 AM"}; 
+        int strings_length = strings.length;
+        DateFormat full = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.US); 
+        String expected = "March 1, 2000 1:23:45 AM ";
+        for (int i = 0; i < strings_length; ++i) {
+            final String text = strings[i];
+            for (int j = 0; j < looks_length; ++j) {
+                int dateLook = looks[j];
+                for (int k = 0; k < looks_length; ++k) {
+                    int timeLook = looks[k];
+                    DateFormat df = DateFormat.getDateTimeInstance(dateLook, timeLook, Locale.US); 
+                    String prefix = text + ", " + dateLook + "/" + timeLook + ": "; 
+                    try {
+                        Date when = df.parse(text);
+                        if (when == null) {
+                            errln(prefix + "SHOULD NOT HAPPEN: parse returned null.");
+                            continue;
+                        }  
+                        if (when != null) {
+                            String format;
+                            format = full.format(when);
+                            logln(prefix + "OK: " + format);
+                            if (!format.substring(0, expected.length()).equals(expected))
+                                errln("FAIL: Expected " + expected);
+                        }
+                    } catch(java.text.ParseException e) {
+                        logln(e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+    
+    /**
+     * Verify the correct behavior when parsing an array of inputs against an
+     * array of patterns, with known results.  The results are encoded after
+     * the input strings in each row.
+     */
+    public void TestBadInput135a() {
+    
+        SimpleDateFormat dateParse = new SimpleDateFormat("", Locale.US);
+        final String ss;
+        Date date;
+        String[] parseFormats ={"MMMM d, yyyy", "MMMM d yyyy", "M/d/yy",
+                                "d MMMM, yyyy", "d MMMM yyyy",  "d MMMM",
+                                "MMMM d", "yyyy", "h:mm a MMMM d, yyyy" };
+        String[] inputStrings = {
+            "bogus string", null, null, null, null, null, null, null, null, null,
+                "April 1, 1997", "April 1, 1997", null, null, null, null, null, "April 1", null, null,
+                "Jan 1, 1970", "January 1, 1970", null, null, null, null, null, "January 1", null, null,
+                "Jan 1 2037", null, "January 1 2037", null, null, null, null, "January 1", null, null,
+                "1/1/70", null, null, "1/1/70", null, null, null, null, "0001", null,
+                "5 May 1997", null, null, null, null, "5 May 1997", "5 May", null, "0005", null,
+                "16 May", null, null, null, null, null, "16 May", null, "0016", null,
+                "April 30", null, null, null, null, null, null, "April 30", null, null,
+                "1998", null, null, null, null, null, null, null, "1998", null,
+                "1", null, null, null, null, null, null, null, "0001", null,
+                "3:00 pm Jan 1, 1997", null, null, null, null, null, null, null, "0003", "3:00 PM January 1, 1997",
+                };
+        final int PF_LENGTH = parseFormats.length;
+        final int INPUT_LENGTH = inputStrings.length;
+    
+        dateParse.applyPattern("d MMMM, yyyy");
+        dateParse.setTimeZone(TimeZone.getDefault());
+        ss = "not parseable";
+        //    String thePat;
+        logln("Trying to parse \"" + ss + "\" with " + dateParse.toPattern());
+        try {
+            date = dateParse.parse(ss);
+        } catch (Exception ex) {
+            logln("FAIL:" + ex);
+        }
+        for (int i = 0; i < INPUT_LENGTH; i += (PF_LENGTH + 1)) {
+            ParsePosition parsePosition = new ParsePosition(0);
+            String s = inputStrings[i];
+            for (int index = 0; index < PF_LENGTH; ++index) {
+                final String expected = inputStrings[i + 1 + index];
+                dateParse.applyPattern(parseFormats[index]);
+                dateParse.setTimeZone(TimeZone.getDefault());
+                try {
+                    parsePosition.setIndex(0);
+                    date = dateParse.parse(s, parsePosition);
+                    if (parsePosition.getIndex() != 0) {
+                        String s1, s2;
+                        s1 = s.substring(0, parsePosition.getIndex());
+                        s2 = s.substring(parsePosition.getIndex(), s.length());
+                        if (date == null) {
+                            errln("ERROR: null result fmt=\"" + parseFormats[index]
+                                    + "\" pos=" + parsePosition.getIndex()
+                                    + " " + s1 + "|" + s2);
+                        } else {
+                            String result = ((DateFormat) dateParse).format(date);
+                            logln("Parsed \"" + s + "\" using \"" + dateParse.toPattern() + "\" to: " + result);
+                            if (expected == null)
+                                errln("FAIL: Expected parse failure");
+                            else
+                                if (!result.equals(expected))
+                                    errln("FAIL: Expected " + expected);
+                        }
+                    } else
+                        if (expected != null) {
+                            errln("FAIL: Expected " + expected + " from \"" + s
+                                    + "\" with \"" + dateParse.toPattern()+ "\"");
+                        }
+                } catch (Exception ex) {
+                    logln("FAIL:" + ex);
+                }
+            }
+        }
+    
+    }
+    
+    /**
+     * Test the parsing of two-digit years.
+     */
+    public void TestTwoDigitYear() {
+        DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(117 + 1900, Calendar.JUNE, 5);
+        parse2DigitYear(fmt, "6/5/17", cal.getTime());
+        cal.clear();
+        cal.set(34 + 1900, Calendar.JUNE, 4);
+        parse2DigitYear(fmt, "6/4/34", cal.getTime());
+    }
+    
+    // internal test subroutine, used by TestTwoDigitYear
+    public void parse2DigitYear(DateFormat fmt, String str, Date expected) {
+        try {
+            Date d = fmt.parse(str);
+            logln("Parsing \""+ str+ "\" with "+ ((SimpleDateFormat) fmt).toPattern()
+                    + "  => "+ d); 
+            if (!d.equals(expected))
+                errln( "FAIL: Expected " + expected);
+        } catch (ParseException e) {
+            errln(e.getMessage());
+        }
+    }
+    
+    /**
+     * Test the formatting of time zones.
+     */
+    public void TestDateFormatZone061() {
+        Date date;
+        DateFormat formatter;
+        date = new Date(859248000000l);
+        logln("Date 1997/3/25 00:00 GMT: " + date);
+        formatter = new SimpleDateFormat("dd-MMM-yyyyy HH:mm", Locale.UK);
+        formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
+        String temp = formatter.format(date);
+        logln("Formatted in GMT to: " + temp);
+        try {
+            Date tempDate = formatter.parse(temp);
+            logln("Parsed to: " + tempDate);
+            if (!tempDate.equals(date))
+                errln("FAIL: Expected " + date);
+        } catch (Throwable t) {
+            System.out.println(t);
+        }
+    
+    }
+    
+    /**
+     * Test the formatting of time zones.
+     */
+    public void TestDateFormatZone146() {
+        TimeZone saveDefault = TimeZone.getDefault();
+    
+        //try {
+        TimeZone thedefault = TimeZone.getTimeZone("GMT");
+        TimeZone.setDefault(thedefault);
+        // java.util.Locale.setDefault(new java.util.Locale("ar", "", ""));
+    
+        // check to be sure... its GMT all right
+        TimeZone testdefault = TimeZone.getDefault();
+        String testtimezone = testdefault.getID();
+        if (testtimezone.equals("GMT"))
+            logln("Test timezone = " + testtimezone);
+        else
+            errln("Test timezone should be GMT, not " + testtimezone);
+    
+        // now try to use the default GMT time zone
+        GregorianCalendar greenwichcalendar = new GregorianCalendar(1997, 3, 4, 23, 0);
+        //*****************************greenwichcalendar.setTimeZone(TimeZone.getDefault());
+        //greenwichcalendar.set(1997, 3, 4, 23, 0);
+        // try anything to set hour to 23:00 !!!
+        greenwichcalendar.set(Calendar.HOUR_OF_DAY, 23);
+        // get time
+        Date greenwichdate = greenwichcalendar.getTime();
+        // format every way
+        String DATA[] = {
+                "simple format:  ", "04/04/97 23:00 GMT", 
+                "MM/dd/yy HH:mm z", "full format:    ", 
+                "Friday, April 4, 1997 11:00:00 o'clock PM GMT", 
+                "EEEE, MMMM d, yyyy h:mm:ss 'o''clock' a z", 
+                "long format:    ", "April 4, 1997 11:00:00 PM GMT", 
+                "MMMM d, yyyy h:mm:ss a z", "default format: ", 
+                "04-Apr-97 11:00:00 PM", "dd-MMM-yy h:mm:ss a", 
+                "short format:   ", "4/4/97 11:00 PM", 
+                "M/d/yy h:mm a"}; 
+        int DATA_length = DATA.length;
+    
+        for (int i = 0; i < DATA_length; i += 3) {
+            DateFormat fmt = new SimpleDateFormat(DATA[i + 2], Locale.ENGLISH);
+            fmt.setCalendar(greenwichcalendar);
+            String result = fmt.format(greenwichdate);
+            logln(DATA[i] + result);
+            if (!result.equals(DATA[i + 1]))
+                errln("FAIL: Expected " + DATA[i + 1] + ", got " + result);
+        }
+        //}
+        //finally {
+        TimeZone.setDefault(saveDefault);
+        //}
+    
+    }
+    
+    /**
+     * Test the formatting of dates in different locales.
+     */
+    public void TestLocaleDateFormat() {
+    
+        Date testDate = new Date(874306800000l); //Mon Sep 15 00:00:00 PDT 1997
+        DateFormat dfFrench = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.FRENCH);
+        DateFormat dfUS = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.US);
+        //Set TimeZone = PDT
+        TimeZone tz = TimeZone.getTimeZone("PST");
+        dfFrench.setTimeZone(tz);
+        dfUS.setTimeZone(tz);
+        String expectedFRENCH_JDK12 = "lundi 15 septembre 1997 00 h 00 GMT-07:00";
+        //String expectedFRENCH = "lundi 15 septembre 1997 00 h 00 PDT";
+        String expectedUS = "Monday, September 15, 1997 12:00:00 AM PDT";
+        logln("Date set to : " + testDate);
+        String out = dfFrench.format(testDate);
+        logln("Date Formated with French Locale " + out);
+        //fix the jdk resources differences between jdk 1.2 and jdk 1.3
+		/* our own data only has GMT-xxxx information here
+        String javaVersion = System.getProperty("java.version");
+        if (javaVersion.startsWith("1.2")) {
+            if (!out.equals(expectedFRENCH_JDK12))
+                errln("FAIL: Expected " + expectedFRENCH_JDK12);
+        } else {
+            if (!out.equals(expectedFRENCH))
+                errln("FAIL: Expected " + expectedFRENCH);
+        }
+		*/
+		if (!out.equals(expectedFRENCH_JDK12))
+			errln("FAIL: Expected " + expectedFRENCH_JDK12);
+        out = dfUS.format(testDate);
+        logln("Date Formated with US Locale " + out);
+        if (!out.equals(expectedUS))
+            errln("FAIL: Expected " + expectedUS);
+    }
+
+    /**
+     * Test DateFormat(Calendar) API
+     */
+    public void TestDateFormatCalendar() {
+        DateFormat date=null, time=null, full=null;
+        Calendar cal=null;
+        ParsePosition pos = new ParsePosition(0);
+        String str;
+        Date when;
+
+        /* Create a formatter for date fields. */
+        date = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        if (date == null) {
+            errln("FAIL: getDateInstance failed");
+            return;
+        }
+
+        /* Create a formatter for time fields. */
+        time = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US);
+        if (time == null) {
+            errln("FAIL: getTimeInstance failed");
+            return;
+        }
+
+        /* Create a full format for output */
+        full = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL,
+                                              Locale.US);
+        if (full == null) {
+            errln("FAIL: getInstance failed");
+            return;
+        }
+
+        /* Create a calendar */
+        cal = Calendar.getInstance(Locale.US);
+        if (cal == null) {
+            errln("FAIL: Calendar.getInstance failed");
+            return;
+        }
+
+        /* Parse the date */
+        cal.clear();
+        str = "4/5/2001";
+        pos.setIndex(0);
+        date.parse(str, cal, pos);
+        if (pos.getIndex() != str.length()) {
+            errln("FAIL: DateFormat.parse(4/5/2001) failed at " +
+                  pos.getIndex());
+            return;
+        }
+
+        /* Parse the time */
+        str = "5:45 PM";
+        pos.setIndex(0);
+        time.parse(str, cal, pos);
+        if (pos.getIndex() != str.length()) {
+            errln("FAIL: DateFormat.parse(17:45) failed at " +
+                  pos.getIndex());
+            return;
+        }
+    
+        /* Check result */
+        when = cal.getTime();
+        str = full.format(when);
+        // Thursday, April 5, 2001 5:45:00 PM PDT 986517900000
+        if (when.getTime() == 986517900000.0) {
+            logln("Ok: Parsed result: " + str);
+        } else {
+            errln("FAIL: Parsed result: " + str + ", exp 4/5/2001 5:45 PM");
+        }
+    }
+
+    /**
+     * Test DateFormat's parsing of space characters.  See jitterbug 1916.
+     */
+    public void TestSpaceParsing() {
+
+        String DATA[] = {
+            "yyyy MM dd",
+
+            // pattern, input, expexted output (in quotes)
+            "MMMM d yy", " 04 05 06",  null, // MMMM wants Apr/April
+            null,        "04 05 06",   null,
+            "MM d yy",   " 04 05 06",  "2006 04 05",
+            null,        "04 05 06",   "2006 04 05",
+            "MMMM d yy", " Apr 05 06", "2006 04 05",
+            null,        "Apr 05 06",  "2006 04 05",
+        };
+
+        expectParse(DATA, new Locale("en", "", ""));
+    }
+
+    /**
+     * Test handling of "HHmmss" pattern.
+     */
+    public void TestExactCountFormat() {
+        String DATA[] = {
+            "yyyy MM dd HH:mm:ss",
+
+            // pattern, input, expected parse or null if expect parse failure
+            "HHmmss", "123456", "1970 01 01 12:34:56",
+            null,     "12345",  "1970 01 01 01:23:45",
+            null,     "1234",   null,
+            null,     "00-05",  null,
+            null,     "12-34",  null,
+            null,     "00+05",  null,
+            "ahhmm",  "PM730",  "1970 01 01 19:30:00",
+        };
+
+        expectParse(DATA, new Locale("en", "", ""));
+    }
+
+    /**
+     * Test handling of white space.
+     */
+    public void TestWhiteSpaceParsing() {
+        String DATA[] = {
+            "yyyy MM dd",
+
+            // pattern, input, expected parse or null if expect parse failure
+
+            // Pattern space run should parse input text space run
+            "MM   d yy",   " 04 01 03",    "2003 04 01",
+            null,          " 04  01   03 ", "2003 04 01",
+        };
+
+        expectParse(DATA, new Locale("en", "", ""));
+    }
+
+    public void TestCoverage() {
+        Date now = new Date();
+        Calendar cal = new GregorianCalendar();
+        DateFormat f = DateFormat.getTimeInstance();
+        logln("time: " + f.format(now));
+
+        int hash = f.hashCode(); // sigh, everyone overrides this
+        
+        f = DateFormat.getInstance(cal);
+        if(hash == f.hashCode()){
+            errln("FAIL: hashCode equal for inequal objects");
+        }
+        logln("time again: " + f.format(now));
+
+        f = DateFormat.getTimeInstance(cal, DateFormat.FULL);
+        logln("time yet again: " + f.format(now));
+
+        ResourceBundle rb = ICULocaleData.getLocaleElements("de_DE");
+        DateFormatSymbols sym = new DateFormatSymbols(rb, Locale.GERMANY);
+        DateFormatSymbols sym2 = (DateFormatSymbols)sym.clone();
+        if (sym.hashCode() != sym2.hashCode()) {
+            errln("fail, date format symbols hashcode not equal");
+        }
+        if (!sym.equals(sym2)) {
+            errln("fail, date format symbols not equal");
+        }
+    }
+
+    /**
+     * Test parsing.  Input is an array that starts with the following
+     * header:
+     *
+     * [0]   = pattern string to parse [i+2] with
+     *
+     * followed by test cases, each of which is 3 array elements:
+     *
+     * [i]   = pattern, or null to reuse prior pattern
+     * [i+1] = input string
+     * [i+2] = expected parse result (parsed with pattern [0])
+     *
+     * If expect parse failure, then [i+2] should be null.
+     */
+    void expectParse(String[] data, Locale loc) {
+        Date FAIL = null;
+        String FAIL_STR = "parse failure";
+        int i = 0;
+
+        SimpleDateFormat fmt = new SimpleDateFormat("", loc);
+        SimpleDateFormat ref = new SimpleDateFormat(data[i++], loc);
+        SimpleDateFormat gotfmt = new SimpleDateFormat("G yyyy MM dd HH:mm:ss z", loc);
+
+        String currentPat = null;
+        while (i<data.length) {
+            String pattern  = data[i++];
+            String input    = data[i++];
+            String expected = data[i++];
+
+            if (pattern != null) {
+                fmt.applyPattern(pattern);
+                currentPat = pattern;
+            }
+            String gotstr = FAIL_STR;
+            Date got;
+            try {
+                got = fmt.parse(input);
+                gotstr = gotfmt.format(got);
+            } catch (ParseException e1) {
+                got = FAIL;
+            }
+
+            Date exp = FAIL;
+            String expstr = FAIL_STR;
+            if (expected != null) {
+                expstr = expected;
+                try {
+                    exp = ref.parse(expstr);
+                } catch (ParseException e2) {
+                    errln("FAIL: Internal test error");
+                }
+            }
+
+            if (got == exp || (got != null && got.equals(exp))) {
+                logln("Ok: " + input + " x " +
+                      currentPat + " => " + gotstr);                
+            } else {
+                errln("FAIL: " + input + " x " +
+                      currentPat + " => " + gotstr + ", expected " +
+                      expstr);
+            }
+        }    
+    }
+}
diff --git a/src/com/ibm/icu/dev/test/format/IntlTestDateFormatAPI.java b/src/com/ibm/icu/dev/test/format/IntlTestDateFormatAPI.java
new file mode 100755
index 0000000..848c438
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/format/IntlTestDateFormatAPI.java
@@ -0,0 +1,208 @@
+/*****************************************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/IntlTestDateFormatAPI.java,v $ 
+ * $Date: 2003/09/04 00:58:16 $ 
+ * $Revision: 1.9 $
+ *
+ *****************************************************************************************
+ **/
+
+/** 
+ * Port From:   JDK 1.4b1 : java.text.Format.IntlTestDateFormatAPI
+ * Source File: java/text/format/IntlTestDateFormatAPI.java
+ **/
+
+/*
+    @test 1.4 98/03/06
+    @summary test International Date Format API
+*/
+/***************************************************************************
+*
+*   Copyright (C) 1996-2003, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*
+************************************************************************/
+
+package com.ibm.icu.dev.test.format;
+
+import com.ibm.icu.util.*;
+import com.ibm.icu.text.*;
+import java.util.Locale;
+import java.util.Date;
+import java.util.TimeZone;
+import java.text.ParsePosition;
+import java.text.FieldPosition;
+import java.text.ParseException;
+
+public class IntlTestDateFormatAPI extends com.ibm.icu.dev.test.TestFmwk
+{
+    public static void main(String[] args) throws Exception {
+        new IntlTestDateFormatAPI().run(args);
+    }
+
+    // Test that the equals method works correctly.
+    public void TestEquals()
+    {
+        // Create two objects at different system times
+        DateFormat a = DateFormat.getInstance();
+        Date start = Calendar.getInstance().getTime();
+        while (true) {
+            // changed to remove compiler warnings.
+            if (!start.equals(Calendar.getInstance().getTime())) {
+                break; // Wait for time to change
+            }
+        }
+        DateFormat b = DateFormat.getInstance();
+
+        if (!(a.equals(b)))
+            errln("FAIL: DateFormat objects created at different times are unequal.");
+
+        // Why has this test been disabled??? - aliu
+//        if (b instanceof SimpleDateFormat)
+//        {
+//            //double ONE_YEAR = 365*24*60*60*1000.0; //The variable is never used
+//            try {
+//                ((SimpleDateFormat)b).setTwoDigitStartDate(start.getTime() + 50*ONE_YEAR);
+//                if (a.equals(b))
+//                    errln("FAIL: DateFormat objects with different two digit start dates are equal.");
+//            }
+//            catch (Exception e) {
+//                errln("FAIL: setTwoDigitStartDate failed.");
+//            }
+//        }
+    }
+
+    // This test checks various generic API methods in DateFormat to achieve 100% API coverage.
+    public void TestAPI()
+    {
+        logln("DateFormat API test---"); logln("");
+        Locale.setDefault(Locale.ENGLISH);
+
+
+        // ======= Test constructors
+
+        logln("Testing DateFormat constructors");
+
+        DateFormat def = DateFormat.getInstance();
+        DateFormat fr = DateFormat.getTimeInstance(DateFormat.FULL, Locale.FRENCH);
+        DateFormat it = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.ITALIAN);
+        DateFormat de = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.GERMAN);
+
+        // ======= Test equality
+
+        logln("Testing equality operator");
+
+        if( fr.equals(it) ) {
+            errln("ERROR: equals failed");
+        }
+
+        // ======= Test various format() methods
+
+        logln("Testing various format() methods");
+
+        Date d = new Date((long)837039928046.0);
+
+        StringBuffer res1 = new StringBuffer();
+        StringBuffer res2 = new StringBuffer();
+        String res3 = new String();
+        FieldPosition pos1 = new FieldPosition(0);
+        FieldPosition pos2 = new FieldPosition(0);
+
+        res1 = fr.format(d, res1, pos1);
+        logln("" + d.getTime() + " formatted to " + res1);
+
+        res2 = it.format(d, res2, pos2);
+        logln("" + d.getTime() + " formatted to " + res2);
+
+        res3 = de.format(d);
+        logln("" + d.getTime() + " formatted to " + res3);
+
+        // ======= Test parse()
+
+        logln("Testing parse()");
+
+        String text = new String("02/03/76 2:50 AM, CST");
+        Object result1 = new Date();
+        Date result2 = new Date();
+        Date result3 = new Date();
+        ParsePosition pos = new ParsePosition(0);
+        ParsePosition pos01 = new ParsePosition(0);
+
+        result1 = def.parseObject(text, pos);
+        if (result1 == null) {
+            errln("ERROR: parseObject() failed for " + text);
+        }
+        logln(text + " parsed into " + ((Date)result1).getTime());
+
+        try {
+            result2 = def.parse(text);
+        }
+        catch (ParseException e) {
+            errln("ERROR: parse() failed");
+        }
+        logln(text + " parsed into " + result2.getTime());
+
+        result3 = def.parse(text, pos01);
+        if (result3 == null) {
+            errln("ERROR: parse() failed for " + text);
+        }
+        logln(text + " parsed into " + result3.getTime());
+
+
+        // ======= Test getters and setters
+
+        logln("Testing getters and setters");
+
+        final Locale[] locales = DateFormat.getAvailableLocales();
+        long count = locales.length;
+        logln("Got " + count + " locales" );
+        for(int i = 0; i < count; i++) {
+            String name;
+            name = locales[i].getDisplayName();
+            logln(name);
+        }
+
+        fr.setLenient(it.isLenient());
+        if(fr.isLenient() != it.isLenient()) {
+            errln("ERROR: setLenient() failed");
+        }
+
+        final Calendar cal = def.getCalendar();
+        Calendar newCal = (Calendar) cal.clone();
+        de.setCalendar(newCal);
+        it.setCalendar(newCal);
+        if( ! de.getCalendar().equals(it.getCalendar())) {
+            errln("ERROR: set Calendar() failed");
+        }
+
+        final NumberFormat nf = def.getNumberFormat();
+        NumberFormat newNf = (NumberFormat) nf.clone();
+        de.setNumberFormat(newNf);
+        it.setNumberFormat(newNf);
+        if( ! de.getNumberFormat().equals(it.getNumberFormat())) {
+            errln("ERROR: set NumberFormat() failed");
+        }
+
+        final TimeZone tz = def.getTimeZone();
+        TimeZone newTz = (TimeZone) tz.clone();
+        de.setTimeZone(newTz);
+        it.setTimeZone(newTz);
+        if( ! de.getTimeZone().equals(it.getTimeZone())) {
+            errln("ERROR: set TimeZone() failed");
+        }
+
+        // ======= Test getStaticClassID()
+
+//        logln("Testing instanceof()");
+
+//        try {
+//            DateFormat test = new SimpleDateFormat();
+
+//            if (! (test instanceof SimpleDateFormat)) {
+//                errln("ERROR: instanceof failed");
+//            }
+//        }
+//        catch (Exception e) {
+//            errln("ERROR: Couldn't create a DateFormat");
+//        }
+    }
+}
diff --git a/src/com/ibm/icu/impl/ZoneMeta.java b/src/com/ibm/icu/impl/ZoneMeta.java
new file mode 100644
index 0000000..b501308
--- /dev/null
+++ b/src/com/ibm/icu/impl/ZoneMeta.java
@@ -0,0 +1,190 @@
+/*
+**********************************************************************
+* Copyright (c) 2003, International Business Machines
+* Corporation and others.  All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: September 4 2003
+* Since: ICU 2.8
+**********************************************************************
+*/
+package com.ibm.icu.impl;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.TimeZone;
+
+/**
+ * This class, not to be instantiated, implements the meta-data
+ * missing from the underlying core JDK implementation of time zones.
+ * There are two missing features: Obtaining a list of available zones
+ * for a given country (as defined by the Olson database), and
+ * obtaining a list of equivalent zones for a given zone (as defined
+ * by Olson links).
+ *
+ * This class uses a data class, ZoneMetaData, which is created by the
+ * tool tz2icu.
+ *
+ * @author Alan Liu
+ * @since ICU 2.8
+ */
+public final class ZoneMeta {
+
+    /**
+     * Returns a String array containing all system TimeZone IDs
+     * associated with the given country.  These IDs may be passed to
+     * <code>TimeZone.getTimeZone()</code> to construct the
+     * corresponding TimeZone object.
+     * @param a two-letter ISO 3166 country code, or <code>null</code>
+     * to return zones not associated with any country
+     * @return an array of IDs for system TimeZones in the given
+     * country.  If there are none, return a zero-length array.
+     */
+    public static synchronized String[] getAvailableIDs(String country) {
+        if (country == null) {
+            country = "";
+        }
+        if (COUNTRY_MAP == null) {
+            Set valid = getValidIDs();
+            Set unused = new TreeSet(valid);
+
+            ArrayList list = new ArrayList(); // reuse this below
+
+            COUNTRY_MAP = new TreeMap();
+            for (int i=0; i<ZoneMetaData.COUNTRY.length; ++i) {
+                String[] z = ZoneMetaData.COUNTRY[i];
+
+                // Add all valid IDs to list
+                list.clear();
+                for (int j=1; j<z.length; ++j) {
+                    if (valid.contains(z[j])) {
+                        list.add(z[j]);
+                        unused.remove(z[j]);
+                    }
+                }
+                
+                COUNTRY_MAP.put(z[0], list.toArray(EMPTY));
+            }
+
+            // If there are zones in the underlying JDK that are NOT
+            // in our metadata, then assign them to the non-country.
+            // (Better than nothing.)
+            if (unused.size() > 0) {
+                list.clear();
+                list.add(Arrays.asList((String[]) COUNTRY_MAP.get("")));
+                list.add(unused);
+                Collections.sort(list);                
+                COUNTRY_MAP.put("", list.toArray(EMPTY));
+            }
+        }
+        String[] result = (String[]) COUNTRY_MAP.get(country);
+        if (result == null) {
+            result = EMPTY; // per API spec
+        }
+        return result;
+    }
+
+    /**
+     * Returns the number of IDs in the equivalency group that
+     * includes the given ID.  An equivalency group contains zones
+     * that behave identically to the given zone.
+     *
+     * <p>If there are no equivalent zones, then this method returns
+     * 0.  This means either the given ID is not a valid zone, or it
+     * is and there are no other equivalent zones.
+     * @param id a system time zone ID
+     * @return the number of zones in the equivalency group containing
+     * 'id', or zero if there are no equivalent zones.
+     * @see #getEquivalentID
+     */
+    public static synchronized int countEquivalentIDs(String id) {
+        if (EQUIV_MAP == null) {
+            createEquivMap();
+        }
+        String[] result = (String[]) EQUIV_MAP.get(id);
+        return (result == null) ? 0 : result.length;
+    }
+
+    /**
+     * Returns an ID in the equivalency group that includes the given
+     * ID.  An equivalency group contains zones that behave
+     * identically to the given zone.
+     *
+     * <p>The given index must be in the range 0..n-1, where n is the
+     * value returned by <code>countEquivalentIDs(id)</code>.  For
+     * some value of 'index', the returned value will be equal to the
+     * given id.  If the given id is not a valid system time zone, or
+     * if 'index' is out of range, then returns an empty string.
+     * @param id a system time zone ID
+     * @param index a value from 0 to n-1, where n is the value
+     * returned by <code>countEquivalentIDs(id)</code>
+     * @return the ID of the index-th zone in the equivalency group
+     * containing 'id', or an empty string if 'id' is not a valid
+     * system ID or 'index' is out of range
+     * @see #countEquivalentIDs
+     */
+    public static synchronized String getEquivalentID(String id, int index) {
+        if (EQUIV_MAP == null) {
+            createEquivMap();
+        }
+        String[] a = (String[]) EQUIV_MAP.get(id);
+        return (a != null && index >= 0 && index < a.length) ?
+            a[index] : "";
+    }
+
+    /**
+     * Create the equivalency map.
+     */
+    private static void createEquivMap() {
+        EQUIV_MAP = new TreeMap();
+
+        Set valid = getValidIDs();
+
+        ArrayList list = new ArrayList(); // reuse this below
+
+        for (int i=0; i<ZoneMetaData.EQUIV.length; ++i) {
+            String[] z = ZoneMetaData.EQUIV[i];
+            list.clear();
+            for (int j=0; j<z.length; ++j) {
+                if (valid.contains(z[j])) {
+                    list.add(z[j]);
+                }
+            }
+            if (list.size() > 1) {
+                String[] a = (String[]) list.toArray(EMPTY);
+                for (int j=0; j<a.length; ++j) {
+                    EQUIV_MAP.put(a[j], a);
+                }
+            }
+        }
+    }
+
+    private static Set getValidIDs() {
+        // Construct list of time zones that are valid, according
+        // to the current underlying core JDK.  We have to do this
+        // at runtime since we don't know what we're running on.
+        Set valid = new TreeSet();
+        valid.addAll(Arrays.asList(TimeZone.getAvailableIDs()));
+        return valid;
+    }
+
+    /**
+     * Empty string array.
+     */
+    private static final String[] EMPTY = new String[0];
+
+    /**
+     * Map of country codes to zone lists.
+     */
+    private static Map COUNTRY_MAP = null;
+
+    /**
+     * Map of zones to equivalent zone lists.
+     */
+    private static Map EQUIV_MAP = null;
+}
diff --git a/src/com/ibm/icu/impl/ZoneMetaData.java b/src/com/ibm/icu/impl/ZoneMetaData.java
new file mode 100644
index 0000000..de73de6
--- /dev/null
+++ b/src/com/ibm/icu/impl/ZoneMetaData.java
@@ -0,0 +1,354 @@
+//---------------------------------------------------------
+// Copyright (C) 2003, International Business Machines
+// Corporation and others.  All Rights Reserved.
+//---------------------------------------------------------
+// Build tool: tz2icu
+// Build date: Thu Sep  4 13:58:40 2003
+// Olson source: ftp://elsie.nci.nih.gov/pub/
+//---------------------------------------------------------
+// >> !!! >>   THIS IS A MACHINE-GENERATED FILE   << !!! <<
+// >> !!! >>>            DO NOT EDIT             <<< !!! <<
+//---------------------------------------------------------
+
+package com.ibm.icu.impl;
+
+public final class ZoneMetaData {
+  public static final String[][] EQUIV = {
+    { "Africa/Addis_Ababa", "EAT" },
+    { "ART", "Africa/Cairo", "Egypt" },
+    { "Africa/Harare", "CAT" },
+    { "Africa/Tripoli", "Libya" },
+    { "America/Adak", "America/Atka", "US/Aleutian" },
+    { "AST", "America/Anchorage", "SystemV/YST9YDT", "US/Alaska" },
+    { "AGT", "America/Buenos_Aires" },
+    { "America/Chicago", "CST", "CST6CDT", "SystemV/CST6CDT", "US/Central" },
+    { "America/Cordoba", "America/Rosario" },
+    { "America/Denver", "America/Shiprock", "MST7MDT", "Navajo", "SystemV/MST7MDT", "US/Mountain" },
+    { "America/Detroit", "US/Michigan" },
+    { "America/Edmonton", "Canada/Mountain" },
+    { "America/Halifax", "Canada/Atlantic", "SystemV/AST4ADT" },
+    { "America/Havana", "Cuba" },
+    { "America/Indiana/Knox", "America/Knox_IN", "US/Indiana-Starke" },
+    { "America/Fort_Wayne", "America/Indiana/Indianapolis", "America/Indianapolis", "EST", "IET", "SystemV/EST5", "US/East-Indiana" },
+    { "America/Jamaica", "Jamaica" },
+    { "America/Los_Angeles", "PST", "PST8PDT", "SystemV/PST8PDT", "US/Pacific", "US/Pacific-New" },
+    { "America/Kentucky/Louisville", "America/Louisville" },
+    { "America/Manaus", "Brazil/West" },
+    { "America/Mazatlan", "Mexico/BajaSur" },
+    { "America/Mexico_City", "Mexico/General" },
+    { "America/Montreal", "Canada/Eastern" },
+    { "America/New_York", "EST5EDT", "SystemV/EST5EDT", "US/Eastern" },
+    { "America/Noronha", "Brazil/DeNoronha" },
+    { "America/Phoenix", "MST", "PNT", "SystemV/MST7", "US/Arizona" },
+    { "America/Puerto_Rico", "PRT", "SystemV/AST4" },
+    { "America/Regina", "Canada/East-Saskatchewan", "Canada/Saskatchewan", "SystemV/CST6" },
+    { "America/Porto_Acre", "America/Rio_Branco", "Brazil/Acre" },
+    { "America/Santiago", "Chile/Continental" },
+    { "America/Sao_Paulo", "BET", "Brazil/East" },
+    { "America/St_Johns", "CNT", "Canada/Newfoundland" },
+    { "America/St_Thomas", "America/Virgin" },
+    { "America/Ensenada", "America/Tijuana", "Mexico/BajaNorte" },
+    { "America/Vancouver", "Canada/Pacific" },
+    { "America/Whitehorse", "Canada/Yukon" },
+    { "America/Winnipeg", "Canada/Central" },
+    { "Antarctica/McMurdo", "Antarctica/South_Pole" },
+    { "Asia/Ashgabat", "Asia/Ashkhabad" },
+    { "Asia/Calcutta", "IST" },
+    { "Asia/Chongqing", "Asia/Chungking" },
+    { "Asia/Dacca", "Asia/Dhaka", "BST" },
+    { "Asia/Hong_Kong", "Hongkong" },
+    { "Asia/Jerusalem", "Asia/Tel_Aviv", "Israel" },
+    { "Asia/Karachi", "PLT" },
+    { "Asia/Macao", "Asia/Macau" },
+    { "Asia/Makassar", "Asia/Ujung_Pandang" },
+    { "Asia/Nicosia", "Europe/Nicosia" },
+    { "Asia/Riyadh87", "Mideast/Riyadh87" },
+    { "Asia/Riyadh88", "Mideast/Riyadh88" },
+    { "Asia/Riyadh89", "Mideast/Riyadh89" },
+    { "Asia/Saigon", "VST" },
+    { "Asia/Seoul", "ROK" },
+    { "Asia/Shanghai", "CTT", "PRC" },
+    { "Asia/Singapore", "Singapore" },
+    { "Asia/Taipei", "ROC" },
+    { "Asia/Tehran", "Iran" },
+    { "Asia/Thimbu", "Asia/Thimphu" },
+    { "Asia/Tokyo", "JST", "Japan" },
+    { "Asia/Ulaanbaatar", "Asia/Ulan_Bator" },
+    { "Asia/Yerevan", "NET" },
+    { "Atlantic/Reykjavik", "Iceland" },
+    { "Australia/Adelaide", "Australia/South" },
+    { "Australia/Brisbane", "Australia/Queensland" },
+    { "Australia/Broken_Hill", "Australia/Yancowinna" },
+    { "ACT", "Australia/Darwin", "Australia/North" },
+    { "Australia/Hobart", "Australia/Tasmania" },
+    { "Australia/LHI", "Australia/Lord_Howe" },
+    { "Australia/Melbourne", "Australia/Victoria" },
+    { "Australia/Perth", "Australia/West" },
+    { "AET", "Australia/ACT", "Australia/Canberra", "Australia/NSW", "Australia/Sydney" },
+    { "Etc/GMT", "Etc/GMT+0", "Etc/GMT-0", "Etc/GMT0", "Etc/Greenwich", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich" },
+    { "Etc/UCT", "UCT" },
+    { "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UTC", "Universal", "Zulu" },
+    { "Europe/Belgrade", "Europe/Ljubljana", "Europe/Sarajevo", "Europe/Skopje", "Europe/Zagreb" },
+    { "Europe/Chisinau", "Europe/Tiraspol" },
+    { "Eire", "Europe/Dublin" },
+    { "Asia/Istanbul", "Europe/Istanbul", "Turkey" },
+    { "Europe/Lisbon", "Portugal" },
+    { "Europe/London", "GB", "GB-Eire" },
+    { "Europe/Moscow", "W-SU" },
+    { "Arctic/Longyearbyen", "Atlantic/Jan_Mayen", "Europe/Oslo" },
+    { "ECT", "Europe/Paris" },
+    { "Europe/Bratislava", "Europe/Prague" },
+    { "Europe/Rome", "Europe/San_Marino", "Europe/Vatican" },
+    { "Europe/Warsaw", "Poland" },
+    { "MIT", "Pacific/Apia" },
+    { "NST", "NZ", "Pacific/Auckland" },
+    { "NZ-CHAT", "Pacific/Chatham" },
+    { "Chile/EasterIsland", "Pacific/Easter" },
+    { "Pacific/Gambier", "SystemV/YST9" },
+    { "Pacific/Guadalcanal", "SST" },
+    { "HST", "Pacific/Honolulu", "SystemV/HST10", "US/Hawaii" },
+    { "Kwajalein", "Pacific/Kwajalein" },
+    { "Pacific/Pago_Pago", "Pacific/Samoa", "US/Samoa" },
+    { "Pacific/Pitcairn", "SystemV/PST8" }
+  };
+  public static final String[][] COUNTRY = {
+    { "", "Asia/Riyadh87", "Asia/Riyadh88", "Asia/Riyadh89", "CET", "EET", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "Factory", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "MET", "Mideast/Riyadh87", "Mideast/Riyadh88", "Mideast/Riyadh89", "UCT", "UTC", "Universal", "WET", "Zulu" },
+    { "AD", "Europe/Andorra" },
+    { "AE", "Asia/Dubai" },
+    { "AF", "Asia/Kabul" },
+    { "AG", "America/Antigua" },
+    { "AI", "America/Anguilla" },
+    { "AL", "Europe/Tirane" },
+    { "AM", "Asia/Yerevan", "NET" },
+    { "AN", "America/Curacao" },
+    { "AO", "Africa/Luanda" },
+    { "AQ", "Antarctica/Casey", "Antarctica/Davis", "Antarctica/DumontDUrville", "Antarctica/Mawson", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", "Antarctica/South_Pole", "Antarctica/Syowa", "Antarctica/Vostok" },
+    { "AR", "AGT", "America/Buenos_Aires", "America/Catamarca", "America/Cordoba", "America/Jujuy", "America/Mendoza", "America/Rosario" },
+    { "AS", "Pacific/Pago_Pago", "Pacific/Samoa", "US/Samoa" },
+    { "AT", "Europe/Vienna" },
+    { "AU", "ACT", "AET", "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", "Australia/Canberra", "Australia/Darwin", "Australia/Hobart", "Australia/LHI", "Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", "Australia/NSW", "Australia/North", "Australia/Perth", "Australia/Queensland", "Australia/South", "Australia/Sydney", "Australia/Tasmania", "Australia/Victoria", "Australia/West", "Australia/Yancowinna" },
+    { "AW", "America/Aruba" },
+    { "AZ", "Asia/Baku" },
+    { "BA", "Europe/Sarajevo" },
+    { "BB", "America/Barbados" },
+    { "BD", "Asia/Dacca", "Asia/Dhaka", "BST" },
+    { "BE", "Europe/Brussels" },
+    { "BF", "Africa/Ouagadougou" },
+    { "BG", "Europe/Sofia" },
+    { "BH", "Asia/Bahrain" },
+    { "BI", "Africa/Bujumbura" },
+    { "BJ", "Africa/Porto-Novo" },
+    { "BM", "Atlantic/Bermuda" },
+    { "BN", "Asia/Brunei" },
+    { "BO", "America/La_Paz" },
+    { "BR", "America/Araguaina", "America/Belem", "America/Boa_Vista", "America/Cuiaba", "America/Eirunepe", "America/Fortaleza", "America/Maceio", "America/Manaus", "America/Noronha", "America/Porto_Acre", "America/Porto_Velho", "America/Recife", "America/Rio_Branco", "America/Sao_Paulo", "BET", "Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West" },
+    { "BS", "America/Nassau" },
+    { "BT", "Asia/Thimbu", "Asia/Thimphu" },
+    { "BW", "Africa/Gaborone" },
+    { "BY", "Europe/Minsk" },
+    { "BZ", "America/Belize" },
+    { "CA", "America/Cambridge_Bay", "America/Dawson", "America/Dawson_Creek", "America/Edmonton", "America/Glace_Bay", "America/Goose_Bay", "America/Halifax", "America/Inuvik", "America/Iqaluit", "America/Montreal", "America/Nipigon", "America/Pangnirtung", "America/Rainy_River", "America/Rankin_Inlet", "America/Regina", "America/St_Johns", "America/Swift_Current", "America/Thunder_Bay", "America/Vancouver", "America/Whitehorse", "America/Winnipeg", "America/Yellowknife", "CNT", "Canada/Atlantic", "Canada/Central", "Canada/East-Saskatchewan", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "SystemV/AST4ADT", "SystemV/CST6" },
+    { "CC", "Indian/Cocos" },
+    { "CD", "Africa/Kinshasa", "Africa/Lubumbashi" },
+    { "CF", "Africa/Bangui" },
+    { "CG", "Africa/Brazzaville" },
+    { "CH", "Europe/Zurich" },
+    { "CI", "Africa/Abidjan" },
+    { "CK", "Pacific/Rarotonga" },
+    { "CL", "America/Santiago", "Chile/Continental", "Chile/EasterIsland", "Pacific/Easter" },
+    { "CM", "Africa/Douala" },
+    { "CN", "Asia/Chongqing", "Asia/Chungking", "Asia/Harbin", "Asia/Kashgar", "Asia/Shanghai", "Asia/Urumqi", "CTT", "PRC" },
+    { "CO", "America/Bogota" },
+    { "CR", "America/Costa_Rica" },
+    { "CU", "America/Havana", "Cuba" },
+    { "CV", "Atlantic/Cape_Verde" },
+    { "CX", "Indian/Christmas" },
+    { "CY", "Asia/Nicosia", "Europe/Nicosia" },
+    { "CZ", "Europe/Bratislava", "Europe/Prague" },
+    { "DE", "Europe/Berlin" },
+    { "DJ", "Africa/Djibouti" },
+    { "DK", "Europe/Copenhagen" },
+    { "DM", "America/Dominica" },
+    { "DO", "America/Santo_Domingo" },
+    { "DZ", "Africa/Algiers" },
+    { "EC", "America/Guayaquil", "Pacific/Galapagos" },
+    { "EE", "Europe/Tallinn" },
+    { "EG", "ART", "Africa/Cairo", "Egypt" },
+    { "EH", "Africa/El_Aaiun" },
+    { "ER", "Africa/Asmera" },
+    { "ES", "Africa/Ceuta", "Atlantic/Canary", "Europe/Madrid" },
+    { "ET", "Africa/Addis_Ababa", "EAT" },
+    { "FI", "Europe/Helsinki" },
+    { "FJ", "Pacific/Fiji" },
+    { "FK", "Atlantic/Stanley" },
+    { "FM", "Pacific/Kosrae", "Pacific/Ponape", "Pacific/Truk", "Pacific/Yap" },
+    { "FO", "Atlantic/Faeroe" },
+    { "FR", "ECT", "Europe/Paris" },
+    { "GA", "Africa/Libreville" },
+    { "GB", "Europe/Belfast", "Europe/London", "GB", "GB-Eire" },
+    { "GD", "America/Grenada" },
+    { "GE", "Asia/Tbilisi" },
+    { "GF", "America/Cayenne" },
+    { "GH", "Africa/Accra" },
+    { "GI", "Europe/Gibraltar" },
+    { "GL", "America/Danmarkshavn", "America/Godthab", "America/Scoresbysund", "America/Thule" },
+    { "GM", "Africa/Banjul" },
+    { "GN", "Africa/Conakry" },
+    { "GP", "America/Guadeloupe" },
+    { "GQ", "Africa/Malabo" },
+    { "GR", "Europe/Athens" },
+    { "GS", "Atlantic/South_Georgia" },
+    { "GT", "America/Guatemala" },
+    { "GU", "Pacific/Guam" },
+    { "GW", "Africa/Bissau" },
+    { "GY", "America/Guyana" },
+    { "HK", "Asia/Hong_Kong", "Hongkong" },
+    { "HN", "America/Tegucigalpa" },
+    { "HR", "Europe/Zagreb" },
+    { "HT", "America/Port-au-Prince" },
+    { "HU", "Europe/Budapest" },
+    { "ID", "Asia/Jakarta", "Asia/Jayapura", "Asia/Makassar", "Asia/Pontianak", "Asia/Ujung_Pandang" },
+    { "IE", "Eire", "Europe/Dublin" },
+    { "IL", "Asia/Jerusalem", "Asia/Tel_Aviv", "Israel" },
+    { "IN", "Asia/Calcutta", "IST" },
+    { "IO", "Indian/Chagos" },
+    { "IQ", "Asia/Baghdad" },
+    { "IR", "Asia/Tehran", "Iran" },
+    { "IS", "Atlantic/Reykjavik", "Iceland" },
+    { "IT", "Europe/Rome", "Europe/San_Marino", "Europe/Vatican" },
+    { "JM", "America/Jamaica", "Jamaica" },
+    { "JO", "Asia/Amman" },
+    { "JP", "Asia/Tokyo", "JST", "Japan" },
+    { "KE", "Africa/Nairobi" },
+    { "KG", "Asia/Bishkek" },
+    { "KH", "Asia/Phnom_Penh" },
+    { "KI", "Pacific/Enderbury", "Pacific/Kiritimati", "Pacific/Tarawa" },
+    { "KM", "Indian/Comoro" },
+    { "KN", "America/St_Kitts" },
+    { "KP", "Asia/Pyongyang" },
+    { "KR", "Asia/Seoul", "ROK" },
+    { "KW", "Asia/Kuwait" },
+    { "KY", "America/Cayman" },
+    { "KZ", "Asia/Almaty", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Oral", "Asia/Qyzylorda" },
+    { "LA", "Asia/Vientiane" },
+    { "LB", "Asia/Beirut" },
+    { "LC", "America/St_Lucia" },
+    { "LI", "Europe/Vaduz" },
+    { "LK", "Asia/Colombo" },
+    { "LR", "Africa/Monrovia" },
+    { "LS", "Africa/Maseru" },
+    { "LT", "Europe/Vilnius" },
+    { "LU", "Europe/Luxembourg" },
+    { "LV", "Europe/Riga" },
+    { "LY", "Africa/Tripoli", "Libya" },
+    { "MA", "Africa/Casablanca" },
+    { "MC", "Europe/Monaco" },
+    { "MD", "Europe/Chisinau", "Europe/Tiraspol" },
+    { "MG", "Indian/Antananarivo" },
+    { "MH", "Kwajalein", "Pacific/Kwajalein", "Pacific/Majuro" },
+    { "MK", "Europe/Skopje" },
+    { "ML", "Africa/Bamako", "Africa/Timbuktu" },
+    { "MM", "Asia/Rangoon" },
+    { "MN", "Asia/Choibalsan", "Asia/Hovd", "Asia/Ulaanbaatar", "Asia/Ulan_Bator" },
+    { "MO", "Asia/Macao", "Asia/Macau" },
+    { "MP", "Pacific/Saipan" },
+    { "MQ", "America/Martinique" },
+    { "MR", "Africa/Nouakchott" },
+    { "MS", "America/Montserrat" },
+    { "MT", "Europe/Malta" },
+    { "MU", "Indian/Mauritius" },
+    { "MV", "Indian/Maldives" },
+    { "MW", "Africa/Blantyre" },
+    { "MX", "America/Cancun", "America/Chihuahua", "America/Ensenada", "America/Hermosillo", "America/Mazatlan", "America/Merida", "America/Mexico_City", "America/Monterrey", "America/Tijuana", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General" },
+    { "MY", "Asia/Kuala_Lumpur", "Asia/Kuching" },
+    { "MZ", "Africa/Maputo" },
+    { "NA", "Africa/Windhoek" },
+    { "NC", "Pacific/Noumea" },
+    { "NE", "Africa/Niamey" },
+    { "NF", "Pacific/Norfolk" },
+    { "NG", "Africa/Lagos" },
+    { "NI", "America/Managua" },
+    { "NL", "Europe/Amsterdam" },
+    { "NO", "Arctic/Longyearbyen", "Atlantic/Jan_Mayen", "Europe/Oslo" },
+    { "NP", "Asia/Katmandu" },
+    { "NR", "Pacific/Nauru" },
+    { "NU", "Pacific/Niue" },
+    { "NZ", "NST", "NZ", "NZ-CHAT", "Pacific/Auckland", "Pacific/Chatham" },
+    { "OM", "Asia/Muscat" },
+    { "PA", "America/Panama" },
+    { "PE", "America/Lima" },
+    { "PF", "Pacific/Gambier", "Pacific/Marquesas", "Pacific/Tahiti", "SystemV/YST9" },
+    { "PG", "Pacific/Port_Moresby" },
+    { "PH", "Asia/Manila" },
+    { "PK", "Asia/Karachi", "PLT" },
+    { "PL", "Europe/Warsaw", "Poland" },
+    { "PM", "America/Miquelon" },
+    { "PN", "Pacific/Pitcairn", "SystemV/PST8" },
+    { "PR", "America/Puerto_Rico", "PRT", "SystemV/AST4" },
+    { "PS", "Asia/Gaza" },
+    { "PT", "Atlantic/Azores", "Atlantic/Madeira", "Europe/Lisbon", "Portugal" },
+    { "PW", "Pacific/Palau" },
+    { "PY", "America/Asuncion" },
+    { "QA", "Asia/Qatar" },
+    { "RE", "Indian/Reunion" },
+    { "RO", "Europe/Bucharest" },
+    { "RU", "Asia/Anadyr", "Asia/Irkutsk", "Asia/Kamchatka", "Asia/Krasnoyarsk", "Asia/Magadan", "Asia/Novosibirsk", "Asia/Omsk", "Asia/Sakhalin", "Asia/Vladivostok", "Asia/Yakutsk", "Asia/Yekaterinburg", "Europe/Kaliningrad", "Europe/Moscow", "Europe/Samara", "W-SU" },
+    { "RW", "Africa/Kigali" },
+    { "SA", "Asia/Riyadh" },
+    { "SB", "Pacific/Guadalcanal", "SST" },
+    { "SC", "Indian/Mahe" },
+    { "SD", "Africa/Khartoum" },
+    { "SE", "Europe/Stockholm" },
+    { "SG", "Asia/Singapore", "Singapore" },
+    { "SH", "Atlantic/St_Helena" },
+    { "SI", "Europe/Ljubljana" },
+    { "SJ", "Arctic/Longyearbyen", "Atlantic/Jan_Mayen" },
+    { "SK", "Europe/Bratislava" },
+    { "SL", "Africa/Freetown" },
+    { "SM", "Europe/San_Marino" },
+    { "SN", "Africa/Dakar" },
+    { "SO", "Africa/Mogadishu" },
+    { "SR", "America/Paramaribo" },
+    { "ST", "Africa/Sao_Tome" },
+    { "SV", "America/El_Salvador" },
+    { "SY", "Asia/Damascus" },
+    { "SZ", "Africa/Mbabane" },
+    { "TC", "America/Grand_Turk" },
+    { "TD", "Africa/Ndjamena" },
+    { "TF", "Indian/Kerguelen" },
+    { "TG", "Africa/Lome" },
+    { "TH", "Asia/Bangkok" },
+    { "TJ", "Asia/Dushanbe" },
+    { "TK", "Pacific/Fakaofo" },
+    { "TL", "Asia/Dili" },
+    { "TM", "Asia/Ashgabat", "Asia/Ashkhabad" },
+    { "TN", "Africa/Tunis" },
+    { "TO", "Pacific/Tongatapu" },
+    { "TR", "Asia/Istanbul", "Europe/Istanbul", "Turkey" },
+    { "TT", "America/Port_of_Spain" },
+    { "TV", "Pacific/Funafuti" },
+    { "TW", "Asia/Taipei", "ROC" },
+    { "TZ", "Africa/Dar_es_Salaam" },
+    { "UA", "Europe/Kiev", "Europe/Simferopol", "Europe/Uzhgorod", "Europe/Zaporozhye" },
+    { "UG", "Africa/Kampala" },
+    { "UM", "Pacific/Johnston", "Pacific/Midway", "Pacific/Wake" },
+    { "US", "AST", "America/Adak", "America/Anchorage", "America/Atka", "America/Boise", "America/Chicago", "America/Denver", "America/Detroit", "America/Fort_Wayne", "America/Indiana/Indianapolis", "America/Indiana/Knox", "America/Indiana/Marengo", "America/Indiana/Vevay", "America/Indianapolis", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Knox_IN", "America/Los_Angeles", "America/Louisville", "America/Menominee", "America/New_York", "America/Nome", "America/North_Dakota/Center", "America/Phoenix", "America/Shiprock", "America/Yakutat", "CST", "CST6CDT", "EST", "EST5EDT", "HST", "IET", "MST", "MST7MDT", "Navajo", "PNT", "PST", "PST8PDT", "Pacific/Honolulu", "SystemV/CST6CDT", "SystemV/EST5", "SystemV/EST5EDT", "SystemV/HST10", "SystemV/MST7", "SystemV/MST7MDT", "SystemV/PST8PDT", "SystemV/YST9YDT", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Pacific-New" },
+    { "UY", "America/Montevideo" },
+    { "UZ", "Asia/Samarkand", "Asia/Tashkent" },
+    { "VA", "Europe/Vatican" },
+    { "VC", "America/St_Vincent" },
+    { "VE", "America/Caracas" },
+    { "VG", "America/Tortola" },
+    { "VI", "America/St_Thomas", "America/Virgin" },
+    { "VN", "Asia/Saigon", "VST" },
+    { "VU", "Pacific/Efate" },
+    { "WF", "Pacific/Wallis" },
+    { "WS", "MIT", "Pacific/Apia" },
+    { "YE", "Asia/Aden" },
+    { "YT", "Indian/Mayotte" },
+    { "YU", "Europe/Belgrade", "Europe/Ljubljana", "Europe/Sarajevo", "Europe/Skopje", "Europe/Zagreb" },
+    { "ZA", "Africa/Johannesburg" },
+    { "ZM", "Africa/Lusaka" },
+    { "ZW", "Africa/Harare", "CAT" }
+  };
+}
diff --git a/src/com/ibm/icu/text/DateFormat.java b/src/com/ibm/icu/text/DateFormat.java
new file mode 100755
index 0000000..cac9648
--- /dev/null
+++ b/src/com/ibm/icu/text/DateFormat.java
@@ -0,0 +1,911 @@
+/*
+*   Copyright (C) 1996-2003, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*/
+
+package com.ibm.icu.text;
+
+import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.util.Calendar;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.TimeZone;
+
+/**
+ * DateFormat is an abstract class for date/time formatting subclasses which
+ * formats and parses dates or time in a language-independent manner.
+ * The date/time formatting subclass, such as SimpleDateFormat, allows for
+ * formatting (i.e., date -> text), parsing (text -> date), and
+ * normalization.  The date is represented as a <code>Date</code> object or
+ * as the milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * <p>DateFormat provides many class methods for obtaining default date/time
+ * formatters based on the default or a given loacle and a number of formatting
+ * styles. The formatting styles include FULL, LONG, MEDIUM, and SHORT. More
+ * detail and examples of using these styles are provided in the method
+ * descriptions.
+ *
+ * <p>DateFormat helps you to format and parse dates for any locale.
+ * Your code can be completely independent of the locale conventions for
+ * months, days of the week, or even the calendar format: lunar vs. solar.
+ *
+ * <p>To format a date for the current Locale, use one of the
+ * static factory methods:
+ * <pre>
+ *  myString = DateFormat.getDateInstance().format(myDate);
+ * </pre>
+ * <p>If you are formatting multiple numbers, it is
+ * more efficient to get the format and use it multiple times so that
+ * the system doesn't have to fetch the information about the local
+ * language and country conventions multiple times.
+ * <pre>
+ *  DateFormat df = DateFormat.getDateInstance();
+ *  for (int i = 0; i < a.length; ++i) {
+ *    output.println(df.format(myDate[i]) + "; ");
+ *  }
+ * </pre>
+ * <p>To format a number for a different Locale, specify it in the
+ * call to getDateInstance().
+ * <pre>
+ *  DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
+ * </pre>
+ * <p>You can use a DateFormat to parse also.
+ * <pre>
+ *  myDate = df.parse(myString);
+ * </pre>
+ * <p>Use getDateInstance to get the normal date format for that country.
+ * There are other static factory methods available.
+ * Use getTimeInstance to get the time format for that country.
+ * Use getDateTimeInstance to get a date and time format. You can pass in 
+ * different options to these factory methods to control the length of the
+ * result; from SHORT to MEDIUM to LONG to FULL. The exact result depends
+ * on the locale, but generally:
+ * <ul><li>SHORT is completely numeric, such as 12.13.52 or 3:30pm
+ * <li>MEDIUM is longer, such as Jan 12, 1952
+ * <li>LONG is longer, such as January 12, 1952 or 3:30:32pm
+ * <li>FULL is pretty completely specified, such as
+ * Tuesday, April 12, 1952 AD or 3:30:42pm PST.
+ * </ul>
+ *
+ * <p>You can also set the time zone on the format if you wish.
+ * If you want even more control over the format or parsing,
+ * (or want to give your users more control),
+ * you can try casting the DateFormat you get from the factory methods
+ * to a SimpleDateFormat. This will work for the majority
+ * of countries; just remember to put it in a try block in case you
+ * encounter an unusual one.
+ *
+ * <p>You can also use forms of the parse and format methods with
+ * ParsePosition and FieldPosition to
+ * allow you to
+ * <ul><li>progressively parse through pieces of a string.
+ * <li>align any particular field, or find out where it is for selection
+ * on the screen.
+ * </ul>
+ *
+ * <h4>Synchronization</h4>
+ *
+ * Date formats are not synchronized. It is recommended to create separate 
+ * format instances for each thread. If multiple threads access a format 
+ * concurrently, it must be synchronized externally. 
+ *
+ * @see          Format
+ * @see          NumberFormat
+ * @see          SimpleDateFormat
+ * @see          com.ibm.icu.util.Calendar
+ * @see          com.ibm.icu.util.GregorianCalendar
+ * @see          com.ibm.icu.util.TimeZone
+ * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
+ * @stable ICU 2.0
+ */
+public abstract class DateFormat extends Format {
+
+    /**
+     * The calendar that <code>DateFormat</code> uses to produce the time field
+     * values needed to implement date and time formatting.  Subclasses should
+     * initialize this to a calendar appropriate for the locale associated with
+     * this <code>DateFormat</code>.
+     * @serial
+     * @stable ICU 2.0
+     */
+    protected Calendar calendar;
+
+    /**
+     * The number formatter that <code>DateFormat</code> uses to format numbers
+     * in dates and times.  Subclasses should initialize this to a number format
+     * appropriate for the locale associated with this <code>DateFormat</code>.
+     * @serial
+     * @stable ICU 2.0
+     */
+    protected NumberFormat numberFormat;
+
+    /**
+     * Useful constant for ERA field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int ERA_FIELD = 0;
+
+    /**
+     * Useful constant for YEAR field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int YEAR_FIELD = 1;
+
+    /**
+     * Useful constant for MONTH field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int MONTH_FIELD = 2;
+
+    /**
+     * Useful constant for DATE field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int DATE_FIELD = 3;
+
+    /**
+     * Useful constant for one-based HOUR_OF_DAY field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * HOUR_OF_DAY1_FIELD is used for the one-based 24-hour clock.
+     * For example, 23:59 + 01:00 results in 24:59.
+     * @stable ICU 2.0
+     */
+    public final static int HOUR_OF_DAY1_FIELD = 4;
+
+    /**
+     * Useful constant for zero-based HOUR_OF_DAY field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * HOUR_OF_DAY0_FIELD is used for the zero-based 24-hour clock.
+     * For example, 23:59 + 01:00 results in 00:59.
+     * @stable ICU 2.0
+     */
+    public final static int HOUR_OF_DAY0_FIELD = 5;
+
+    /**
+     * Useful constant for MINUTE field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int MINUTE_FIELD = 6;
+
+    /**
+     * Useful constant for SECOND field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int SECOND_FIELD = 7;
+
+    /**
+     * Useful constant for MILLISECOND field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int MILLISECOND_FIELD = 8;
+
+    /**
+     * Useful constant for DAY_OF_WEEK field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_WEEK_FIELD = 9;
+
+    /**
+     * Useful constant for DAY_OF_YEAR field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_YEAR_FIELD = 10;
+
+    /**
+     * Useful constant for DAY_OF_WEEK_IN_MONTH field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11;
+
+    /**
+     * Useful constant for WEEK_OF_YEAR field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int WEEK_OF_YEAR_FIELD = 12;
+
+    /**
+     * Useful constant for WEEK_OF_MONTH field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int WEEK_OF_MONTH_FIELD = 13;
+
+    /**
+     * Useful constant for AM_PM field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int AM_PM_FIELD = 14;
+
+    /**
+     * Useful constant for one-based HOUR field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * HOUR1_FIELD is used for the one-based 12-hour clock.
+     * For example, 11:30 PM + 1 hour results in 12:30 AM.
+     * @stable ICU 2.0
+     */
+    public final static int HOUR1_FIELD = 15;
+
+    /**
+     * Useful constant for zero-based HOUR field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * HOUR0_FIELD is used for the zero-based 12-hour clock.
+     * For example, 11:30 PM + 1 hour results in 00:30 AM.
+     * @stable ICU 2.0
+     */
+    public final static int HOUR0_FIELD = 16;
+
+    /**
+     * Useful constant for TIMEZONE field alignment.
+     * Used in FieldPosition of date/time formatting.
+     * @stable ICU 2.0
+     */
+    public final static int TIMEZONE_FIELD = 17;
+
+    // Proclaim serial compatibility with 1.1 FCS
+    private static final long serialVersionUID = 7218322306649953788L;
+
+    /**
+     * Overrides Format.
+     * Formats a time object into a time string. Examples of time objects
+     * are a time value expressed in milliseconds and a Date object.
+     * @param obj must be a Number or a Date or a Calendar.
+     * @param toAppendTo the string buffer for the returning time string.
+     * @return the formatted time string.
+     * @param fieldPosition keeps track of the position of the field
+     * within the returned string.
+     * On input: an alignment field,
+     * if desired. On output: the offsets of the alignment field. For
+     * example, given a time text "1996.07.10 AD at 15:08:56 PDT",
+     * if the given fieldPosition is DateFormat.YEAR_FIELD, the
+     * begin index and end index of fieldPosition will be set to
+     * 0 and 4, respectively.
+     * Notice that if the same time field appears
+     * more than once in a pattern, the fieldPosition will be set for the first
+     * occurence of that time field. For instance, formatting a Date to
+     * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern
+     * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD,
+     * the begin index and end index of fieldPosition will be set to
+     * 5 and 8, respectively, for the first occurence of the timezone
+     * pattern character 'z'.
+     * @see java.text.Format
+     * @stable ICU 2.0
+     */
+    public final StringBuffer format(Object obj, StringBuffer toAppendTo,
+                                     FieldPosition fieldPosition)
+    {
+        if (obj instanceof Calendar)
+            return format( (Calendar)obj, toAppendTo, fieldPosition );
+        else if (obj instanceof Date)
+            return format( (Date)obj, toAppendTo, fieldPosition );
+        else if (obj instanceof Number)
+            return format( new Date(((Number)obj).longValue()),
+                          toAppendTo, fieldPosition );
+        else 
+            throw new IllegalArgumentException("Cannot format given Object as a Date");
+    }
+
+    /**
+     * Formats a date into a date/time string.
+     * @param cal a Calendar set to the date and time to be formatted
+     * into a date/time string.
+     * @param toAppendTo the string buffer for the returning date/time string.
+     * @param fieldPosition keeps track of the position of the field
+     * within the returned string.
+     * On input: an alignment field,
+     * if desired. On output: the offsets of the alignment field. For
+     * example, given a time text "1996.07.10 AD at 15:08:56 PDT",
+     * if the given fieldPosition is DateFormat.YEAR_FIELD, the
+     * begin index and end index of fieldPosition will be set to
+     * 0 and 4, respectively.
+     * Notice that if the same time field appears
+     * more than once in a pattern, the fieldPosition will be set for the first
+     * occurence of that time field. For instance, formatting a Date to
+     * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern
+     * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD,
+     * the begin index and end index of fieldPosition will be set to
+     * 5 and 8, respectively, for the first occurence of the timezone
+     * pattern character 'z'.
+     * @return the formatted date/time string.
+     * @stable ICU 2.0
+     */
+    public abstract StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+                                        FieldPosition fieldPosition);
+
+    /**
+     * Formats a Date into a date/time string.
+     * @param date a Date to be formatted into a date/time string.
+     * @param toAppendTo the string buffer for the returning date/time string.
+     * @param fieldPosition keeps track of the position of the field
+     * within the returned string.
+     * On input: an alignment field,
+     * if desired. On output: the offsets of the alignment field. For
+     * example, given a time text "1996.07.10 AD at 15:08:56 PDT",
+     * if the given fieldPosition is DateFormat.YEAR_FIELD, the
+     * begin index and end index of fieldPosition will be set to
+     * 0 and 4, respectively.
+     * Notice that if the same time field appears
+     * more than once in a pattern, the fieldPosition will be set for the first
+     * occurence of that time field. For instance, formatting a Date to
+     * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern
+     * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD,
+     * the begin index and end index of fieldPosition will be set to
+     * 5 and 8, respectively, for the first occurence of the timezone
+     * pattern character 'z'.
+     * @return the formatted date/time string.
+     * @stable ICU 2.0
+     */
+    public final StringBuffer format(Date date, StringBuffer toAppendTo,
+                                     FieldPosition fieldPosition) {
+        // Use our Calendar object
+        calendar.setTime(date);
+        return format(calendar, toAppendTo, fieldPosition);
+    }
+
+    /**
+     * Formats a Date into a date/time string.
+     * @param date the time value to be formatted into a time string.
+     * @return the formatted time string.
+     * @stable ICU 2.0
+     */
+    public final String format(Date date)
+    {
+        return format(date, new StringBuffer(),new FieldPosition(0)).toString();
+    }
+
+    /**
+     * Parse a date/time string.
+     *
+     * @param text  The date/time string to be parsed
+     *
+     * @return      A Date, or null if the input could not be parsed
+     *
+     * @exception  ParseException  If the given string cannot be parsed as a date.
+     *
+     * @see #parse(String, ParsePosition)
+     * @stable ICU 2.0
+     */
+    public Date parse(String text) throws ParseException
+    {
+        ParsePosition pos = new ParsePosition(0);
+        Date result = parse(text, pos);
+        if (pos.getIndex() == 0) // ICU4J
+            throw new ParseException("Unparseable date: \"" + text + "\"" ,
+                                     pos.getErrorIndex()); // ICU4J
+        return result;
+    }
+
+    /**
+     * Parse a date/time string according to the given parse position.
+     * For example, a time text "07/10/96 4:5 PM, PDT" will be parsed
+     * into a Calendar that is equivalent to Date(837039928046).  The
+     * caller should clear the calendar before calling this method,
+     * unless existing field information is to be kept.
+     *
+     * <p> By default, parsing is lenient: If the input is not in the form used
+     * by this object's format method but can still be parsed as a date, then
+     * the parse succeeds.  Clients may insist on strict adherence to the
+     * format by calling setLenient(false).
+     *
+     * @see #setLenient(boolean)
+     *
+     * @param text  The date/time string to be parsed
+     *
+     * @param cal   The calendar into which parsed data will be stored.
+     *              In general, this should be cleared before calling this
+     *              method.  If this parse fails, the calendar may still
+     *              have been modified.
+     *
+     * @param pos   On input, the position at which to start parsing; on
+     *              output, the position at which parsing terminated, or the
+     *              start position if the parse failed.
+     * @stable ICU 2.0
+     */
+    public abstract void parse(String text, Calendar cal, ParsePosition pos);
+
+    /**
+     * Parse a date/time string according to the given parse position.  For
+     * example, a time text "07/10/96 4:5 PM, PDT" will be parsed into a Date
+     * that is equivalent to Date(837039928046).
+     *
+     * <p> By default, parsing is lenient: If the input is not in the form used
+     * by this object's format method but can still be parsed as a date, then
+     * the parse succeeds.  Clients may insist on strict adherence to the
+     * format by calling setLenient(false).
+     *
+     * @see #setLenient(boolean)
+     *
+     * @param text  The date/time string to be parsed
+     *
+     * @param pos   On input, the position at which to start parsing; on
+     *              output, the position at which parsing terminated, or the
+     *              start position if the parse failed.
+     *
+     * @return      A Date, or null if the input could not be parsed
+     * @stable ICU 2.0
+     */
+    public final Date parse(String text, ParsePosition pos) {
+        int start = pos.getIndex();
+        calendar.clear();
+        parse(text, calendar, pos);
+        if (pos.getIndex() != start) {
+            try {
+                return calendar.getTime();
+            } catch (IllegalArgumentException e) {
+                // This occurs if the calendar is non-lenient and there is
+                // an out-of-range field.  We don't know which field was
+                // illegal so we set the error index to the start.
+                pos.setIndex(start);
+                pos.setErrorIndex(start);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Parse a date/time string into an Object.  This convenience method simply
+     * calls parse(String, ParsePosition).
+     *
+     * @see #parse(String, ParsePosition)
+     * @stable ICU 2.0
+     */
+    public Object parseObject (String source, ParsePosition pos)
+    {
+        return parse(source, pos);
+    }
+
+    /**
+     * Constant for full style pattern.
+     * @stable ICU 2.0
+     */
+    public static final int FULL = 0;
+
+    /**
+     * Constant for long style pattern.
+     * @stable ICU 2.0
+     */
+    public static final int LONG = 1;
+
+    /**
+     * Constant for medium style pattern.
+     * @stable ICU 2.0
+     */
+    public static final int MEDIUM = 2;
+
+    /**
+     * Constant for short style pattern.
+     * @stable ICU 2.0
+     */
+    public static final int SHORT = 3;
+
+    /**
+     * Constant for default style pattern.  Its value is MEDIUM.
+     * @stable ICU 2.0
+     */
+    public static final int DEFAULT = MEDIUM;
+
+    /**
+     * Gets the time formatter with the default formatting style
+     * for the default locale.
+     * @return a time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getTimeInstance()
+    {
+        return get(-1, DEFAULT, Locale.getDefault());
+    }
+
+    /**
+     * Gets the time formatter with the given formatting style
+     * for the default locale.
+     * @param style the given formatting style. For example,
+     * SHORT for "h:mm a" in the US locale.
+     * @return a time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getTimeInstance(int style)
+    {
+        return get(-1, style, Locale.getDefault());
+    }
+
+    /**
+     * Gets the time formatter with the given formatting style
+     * for the given locale.
+     * @param style the given formatting style. For example,
+     * SHORT for "h:mm a" in the US locale.
+     * @param aLocale the given locale.
+     * @return a time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getTimeInstance(int style,
+                                                 Locale aLocale)
+    {
+        return get(-1, style, aLocale);
+    }
+
+    /**
+     * Gets the date formatter with the default formatting style
+     * for the default locale.
+     * @return a date formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getDateInstance()
+    {
+        return get(DEFAULT, -1, Locale.getDefault());
+    }
+
+    /**
+     * Gets the date formatter with the given formatting style
+     * for the default locale.
+     * @param style the given formatting style. For example,
+     * SHORT for "M/d/yy" in the US locale.
+     * @return a date formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getDateInstance(int style)
+    {
+        return get(style, -1, Locale.getDefault());
+    }
+
+    /**
+     * Gets the date formatter with the given formatting style
+     * for the given locale.
+     * @param style the given formatting style. For example,
+     * SHORT for "M/d/yy" in the US locale.
+     * @param aLocale the given locale.
+     * @return a date formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getDateInstance(int style,
+                                                 Locale aLocale)
+    {
+        return get(style, -1, aLocale);
+    }
+
+    /**
+     * Gets the date/time formatter with the default formatting style
+     * for the default locale.
+     * @return a date/time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getDateTimeInstance()
+    {
+        return get(DEFAULT, DEFAULT, Locale.getDefault());
+    }
+
+    /**
+     * Gets the date/time formatter with the given date and time
+     * formatting styles for the default locale.
+     * @param dateStyle the given date formatting style. For example,
+     * SHORT for "M/d/yy" in the US locale.
+     * @param timeStyle the given time formatting style. For example,
+     * SHORT for "h:mm a" in the US locale.
+     * @return a date/time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getDateTimeInstance(int dateStyle,
+                                                       int timeStyle)
+    {
+        return get(dateStyle, timeStyle, Locale.getDefault());
+    }
+
+    /**
+     * Gets the date/time formatter with the given formatting styles
+     * for the given locale.
+     * @param dateStyle the given date formatting style.
+     * @param timeStyle the given time formatting style.
+     * @param aLocale the given locale.
+     * @return a date/time formatter.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat
+        getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
+    {
+        return get(dateStyle, timeStyle, aLocale);
+    }
+
+    /**
+     * Get a default date/time formatter that uses the SHORT style for both the
+     * date and the time.
+     * @stable ICU 2.0
+     */
+    public final static DateFormat getInstance() {
+        return getDateTimeInstance(SHORT, SHORT);
+    }
+
+    /**
+     * Gets the set of locales for which DateFormats are installed.
+     * @return the set of locales for which DateFormats are installed.
+     * @stable ICU 2.0
+     */
+    public static Locale[] getAvailableLocales()
+    {
+        return ICULocaleData.getAvailableLocales();
+    }
+
+    /**
+     * Set the calendar to be used by this date format.  Initially, the default
+     * calendar for the specified or default locale is used.
+     * @param newCalendar the new Calendar to be used by the date format
+     * @stable ICU 2.0
+     */
+    public void setCalendar(Calendar newCalendar)
+    {
+        this.calendar = newCalendar;
+    }
+
+    /**
+     * Gets the calendar associated with this date/time formatter.
+     * @return the calendar associated with this date/time formatter.
+     * @stable ICU 2.0
+     */
+    public Calendar getCalendar()
+    {
+        return calendar;
+    }
+
+    /**
+     * Allows you to set the number formatter.
+     * @param newNumberFormat the given new NumberFormat.
+     * @stable ICU 2.0
+     */
+    public void setNumberFormat(NumberFormat newNumberFormat)
+    {
+        this.numberFormat = newNumberFormat;
+        /*In order to parse String like "11.10.2001" to DateTime correctly 
+          in Locale("fr","CH") [Richard/GCL]
+        */
+        this.numberFormat.setParseIntegerOnly(true);
+    }
+
+    /**
+     * Gets the number formatter which this date/time formatter uses to
+     * format and parse a time.
+     * @return the number formatter which this date/time formatter uses.
+     * @stable ICU 2.0
+     */
+    public NumberFormat getNumberFormat()
+    {
+        return numberFormat;
+    }
+
+    /**
+     * Sets the time zone for the calendar of this DateFormat object.
+     * @param zone the given new time zone.
+     * @stable ICU 2.0
+     */
+    public void setTimeZone(TimeZone zone)
+    {
+        calendar.setTimeZone(zone);
+    }
+
+    /**
+     * Gets the time zone.
+     * @return the time zone associated with the calendar of DateFormat.
+     * @stable ICU 2.0
+     */
+    public TimeZone getTimeZone()
+    {
+        return calendar.getTimeZone();
+    }
+
+    /**
+     * Specify whether or not date/time parsing is to be lenient.  With
+     * lenient parsing, the parser may use heuristics to interpret inputs that
+     * do not precisely match this object's format.  With strict parsing,
+     * inputs must match this object's format.
+     * @param lenient when true, parsing is lenient
+     * @see com.ibm.icu.util.Calendar#setLenient
+     * @stable ICU 2.0
+     */
+    public void setLenient(boolean lenient)
+    {
+        calendar.setLenient(lenient);
+    }
+
+    /**
+     * Tell whether date/time parsing is to be lenient.
+     * @stable ICU 2.0
+     */
+    public boolean isLenient()
+    {
+        return calendar.isLenient();
+    }
+
+    /**
+     * Overrides hashCode
+     * @stable ICU 2.0
+     */
+    ///CLOVER:OFF
+    public int hashCode() {
+        return numberFormat.hashCode();
+        // just enough fields for a reasonable distribution
+    }
+    ///CLOVER:ON
+
+    /**
+     * Overrides equals
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        DateFormat other = (DateFormat) obj;
+        return (calendar.isEquivalentTo(other.calendar) &&
+                numberFormat.equals(other.numberFormat));
+    }
+
+    /**
+     * Overrides Cloneable
+     * @stable ICU 2.0
+     */
+    public Object clone()
+    {
+        DateFormat other = (DateFormat) super.clone();
+        other.calendar = (Calendar) calendar.clone();
+        other.numberFormat = (NumberFormat) numberFormat.clone();
+        return other;
+    }
+
+    /**
+     * Creates a DateFormat with the given time and/or date style in the given
+     * locale.
+     * @param dateStyle a value from 0 to 3 indicating the time format,
+     * or -1 to indicate no date
+     * @param timeStyle a value from 0 to 3 indicating the time format,
+     * or -1 to indicate no time
+     * @param loc the locale for the format
+     */
+    private static DateFormat get(int dateStyle, int timeStyle, Locale loc) {
+        if (timeStyle < -1 || timeStyle > 3) {
+            throw new IllegalArgumentException("Illegal time style " + timeStyle);
+        }
+        if (dateStyle < -1 || dateStyle > 3) {
+            throw new IllegalArgumentException("Illegal date style " + dateStyle);
+        }
+        try {
+            return new SimpleDateFormat(timeStyle, dateStyle, loc);
+        } catch (MissingResourceException e) {
+            return new SimpleDateFormat("M/d/yy h:mm a");
+        }
+    }
+
+    /**
+     * Create a new date format.
+     * @stable ICU 2.0
+     */
+    protected DateFormat() {}
+
+    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    //-------------------------------------------------------------------------
+    // Public static interface for creating custon DateFormats for different
+    // types of Calendars.
+    //-------------------------------------------------------------------------
+    
+    /**
+     * Create a {@link DateFormat} object that can be used to format dates in
+     * the calendar system specified by <code>cal</code>.
+     * <p>
+     * @param cal   The calendar system for which a date format is desired.
+     *
+     * @param dateStyle The type of date format desired.  This can be
+     *              {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM},
+     *              etc.
+     *
+     * @param locale The locale for which the date format is desired.
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getDateInstance(Calendar cal, int dateStyle, Locale locale)
+    {
+        return getDateTimeInstance(cal, dateStyle, -1, locale);
+    }
+    
+    /**
+     * Create a {@link DateFormat} object that can be used to format times in
+     * the calendar system specified by <code>cal</code>.
+     * <p>
+     * <b>Note:</b> When this functionality is moved into the core JDK, this method
+     * will probably be replaced by a new overload of {@link DateFormat#getInstance}.
+     * <p>
+     * @param cal   The calendar system for which a time format is desired.
+     *
+     * @param timeStyle The type of time format desired.  This can be
+     *              {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM},
+     *              etc.
+     *
+     * @param locale The locale for which the time format is desired.
+     *
+     * @see DateFormat#getTimeInstance
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getTimeInstance(Calendar cal, int timeStyle, Locale locale)
+    {
+        return getDateTimeInstance(cal, -1, timeStyle, locale);
+    }
+    
+    /**
+     * Create a {@link DateFormat} object that can be used to format dates and times in
+     * the calendar system specified by <code>cal</code>.
+     * <p>
+     * <b>Note:</b> When this functionality is moved into the core JDK, this method
+     * will probably be replaced by a new overload of {@link DateFormat#getInstance}.
+     * <p>
+     * @param cal   The calendar system for which a date/time format is desired.
+     *
+     * @param dateStyle The type of date format desired.  This can be
+     *              {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM},
+     *              etc.
+     *
+     * @param timeStyle The type of time format desired.  This can be
+     *              {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM},
+     *              etc.
+     *
+     * @param locale The locale for which the date/time format is desired.
+     *
+     * @see DateFormat#getDateTimeInstance
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getDateTimeInstance(Calendar cal, int dateStyle,
+                                                 int timeStyle, Locale locale)
+    {
+        return cal.getDateTimeFormat(dateStyle, timeStyle, locale);
+    }
+
+    /**
+     * Convenience overload
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getInstance(Calendar cal, Locale locale) {
+        return getDateTimeInstance(cal, SHORT, SHORT, locale);
+    }
+
+    /**
+     * Convenience overload
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getInstance(Calendar cal) {
+        return getInstance(cal, Locale.getDefault());
+    }
+
+    /**
+     * Convenience overload
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getDateInstance(Calendar cal, int dateStyle) {
+        return getDateInstance(cal, dateStyle, Locale.getDefault());
+    }
+
+    /**
+     * Convenience overload
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getTimeInstance(Calendar cal, int timeStyle) {
+        return getTimeInstance(cal, timeStyle, Locale.getDefault());
+    }
+
+    /**
+     * Convenience overload
+     * @stable ICU 2.0
+     */
+    static final public DateFormat getDateTimeInstance(Calendar cal, int dateStyle, int timeStyle) {
+        return getDateTimeInstance(cal, dateStyle, timeStyle, Locale.getDefault());
+    }
+}
diff --git a/src/com/ibm/icu/text/DateFormatSymbols.java b/src/com/ibm/icu/text/DateFormatSymbols.java
new file mode 100755
index 0000000..2e56b7a
--- /dev/null
+++ b/src/com/ibm/icu/text/DateFormatSymbols.java
@@ -0,0 +1,738 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/DateFormatSymbols.java,v $
+ * $Date: 2003/09/04 23:07:34 $
+ * $Revision: 1.20 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.text;
+
+import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.impl.Utility;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.impl.ZoneMeta;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * <code>DateFormatSymbols</code> is a public class for encapsulating
+ * localizable date-time formatting data, such as the names of the
+ * months, the names of the days of the week, and the time zone data.
+ * <code>DateFormat</code> and <code>SimpleDateFormat</code> both use
+ * <code>DateFormatSymbols</code> to encapsulate this information.
+ *
+ * <p>
+ * Typically you shouldn't use <code>DateFormatSymbols</code> directly.
+ * Rather, you are encouraged to create a date-time formatter with the
+ * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>,
+ * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>.
+ * These methods automatically create a <code>DateFormatSymbols</code> for
+ * the formatter so that you don't have to. After the
+ * formatter is created, you may modify its format pattern using the
+ * <code>setPattern</code> method. For more information about
+ * creating formatters using <code>DateFormat</code>'s factory methods,
+ * see {@link DateFormat}.
+ *
+ * <p>
+ * If you decide to create a date-time formatter with a specific
+ * format pattern for a specific locale, you can do so with:
+ * <blockquote>
+ * <pre>
+ * new SimpleDateFormat(aPattern, new DateFormatSymbols(aLocale)).
+ * </pre>
+ * </blockquote>
+ *
+ * <p>
+ * <code>DateFormatSymbols</code> objects are clonable. When you obtain
+ * a <code>DateFormatSymbols</code> object, feel free to modify the
+ * date-time formatting data. For instance, you can replace the localized
+ * date-time format pattern characters with the ones that you feel easy
+ * to remember. Or you can change the representative cities
+ * to your favorite ones.
+ *
+ * <p>
+ * New <code>DateFormatSymbols</code> subclasses may be added to support
+ * <code>SimpleDateFormat</code> for date-time formatting for additional locales.
+
+ * @see          DateFormat
+ * @see          SimpleDateFormat
+ * @see          com.ibm.icu.util.SimpleTimeZone
+ * @author       Chen-Lieh Huang
+ * @stable ICU 2.0
+ */
+public class DateFormatSymbols implements Serializable, Cloneable {
+
+    // TODO make sure local pattern char string is 18 characters long,
+    // that is, that it encompasses the new 'u' char for
+    // EXTENDED_YEAR.  Two options: 1. Make sure resource data is
+    // correct; 2. Make code add in 'u' at end if len == 17.
+
+    /**
+     * Construct a DateFormatSymbols object by loading format data from
+     * resources for the default locale.
+     *
+     * @throws  java.util.MissingResourceException
+     *          if the resources for the default locale cannot be
+     *          found or cannot be loaded.
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols()
+    {
+        initializeData(Locale.getDefault());
+    }
+
+    /**
+     * Construct a DateFormatSymbols object by loading format data from
+     * resources for the given locale.
+     *
+     * @throws  java.util.MissingResourceException
+     *          if the resources for the specified locale cannot be
+     *          found or cannot be loaded.
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols(Locale locale)
+    {
+        initializeData(locale);
+    }
+
+    /**
+     * Era strings. For example: "AD" and "BC".  An array of 2 strings,
+     * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
+     * @serial
+     */
+    String eras[] = null;
+
+    /**
+     * Month strings. For example: "January", "February", etc.  An array
+     * of 13 strings (some calendars have 13 months), indexed by
+     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
+     * @serial
+     */
+    String months[] = null;
+
+    /**
+     * Short month strings. For example: "Jan", "Feb", etc.  An array of
+     * 13 strings (some calendars have 13 months), indexed by
+     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
+
+     * @serial
+     */
+    String shortMonths[] = null;
+
+    /**
+     * Weekday strings. For example: "Sunday", "Monday", etc.  An array
+     * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc.
+     * The element <code>weekdays[0]</code> is ignored.
+     * @serial
+     */
+    String weekdays[] = null;
+
+    /**
+     * Short weekday strings. For example: "Sun", "Mon", etc.  An array
+     * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc.
+     * The element <code>shortWeekdays[0]</code> is ignored.
+     * @serial
+     */
+    String shortWeekdays[] = null;
+
+    /**
+     * AM and PM strings. For example: "AM" and "PM".  An array of
+     * 2 strings, indexed by <code>Calendar.AM</code> and
+     * <code>Calendar.PM</code>.
+     * @serial
+     */
+    String ampms[] = null;
+
+    /**
+     * Localized names of time zones in this locale.  This is a
+     * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
+     * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
+     * entry containing the localized names for a single <code>TimeZone</code>.
+     * Each such row contains (with <code>i</code> ranging from
+     * 0..<em>n</em>-1):
+     * <ul>
+     * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
+     * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
+     * time</li>
+     * <li><code>zoneStrings[i][2]</code> - short name of zone in
+     * standard time</li>
+     * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
+     * savings time</li>
+     * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
+     * savings time</li>
+     * </ul>
+     * The zone ID is <em>not</em> localized; it corresponds to the ID
+     * value associated with a system time zone object.  All other entries
+     * are localized names.  If a zone does not implement daylight savings
+     * time, the daylight savings time names are ignored.
+     * @see com.ibm.icu.util.TimeZone
+     * @serial
+     */
+    String zoneStrings[][] = null;
+
+    /**
+     * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
+     * All locales use the same these unlocalized pattern characters.
+     */
+    static final String  patternChars = "GyMdkHmsSEDFwWahKzu";
+
+    /**
+     * Localized date-time pattern characters. For example, a locale may
+     * wish to use 'u' rather than 'y' to represent years in its date format
+     * pattern strings.
+     * This string must be exactly 18 characters long, with the index of
+     * the characters described by <code>DateFormat.ERA_FIELD</code>,
+     * <code>DateFormat.YEAR_FIELD</code>, etc.  Thus, if the string were
+     * "Xz...", then localized patterns would use 'X' for era and 'z' for year.
+     * @serial
+     */
+    String  localPatternChars = null;
+
+    /* use serialVersionUID from JDK 1.1.4 for interoperability */
+    static final long serialVersionUID = -5987973545549424702L;
+
+    /**
+     * Gets era strings. For example: "AD" and "BC".
+     * @return the era strings.
+     * @stable ICU 2.0
+     */
+    public String[] getEras() {
+        return duplicate(eras);
+    }
+
+    /**
+     * Sets era strings. For example: "AD" and "BC".
+     * @param newEras the new era strings.
+     * @stable ICU 2.0
+     */
+    public void setEras(String[] newEras) {
+        eras = duplicate(newEras);
+    }
+
+    /**
+     * Gets month strings. For example: "January", "February", etc.
+     * @return the month strings.
+     * @stable ICU 2.0
+     */
+    public String[] getMonths() {
+        return duplicate(months);
+    }
+
+    /**
+     * Sets month strings. For example: "January", "February", etc.
+     * @param newMonths the new month strings.
+     * @stable ICU 2.0
+     */
+    public void setMonths(String[] newMonths) {
+        months = duplicate(newMonths);
+    }
+
+    /**
+     * Gets short month strings. For example: "Jan", "Feb", etc.
+     * @return the short month strings.
+     * @stable ICU 2.0
+     */
+    public String[] getShortMonths() {
+        return duplicate(shortMonths);
+    }
+
+    /**
+     * Sets short month strings. For example: "Jan", "Feb", etc.
+     * @param newShortMonths the new short month strings.
+     * @stable ICU 2.0
+     */
+    public void setShortMonths(String[] newShortMonths) {
+        shortMonths = duplicate(newShortMonths);
+    }
+
+    /**
+     * Gets weekday strings. For example: "Sunday", "Monday", etc.
+     * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc. to index the result array.
+     * @stable ICU 2.0
+     */
+    public String[] getWeekdays() {
+        return duplicate(weekdays);
+    }
+
+    /**
+     * Sets weekday strings. For example: "Sunday", "Monday", etc.
+     * @param newWeekdays the new weekday strings. The array should
+     * be indexed by <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc.
+     * @stable ICU 2.0
+     */
+    public void setWeekdays(String[] newWeekdays) {
+        weekdays = duplicate(newWeekdays);
+    }
+
+    /**
+     * Gets short weekday strings. For example: "Sun", "Mon", etc.
+     * @return the short weekday strings. Use <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc. to index the result array.
+     * @stable ICU 2.0
+     */
+    public String[] getShortWeekdays() {
+        return duplicate(shortWeekdays);
+    }
+
+    /**
+     * Sets short weekday strings. For example: "Sun", "Mon", etc.
+     * @param newShortWeekdays the new short weekday strings. The array should
+     * be indexed by <code>Calendar.SUNDAY</code>,
+     * <code>Calendar.MONDAY</code>, etc.
+     * @stable ICU 2.0
+     */
+    public void setShortWeekdays(String[] newShortWeekdays) {
+        shortWeekdays = duplicate(newShortWeekdays);
+    }
+
+    /**
+     * Gets ampm strings. For example: "AM" and "PM".
+     * @return the weekday strings.
+     * @stable ICU 2.0
+     */
+    public String[] getAmPmStrings() {
+        return duplicate(ampms);
+    }
+
+    /**
+     * Sets ampm strings. For example: "AM" and "PM".
+     * @param newAmpms the new ampm strings.
+     * @stable ICU 2.0
+     */
+    public void setAmPmStrings(String[] newAmpms) {
+        ampms = duplicate(newAmpms);
+    }
+
+    /**
+     * Gets timezone strings.
+     * @return the timezone strings.
+     * @stable ICU 2.0
+     */
+    public String[][] getZoneStrings() {
+        return duplicate(zoneStrings);
+    }
+
+    /**
+     * Sets timezone strings.
+     * @param newZoneStrings the new timezone strings.
+     * @stable ICU 2.0
+     */
+    public void setZoneStrings(String[][] newZoneStrings) {
+        zoneStrings = duplicate(newZoneStrings);
+    }
+
+    /**
+     * Gets localized date-time pattern characters. For example: 'u', 't', etc.
+     * @return the localized date-time pattern characters.
+     * @stable ICU 2.0
+     */
+    public String getLocalPatternChars() {
+        return new String(localPatternChars);
+    }
+
+    /**
+     * Sets localized date-time pattern characters. For example: 'u', 't', etc.
+     * @param newLocalPatternChars the new localized date-time
+     * pattern characters.
+     * @stable ICU 2.0
+     */
+    public void setLocalPatternChars(String newLocalPatternChars) {
+        localPatternChars = newLocalPatternChars;
+    }
+
+    /**
+     * Overrides Cloneable
+     * @stable ICU 2.0
+     */
+    public Object clone()
+    {
+        try {
+            DateFormatSymbols other = (DateFormatSymbols)super.clone();
+            copyMembers(this, other);
+            return other;
+        } catch (CloneNotSupportedException e) {
+            throw new InternalError();
+        }
+    }
+
+    /**
+     * Override hashCode.
+     * Generates a hash code for the DateFormatSymbols object.
+     * @stable ICU 2.0
+     */
+    public int hashCode() {
+        int hashcode = 0;
+        for (int index = 0; index < this.zoneStrings[0].length; ++index)
+            hashcode ^= this.zoneStrings[0][index].hashCode();
+        return hashcode;
+    }
+
+    /**
+     * Override equals
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object obj)
+    {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        DateFormatSymbols that = (DateFormatSymbols) obj;
+        return (Utility.arrayEquals(eras, that.eras)
+                && Utility.arrayEquals(months, that.months)
+                && Utility.arrayEquals(shortMonths, that.shortMonths)
+                && Utility.arrayEquals(weekdays, that.weekdays)
+                && Utility.arrayEquals(shortWeekdays, that.shortWeekdays)
+                && Utility.arrayEquals(ampms, that.ampms)
+                && Utility.arrayEquals(zoneStrings, that.zoneStrings)
+                && Utility.arrayEquals(localPatternChars,
+                                       that.localPatternChars));
+    }
+
+    // =======================privates===============================
+
+    /**
+     * Useful constant for defining timezone offsets.
+     */
+    static final int millisPerHour = 60*60*1000;
+
+
+    private void initializeData(Locale desiredLocale)
+    {
+        ResourceBundle rb = ICULocaleData.getLocaleElements(desiredLocale);
+
+        // FIXME: cache only ResourceBundle. Hence every time, will do
+        // getObject(). This won't be necessary if the Resource itself
+        // is cached.
+        eras = rb.getStringArray("Eras");
+        months = rb.getStringArray("MonthNames");
+        shortMonths = rb.getStringArray("MonthAbbreviations");
+
+        String[] lWeekdays = rb.getStringArray("DayNames");
+        weekdays = new String[8];
+        weekdays[0] = "";  // 1-based
+        System.arraycopy(lWeekdays, 0, weekdays, 1, lWeekdays.length);
+
+        String[] sWeekdays = rb.getStringArray("DayAbbreviations");
+        shortWeekdays = new String[8];
+        shortWeekdays[0] = "";  // 1-based
+        System.arraycopy(sWeekdays, 0, shortWeekdays, 1, sWeekdays.length);
+
+        ampms = rb.getStringArray("AmPmMarkers");
+
+        // hack around class cast problem
+        // zoneStrings = (String[][])rb.getObject("zoneStrings");
+        Object[] zoneObject = (Object[])rb.getObject("zoneStrings");
+        zoneStrings = new String[zoneObject.length][];
+        for (int i = 0; i < zoneStrings.length; ++i) {
+            zoneStrings[i] = (String[])zoneObject[i];
+        }
+
+        localPatternChars = rb.getString("localPatternChars");
+    }
+
+    /**
+     * Package private: used by SimpleDateFormat
+     * Gets the index for the given time zone ID to obtain the timezone
+     * strings for formatting. The time zone ID is just for programmatic
+     * lookup. NOT LOCALIZED!!!
+     * @param ID the given time zone ID.
+     * @return the index of the given time zone ID.  Returns -1 if
+     * the given time zone ID can't be located in the DateFormatSymbols object.
+     * @see com.ibm.icu.util.SimpleTimeZone
+     */
+    final int getZoneIndex(String ID) {
+        int result = _getZoneIndex(ID);
+        if (result >= 0) {
+            return result;
+        }
+        // Do a search through the equivalency group for the given ID
+        int n = ZoneMeta.countEquivalentIDs(ID);
+        if (n > 1) {
+            for (int i=0; i<n; ++i) {
+                String equivID = ZoneMeta.getEquivalentID(ID, i);
+                if (!equivID.equals(ID)) {
+                    int equivResult = _getZoneIndex(equivID);
+                    if (equivResult >= 0) {
+                        return equivResult;
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+    
+    /**
+     * Lookup the given ID.  Do NOT do an equivalency search.
+     */
+    private int _getZoneIndex(String ID)
+    {
+        for (int index=0; index<zoneStrings.length; index++)
+            {
+                if (ID.equalsIgnoreCase(zoneStrings[index][0])) return index;
+            }
+
+        return -1;
+    }
+
+    /**
+     * Clones an array of Strings.
+     * @param srcArray the source array to be cloned.
+     * @param count the number of elements in the given source array.
+     * @return a cloned array.
+     */
+    private final String[] duplicate(String[] srcArray)
+    {
+        return (String[])srcArray.clone();
+    }
+
+    private final String[][] duplicate(String[][] srcArray)
+    {
+        String[][] aCopy = new String[srcArray.length][];
+        for (int i = 0; i < srcArray.length; ++i)
+            aCopy[i] = duplicate(srcArray[i]);
+        return aCopy;
+    }
+
+    /**
+     * Clones all the data members from the source DateFormatSymbols to
+     * the target DateFormatSymbols. This is only for subclasses.
+     * @param src the source DateFormatSymbols.
+     * @param dst the target DateFormatSymbols.
+     */
+    private final void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
+    {
+        dst.eras = duplicate(src.eras);
+        dst.months = duplicate(src.months);
+        dst.shortMonths = duplicate(src.shortMonths);
+        dst.weekdays = duplicate(src.weekdays);
+        dst.shortWeekdays = duplicate(src.shortWeekdays);
+        dst.ampms = duplicate(src.ampms);
+        dst.zoneStrings = duplicate(src.zoneStrings);
+        dst.localPatternChars = new String (src.localPatternChars);
+    }
+
+    /**
+     * Compares the equality of the two arrays of String.
+     * @param current this String array.
+     * @param other that String array.
+    private final boolean equals(String[] current, String[] other)
+    {
+        int count = current.length;
+
+        for (int i = 0; i < count; ++i)
+            if (!current[i].equals(other[i]))
+                return false;
+        return true;
+    }
+     */
+
+    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    /**
+     * Get the {@link DateFormatSymbols} object that should be used to format a
+     * calendar system's dates in the given locale.
+     * <p>
+     * <b>Subclassing:</b><br>
+     * When creating a new Calendar subclass, you must create the
+     * {@link ResourceBundle ResourceBundle}
+     * containing its {@link DateFormatSymbols DateFormatSymbols} in a specific place.
+     * The resource bundle name is based on the calendar's fully-specified
+     * class name, with ".resources" inserted at the end of the package name
+     * (just before the class name) and "Symbols" appended to the end.
+     * For example, the bundle corresponding to "com.ibm.icu.util.HebrewCalendar"
+     * is "com.ibm.icu.impl.data.HebrewCalendarSymbols".
+     * <p>
+     * Within the ResourceBundle, this method searches for five keys:
+     * <ul>
+     * <li><b>DayNames</b> -
+     *      An array of strings corresponding to each possible
+     *      value of the <code>DAY_OF_WEEK</code> field.  Even though
+     *      <code>DAY_OF_WEEK</code> starts with <code>SUNDAY</code> = 1,
+     *      This array is 0-based; the name for Sunday goes in the
+     *      first position, at index 0.  If this key is not found
+     *      in the bundle, the day names are inherited from the
+     *      default <code>DateFormatSymbols</code> for the requested locale.
+     *
+     * <li><b>DayAbbreviations</b> -
+     *      An array of abbreviated day names corresponding
+     *      to the values in the "DayNames" array.  If this key
+     *      is not found in the resource bundle, the "DayNames"
+     *      values are used instead.  If neither key is found,
+     *      the day abbreviations are inherited from the default
+     *      <code>DateFormatSymbols</code> for the locale.
+     *
+     * <li><b>MonthNames</b> -
+     *      An array of strings corresponding to each possible
+     *      value of the <code>MONTH</code> field.  If this key is not found
+     *      in the bundle, the month names are inherited from the
+     *      default <code>DateFormatSymbols</code> for the requested locale.
+     *
+     * <li><b>MonthAbbreviations</b> -
+     *      An array of abbreviated day names corresponding
+     *      to the values in the "MonthNames" array.  If this key
+     *      is not found in the resource bundle, the "MonthNames"
+     *      values are used instead.  If neither key is found,
+     *      the day abbreviations are inherited from the default
+     *      <code>DateFormatSymbols</code> for the locale.
+     *
+     * <li><b>Eras</b> -
+     *      An array of strings corresponding to each possible
+     *      value of the <code>ERA</code> field.  If this key is not found
+     *      in the bundle, the era names are inherited from the
+     *      default <code>DateFormatSymbols</code> for the requested locale.
+     * </ul>
+     * <p>
+     * @param cal       The calendar system whose date format symbols are desired.
+     * @param locale    The locale whose symbols are desired.
+     *
+     * @see DateFormatSymbols#DateFormatSymbols(java.util.Locale)
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols(Calendar cal, Locale locale) {
+        this(cal==null?null:cal.getClass(), locale);
+    }
+
+    /**
+     * Variant of DateFormatSymbols(Calendar, Locale) that takes the Calendar class
+     * instead of a Calandar instance.
+     * @see #DateFormatSymbols(Calendar, Locale)
+     * @draft ICU 2.2
+     */
+    public DateFormatSymbols(Class calendarClass, Locale locale) {
+        this(locale); // old-style construction
+        if (calendarClass != null) {
+            ResourceBundle bundle = null;
+            try {
+                bundle = getDateFormatBundle(calendarClass, locale);
+            } catch (MissingResourceException e) {
+                //if (!(cal instanceof GregorianCalendar)) {
+                if (!(GregorianCalendar.class.isAssignableFrom(calendarClass))) {
+                    // Ok for symbols to be missing for a Gregorian calendar, but
+                    // not for any other type.
+                    throw e;
+                }
+            }
+            constructCalendarSpecific(bundle);
+        }
+    }
+
+    /**
+     * Fetch a custom calendar's DateFormatSymbols out of the given resource
+     * bundle.  Symbols that are not overridden are inherited from the
+     * default DateFormatSymbols for the locale.
+     * @see DateFormatSymbols#DateFormatSymbols
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols(ResourceBundle bundle, Locale locale) {
+        // Get the default symbols for the locale, since most
+        // calendars will only need to override month names and will
+        // want everything else the same
+        this(locale); // old-style construction
+        constructCalendarSpecific(bundle);
+    }
+
+    /**
+     * Given a resource bundle specific to the given Calendar class,
+     * initialize this object.  Member variables will have already been
+     * initialized using the default mechanism, so only those that differ
+     * from or supplement the standard resource data need be handled here.
+     * If subclasses override this method, they should call
+     * <code>super.constructCalendarSpecific(bundle)</code> as needed to
+     * handle the "DayNames", "DayAbbreviations", "MonthNames",
+     * "MonthAbbreviations", and "Eras" resource data.
+     * @stable ICU 2.0
+     */
+    protected void constructCalendarSpecific(ResourceBundle bundle) {
+
+        // Fetch the day names from the resource bundle.  If they're not found,
+        // it's ok; we'll just use the default ones.
+        // Allow a null ResourceBundle just for the sake of completeness;
+        // this is useful for calendars that don't have any overridden symbols
+
+        if (bundle != null) {
+            try {
+                String[] temp = bundle.getStringArray("DayNames");
+                setWeekdays(temp);
+                setShortWeekdays(temp);
+
+                temp = bundle.getStringArray("DayAbbreviations");
+                setShortWeekdays( temp );
+            } catch (MissingResourceException e) {}
+
+            try {
+                String[] temp = bundle.getStringArray("MonthNames");
+                setMonths( temp );
+                setShortMonths( temp );
+
+                temp = bundle.getStringArray("MonthAbbreviations");
+                setShortMonths( temp );
+            } catch (MissingResourceException e) {}
+
+            try {
+                String[] temp = bundle.getStringArray("Eras");
+                setEras( temp );
+            } catch (MissingResourceException e) {}
+        }
+    }
+
+    /**
+     * Find the ResourceBundle containing the date format information for
+     * a specified calendar subclass in a given locale.
+     * <p>
+     * The resource bundle name is based on the calendar's fully-specified
+     * class name, with ".resources" inserted at the end of the package name
+     * (just before the class name) and "Symbols" appended to the end.
+     * For example, the bundle corresponding to "com.ibm.icu.util.HebrewCalendar"
+     * is "com.ibm.icu.impl.data.HebrewCalendarSymbols".
+     * @stable ICU 2.0
+     */
+    static public ResourceBundle getDateFormatBundle(Class calendarClass, Locale locale)
+        throws MissingResourceException {
+
+        // Find the calendar's class name, which we're going to use to construct the
+        // resource bundle name.
+        String fullName = calendarClass.getName();
+        int lastDot = fullName.lastIndexOf('.');
+        String className = fullName.substring(lastDot+1);
+
+        String bundleName = className + "Symbols";
+
+        ResourceBundle result = null;
+        try {
+            result = ICULocaleData.getResourceBundle(bundleName, locale);
+        }
+        catch (MissingResourceException e) {
+            //if (!(cal instanceof GregorianCalendar)) {
+            if (!(GregorianCalendar.class.isAssignableFrom(calendarClass))) {
+                // Ok for symbols to be missing for a Gregorian calendar, but
+                // not for any other type.
+                throw e;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Variant of getDateFormatBundle(java.lang.Class, java.util.Locale) that takes
+     * a Calendar instance instead of a Calendar class.
+     * @see #getDateFormatBundle(java.lang.Class, java.util.Locale)
+     * @draft ICU 2.2
+     */
+    static public ResourceBundle getDateFormatBundle(Calendar cal, Locale locale)
+        throws MissingResourceException {
+        return getDateFormatBundle(cal==null?null:cal.getClass(), locale);
+    }
+}
diff --git a/src/com/ibm/icu/text/SimpleDateFormat.java b/src/com/ibm/icu/text/SimpleDateFormat.java
new file mode 100755
index 0000000..8fd62df
--- /dev/null
+++ b/src/com/ibm/icu/text/SimpleDateFormat.java
@@ -0,0 +1,1471 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/SimpleDateFormat.java,v $ 
+ * $Date: 2003/09/04 00:59:36 $ 
+ * $Revision: 1.23 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.text;
+
+import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.impl.UCharacterProperty;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.text.FieldPosition;
+import java.text.MessageFormat;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.TimeZone;
+import java.lang.reflect.Method;
+
+/**
+ * <code>SimpleDateFormat</code> is a concrete class for formatting and
+ * parsing dates in a locale-sensitive manner. It allows for formatting
+ * (date -> text), parsing (text -> date), and normalization.
+ *
+ * <p>
+ * <code>SimpleDateFormat</code> allows you to start by choosing
+ * any user-defined patterns for date-time formatting. However, you
+ * are encouraged to create a date-time formatter with either
+ * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
+ * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
+ * of these class methods can return a date/time formatter initialized
+ * with a default format pattern. You may modify the format pattern
+ * using the <code>applyPattern</code> methods as desired.
+ * For more information on using these methods, see
+ * {@link DateFormat}.
+ *
+ * <p>
+ * <strong>Time Format Syntax:</strong>
+ * <p>
+ * To specify the time format use a <em>time pattern</em> string.
+ * In this pattern, all ASCII letters are reserved as pattern letters,
+ * which are defined as the following:
+ * <blockquote>
+ * <pre>
+ * Symbol   Meaning                 Presentation        Example
+ * ------   -------                 ------------        -------
+ * G        era designator          (Text)              AD
+ * y        year                    (Number)            1996
+ * u        extended year           (Number)            4601
+ * M        month in year           (Text & Number)     July & 07
+ * d        day in month            (Number)            10
+ * h        hour in am/pm (1~12)    (Number)            12
+ * H        hour in day (0~23)      (Number)            0
+ * m        minute in hour          (Number)            30
+ * s        second in minute        (Number)            55
+ * S        millisecond             (Number)            978
+ * E        day in week             (Text)              Tuesday
+ * D        day in year             (Number)            189
+ * F        day of week in month    (Number)            2 (2nd Wed in July)
+ * w        week in year            (Number)            27
+ * W        week in month           (Number)            2
+ * a        am/pm marker            (Text)              PM
+ * k        hour in day (1~24)      (Number)            24
+ * K        hour in am/pm (0~11)    (Number)            0
+ * z        time zone               (Text)              Pacific Standard Time
+ * '        escape for text         (Delimiter)
+ * ''       single quote            (Literal)           '
+ * </pre>
+ * </blockquote>
+ * The count of pattern letters determine the format.
+ * <p>
+ * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
+ * &lt; 4--use short or abbreviated form if one exists.
+ * <p>
+ * <strong>(Number)</strong>: the minimum number of digits. Shorter
+ * numbers are zero-padded to this amount. Year is handled specially;
+ * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
+ * <p>
+ * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
+ * <p>
+ * Any characters in the pattern that are not in the ranges of ['a'..'z']
+ * and ['A'..'Z'] will be treated as quoted text. For instance, characters
+ * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
+ * even they are not embraced within single quotes.
+ * <p>
+ * A pattern containing any invalid pattern letter will result in a thrown
+ * exception during formatting or parsing.
+ *
+ * <p>
+ * <strong>Examples Using the US Locale:</strong>
+ * <blockquote>
+ * <pre>
+ * Format Pattern                         Result
+ * --------------                         -------
+ * "yyyy.MM.dd G 'at' HH:mm:ss z"    ->>  1996.07.10 AD at 15:08:56 PDT
+ * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
+ * "h:mm a"                          ->>  12:08 PM
+ * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
+ * "K:mm a, z"                       ->>  0:00 PM, PST
+ * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM
+ * </pre>
+ * </blockquote>
+ * <strong>Code Sample:</strong>
+ * <blockquote>
+ * <pre>
+ * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
+ * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
+ * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
+ * <br>
+ * // Format the current time.
+ * SimpleDateFormat formatter
+ *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
+ * Date currentTime_1 = new Date();
+ * String dateString = formatter.format(currentTime_1);
+ * <br>
+ * // Parse the previous string back into a Date.
+ * ParsePosition pos = new ParsePosition(0);
+ * Date currentTime_2 = formatter.parse(dateString, pos);
+ * </pre>
+ * </blockquote>
+ * In the example, the time value <code>currentTime_2</code> obtained from
+ * parsing will be equal to <code>currentTime_1</code>. However, they may not be
+ * equal if the am/pm marker 'a' is left out from the format pattern while
+ * the "hour in am/pm" pattern symbol is used. This information loss can
+ * happen when formatting the time in PM.
+ *
+ * <p>
+ * When parsing a date string using the abbreviated year pattern ("yy"),
+ * SimpleDateFormat must interpret the abbreviated year
+ * relative to some century.  It does this by adjusting dates to be
+ * within 80 years before and 20 years after the time the SimpleDateFormat
+ * instance is created. For example, using a pattern of "MM/dd/yy" and a
+ * SimpleDateFormat instance created on Jan 1, 1997,  the string
+ * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
+ * would be interpreted as May 4, 1964.
+ * During parsing, only strings consisting of exactly two digits, as defined by
+ * {@link java.lang.Character#isDigit(char)}, will be parsed into the default
+ * century.
+ * Any other numeric string, such as a one digit string, a three or more digit
+ * string, or a two digit string that isn't all digits (for example, "-1"), is
+ * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
+ * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
+ *
+ * <p>
+ * If the year pattern does not have exactly two 'y' characters, the year is
+ * interpreted literally, regardless of the number of digits.  So using the
+ * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
+ *
+ * <p>
+ * When numeric fields abut one another directly, with no intervening delimiter
+ * characters, they constitute a run of abutting numeric fields.  Such runs are
+ * parsed specially.  For example, the format "HHmmss" parses the input text
+ * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
+ * parse "1234".  In other words, the leftmost field of the run is flexible,
+ * while the others keep a fixed width.  If the parse fails anywhere in the run,
+ * then the leftmost field is shortened by one character, and the entire run is
+ * parsed again. This is repeated until either the parse succeeds or the
+ * leftmost field is one character in length.  If the parse still fails at that
+ * point, the parse of the run fails.
+ *
+ * <p>
+ * For time zones that have no names, use strings GMT+hours:minutes or
+ * GMT-hours:minutes.
+ *
+ * <p>
+ * The calendar defines what is the first day of the week, the first week
+ * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
+ * time zone. There is one common decimal format to handle all the numbers;
+ * the digit count is handled programmatically according to the pattern.
+ *
+ * <h4>Synchronization</h4>
+ *
+ * Date formats are not synchronized. It is recommended to create separate 
+ * format instances for each thread. If multiple threads access a format 
+ * concurrently, it must be synchronized externally. 
+ *
+ * @see          com.ibm.icu.util.Calendar
+ * @see          com.ibm.icu.util.GregorianCalendar
+ * @see          com.ibm.icu.util.TimeZone
+ * @see          DateFormat
+ * @see          DateFormatSymbols
+ * @see          DecimalFormat
+ * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
+ * @stable ICU 2.0
+ */
+public class SimpleDateFormat extends DateFormat {
+
+    // the official serial version ID which says cryptically
+    // which version we're compatible with
+    static final long serialVersionUID = 4774881970558875024L;
+
+    // the internal serial version which says which version was written
+    // - 0 (default) for version up to JDK 1.1.3
+    // - 1 for version from JDK 1.1.4, which includes a new field
+    static final int currentSerialVersion = 1;
+
+    /**
+     * The version of the serialized data on the stream.  Possible values:
+     * <ul>
+     * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
+     * has no <code>defaultCenturyStart</code> on stream.
+     * <li><b>1</b> JDK 1.1.4 or later.  This version adds
+     * <code>defaultCenturyStart</code>.
+     * </ul>
+     * When streaming out this class, the most recent format
+     * and the highest allowable <code>serialVersionOnStream</code>
+     * is written.
+     * @serial
+     */
+    private int serialVersionOnStream = currentSerialVersion;
+
+    /**
+     * The pattern string of this formatter.  This is always a non-localized
+     * pattern.  May not be null.  See class documentation for details.
+     * @serial
+     */
+    private String pattern;
+
+    /**
+     * The symbols used by this formatter for week names, month names,
+     * etc.  May not be null.
+     * @serial
+     * @see DateFormatSymbols
+     */
+    private DateFormatSymbols formatData;
+
+    /**
+     * We map dates with two-digit years into the century starting at
+     * <code>defaultCenturyStart</code>, which may be any date.  May
+     * not be null.
+     * @serial
+     * @since JDK1.1.4
+     */
+    private Date defaultCenturyStart;
+
+    transient private int defaultCenturyStartYear;
+
+    private static final int millisPerHour = 60 * 60 * 1000;
+    private static final int millisPerMinute = 60 * 1000;
+
+    // For time zones that have no names, use strings GMT+minutes and
+    // GMT-minutes. For instance, in France the time zone is GMT+60.
+    private static final String GMT_PLUS = "GMT+";
+    private static final String GMT_MINUS = "GMT-";
+    private static final String GMT = "GMT";
+
+    // This prefix is designed to NEVER MATCH real text, in order to
+    // suppress the parsing of negative numbers.  Adjust as needed (if
+    // this becomes valid Unicode).
+    private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
+
+    /**
+     * Cache to hold the DateTimePatterns of a Locale.
+     */
+    private static Hashtable cachedLocaleData = new Hashtable(3);
+
+    /**
+     * Construct a SimpleDateFormat using the default pattern for the default
+     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     *
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat() {
+        this(SHORT, SHORT, Locale.getDefault());
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern in the default
+     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern)
+    {
+        this(pattern, Locale.getDefault());
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern and locale.
+     * <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern, Locale loc)
+    {
+        this.pattern = pattern;
+        this.formatData = new DateFormatSymbols(loc);
+        initialize(loc);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern and
+     * locale-specific symbol data.
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
+    {
+        this.pattern = pattern;
+        this.formatData = (DateFormatSymbols) formatData.clone();
+        initialize(Locale.getDefault());
+    }
+
+    /* Package-private, called by DateFormat factory methods */
+    SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
+        /* try the cache first */
+        String[] dateTimePatterns = (String[]) cachedLocaleData.get(loc);
+        if (dateTimePatterns == null) { /* cache miss */
+            ResourceBundle r = ICULocaleData.getLocaleElements(loc);
+            dateTimePatterns = r.getStringArray("DateTimePatterns");
+            /* update cache */
+            cachedLocaleData.put(loc, dateTimePatterns);
+        }
+        formatData = new DateFormatSymbols(loc);
+        if ((timeStyle >= 0) && (dateStyle >= 0)) {
+            Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
+                                     dateTimePatterns[dateStyle + 4]};
+            pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
+        }
+        else if (timeStyle >= 0) {
+            pattern = dateTimePatterns[timeStyle];
+        }
+        else if (dateStyle >= 0) {
+            pattern = dateTimePatterns[dateStyle + 4];
+        }
+        else {
+            throw new IllegalArgumentException("No date or time style specified");
+        }
+
+        initialize(loc);
+    }
+
+    /* Initialize calendar and numberFormat fields */
+    private void initialize(Locale loc) {
+        // The format object must be constructed using the symbols for this zone.
+        // However, the calendar should use the current default TimeZone.
+        // If this is not contained in the locale zone strings, then the zone
+        // will be formatted using generic GMT+/-H:MM nomenclature.
+        calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
+        numberFormat = NumberFormat.getInstance(loc);
+        numberFormat.setGroupingUsed(false);
+        if (numberFormat instanceof DecimalFormat)
+            ((DecimalFormat)numberFormat).setDecimalSeparatorAlwaysShown(false);
+        numberFormat.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
+        numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
+
+        initializeDefaultCentury();
+    }
+
+    /* Initialize the fields we use to disambiguate ambiguous years. Separate
+     * so we can call it from readObject().
+     */
+    private void initializeDefaultCentury() {
+        calendar.setTime( new Date() );
+        calendar.add( Calendar.YEAR, -80 );
+        parseAmbiguousDatesAsAfter(calendar.getTime());
+    }
+
+    /* Define one-century window into which to disambiguate dates using
+     * two-digit years.
+     */
+    private void parseAmbiguousDatesAsAfter(Date startDate) {
+        defaultCenturyStart = startDate;
+        calendar.setTime(startDate);
+        defaultCenturyStartYear = calendar.get(Calendar.YEAR);
+    }
+
+    /**
+     * Sets the 100-year period 2-digit years will be interpreted as being in
+     * to begin on the date the user specifies.
+     * @param startDate During parsing, two digit years will be placed in the range
+     * <code>startDate</code> to <code>startDate + 100 years</code>.
+     * @stable ICU 2.0
+     */
+    public void set2DigitYearStart(Date startDate) {
+        parseAmbiguousDatesAsAfter(startDate);
+    }
+
+    /**
+     * Returns the beginning date of the 100-year period 2-digit years are interpreted
+     * as being within.
+     * @return the start of the 100-year period into which two digit years are
+     * parsed
+     * @stable ICU 2.0
+     */
+    public Date get2DigitYearStart() {
+        return defaultCenturyStart;
+    }
+
+    /**
+     * Overrides DateFormat.
+     * <p>Formats a date or time, which is the standard millis
+     * since January 1, 1970, 00:00:00 GMT.
+     * <p>Example: using the US locale:
+     * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
+     * @param date the date-time value to be formatted into a date-time string.
+     * @param toAppendTo where the new date-time text is to be appended.
+     * @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.
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+                               FieldPosition pos)
+    {
+        // Initialize
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        boolean inQuote = false; // true when between single quotes
+        char prevCh = 0; // previous pattern character
+        int count = 0;  // number of time prevCh repeated
+        for (int i=0; i<pattern.length(); ++i) {
+            char ch = pattern.charAt(i);
+            // Use subFormat() to format a repeated pattern character
+            // when a different pattern or non-pattern character is seen
+            if (ch != prevCh && count > 0) {
+                toAppendTo.append(
+                        subFormat(prevCh, count, toAppendTo.length(), pos, formatData, cal));
+                count = 0;
+            }
+            if (ch == '\'') {
+                // Consecutive single quotes are a single quote literal,
+                // either outside of quotes or between quotes
+                if ((i+1)<pattern.length() && pattern.charAt(i+1) == '\'') {
+                    toAppendTo.append('\'');
+                    ++i;
+                } else {
+                    inQuote = !inQuote;
+                }
+            } else if (!inQuote
+                       && (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')) {
+                // ch is a date-time pattern character to be interpreted
+                // by subFormat(); count the number of times it is repeated
+                prevCh = ch;
+                ++count;
+            }
+            else {
+                // Append quoted characters and unquoted non-pattern characters
+                toAppendTo.append(ch);
+            }
+        }
+        // Format the last item in the pattern, if any
+        if (count > 0) {
+            toAppendTo.append(
+                    subFormat(prevCh, count, toAppendTo.length(), pos, formatData, cal));
+        }
+        return toAppendTo;
+    }
+
+    // Map index into pattern character string to Calendar field number
+    private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
+    {
+        Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
+        Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
+        Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
+        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.EXTENDED_YEAR
+    };
+
+    // Map index into pattern character string to DateFormat field number
+    private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
+        DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
+        DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
+        DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
+        DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
+        DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
+        DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
+        DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
+        DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
+        DateFormat.TIMEZONE_FIELD,
+    };
+
+    /**
+     * Format a single field, given its pattern character.  Subclasses may
+     * override this method in order to modify or add formatting
+     * capabilities.
+     * @param ch the pattern character
+     * @param count the number of times ch is repeated in the pattern
+     * @param beginOffset the offset of the output string at the start of
+     * this field; used to set pos when appropriate
+     * @param pos receives the position of a field, when appropriate
+     * @param formatData the symbols for this formatter
+     * @stable ICU 2.0
+     */
+    protected String subFormat(char ch, int count, int beginOffset,
+                               FieldPosition pos, DateFormatSymbols formatData,
+                               Calendar cal)
+         throws IllegalArgumentException
+    {
+        int     patternCharIndex = -1;
+        int     maxIntCount = Integer.MAX_VALUE;
+        String  current = "";
+
+        if ((patternCharIndex=DateFormatSymbols.patternChars.indexOf(ch)) == -1) {
+            throw new IllegalArgumentException("Illegal pattern character " +
+                                               "'" + ch + "'");
+        }
+
+        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+        int value = cal.get(field);
+
+        switch (patternCharIndex) {
+        case 0: // 'G' - ERA
+            current = formatData.eras[value];
+            break;
+        case 1: // 'y' - YEAR
+            /* According to the specification, if the number of pattern letters ('y') is 2, 
+             * the year is truncated to 2 digits; otherwise it is interpreted as a number. 
+             * But the original code process 'y', 'yy', 'yyy' in the same way. and process 
+             * patterns with 4 or more than 4 'y' characters in the same way. 
+             * So I change the codes to meet the specification. [Richard/GCl]
+             */
+            if (count == 2)
+                current = zeroPaddingNumber(value, 2, 2); // clip 1996 to 96
+            else //count = 1 or count > 2
+                current = zeroPaddingNumber(value, count, maxIntCount);
+            break;
+        case 2: // 'M' - MONTH
+            if (count >= 4)
+                current = formatData.months[value];
+            else if (count == 3)
+                current = formatData.shortMonths[value];
+            else
+                current = zeroPaddingNumber(value+1, count, maxIntCount);
+            break;
+        case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
+            if (value == 0)
+                current = zeroPaddingNumber(
+                                            cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
+                                            count, maxIntCount);
+            else
+                current = zeroPaddingNumber(value, count, maxIntCount);
+            break;
+        case 9: // 'E' - DAY_OF_WEEK
+            if (count >= 4)
+                current = formatData.weekdays[value];
+            else // count < 4, use abbreviated form if exists
+                current = formatData.shortWeekdays[value];
+            break;
+        case 14:    // 'a' - AM_PM
+            current = formatData.ampms[value];
+            break;
+        case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
+            if (value == 0)
+                current = zeroPaddingNumber(
+                                            cal.getLeastMaximum(Calendar.HOUR)+1,
+                                            count, maxIntCount);
+            else
+                current = zeroPaddingNumber(value, count, maxIntCount);
+            break;
+        case 17: // 'z' - ZONE_OFFSET
+            int zoneIndex
+                = formatData.getZoneIndex (cal.getTimeZone().getID());
+            if (zoneIndex == -1)
+            {
+                // For time zones that have no names, use strings
+                // GMT+hours:minutes and GMT-hours:minutes.
+                // For instance, France time zone uses GMT+01:00.
+                StringBuffer zoneString = new StringBuffer();
+
+                value = cal.get(Calendar.ZONE_OFFSET) +
+                    cal.get(Calendar.DST_OFFSET);
+
+                if (value < 0)
+                {
+                    zoneString.append(GMT_MINUS);
+                    value = -value; // suppress the '-' sign for text display.
+                }
+                else
+                    zoneString.append(GMT_PLUS);
+                zoneString.append(
+                                  zeroPaddingNumber((int)(value/millisPerHour), 2, 2));
+                zoneString.append(':');
+                zoneString.append(
+                                  zeroPaddingNumber(
+                                                    (int)((value%millisPerHour)/millisPerMinute), 2, 2));
+                current = zoneString.toString();
+            }
+            else if (cal.get(Calendar.DST_OFFSET) != 0)
+            {
+                if (count >= 4)
+                    current = formatData.zoneStrings[zoneIndex][3];
+                else
+                    // count < 4, use abbreviated form if exists
+                    current = formatData.zoneStrings[zoneIndex][4];
+            }
+            else
+            {
+                if (count >= 4)
+                    current = formatData.zoneStrings[zoneIndex][1];
+                else
+                    current = formatData.zoneStrings[zoneIndex][2];
+            }
+            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 18: // 'u' - EXTENDED_YEAR
+            current = zeroPaddingNumber(value, count, maxIntCount);
+            break;
+        } // switch (patternCharIndex)
+
+        if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
+            // set for the first occurence only.
+            if (pos.getBeginIndex() == 0 && pos.getEndIndex() == 0) {
+                pos.setBeginIndex(beginOffset);
+                pos.setEndIndex(beginOffset + current.length());
+            }
+        }
+
+        return current;
+    }
+
+    /**
+     * Formats a number with the specified minimum and maximum number of digits.
+     * @stable ICU 2.0
+     */
+    protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
+    {
+        numberFormat.setMinimumIntegerDigits(minDigits);
+        numberFormat.setMaximumIntegerDigits(maxDigits);
+        return numberFormat.format(value);
+    }
+
+    /**
+     * Format characters that indicate numeric fields.  The character
+     * at index 0 is treated specially.
+     */
+    private static final String NUMERIC_FORMAT_CHARS = "MyudhHmsSDFwWkK";
+
+    /**
+     * Return true if the given format character, occuring count
+     * times, represents a numeric field.
+     */
+    private static final boolean isNumeric(char formatChar, int count) {
+        int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
+        return (i > 0 || (i == 0 && count < 3));
+    }
+
+    /**
+     * Overrides DateFormat
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public void parse(String text, Calendar cal, ParsePosition parsePos)
+    {
+        int pos = parsePos.getIndex();
+        int start = pos;
+        boolean[] ambiguousYear = {false};
+        int count = 0;
+
+        // For parsing abutting numeric fields. 'abutPat' is the
+        // offset into 'pattern' of the first of 2 or more abutting
+        // numeric fields.  'abutStart' is the offset into 'text'
+        // where parsing the fields begins. 'abutPass' starts off as 0
+        // and increments each time we try to parse the fields.
+        int abutPat = -1; // If >=0, we are in a run of abutting numeric fields
+        int abutStart = 0;
+        int abutPass = 0;
+        boolean inQuote = false;
+
+        for (int i=0; i<pattern.length(); ++i) {
+            char ch = pattern.charAt(i);
+
+            // Handle alphabetic field characters.
+            if (!inQuote && (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z')) {
+                int fieldPat = i;
+
+                // Count the length of this field specifier
+                count = 1;
+                while ((i+1)<pattern.length() &&
+                       pattern.charAt(i+1) == ch) {
+                    ++count;
+                    ++i;
+                }
+
+                if (isNumeric(ch, count)) {
+                    if (abutPat < 0) {
+                        // Determine if there is an abutting numeric field.  For
+                        // most fields we can just look at the next characters,
+                        // but the 'm' field is either numeric or text,
+                        // depending on the count, so we have to look ahead for
+                        // that field.
+                        if ((i+1)<pattern.length()) {
+                            boolean abutting;
+                            char nextCh = pattern.charAt(i+1);
+                            int k = NUMERIC_FORMAT_CHARS.indexOf(nextCh);
+                            if (k == 0) {
+                                int j = i+2;
+                                while (j<pattern.length() &&
+                                       pattern.charAt(j) == nextCh) {
+                                    ++j;
+                                }
+                                abutting = (j-i) < 4; // nextCount < 3
+                            } else {
+                                abutting = k > 0;
+                            }
+
+                            // Record the start of a set of abutting numeric
+                            // fields.
+                            if (abutting) {
+                                abutPat = fieldPat;
+                                abutStart = pos;
+                                abutPass = 0;
+                            }
+                        }
+                    }
+                } else {
+                    abutPat = -1; // End of any abutting fields
+                }
+
+                // Handle fields within a run of abutting numeric fields.  Take
+                // the pattern "HHmmss" as an example. We will try to parse
+                // 2/2/2 characters of the input text, then if that fails,
+                // 1/2/2.  We only adjust the width of the leftmost field; the
+                // others remain fixed.  This allows "123456" => 12:34:56, but
+                // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we
+                // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
+                if (abutPat >= 0) {
+                    // If we are at the start of a run of abutting fields, then
+                    // shorten this field in each pass.  If we can't shorten
+                    // this field any more, then the parse of this set of
+                    // abutting numeric fields has failed.
+                    if (fieldPat == abutPat) {
+                        count -= abutPass++;
+                        if (count == 0) {
+                            parsePos.setIndex(start);
+                            parsePos.setErrorIndex(pos);
+                            return;
+                        }
+                    }
+
+                    pos = subParse(text, pos, ch, count,
+                                   true, false, ambiguousYear, cal);
+
+                    // If the parse fails anywhere in the run, back up to the
+                    // start of the run and retry.
+                    if (pos < 0) {
+                        i = abutPat - 1;
+                        pos = abutStart;
+                        continue;
+                    }
+                }
+
+                // Handle non-numeric fields and non-abutting numeric
+                // fields.
+                else {
+                    int s = pos;
+                    pos = subParse(text, pos, ch, count,
+                                   false, true, ambiguousYear, cal);
+
+                    if (pos < 0) {
+                        parsePos.setErrorIndex(s);
+                        parsePos.setIndex(start);
+                        return;
+                    }
+                }
+            }
+
+            // Handle literal pattern characters.  These are any
+            // quoted characters and non-alphabetic unquoted
+            // characters.
+            else {
+                
+                abutPat = -1; // End of any abutting fields
+
+                // Handle quotes.  Two consecutive quotes is a quote
+                // literal, inside or outside of quotes.  Otherwise a
+                // quote indicates entry or exit from a quoted region.
+                if (ch == '\'') {
+                    // Match a quote literal '' within OR outside of quotes
+                    if ((i+1)<pattern.length() && pattern.charAt(i+1)==ch) {
+                        ++i; // Skip over doubled quote
+                        // Fall through and treat quote as a literal
+                    } else {
+                        // Enter or exit quoted region
+                        inQuote = !inQuote;
+                        continue;
+                    }
+                }
+
+                // A run of white space in the pattern matches a run
+                // of white space in the input text.
+                if (UCharacterProperty.isRuleWhiteSpace(ch)) {
+                    // Advance over run in pattern
+                    while ((i+1)<pattern.length() &&
+                           UCharacterProperty.isRuleWhiteSpace(pattern.charAt(i+1))) {
+                        ++i;
+                    }
+                    
+                    // Advance over run in input text
+                    int s = pos;
+                    while (pos<text.length() &&
+                           UCharacter.isUWhiteSpace(text.charAt(pos))) {
+                        ++pos;
+                    }
+
+                    // Must see at least one white space char in input
+                    if (pos > s) {
+                        continue;
+                    }
+                } else if (pos<text.length() && text.charAt(pos)==ch) {
+                    // Match a literal
+                    ++pos;
+                    continue;
+                }
+
+                // We fall through to this point if the match fails
+                parsePos.setIndex(start);
+                parsePos.setErrorIndex(pos);
+                return;
+            }
+        }
+
+        // At this point the fields of Calendar have been set.  Calendar
+        // will fill in default values for missing fields when the time
+        // is computed.
+
+        parsePos.setIndex(pos);
+
+        // 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 = cal.getTime();
+        if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
+            cal.add(Calendar.YEAR, 100);
+            parsedDate = cal.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().
+        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 copy = (Calendar)cal.clone();
+                Date parsedDate = copy.getTime();
+                if (parsedDate.before(defaultCenturyStart))
+                {
+                    // We can't use add here because that does a complete() first.
+                    cal.set(Calendar.YEAR, defaultCenturyStartYear + 100);
+                }
+            }
+        }
+        // An IllegalArgumentException will be thrown by Calendar.getTime()
+        // if any fields are out of range, e.g., MONTH == 17.
+        catch (IllegalArgumentException e) {
+            parsePos.setErrorIndex(pos);
+            parsePos.setIndex(start);
+        }
+    }
+
+    /**
+     * Attempt to match the text at a given position against an array of
+     * strings.  Since multiple strings in the array may match (for
+     * example, if the array contains "a", "ab", and "abc", all will match
+     * the input string "abcd") the longest match is returned.  As a side
+     * effect, the given field of <code>cal</code> is set to the index
+     * of the best match, if there is one.
+     * @param text the time text being parsed.
+     * @param start where to start parsing.
+     * @param field the date field being parsed.
+     * @param data the string array to parsed.
+     * @return the new start position if matching succeeded; a negative
+     * number indicating matching failure, otherwise.  As a side effect,
+     * sets the <code>cal</code> field <code>field</code> to the index
+     * of the best match, if matching succeeded.
+     * @stable ICU 2.0
+     */
+    protected int matchString(String text, int start, int field, String[] data, Calendar cal)
+    {
+        int i = 0;
+        int count = data.length;
+
+        if (field == Calendar.DAY_OF_WEEK) i = 1;
+
+        // There may be multiple strings in the data[] array which begin with
+        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+        // We keep track of the longest match, and return that.  Note that this
+        // unfortunately requires us to test all array elements.
+        int bestMatchLength = 0, bestMatch = -1;
+        for (; i<count; ++i)
+        {
+            int length = data[i].length();
+            // Always compare if we have no match yet; otherwise only compare
+            // against potentially better matches (longer strings).
+            if (length > bestMatchLength &&
+                text.regionMatches(true, start, data[i], 0, length))
+            {
+                bestMatch = i;
+                bestMatchLength = length;
+            }
+        }
+        if (bestMatch >= 0)
+        {
+            cal.set(field, bestMatch);
+            return start + bestMatchLength;
+        }
+        return -start;
+    }
+
+    private int matchZoneString(String text, int start, int zoneIndex) {
+    int j;
+    for (j = 1; j <= 4; ++j) {
+        // Checking long and short zones [1 & 2],
+        // and long and short daylight [3 & 4].
+        if (text.regionMatches(true, start,
+                   formatData.zoneStrings[zoneIndex][j], 0,
+                   formatData.zoneStrings[zoneIndex][j].length())) {
+        break;
+        }
+    }
+    return (j > 4) ? -1 : j;
+    }   
+
+    /**
+     * find time zone 'text' matched zoneStrings and set cal
+     */
+    private int subParseZoneString(String text, int start, Calendar cal) {
+    // At this point, check for named time zones by looking through
+    // the locale data from the DateFormatZoneData strings.
+    // Want to be able to parse both short and long forms.
+    int zoneIndex = 
+        formatData.getZoneIndex (getTimeZone().getID());
+    TimeZone tz = null;
+    int j = 0, i = 0;
+    if ((zoneIndex != -1) && ((j = matchZoneString(text, start, zoneIndex)) > 0)) {
+        tz = TimeZone.getTimeZone(formatData.zoneStrings[zoneIndex][0]);
+        i = zoneIndex;
+    }
+    if (tz == null) {
+        zoneIndex = 
+        formatData.getZoneIndex (TimeZone.getDefault().getID());
+        if ((zoneIndex != -1) && ((j = matchZoneString(text, start, zoneIndex)) > 0)) {
+        tz = TimeZone.getTimeZone(formatData.zoneStrings[zoneIndex][0]);
+        i = zoneIndex;
+        }
+    }       
+
+    if (tz == null) {
+        for (i = 0; i < formatData.zoneStrings.length; i++) {
+        if ((j = matchZoneString(text, start, i)) > 0) {
+            tz = TimeZone.getTimeZone(formatData.zoneStrings[i][0]);
+            break;
+        }
+        }
+    }
+    if (tz != null) { // Matched any ?
+        cal.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
+        int savings = 0;
+        if (j >= 3) {
+            // TODO: When JDK 1.3 support is dropped, change the following
+            // try/catch block to "savings = tz.getDSTSavings();".
+
+            // As of ICU 2.8 we support both 1.4 and 1.3.  When 1.3
+            // support is dropped, we can call
+            // TimeZone.getDSTSavings() directly.  Until then, we use
+            // reflection to call getDSTSavings() on either TimeZone
+            // or SimpleTimeZone. - Alan
+            try {
+                // The following works if getDSTSavings is declared in
+                // TimeZone (JDK 1.4) or SimpleTimeZone (JDK 1.3).
+                Method m = tz.getClass().getMethod("getDSTSavings", new Class[0]);
+                savings = ((Integer) m.invoke(tz, new Object[0])).intValue();
+            } catch (Exception e1) {}
+        }
+        cal.set(Calendar.DST_OFFSET, savings);
+        return (start + formatData.zoneStrings[i][j].length());
+    }
+    return 0;
+    }
+
+    /**
+     * Protected method that converts one field of the input string into a
+     * numeric field value in <code>cal</code>.  Returns -start (for
+     * ParsePosition) if failed.  Subclasses may override this method to
+     * modify or add parsing capabilities.
+     * @param text the time text to be parsed.
+     * @param start where to start parsing.
+     * @param ch the pattern character for the date field text to be parsed.
+     * @param count the count of a pattern character.
+     * @param obeyCount if true, then the next field directly abuts this one,
+     * and we should use the count to know when to stop parsing.
+     * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
+     * is true, then a two-digit year was parsed and may need to be readjusted.
+     * @return the new start position if matching succeeded; a negative
+     * number indicating matching failure, otherwise.  As a side effect,
+     * set the appropriate field of <code>cal</code> with the parsed
+     * value.
+     * @stable ICU 2.0
+     */
+    protected int subParse(String text, int start, char ch, int count,
+                           boolean obeyCount, boolean allowNegative,
+                           boolean[] ambiguousYear, Calendar cal)
+    {
+        Number number = null;
+        int value = 0;
+        int i;
+        ParsePosition pos = new ParsePosition(0);
+        int patternCharIndex = -1;
+
+        if ((patternCharIndex=DateFormatSymbols.patternChars.indexOf(ch)) == -1) {
+            return -start;
+        }
+
+        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+
+        // If there are any spaces here, skip over them.  If we hit the end
+        // of the string, then fail.
+        for (;;) {
+            if (start >= text.length()) {
+                return -start;
+            }
+            int c = UTF16.charAt(text, start);
+            if (!UCharacter.isUWhiteSpace(c)) {
+                break;
+            }
+            start += UTF16.getCharCount(c);
+        }
+        pos.setIndex(start);
+
+        // We handle a few special cases here where we need to parse
+        // 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)
+        {
+            // It would be good to unify this with the obeyCount logic below,
+            // but that's going to be difficult.
+            if (obeyCount)
+            {
+                if ((start+count) > text.length()) return -start;
+                number = parseInt(text.substring(0, start+count), pos, allowNegative);
+            }
+            else number = parseInt(text, pos, allowNegative);
+            if (number == null)
+                return -start;
+            value = number.intValue();
+        }
+
+        switch (patternCharIndex)
+        {
+        case 0: // 'G' - ERA
+            return matchString(text, start, Calendar.ERA, formatData.eras, cal);
+        case 1: // 'y' - YEAR
+            // If there are 3 or more YEAR pattern characters, this indicates
+            // that the year value is to be treated literally, without any
+            // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
+            // we made adjustments to place the 2-digit year in the proper
+            // century, for parsed strings from "00" to "99".  Any other string
+            // is treated literally:  "2250", "-1", "1", "002".
+            /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
+            if (count == 2 && (pos.getIndex() - start) == 2
+                && Character.isDigit(text.charAt(start))
+                && 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
+                // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
+                // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
+                // other fields specify a date before 6/18, or 1903 if they specify a
+                // date afterwards.  As a result, 03 is an ambiguous year.  All other
+                // two-digit years are unambiguous.
+                int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
+                ambiguousYear[0] = value == ambiguousTwoDigitYear;
+                value += (defaultCenturyStartYear/100)*100 +
+                    (value < ambiguousTwoDigitYear ? 100 : 0);
+            }
+            cal.set(Calendar.YEAR, value);
+            return pos.getIndex();
+        case 2: // 'M' - MONTH
+            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.]
+                cal.set(Calendar.MONTH, value - 1);
+                return pos.getIndex();
+            }
+            else
+            {
+                // count >= 3 // i.e., MMM or MMMM
+                // Want to be able to parse both short and long forms.
+                // Try count == 4 first:
+                int newStart = 0;
+                if ((newStart=matchString(text, start, Calendar.MONTH,
+                                          formatData.months, cal)) > 0)
+                    return newStart;
+                else // count == 4 failed, now try count == 3
+                    return matchString(text, start, Calendar.MONTH,
+                                       formatData.shortMonths, cal);
+            }
+        case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
+            // [We computed 'value' above.]
+            if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
+            cal.set(Calendar.HOUR_OF_DAY, value);
+            return pos.getIndex();
+        case 9: { // 'E' - DAY_OF_WEEK
+            // 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.weekdays, cal)) > 0)
+                return newStart;
+            else // DDDD failed, now try DDD
+                return matchString(text, start, Calendar.DAY_OF_WEEK,
+                                   formatData.shortWeekdays, cal);
+        }
+        case 14:    // 'a' - AM_PM
+            return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
+        case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
+            // [We computed 'value' above.]
+            if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
+            cal.set(Calendar.HOUR, value);
+            return pos.getIndex();
+        case 17: // 'z' - ZONE_OFFSET
+            // First try to parse generic forms such as GMT-07:00. Do this first
+            // in case localized DateFormatZoneData contains the string "GMT"
+            // for a zone; in that case, we don't want to match the first three
+            // characters of GMT+/-HH:MM etc.
+            {
+                int sign = 0;
+                int offset;
+
+                // For time zones that have no known names, look for strings
+                // of the form:
+                //    GMT[+-]hours:minutes or
+                //    GMT[+-]hhmm or
+                //    GMT.
+                if ((text.length() - start) >= GMT.length() &&
+                    text.regionMatches(true, start, GMT, 0, GMT.length()))
+                {
+                    cal.set(Calendar.DST_OFFSET, 0);
+
+                    pos.setIndex(start + GMT.length());
+
+            try { // try-catch for "GMT" only time zone string
+            if( text.charAt(pos.getIndex()) == '+' ) {
+                sign = 1;
+            } else if( text.charAt(pos.getIndex()) == '-' ) {
+                sign = -1;
+            } 
+            } catch(StringIndexOutOfBoundsException e) {
+                    }
+            if (sign == 0) {
+            cal.set(Calendar.ZONE_OFFSET, 0 );
+            return pos.getIndex();
+            }
+
+                    // Look for hours:minutes or hhmm.
+                    pos.setIndex(pos.getIndex() + 1);
+                    Number tzNumber = numberFormat.parse(text, pos);
+                    if( tzNumber == null) {
+                        return -start;
+                    }
+                    if( text.charAt(pos.getIndex()) == ':' ) {
+                        // This is the hours:minutes case
+                        offset = tzNumber.intValue() * 60;
+                        pos.setIndex(pos.getIndex() + 1);
+                        tzNumber = numberFormat.parse(text, pos);
+                        if( tzNumber == null) {
+                            return -start;
+                        }
+                        offset += tzNumber.intValue();
+                    }
+                    else {
+                        // This is the hhmm case.
+                        offset = tzNumber.intValue();
+                        if( offset < 24 )
+                            offset *= 60;
+                        else
+                            offset = offset % 100 + offset / 100 * 60;
+                    }
+
+                    // Fall through for final processing below of 'offset' and 'sign'.
+                }
+                else {
+                    // At this point, check for named time zones by looking through
+                    // the locale data from the DateFormatZoneData strings.
+                    // Want to be able to parse both short and long forms.
+            i = subParseZoneString(text, start, cal);
+            if (i != 0)
+            return i;
+
+                    // As a last resort, look for numeric timezones of the form
+                    // [+-]hhmm as specified by RFC 822.  This code is actually
+                    // a little more permissive than RFC 822.  It will try to do
+                    // its best with numbers that aren't strictly 4 digits long.
+                    DecimalFormat fmt = new DecimalFormat("+####;-####");
+                    fmt.setParseIntegerOnly(true);
+                    Number tzNumber = fmt.parse( text, pos );
+                    if( tzNumber == null) {
+                        return -start;   // Wasn't actually a number.
+                    }
+                    offset = tzNumber.intValue();
+                    sign = 1;
+                    if( offset < 0 ) {
+                        sign = -1;
+                        offset = -offset;
+                    }
+                    if( offset < 24 )
+                        offset = offset * 60;
+                    else
+                        offset = offset % 100 + offset / 100 * 60;
+
+                    // Fall through for final processing below of 'offset' and 'sign'.
+                }
+
+                // Do the final processing for both of the above cases.  We only
+                // arrive here if the form GMT+/-... or an RFC 822 form was seen.
+                if (sign != 0)
+                {
+                    offset *= millisPerMinute * sign;
+
+                    if (cal.getTimeZone().useDaylightTime())
+                    {
+                        cal.set(Calendar.DST_OFFSET, millisPerHour);
+                        offset -= millisPerHour;
+                    }
+                    cal.set(Calendar.ZONE_OFFSET, offset);
+
+                    return pos.getIndex();
+                }
+            }
+
+            // All efforts to parse a zone failed.
+            return -start;
+
+        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 18: // 'u' - EXTENDED_YEAR
+
+            // Handle "generic" fields
+            if (obeyCount)
+            {
+                if ((start+count) > text.length()) return -start;
+                number = parseInt(text.substring(0, start+count), pos, allowNegative);
+            }
+            else number = parseInt(text, pos, allowNegative);
+            if (number != null) {
+                cal.set(field, number.intValue());
+                return pos.getIndex();
+            }
+            return -start;
+        }
+    }
+
+    /**
+     * Parse an integer using fNumberFormat.  This method is semantically
+     * const, but actually may modify fNumberFormat.
+     */
+    private Number parseInt(String text,
+                            ParsePosition pos,
+                            boolean allowNegative) {
+        String oldPrefix = null;
+        DecimalFormat df = null;
+        if (!allowNegative) {
+            try {
+                df = (DecimalFormat)numberFormat;
+                oldPrefix = df.getNegativePrefix();
+                df.setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
+            } catch (ClassCastException e1) {}
+        }
+        Number number = numberFormat.parse(text, pos);
+        if (df != null) {
+            df.setNegativePrefix(oldPrefix);
+        }
+        return number;
+    }
+
+    /**
+     * Translate a pattern, mapping each character in the from string to the
+     * corresponding character in the to string.
+     */
+    private String translatePattern(String pattern, String from, String to) {
+        StringBuffer result = new StringBuffer();
+        boolean inQuote = false;
+        for (int i = 0; i < pattern.length(); ++i) {
+            char c = pattern.charAt(i);
+            if (inQuote) {
+                if (c == '\'')
+                    inQuote = false;
+            }
+            else {
+                if (c == '\'')
+                    inQuote = true;
+                else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                    int ci = from.indexOf(c);
+                    if (ci == -1)
+                        throw new IllegalArgumentException("Illegal pattern " +
+                                                           " character '" +
+                                                           c + "'");
+                    c = to.charAt(ci);
+                }
+            }
+            result.append(c);
+        }
+        if (inQuote)
+            throw new IllegalArgumentException("Unfinished quote in pattern");
+        return result.toString();
+    }
+
+    /**
+     * Return a pattern string describing this date format.
+     * @stable ICU 2.0
+     */
+    public String toPattern() {
+        return pattern;
+    }
+
+    /**
+     * Return a localized pattern string describing this date format.
+     * @stable ICU 2.0
+     */
+    public String toLocalizedPattern() {
+        return translatePattern(pattern,
+                                DateFormatSymbols.patternChars,
+                                formatData.localPatternChars);
+    }
+
+    /**
+     * Apply the given unlocalized pattern string to this date format.
+     * @stable ICU 2.0
+     */
+    public void applyPattern (String pattern)
+    {
+        this.pattern = pattern;
+    }
+
+    /**
+     * Apply the given localized pattern string to this date format.
+     * @stable ICU 2.0
+     */
+    public void applyLocalizedPattern(String pattern) {
+        this.pattern = translatePattern(pattern,
+                                        formatData.localPatternChars,
+                                        DateFormatSymbols.patternChars);
+    }
+
+    /**
+     * Gets the date/time formatting data.
+     * @return a copy of the date-time formatting data associated
+     * with this date-time formatter.
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols getDateFormatSymbols()
+    {
+        return (DateFormatSymbols)formatData.clone();
+    }
+
+    /**
+     * Allows you to set the date/time formatting data.
+     * @param newFormatData the given date-time formatting data.
+     * @stable ICU 2.0
+     */
+    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
+    {
+        this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
+    }
+
+    /**
+     * Method for subclasses to access the DateFormatSymbols.
+     * @stable ICU 2.0
+     */
+    protected DateFormatSymbols getSymbols() {
+        return formatData;
+    }
+
+    /**
+     * Overrides Cloneable
+     * @stable ICU 2.0
+     */
+    public Object clone() {
+        SimpleDateFormat other = (SimpleDateFormat) super.clone();
+        other.formatData = (DateFormatSymbols) formatData.clone();
+        return other;
+    }
+
+    /**
+     * Override hashCode.
+     * Generates the hash code for the SimpleDateFormat object
+     * @stable ICU 2.0
+     */
+    public int hashCode()
+    {
+        return pattern.hashCode();
+        // just enough fields for a reasonable distribution
+    }
+
+    /**
+     * Override equals.
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object obj)
+    {
+        if (!super.equals(obj)) return false; // super does class check
+        SimpleDateFormat that = (SimpleDateFormat) obj;
+        return (pattern.equals(that.pattern)
+                && formatData.equals(that.formatData));
+    }
+
+    /**
+     * Override readObject.
+     */
+    private void readObject(ObjectInputStream stream)
+         throws IOException, ClassNotFoundException {
+             stream.defaultReadObject();
+             if (serialVersionOnStream < 1) {
+                 // didn't have defaultCenturyStart field
+                 initializeDefaultCentury();
+             }
+             else {
+                 // fill in dependent transient field
+                 parseAmbiguousDatesAsAfter(defaultCenturyStart);
+             }
+             serialVersionOnStream = currentSerialVersion;
+    }
+}
diff --git a/src/com/ibm/icu/util/BuddhistCalendar.java b/src/com/ibm/icu/util/BuddhistCalendar.java
new file mode 100755
index 0000000..7f33929
--- /dev/null
+++ b/src/com/ibm/icu/util/BuddhistCalendar.java
@@ -0,0 +1,227 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2000, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/BuddhistCalendar.java,v $ 
+ * $Date: 2003/09/04 01:00:29 $ 
+ * $Revision: 1.13 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Locale;
+
+/**
+ * <code>BuddhistCalendar</code> is a subclass of <code>GregorianCalendar</code>
+ * that numbers years since the birth of the Buddha.  This is the civil calendar
+ * in some predominantly Buddhist countries such as Thailand, and it is used for
+ * religious purposes elsewhere.
+ * <p>
+ * The Buddhist calendar is identical to the Gregorian calendar in all respects
+ * except for the year and era.  Years are numbered since the birth of the
+ * Buddha in 543 BC (Gregorian), so that 1 AD (Gregorian) is equivalent to 544
+ * BE (Buddhist Era) and 1998 AD is 2541 BE.
+ * <p>
+ * The Buddhist Calendar has only one allowable era: <code>BE</code>.  If the
+ * calendar is not in lenient mode (see <code>setLenient</code>), dates before
+ * 1/1/1 BE are rejected with an <code>IllegalArgumentException</code>.
+ *
+ * @see com.ibm.icu.util.GregorianCalendar
+ *
+ * @author Laura Werner
+ * @author Alan Liu
+ * @draft ICU 2.4
+ */
+public class BuddhistCalendar extends GregorianCalendar {
+    
+    private static String copyright = "Copyright \u00a9 1998 IBM Corp. All Rights Reserved.";
+
+    //-------------------------------------------------------------------------
+    // Constructors...
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constant for the Buddhist Era.  This is the only allowable <code>ERA</code>
+     * value for the Buddhist calendar.
+     *
+     * @see com.ibm.icu.util.Calendar#ERA
+     * @draft ICU 2.4
+     */
+    public static final int BE = 0;
+    
+    /**
+     * Constructs a <code>BuddhistCalendar</code> using the current time
+     * in the default time zone with the default locale.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar() {
+        super();
+    }
+
+    /**
+     * Constructs a <code>BuddhistCalendar</code> based on the current time
+     * in the given time zone with the default locale.
+     *
+     * @param zone the given time zone.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(TimeZone zone) {
+        super(zone);
+    }
+
+    /**
+     * Constructs a <code>BuddhistCalendar</code> based on the current time
+     * in the default time zone with the given locale.
+     *
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(Locale aLocale) {
+        super(aLocale);
+    }
+
+    /**
+     * Constructs a <code>BuddhistCalendar</code> based on the current time
+     * in the given time zone with the given locale.
+     *
+     * @param zone the given time zone.
+     *
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(TimeZone zone, Locale aLocale) {
+        super(zone, aLocale);
+    }
+
+    /**
+     * Constructs a <code>BuddhistCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param date      The date to which the new calendar is set.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(Date date) {
+        this();
+        setTime(date);
+    }
+
+    /**
+     * Constructs a <code>BuddhistCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.
+     *                  The value is 0-based. e.g., 0 for January.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} time field.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(int year, int month, int date) {
+        super(year, month, date);
+    }
+
+    /**
+     * Constructs a BuddhistCalendar with the given date
+     * and time set for the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.
+     *                  The value is 0-based. e.g., 0 for January.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} time field.
+     *
+     * @param hour      The value used to set the calendar's {@link #HOUR_OF_DAY HOUR_OF_DAY} time field.
+     *
+     * @param minute    The value used to set the calendar's {@link #MINUTE MINUTE} time field.
+     *
+     * @param second    The value used to set the calendar's {@link #SECOND SECOND} time field.
+     * @draft ICU 2.4
+     */
+    public BuddhistCalendar(int year, int month, int date, int hour,
+                             int minute, int second)
+    {
+        super(year, month, date, hour, minute, second);
+    }
+
+
+    //-------------------------------------------------------------------------
+    // The only practical difference from a Gregorian calendar is that years
+    // are numbered since the birth of the Buddha.  A couple of overrides will
+    // take care of that....
+    //-------------------------------------------------------------------------
+    
+    // Starts in -543 AD, ie 544 BC
+    private static final int BUDDHIST_ERA_START = -543;
+
+    /**
+     * @draft ICU 2.4
+     */    
+    protected int handleGetExtendedYear() {
+        int year;
+        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
+            year = internalGet(EXTENDED_YEAR, 1);
+        } else {
+            // Ignore the era, as there is only one
+            year = internalGet(YEAR, 1);
+        }
+        return year;
+    }
+
+    // Return JD of start of given month/year
+    /**
+     * @draft ICU 2.4
+     */    
+    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
+        return super.handleComputeMonthStart(eyear + BUDDHIST_ERA_START, month, useMonth);
+    }
+
+    /**
+     * @draft ICU 2.4
+     */    
+    protected void handleComputeFields(int julianDay) {
+        super.handleComputeFields(julianDay);
+        int y = internalGet(EXTENDED_YEAR) - BUDDHIST_ERA_START;
+        internalSet(EXTENDED_YEAR, y);
+        internalSet(ERA, 0);
+        internalSet(YEAR, y);
+    }
+
+    /**
+     * Override GregorianCalendar.  There is only one Buddhist ERA.  We
+     * should really handle YEAR, YEAR_WOY, and EXTENDED_YEAR here too to
+     * implement the 1..5000000 range, but it's not critical.
+     * @draft ICU 2.4
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        if (field == ERA) {
+            return BE;
+        }
+        return super.handleGetLimit(field, limitType);
+    }
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new BuddhistCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Buddhist";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
diff --git a/src/com/ibm/icu/util/Calendar.java b/src/com/ibm/icu/util/Calendar.java
new file mode 100755
index 0000000..5fc96e2
--- /dev/null
+++ b/src/com/ibm/icu/util/Calendar.java
@@ -0,0 +1,4900 @@
+/*
+*   Copyright (C) 1996-2003, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*/
+
+
+package com.ibm.icu.util;
+
+import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.impl.ICUService.Factory;
+import com.ibm.icu.impl.ICULocaleService;
+import com.ibm.icu.impl.ICULocaleService.LocaleKeyFactory;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.DateFormatSymbols;
+import com.ibm.icu.text.SimpleDateFormat;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * <code>Calendar</code> is an abstract base class for converting between
+ * a <code>Date</code> object and a set of integer fields such as
+ * <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>, <code>HOUR</code>,
+ * and so on. (A <code>Date</code> object represents a specific instant in
+ * time with millisecond precision. See
+ * {@link Date}
+ * for information about the <code>Date</code> class.)
+ *
+ * <p><b>Note:</b>  This class is similar, but not identical, to the class
+ * <code>java.util.Calendar</code>.  Changes are detailed below.
+ *
+ * <p>
+ * Subclasses of <code>Calendar</code> interpret a <code>Date</code>
+ * according to the rules of a specific calendar system.  ICU4J contains
+ * several subclasses implementing different international calendar systems.
+ *
+ * <p>
+ * Like other locale-sensitive classes, <code>Calendar</code> provides a
+ * class method, <code>getInstance</code>, for getting a generally useful
+ * object of this type. <code>Calendar</code>'s <code>getInstance</code> method
+ * returns a <code>GregorianCalendar</code> object whose
+ * time fields have been initialized with the current date and time:
+ * <blockquote>
+ * <pre>
+ * Calendar rightNow = Calendar.getInstance();
+ * </pre>
+ * </blockquote>
+ *
+ * <p>A <code>Calendar</code> object can produce all the time field values
+ * needed to implement the date-time formatting for a particular language and
+ * calendar style (for example, Japanese-Gregorian, Japanese-Traditional).
+ * <code>Calendar</code> defines the range of values returned by certain fields,
+ * as well as their meaning.  For example, the first month of the year has value
+ * <code>MONTH</code> == <code>JANUARY</code> for all calendars.  Other values
+ * are defined by the concrete subclass, such as <code>ERA</code> and
+ * <code>YEAR</code>.  See individual field documentation and subclass
+ * documentation for details.
+ *
+ * <p>When a <code>Calendar</code> is <em>lenient</em>, it accepts a wider range
+ * of field values than it produces.  For example, a lenient
+ * <code>GregorianCalendar</code> interprets <code>MONTH</code> ==
+ * <code>JANUARY</code>, <code>DAY_OF_MONTH</code> == 32 as February 1.  A
+ * non-lenient <code>GregorianCalendar</code> throws an exception when given
+ * out-of-range field settings.  When calendars recompute field values for
+ * return by <code>get()</code>, they normalize them.  For example, a
+ * <code>GregorianCalendar</code> always produces <code>DAY_OF_MONTH</code>
+ * values between 1 and the length of the month.
+ *
+ * <p><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
+ * (from 1 to 7).  These numbers are taken from the locale resource data when a
+ * <code>Calendar</code> is constructed.  They may also be specified explicitly
+ * through the API.
+ *
+ * <p>When setting or getting the <code>WEEK_OF_MONTH</code> or
+ * <code>WEEK_OF_YEAR</code> fields, <code>Calendar</code> must determine the
+ * first week of the month or year as a reference point.  The first week of a
+ * month or year is defined as the earliest seven day period beginning on
+ * <code>getFirstDayOfWeek()</code> and containing at least
+ * <code>getMinimalDaysInFirstWeek()</code> days of that month or year.  Weeks
+ * numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow
+ * it.  Note that the normalized numbering returned by <code>get()</code> may be
+ * different.  For example, a specific <code>Calendar</code> subclass may
+ * designate the week before week 1 of a year as week <em>n</em> of the previous
+ * year.
+ *
+ * <p> When computing a <code>Date</code> from time fields, two special
+ * circumstances may arise: there may be insufficient information to compute the
+ * <code>Date</code> (such as only year and month but no day in the month), or
+ * there may be inconsistent information (such as "Tuesday, July 15, 1996" --
+ * July 15, 1996 is actually a Monday).
+ *
+ * <p>
+ * <strong>Insufficient information.</strong> The calendar will use default
+ * information to specify the missing fields. This may vary by calendar; for
+ * the Gregorian calendar, the default for a field is the same as that of the
+ * start of the epoch: i.e., YEAR = 1970, MONTH = JANUARY, DATE = 1, etc.
+ *
+ * <p>
+ * <strong>Inconsistent information.</strong> If fields conflict, the calendar
+ * will give preference to fields set more recently. For example, when
+ * determining the day, the calendar will look for one of the following
+ * combinations of fields.  The most recent combination, as determined by the
+ * most recently set single field, will be used.
+ *
+ * <blockquote>
+ * <pre>
+ * MONTH + DAY_OF_MONTH
+ * MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
+ * MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
+ * DAY_OF_YEAR
+ * DAY_OF_WEEK + WEEK_OF_YEAR
+ * </pre>
+ * </blockquote>
+ *
+ * For the time of day:
+ *
+ * <blockquote>
+ * <pre>
+ * HOUR_OF_DAY
+ * AM_PM + HOUR
+ * </pre>
+ * </blockquote>
+ *
+ * <p>
+ * <strong>Note:</strong> for some non-Gregorian calendars, different
+ * fields may be necessary for complete disambiguation. For example, a full
+ * specification of the historial Arabic astronomical calendar requires year,
+ * month, day-of-month <em>and</em> day-of-week in some cases.
+ *
+ * <p>
+ * <strong>Note:</strong> There are certain possible ambiguities in
+ * interpretation of certain singular times, which are resolved in the
+ * following ways:
+ * <ol>
+ *     <li> 24:00:00 "belongs" to the following day. That is,
+ *          23:59 on Dec 31, 1969 &lt; 24:00 on Jan 1, 1970 &lt; 24:01:00 on Jan 1, 1970
+ *
+ *     <li> Although historically not precise, midnight also belongs to "am",
+ *          and noon belongs to "pm", so on the same day,
+ *          12:00 am (midnight) &lt; 12:01 am, and 12:00 pm (noon) &lt; 12:01 pm
+ * </ol>
+ *
+ * <p>
+ * The date or time format strings are not part of the definition of a
+ * calendar, as those must be modifiable or overridable by the user at
+ * runtime. Use {@link DateFormat}
+ * to format dates.
+ *
+ * <p><strong>Field manipulation methods</strong></p>
+ *
+ * <p><code>Calendar</code> fields can be changed using three methods:
+ * <code>set()</code>, <code>add()</code>, and <code>roll()</code>.</p>
+ *
+ * <p><strong><code>set(f, value)</code></strong> changes field
+ * <code>f</code> to <code>value</code>.  In addition, it sets an
+ * internal member variable to indicate that field <code>f</code> has
+ * been changed. Although field <code>f</code> is changed immediately,
+ * the calendar's milliseconds is not recomputed until the next call to
+ * <code>get()</code>, <code>getTime()</code>, or
+ * <code>getTimeInMillis()</code> is made. Thus, multiple calls to
+ * <code>set()</code> do not trigger multiple, unnecessary
+ * computations. As a result of changing a field using
+ * <code>set()</code>, other fields may also change, depending on the
+ * field, the field value, and the calendar system. In addition,
+ * <code>get(f)</code> will not necessarily return <code>value</code>
+ * after the fields have been recomputed. The specifics are determined by
+ * the concrete calendar class.</p>
+ *
+ * <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
+ * originally set to August 31, 1999. Calling <code>set(Calendar.MONTH,
+ * Calendar.SEPTEMBER)</code> sets the calendar to September 31,
+ * 1999. This is a temporary internal representation that resolves to
+ * October 1, 1999 if <code>getTime()</code>is then called. However, a
+ * call to <code>set(Calendar.DAY_OF_MONTH, 30)</code> before the call to
+ * <code>getTime()</code> sets the calendar to September 30, 1999, since
+ * no recomputation occurs after <code>set()</code> itself.</p>
+ *
+ * <p><strong><code>add(f, delta)</code></strong> adds <code>delta</code>
+ * to field <code>f</code>.  This is equivalent to calling <code>set(f,
+ * get(f) + delta)</code> with two adjustments:</p>
+ *
+ * <blockquote>
+ *   <p><strong>Add rule 1</strong>. The value of field <code>f</code>
+ *   after the call minus the value of field <code>f</code> before the
+ *   call is <code>delta</code>, modulo any overflow that has occurred in
+ *   field <code>f</code>. Overflow occurs when a field value exceeds its
+ *   range and, as a result, the next larger field is incremented or
+ *   decremented and the field value is adjusted back into its range.</p>
+ *
+ *   <p><strong>Add rule 2</strong>. If a smaller field is expected to be
+ *   invariant, but &nbsp; it is impossible for it to be equal to its
+ *   prior value because of changes in its minimum or maximum after field
+ *   <code>f</code> is changed, then its value is adjusted to be as close
+ *   as possible to its expected value. A smaller field represents a
+ *   smaller unit of time. <code>HOUR</code> is a smaller field than
+ *   <code>DAY_OF_MONTH</code>. No adjustment is made to smaller fields
+ *   that are not expected to be invariant. The calendar system
+ *   determines what fields are expected to be invariant.</p>
+ * </blockquote>
+ *
+ * <p>In addition, unlike <code>set()</code>, <code>add()</code> forces
+ * an immediate recomputation of the calendar's milliseconds and all
+ * fields.</p>
+ *
+ * <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
+ * originally set to August 31, 1999. Calling <code>add(Calendar.MONTH,
+ * 13)</code> sets the calendar to September 30, 2000. <strong>Add rule
+ * 1</strong> sets the <code>MONTH</code> field to September, since
+ * adding 13 months to August gives September of the next year. Since
+ * <code>DAY_OF_MONTH</code> cannot be 31 in September in a
+ * <code>GregorianCalendar</code>, <strong>add rule 2</strong> sets the
+ * <code>DAY_OF_MONTH</code> to 30, the closest possible value. Although
+ * it is a smaller field, <code>DAY_OF_WEEK</code> is not adjusted by
+ * rule 2, since it is expected to change when the month changes in a
+ * <code>GregorianCalendar</code>.</p>
+ *
+ * <p><strong><code>roll(f, delta)</code></strong> adds
+ * <code>delta</code> to field <code>f</code> without changing larger
+ * fields. This is equivalent to calling <code>add(f, delta)</code> with
+ * the following adjustment:</p>
+ *
+ * <blockquote>
+ *   <p><strong>Roll rule</strong>. Larger fields are unchanged after the
+ *   call. A larger field represents a larger unit of
+ *   time. <code>DAY_OF_MONTH</code> is a larger field than
+ *   <code>HOUR</code>.</p>
+ * </blockquote>
+ *
+ * <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
+ * originally set to August 31, 1999. Calling <code>roll(Calendar.MONTH,
+ * 8)</code> sets the calendar to April 30, <strong>1999</strong>.  Add
+ * rule 1 sets the <code>MONTH</code> field to April. Using a
+ * <code>GregorianCalendar</code>, the <code>DAY_OF_MONTH</code> cannot
+ * be 31 in the month April. Add rule 2 sets it to the closest possible
+ * value, 30. Finally, the <strong>roll rule</strong> maintains the
+ * <code>YEAR</code> field value of 1999.</p>
+ *
+ * <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
+ * originally set to Sunday June 6, 1999. Calling
+ * <code>roll(Calendar.WEEK_OF_MONTH, -1)</code> sets the calendar to
+ * Tuesday June 1, 1999, whereas calling
+ * <code>add(Calendar.WEEK_OF_MONTH, -1)</code> sets the calendar to
+ * Sunday May 30, 1999. This is because the roll rule imposes an
+ * additional constraint: The <code>MONTH</code> must not change when the
+ * <code>WEEK_OF_MONTH</code> is rolled. Taken together with add rule 1,
+ * the resultant date must be between Tuesday June 1 and Saturday June
+ * 5. According to add rule 2, the <code>DAY_OF_WEEK</code>, an invariant
+ * when changing the <code>WEEK_OF_MONTH</code>, is set to Tuesday, the
+ * closest possible value to Sunday (where Sunday is the first day of the
+ * week).</p>
+ *
+ * <p><strong>Usage model</strong>. To motivate the behavior of
+ * <code>add()</code> and <code>roll()</code>, consider a user interface
+ * component with increment and decrement buttons for the month, day, and
+ * year, and an underlying <code>GregorianCalendar</code>. If the
+ * interface reads January 31, 1999 and the user presses the month
+ * increment button, what should it read? If the underlying
+ * implementation uses <code>set()</code>, it might read March 3, 1999. A
+ * better result would be February 28, 1999. Furthermore, if the user
+ * presses the month increment button again, it should read March 31,
+ * 1999, not March 28, 1999. By saving the original date and using either
+ * <code>add()</code> or <code>roll()</code>, depending on whether larger
+ * fields should be affected, the user interface can behave as most users
+ * will intuitively expect.</p>
+ *
+ * <p><b>Note:</b> You should always use {@link #roll roll} and {@link #add add} rather
+ * than attempting to perform arithmetic operations directly on the fields
+ * of a <tt>Calendar</tt>.  It is quite possible for <tt>Calendar</tt> subclasses
+ * to have fields with non-linear behavior, for example missing months
+ * or days during non-leap years.  The subclasses' <tt>add</tt> and <tt>roll</tt>
+ * methods will take this into account, while simple arithmetic manipulations
+ * may give invalid results.
+ *
+ * <p><big><big><b>Calendar Architecture in ICU4J</b></big></big></p>
+ *
+ * <p>Recently the implementation of <code>Calendar</code> has changed
+ * significantly in order to better support subclassing. The original
+ * <code>Calendar</code> class was designed to support subclassing, but
+ * it had only one implemented subclass, <code>GregorianCalendar</code>.
+ * With the implementation of several new calendar subclasses, including
+ * the <code>BuddhistCalendar</code>, <code>ChineseCalendar</code>,
+ * <code>HebrewCalendar</code>, <code>IslamicCalendar</code>, and
+ * <code>JapaneseCalendar</code>, the subclassing API has been reworked
+ * thoroughly. This section details the new subclassing API and other
+ * ways in which <code>com.ibm.icu.util.Calendar</code> differs from
+ * <code>java.util.Calendar</code>.
+ * </p>
+ *
+ * <p><big><b>Changes</b></big></p>
+ *
+ * <p>Overview of changes between the classic <code>Calendar</code>
+ * architecture and the new architecture.
+ *
+ * <ul>
+ *
+ *   <li>The <code>fields[]</code> array is <code>private</code> now
+ *     instead of <code>protected</code>.  Subclasses must access it
+ *     using the methods {@link #internalSet} and
+ *     {@link #internalGet}.  <b>Motivation:</b> Subclasses should
+ *     not directly access data members.</li>
+ *
+ *   <li>The <code>time</code> long word is <code>private</code> now
+ *     instead of <code>protected</code>.  Subclasses may access it using
+ *     the method {@link #internalGetTimeInMillis}, which does not
+ *     provoke an update. <b>Motivation:</b> Subclasses should not
+ *     directly access data members.</li>
+ *
+ *   <li>The scope of responsibility of subclasses has been drastically
+ *     reduced. As much functionality as possible is implemented in the
+ *     <code>Calendar</code> base class. As a result, it is much easier
+ *     to subclass <code>Calendar</code>. <b>Motivation:</b> Subclasses
+ *     should not have to reimplement common code. Certain behaviors are
+ *     common across calendar systems: The definition and behavior of
+ *     week-related fields and time fields, the arithmetic
+ *     ({@link #add(int, int) add} and {@link #roll(int, int) roll}) behavior of many
+ *     fields, and the field validation system.</li>
+ *
+ *   <li>The subclassing API has been completely redesigned.</li>
+ *
+ *   <li>The <code>Calendar</code> base class contains some Gregorian
+ *     calendar algorithmic support that subclasses can use (specifically
+ *     in {@link #handleComputeFields}).  Subclasses can use the
+ *     methods <code>getGregorianXxx()</code> to obtain precomputed
+ *     values. <b>Motivation:</b> This is required by all
+ *     <code>Calendar</code> subclasses in order to implement consistent
+ *     time zone behavior, and Gregorian-derived systems can use the
+ *     already computed data.</li>
+ *
+ *   <li>The <code>FIELD_COUNT</code> constant has been removed. Use
+ *     {@link #getFieldCount}.  In addition, framework API has been
+ *     added to allow subclasses to define additional fields.
+ *     <b>Motivation: </b>The number of fields is not constant across
+ *     calendar systems.</li>
+ *
+ *   <li>The range of handled dates has been narrowed from +/-
+ *     ~300,000,000 years to +/- ~5,000,000 years. In practical terms
+ *     this should not affect clients. However, it does mean that client
+ *     code cannot be guaranteed well-behaved results with dates such as
+ *     <code>Date(Long.MIN_VALUE)</code> or
+ *     <code>Date(Long.MAX_VALUE)</code>. Instead, the
+ *     <code>Calendar</code> constants {@link #MIN_DATE},
+ *     {@link #MAX_DATE}, {@link #MIN_MILLIS},
+ *     {@link #MAX_MILLIS}, {@link #MIN_JULIAN}, and
+ *     {@link #MAX_JULIAN} should be used. <b>Motivation:</b> With
+ *     the addition of the {@link #JULIAN_DAY} field, Julian day
+ *     numbers must be restricted to a 32-bit <code>int</code>.  This
+ *     restricts the overall supported range. Furthermore, restricting
+ *     the supported range simplifies the computations by removing
+ *     special case code that was used to accomodate arithmetic overflow
+ *     at millis near <code>Long.MIN_VALUE</code> and
+ *     <code>Long.MAX_VALUE</code>.</li>
+ *
+ *   <li>New fields are implemented: {@link #JULIAN_DAY} defines
+ *     single-field specification of the
+ *     date. {@link #MILLISECONDS_IN_DAY} defines a single-field
+ *     specification of the wall time. {@link #DOW_LOCAL} and
+ *     {@link #YEAR_WOY} implement localized day-of-week and
+ *     week-of-year behavior.</li>
+ *
+ *   <li>Subclasses can access millisecond constants
+ *     {@link #ONE_SECOND}, {@link #ONE_MINUTE},
+ *     {@link #ONE_HOUR}, {@link #ONE_DAY}, and
+ *     {@link #ONE_WEEK} defined in <code>Calendar</code>.</li>
+ *
+ *   <li>New API has been added to suport calendar-specific subclasses
+ *     of <code>DateFormat</code>.</li>
+ *
+ *   <li>Several subclasses have been implemented, representing
+ *     various international calendar systems.</li>
+ *
+ * </ul>
+ *
+ * <p><big><b>Subclass API</b></big></p>
+ *
+ * <p>The original <code>Calendar</code> API was based on the experience
+ * of implementing a only a single subclass,
+ * <code>GregorianCalendar</code>. As a result, all of the subclassing
+ * kinks had not been worked out. The new subclassing API has been
+ * refined based on several implemented subclasses. This includes methods
+ * that must be overridden and methods for subclasses to call. Subclasses
+ * no longer have direct access to <code>fields</code> and
+ * <code>stamp</code>. Instead, they have new API to access
+ * these. Subclasses are able to allocate the <code>fields</code> array
+ * through a protected framework method; this allows subclasses to
+ * specify additional fields. </p>
+ *
+ * <p>More functionality has been moved into the base class. The base
+ * class now contains much of the computational machinery to support the
+ * Gregorian calendar. This is based on two things: (1) Many calendars
+ * are based on the Gregorian calendar (such as the Buddhist and Japanese
+ * imperial calendars). (2) <em>All</em> calendars require basic
+ * Gregorian support in order to handle timezone computations. </p>
+ *
+ * <p>Common computations have been moved into
+ * <code>Calendar</code>. Subclasses no longer compute the week related
+ * fields and the time related fields. These are commonly handled for all
+ * calendars by the base class. </p>
+ *
+ * <p><b>Subclass computation of time <tt>=&gt;</tt> fields</b>
+ *
+ * <p>The {@link #ERA}, {@link #YEAR},
+ * {@link #EXTENDED_YEAR}, {@link #MONTH},
+ * {@link #DAY_OF_MONTH}, and {@link #DAY_OF_YEAR} fields are
+ * computed by the subclass, based on the Julian day. All other fields
+ * are computed by <code>Calendar</code>.
+ *
+ * <ul>
+ *
+ *   <li>Subclasses should implement {@link #handleComputeFields}
+ *     to compute the {@link #ERA}, {@link #YEAR},
+ *     {@link #EXTENDED_YEAR}, {@link #MONTH},
+ *     {@link #DAY_OF_MONTH}, and {@link #DAY_OF_YEAR} fields,
+ *     based on the value of the {@link #JULIAN_DAY} field. If there
+ *     are calendar-specific fields not defined by <code>Calendar</code>,
+ *     they must also be computed. These are the only fields that the
+ *     subclass should compute. All other fields are computed by the base
+ *     class, so time and week fields behave in a consistent way across
+ *     all calendars. The default version of this method in
+ *     <code>Calendar</code> implements a proleptic Gregorian
+ *     calendar. Within this method, subclasses may call
+ *     <code>getGregorianXxx()</code> to obtain the Gregorian calendar
+ *     month, day of month, and extended year for the given date.</li>
+ *
+ * </ul>
+ *
+ * <p><b>Subclass computation of fields <tt>=&gt;</tt> time</b>
+ *
+ * <p>The interpretation of most field values is handled entirely by
+ * <code>Calendar</code>. <code>Calendar</code> determines which fields
+ * are set, which are not, which are set more recently, and so on. In
+ * addition, <code>Calendar</code> handles the computation of the time
+ * from the time fields and handles the week-related fields. The only
+ * thing the subclass must do is determine the extended year, based on
+ * the year fields, and then, given an extended year and a month, it must
+ * return a Julian day number.
+ *
+ * <ul>
+ *
+ *   <li>Subclasses should implement {@link #handleGetExtendedYear}
+ *     to return the extended year for this calendar system, based on the
+ *     {@link #YEAR}, {@link #EXTENDED_YEAR}, and any fields that
+ *     the calendar system uses that are larger than a year, such as
+ *     {@link #ERA}.</li>
+ *
+ *   <li>Subclasses should implement {@link #handleComputeMonthStart}
+ *     to return the Julian day number
+ *     associated with a month and extended year. This is the Julian day
+ *     number of the day before the first day of the month. The month
+ *     number is zero-based. This computation should not depend on any
+ *     field values.</li>
+ *
+ * </ul>
+ *
+ * <p><b>Other methods</b>
+ *
+ * <ul>
+ *
+ *   <li>Subclasses should implement {@link #handleGetMonthLength}
+ *     to return the number of days in a
+ *     given month of a given extended year. The month number, as always,
+ *     is zero-based.</li>
+ *
+ *   <li>Subclasses should implement {@link #handleGetYearLength}
+ *     to return the number of days in the given
+ *     extended year. This method is used by
+ *     <tt>computeWeekFields</tt> to compute the
+ *     {@link #WEEK_OF_YEAR} and {@link #YEAR_WOY} fields.</li>
+ *
+ *   <li>Subclasses should implement {@link #handleGetLimit}
+ *     to return the {@link #MINIMUM},
+ *     {@link #GREATEST_MINIMUM}, {@link #LEAST_MAXIMUM}, or
+ *     {@link #MAXIMUM} of a field, depending on the value of
+ *     <code>limitType</code>. This method only needs to handle the
+ *     fields {@link #ERA}, {@link #YEAR}, {@link #MONTH},
+ *     {@link #WEEK_OF_YEAR}, {@link #WEEK_OF_MONTH},
+ *     {@link #DAY_OF_MONTH}, {@link #DAY_OF_YEAR},
+ *     {@link #DAY_OF_WEEK_IN_MONTH}, {@link #YEAR_WOY}, and
+ *     {@link #EXTENDED_YEAR}.  Other fields are invariant (with
+ *     respect to calendar system) and are handled by the base
+ *     class.</li>
+ *
+ *   <li>Optionally, subclasses may override {@link #validateField}
+ *     to check any subclass-specific fields. If the
+ *     field's value is out of range, the method should throw an
+ *     <code>IllegalArgumentException</code>. The method may call
+ *     <code>super.validateField(field)</code> to handle fields in a
+ *     generic way, that is, to compare them to the range
+ *     <code>getMinimum(field)</code>..<code>getMaximum(field)</code>.</li>
+ *
+ *   <li>Optionally, subclasses may override
+ *     {@link #handleCreateFields} to create an <code>int[]</code>
+ *     array large enough to hold the calendar's fields. This is only
+ *     necessary if the calendar defines additional fields beyond those
+ *     defined by <code>Calendar</code>. The length of the result must be
+ *     at least {@link #BASE_FIELD_COUNT} and no more than
+ *     {@link #MAX_FIELD_COUNT}.</li>
+ *
+ *   <li>Optionally, subclasses may override
+ *     {@link #handleGetDateFormat} to create a
+ *     <code>DateFormat</code> appropriate to this calendar. This is only
+ *     required if a calendar subclass redefines the use of a field (for
+ *     example, changes the {@link #ERA} field from a symbolic field
+ *     to a numeric one) or defines an additional field.</li>
+ *
+ *   <li>Optionally, subclasses may override {@link #roll roll} and
+ *     {@link #add add} to handle fields that are discontinuous. For
+ *     example, in the Hebrew calendar the month &quot;Adar I&quot; only
+ *     occurs in leap years; in other years the calendar jumps from
+ *     Shevat (month #4) to Adar (month #6). The {@link
+ *     HebrewCalendar#add HebrewCalendar.add} and {@link
+ *     HebrewCalendar#roll HebrewCalendar.roll} methods take this into
+ *     account, so that adding 1 month to Shevat gives the proper result
+ *     (Adar) in a non-leap year. The protected utility method {@link
+ *     #pinField pinField} is often useful when implementing these two
+ *     methods. </li>
+ *
+ * </ul>
+ *
+ * <p><big><b>Normalized behavior</b></big>
+ *
+ * <p>The behavior of certain fields has been made consistent across all
+ * calendar systems and implemented in <code>Calendar</code>.
+ *
+ * <ul>
+ *
+ *   <li>Time is normalized. Even though some calendar systems transition
+ *     between days at sunset or at other times, all ICU4J calendars
+ *     transition between days at <em>local zone midnight</em>.  This
+ *     allows ICU4J to centralize the time computations in
+ *     <code>Calendar</code> and to maintain basic correpsondences
+ *     between calendar systems. Affected fields: {@link #AM_PM},
+ *     {@link #HOUR}, {@link #HOUR_OF_DAY}, {@link #MINUTE},
+ *     {@link #SECOND}, {@link #MILLISECOND},
+ *     {@link #ZONE_OFFSET}, and {@link #DST_OFFSET}.</li>
+ *
+ *   <li>DST behavior is normalized. Daylight savings time behavior is
+ *     computed the same for all calendar systems, and depends on the
+ *     value of several <code>GregorianCalendar</code> fields: the
+ *     {@link #YEAR}, {@link #MONTH}, and
+ *     {@link #DAY_OF_MONTH}. As a result, <code>Calendar</code>
+ *     always computes these fields, even for non-Gregorian calendar
+ *     systems. These fields are available to subclasses.</li>
+ *
+ *   <li>Weeks are normalized. Although locales define the week
+ *     differently, in terms of the day on which it starts, and the
+ *     designation of week number one of a month or year, they all use a
+ *     common mechanism. Furthermore, the day of the week has a simple
+ *     and consistent definition throughout history. For example,
+ *     although the Gregorian calendar introduced a discontinuity when
+ *     first instituted, the day of week was not disrupted. For this
+ *     reason, the fields {@link #DAY_OF_WEEK}, <code>WEEK_OF_YEAR,
+ *     WEEK_OF_MONTH</code>, {@link #DAY_OF_WEEK_IN_MONTH},
+ *     {@link #DOW_LOCAL}, {@link #YEAR_WOY} are all computed in
+ *     a consistent way in the base class, based on the
+ *     {@link #EXTENDED_YEAR}, {@link #DAY_OF_YEAR},
+ *     {@link #MONTH}, and {@link #DAY_OF_MONTH}, which are
+ *     computed by the subclass.</li>
+ *
+ * </ul>
+ *
+ * <p><big><b>Supported range</b></big>
+ *
+ * <p>The allowable range of <code>Calendar</code> has been
+ * narrowed. <code>GregorianCalendar</code> used to attempt to support
+ * the range of dates with millisecond values from
+ * <code>Long.MIN_VALUE</code> to <code>Long.MAX_VALUE</code>. This
+ * introduced awkward constructions (hacks) which slowed down
+ * performance. It also introduced non-uniform behavior at the
+ * boundaries. The new <code>Calendar</code> protocol specifies the
+ * maximum range of supportable dates as those having Julian day numbers
+ * of <code>-0x7F000000</code> to <code>+0x7F000000</code>. This
+ * corresponds to years from ~5,000,000 BCE to ~5,000,000 CE. Programmers
+ * should use the constants {@link #MIN_DATE} (or
+ * {@link #MIN_MILLIS} or {@link #MIN_JULIAN}) and
+ * {@link #MAX_DATE} (or {@link #MAX_MILLIS} or
+ * {@link #MAX_JULIAN}) in <code>Calendar</code> to specify an
+ * extremely early or extremely late date.</p>
+ *
+ * <p><big><b>General notes</b></big>
+ *
+ * <ul>
+ *
+ *   <li>Calendars implementations are <em>proleptic</em>. For example,
+ *     even though the Gregorian calendar was not instituted until the
+ *     16th century, the <code>GregorianCalendar</code> class supports
+ *     dates before the historical onset of the calendar by extending the
+ *     calendar system backward in time. Similarly, the
+ *     <code>HebrewCalendar</code> extends backward before the start of
+ *     its epoch into zero and negative years. Subclasses do not throw
+ *     exceptions because a date precedes the historical start of a
+ *     calendar system. Instead, they implement
+ *     {@link #handleGetLimit} to return appropriate limits on
+ *     {@link #YEAR}, {@link #ERA}, etc. fields. Then, if the
+ *     calendar is set to not be lenient, out-of-range field values will
+ *     trigger an exception.</li>
+ *
+ *   <li>Calendar system subclasses compute a <em>extended
+ *     year</em>. This differs from the {@link #YEAR} field in that
+ *     it ranges over all integer values, including zero and negative
+ *     values, and it encapsulates the information of the
+ *     {@link #YEAR} field and all larger fields.  Thus, for the
+ *     Gregorian calendar, the {@link #EXTENDED_YEAR} is computed as
+ *     <code>ERA==AD ? YEAR : 1-YEAR</code>. Another example is the Mayan
+ *     long count, which has years (<code>KUN</code>) and nested cycles
+ *     of years (<code>KATUN</code> and <code>BAKTUN</code>). The Mayan
+ *     {@link #EXTENDED_YEAR} is computed as <code>TUN + 20 * (KATUN
+ *     + 20 * BAKTUN)</code>. The <code>Calendar</code> base class uses
+ *     the {@link #EXTENDED_YEAR} field to compute the week-related
+ *     fields.</li>
+ *
+ * </ul>
+ *
+ * @see          Date
+ * @see          GregorianCalendar
+ * @see          TimeZone
+ * @see          DateFormat
+ * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu, Laura Werner
+ * @stable ICU 2.0
+ */
+public abstract class Calendar implements Serializable, Cloneable {
+
+    // Data flow in Calendar
+    // ---------------------
+
+    // The current time is represented in two ways by Calendar: as UTC
+    // milliseconds from the epoch start (1 January 1970 0:00 UTC), and as local
+    // fields such as MONTH, HOUR, AM_PM, etc.  It is possible to compute the
+    // millis from the fields, and vice versa.  The data needed to do this
+    // conversion is encapsulated by a TimeZone object owned by the Calendar.
+    // The data provided by the TimeZone object may also be overridden if the
+    // user sets the ZONE_OFFSET and/or DST_OFFSET fields directly. The class
+    // keeps track of what information was most recently set by the caller, and
+    // uses that to compute any other information as needed.
+
+    // If the user sets the fields using set(), the data flow is as follows.
+    // This is implemented by the Calendar subclass's computeTime() method.
+    // During this process, certain fields may be ignored.  The disambiguation
+    // algorithm for resolving which fields to pay attention to is described
+    // above.
+
+    //   local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
+    //           |
+    //           | Using Calendar-specific algorithm
+    //           V
+    //   local standard millis
+    //           |
+    //           | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET
+    //           V
+    //   UTC millis (in time data member)
+
+    // If the user sets the UTC millis using setTime(), the data flow is as
+    // follows.  This is implemented by the Calendar subclass's computeFields()
+    // method.
+
+    //   UTC millis (in time data member)
+    //           |
+    //           | Using TimeZone getOffset()
+    //           V
+    //   local standard millis
+    //           |
+    //           | Using Calendar-specific algorithm
+    //           V
+    //   local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
+
+    // In general, a round trip from fields, through local and UTC millis, and
+    // back out to fields is made when necessary.  This is implemented by the
+    // complete() method.  Resolving a partial set of fields into a UTC millis
+    // value allows all remaining fields to be generated from that value.  If
+    // the Calendar is lenient, the fields are also renormalized to standard
+    // ranges when they are regenerated.
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * era, e.g., AD or BC in the Julian calendar. This is a calendar-specific
+     * value; see subclass documentation.
+     * @see GregorianCalendar#AD
+     * @see GregorianCalendar#BC
+     * @stable ICU 2.0
+     */
+    public final static int ERA = 0;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * year. This is a calendar-specific value; see subclass documentation.
+     * @stable ICU 2.0
+     */
+    public final static int YEAR = 1;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * month. This is a calendar-specific value. The first month of the year is
+     * <code>JANUARY</code>; the last depends on the number of months in a year.
+     * @see #JANUARY
+     * @see #FEBRUARY
+     * @see #MARCH
+     * @see #APRIL
+     * @see #MAY
+     * @see #JUNE
+     * @see #JULY
+     * @see #AUGUST
+     * @see #SEPTEMBER
+     * @see #OCTOBER
+     * @see #NOVEMBER
+     * @see #DECEMBER
+     * @see #UNDECIMBER
+     * @stable ICU 2.0
+     */
+    public final static int MONTH = 2;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * week number within the current year.  The first week of the year, as
+     * defined by <code>getFirstDayOfWeek()</code> and
+     * <code>getMinimalDaysInFirstWeek()</code>, has value 1.  Subclasses define
+     * the value of <code>WEEK_OF_YEAR</code> for days before the first week of
+     * the year.
+     * @see #getFirstDayOfWeek
+     * @see #getMinimalDaysInFirstWeek
+     * @stable ICU 2.0
+     */
+    public final static int WEEK_OF_YEAR = 3;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * week number within the current month.  The first week of the month, as
+     * defined by <code>getFirstDayOfWeek()</code> and
+     * <code>getMinimalDaysInFirstWeek()</code>, has value 1.  Subclasses define
+     * the value of <code>WEEK_OF_MONTH</code> for days before the first week of
+     * the month.
+     * @see #getFirstDayOfWeek
+     * @see #getMinimalDaysInFirstWeek
+     * @stable ICU 2.0
+     */
+    public final static int WEEK_OF_MONTH = 4;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * day of the month. This is a synonym for <code>DAY_OF_MONTH</code>.
+     * The first day of the month has value 1.
+     * @see #DAY_OF_MONTH
+     * @stable ICU 2.0
+     */
+    public final static int DATE = 5;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * day of the month. This is a synonym for <code>DATE</code>.
+     * The first day of the month has value 1.
+     * @see #DATE
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_MONTH = 5;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the day
+     * number within the current year.  The first day of the year has value 1.
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_YEAR = 6;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the day
+     * of the week.  This field takes values <code>SUNDAY</code>,
+     * <code>MONDAY</code>, <code>TUESDAY</code>, <code>WEDNESDAY</code>,
+     * <code>THURSDAY</code>, <code>FRIDAY</code>, and <code>SATURDAY</code>.
+     * @see #SUNDAY
+     * @see #MONDAY
+     * @see #TUESDAY
+     * @see #WEDNESDAY
+     * @see #THURSDAY
+     * @see #FRIDAY
+     * @see #SATURDAY
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_WEEK = 7;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * ordinal number of the day of the week within the current month. Together
+     * with the <code>DAY_OF_WEEK</code> field, this uniquely specifies a day
+     * within a month.  Unlike <code>WEEK_OF_MONTH</code> and
+     * <code>WEEK_OF_YEAR</code>, this field's value does <em>not</em> depend on
+     * <code>getFirstDayOfWeek()</code> or
+     * <code>getMinimalDaysInFirstWeek()</code>.  <code>DAY_OF_MONTH 1</code>
+     * through <code>7</code> always correspond to <code>DAY_OF_WEEK_IN_MONTH
+     * 1</code>; <code>8</code> through <code>15</code> correspond to
+     * <code>DAY_OF_WEEK_IN_MONTH 2</code>, and so on.
+     * <code>DAY_OF_WEEK_IN_MONTH 0</code> indicates the week before
+     * <code>DAY_OF_WEEK_IN_MONTH 1</code>.  Negative values count back from the
+     * end of the month, so the last Sunday of a month is specified as
+     * <code>DAY_OF_WEEK = SUNDAY, DAY_OF_WEEK_IN_MONTH = -1</code>.  Because
+     * negative values count backward they will usually be aligned differently
+     * within the month than positive values.  For example, if a month has 31
+     * days, <code>DAY_OF_WEEK_IN_MONTH -1</code> will overlap
+     * <code>DAY_OF_WEEK_IN_MONTH 5</code> and the end of <code>4</code>.
+     * @see #DAY_OF_WEEK
+     * @see #WEEK_OF_MONTH
+     * @stable ICU 2.0
+     */
+    public final static int DAY_OF_WEEK_IN_MONTH = 8;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating
+     * whether the <code>HOUR</code> is before or after noon.
+     * E.g., at 10:04:15.250 PM the <code>AM_PM</code> is <code>PM</code>.
+     * @see #AM
+     * @see #PM
+     * @see #HOUR
+     * @stable ICU 2.0
+     */
+    public final static int AM_PM = 9;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * hour of the morning or afternoon. <code>HOUR</code> is used for the 12-hour
+     * clock.
+     * E.g., at 10:04:15.250 PM the <code>HOUR</code> is 10.
+     * @see #AM_PM
+     * @see #HOUR_OF_DAY
+     * @stable ICU 2.0
+     */
+    public final static int HOUR = 10;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * hour of the day. <code>HOUR_OF_DAY</code> is used for the 24-hour clock.
+     * E.g., at 10:04:15.250 PM the <code>HOUR_OF_DAY</code> is 22.
+     * @see #HOUR
+     * @stable ICU 2.0
+     */
+    public final static int HOUR_OF_DAY = 11;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * minute within the hour.
+     * E.g., at 10:04:15.250 PM the <code>MINUTE</code> is 4.
+     * @stable ICU 2.0
+     */
+    public final static int MINUTE = 12;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * second within the minute.
+     * E.g., at 10:04:15.250 PM the <code>SECOND</code> is 15.
+     * @stable ICU 2.0
+     */
+    public final static int SECOND = 13;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * millisecond within the second.
+     * E.g., at 10:04:15.250 PM the <code>MILLISECOND</code> is 250.
+     * @stable ICU 2.0
+     */
+    public final static int MILLISECOND = 14;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * raw offset from GMT in milliseconds.
+     * @stable ICU 2.0
+     */
+    public final static int ZONE_OFFSET = 15;
+
+    /**
+     * Field number for <code>get</code> and <code>set</code> indicating the
+     * daylight savings offset in milliseconds.
+     * @stable ICU 2.0
+     */
+    public final static int DST_OFFSET = 16;
+
+    /**
+     * Field number for <code>get()</code> and <code>set()</code>
+     * indicating the extended year corresponding to the
+     * <code>WEEK_OF_YEAR</code> field.  This may be one greater or less
+     * than the value of <code>EXTENDED_YEAR</code>.
+     * @stable ICU 2.0
+     */
+    public static final int YEAR_WOY = 17;
+
+    /**
+     * Field number for <code>get()</code> and <code>set()</code>
+     * indicating the localized day of week.  This will be a value from 1
+     * to 7 inclusive, with 1 being the localized first day of the week.
+     * @stable ICU 2.0
+     */
+    public static final int DOW_LOCAL = 18;
+
+    /**
+     * Field number for <code>get()</code> and <code>set()</code>
+     * indicating the extended year.  This is a single number designating
+     * the year of this calendar system, encompassing all supra-year
+     * fields.  For example, for the Julian calendar system, year numbers
+     * are positive, with an era of BCE or CE.  An extended year value for
+     * the Julian calendar system assigns positive values to CE years and
+     * negative values to BCE years, with 1 BCE being year 0.
+     * @stable ICU 2.0
+     */
+    public static final int EXTENDED_YEAR = 19;
+
+    /**
+     * Field number for <code>get()</code> and <code>set()</code>
+     * indicating the modified Julian day number.  This is different from
+     * the conventional Julian day number in two regards.  First, it
+     * demarcates days at local zone midnight, rather than noon GMT.
+     * Second, it is a local number; that is, it depends on the local time
+     * zone.  It can be thought of as a single number that encompasses all
+     * the date-related fields.
+     * @stable ICU 2.0
+     */
+    public static final int JULIAN_DAY = 20;
+
+    /**
+     * Field number for <code>get()</code> and <code>set()</code>
+     * indicating the milliseconds in the day.  This ranges from 0 to
+     * 23:59:59.999 (regardless of DST).  This field behaves
+     * <em>exactly</em> like a composite of all time-related fields, not
+     * including the zone fields.  As such, it also reflects
+     * discontinuities of those fields on DST transition days.  On a day of
+     * DST onset, it will jump forward.  On a day of DST cessation, it will
+     * jump backward.  This reflects the fact that is must be combined with
+     * the DST_OFFSET field to obtain a unique local time value.
+     * @stable ICU 2.0
+     */
+    public static final int MILLISECONDS_IN_DAY = 21;
+
+    /**
+     * The number of fields defined by this class.  Subclasses may define
+     * addition fields starting with this number.
+     * @stable ICU 2.0
+     */
+    protected static final int BASE_FIELD_COUNT = 22;
+
+    /**
+     * The maximum number of fields possible.  Subclasses must not define
+     * more total fields than this number.
+     * @stable ICU 2.0
+     */
+    protected static final int MAX_FIELD_COUNT = 32;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Sunday.
+     * @stable ICU 2.0
+     */
+    public final static int SUNDAY = 1;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Monday.
+     * @stable ICU 2.0
+     */
+    public final static int MONDAY = 2;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Tuesday.
+     * @stable ICU 2.0
+     */
+    public final static int TUESDAY = 3;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Wednesday.
+     * @stable ICU 2.0
+     */
+    public final static int WEDNESDAY = 4;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Thursday.
+     * @stable ICU 2.0
+     */
+    public final static int THURSDAY = 5;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Friday.
+     * @stable ICU 2.0
+     */
+    public final static int FRIDAY = 6;
+
+    /**
+     * Value of the <code>DAY_OF_WEEK</code> field indicating
+     * Saturday.
+     * @stable ICU 2.0
+     */
+    public final static int SATURDAY = 7;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * first month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int JANUARY = 0;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * second month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int FEBRUARY = 1;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * third month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int MARCH = 2;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * fourth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int APRIL = 3;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * fifth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int MAY = 4;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * sixth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int JUNE = 5;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * seventh month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int JULY = 6;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * eighth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int AUGUST = 7;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * ninth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int SEPTEMBER = 8;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * tenth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int OCTOBER = 9;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * eleventh month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int NOVEMBER = 10;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * twelfth month of the year.
+     * @stable ICU 2.0
+     */
+    public final static int DECEMBER = 11;
+
+    /**
+     * Value of the <code>MONTH</code> field indicating the
+     * thirteenth month of the year. Although <code>GregorianCalendar</code>
+     * does not use this value, lunar calendars do.
+     * @stable ICU 2.0
+     */
+    public final static int UNDECIMBER = 12;
+
+    /**
+     * Value of the <code>AM_PM</code> field indicating the
+     * period of the day from midnight to just before noon.
+     * @stable ICU 2.0
+     */
+    public final static int AM = 0;
+
+    /**
+     * Value of the <code>AM_PM</code> field indicating the
+     * period of the day from noon to just before midnight.
+     * @stable ICU 2.0
+     */
+    public final static int PM = 1;
+
+    /**
+     * Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
+     * weekday.
+     * @see #WEEKEND
+     * @see #WEEKEND_ONSET
+     * @see #WEEKEND_CEASE
+     * @see #getDayOfWeekType
+     * @stable ICU 2.0
+     */
+    public static final int WEEKDAY = 0;
+
+    /**
+     * Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
+     * weekend day.
+     * @see #WEEKDAY
+     * @see #WEEKEND_ONSET
+     * @see #WEEKEND_CEASE
+     * @see #getDayOfWeekType
+     * @stable ICU 2.0
+     */
+    public static final int WEEKEND = 1;
+
+    /**
+     * Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
+     * day that starts as a weekday and transitions to the weekend.
+     * Call getWeekendTransition() to get the point of transition.
+     * @see #WEEKDAY
+     * @see #WEEKEND
+     * @see #WEEKEND_CEASE
+     * @see #getDayOfWeekType
+     * @stable ICU 2.0
+     */
+    public static final int WEEKEND_ONSET = 2;
+
+    /**
+     * Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
+     * day that starts as the weekend and transitions to a weekday.
+     * Call getWeekendTransition() to get the point of transition.
+     * @see #WEEKDAY
+     * @see #WEEKEND
+     * @see #WEEKEND_ONSET
+     * @see #getDayOfWeekType
+     * @stable ICU 2.0
+     */
+    public static final int WEEKEND_CEASE = 3;
+
+    /**
+     * The number of milliseconds in one second.
+     * @stable ICU 2.0
+     */
+    protected static final int  ONE_SECOND = 1000;
+
+    /**
+     * The number of milliseconds in one minute.
+     * @stable ICU 2.0
+     */
+    protected static final int  ONE_MINUTE = 60*ONE_SECOND;
+
+    /**
+     * The number of milliseconds in one hour.
+     * @stable ICU 2.0
+     */
+    protected static final int  ONE_HOUR   = 60*ONE_MINUTE;
+
+    /**
+     * The number of milliseconds in one day.  Although ONE_DAY and
+     * ONE_WEEK can fit into ints, they must be longs in order to prevent
+     * arithmetic overflow when performing (bug 4173516).
+     * @stable ICU 2.0
+     */
+    protected static final long ONE_DAY    = 24*ONE_HOUR;
+
+    /**
+     * The number of milliseconds in one week.  Although ONE_DAY and
+     * ONE_WEEK can fit into ints, they must be longs in order to prevent
+     * arithmetic overflow when performing (bug 4173516).
+     * @stable ICU 2.0
+     */
+    protected static final long ONE_WEEK   = 7*ONE_DAY;
+
+    /**
+     * The Julian day of the Gregorian epoch, that is, January 1, 1 on the
+     * Gregorian calendar.
+     * @stable ICU 2.0
+     */
+    protected static final int JAN_1_1_JULIAN_DAY = 1721426;
+
+    /**
+     * The Julian day of the epoch, that is, January 1, 1970 on the
+     * Gregorian calendar.
+     * @stable ICU 2.0
+     */
+    protected static final int EPOCH_JULIAN_DAY   = 2440588;
+
+    /**
+     * The minimum supported Julian day.  This value is equivalent to
+     * <code>MIN_MILLIS</code> and <code>MIN_DATE</code>.
+     * @see #JULIAN_DAY
+     * @stable ICU 2.0
+     */
+    protected static final int MIN_JULIAN = -0x7F000000;
+
+    /**
+     * The minimum supported epoch milliseconds.  This value is equivalent
+     * to <code>MIN_JULIAN</code> and <code>MIN_DATE</code>.
+     * @stable ICU 2.0
+     */
+    protected static final long MIN_MILLIS = -184303902528000000L;
+
+    // Get around bug in jikes 1.12 for now.  Later, use:
+    //protected static final long MIN_MILLIS = (MIN_JULIAN - EPOCH_JULIAN_DAY) * ONE_DAY;
+
+    /**
+     * The minimum supported <code>Date</code>.  This value is equivalent
+     * to <code>MIN_JULIAN</code> and <code>MIN_MILLIS</code>.
+     * @stable ICU 2.0
+     */
+    protected static final Date MIN_DATE = new Date(MIN_MILLIS);
+
+    /**
+     * The maximum supported Julian day.  This value is equivalent to
+     * <code>MAX_MILLIS</code> and <code>MAX_DATE</code>.
+     * @see #JULIAN_DAY
+     * @stable ICU 2.0
+     */
+    protected static final int MAX_JULIAN = +0x7F000000;
+
+    /**
+     * The maximum supported epoch milliseconds.  This value is equivalent
+     * to <code>MAX_JULIAN</code> and <code>MAX_DATE</code>.
+     * @stable ICU 2.0
+     */
+    protected static final long MAX_MILLIS = (MAX_JULIAN - EPOCH_JULIAN_DAY) * ONE_DAY;
+
+    /**
+     * The maximum supported <code>Date</code>.  This value is equivalent
+     * to <code>MAX_JULIAN</code> and <code>MAX_MILLIS</code>.
+     * @stable ICU 2.0
+     */
+    protected static final Date MAX_DATE = new Date(MAX_MILLIS);
+
+    // Internal notes:
+    // Calendar contains two kinds of time representations: current "time" in
+    // milliseconds, and a set of time "fields" representing the current time.
+    // The two representations are usually in sync, but can get out of sync
+    // as follows.
+    // 1. Initially, no fields are set, and the time is invalid.
+    // 2. If the time is set, all fields are computed and in sync.
+    // 3. If a single field is set, the time is invalid.
+    // Recomputation of the time and fields happens when the object needs
+    // to return a result to the user, or use a result for a computation.
+
+    /**
+     * The field values for the currently set time for this calendar.
+     * This is an array of at least <code>BASE_FIELD_COUNT</code> integers.
+     * @see #handleCreateFields
+     * @serial
+     */
+    private transient int           fields[];
+
+    /**
+     * Pseudo-time-stamps which specify when each field was set. There
+     * are two special values, UNSET and INTERNALLY_SET. Values from
+     * MINIMUM_USER_SET to Integer.MAX_VALUE are legal user set values.
+     */
+    private transient int           stamp[];
+
+    /**
+     * The currently set time for this calendar, expressed in milliseconds after
+     * January 1, 1970, 0:00:00 GMT.
+     * @see <tt>isTimeSet</tt>
+     * @serial
+     */
+    private long          time;
+
+    /**
+     * True if then the value of <code>time</code> is valid.
+     * The time is made invalid by a change to an item of <code>field[]</code>.
+     * @see #time
+     * @serial
+     */
+    private transient boolean       isTimeSet;
+
+    /**
+     * True if <code>fields[]</code> are in sync with the currently set time.
+     * If false, then the next attempt to get the value of a field will
+     * force a recomputation of all fields from the current value of
+     * <code>time</code>.
+     * @serial
+     */
+    private transient boolean       areFieldsSet;
+
+    /**
+     * True if all fields have been set.  This is only false in a few
+     * situations: In a newly created, partially constructed object.  After
+     * a call to clear().  In an object just read from a stream using
+     * readObject().  Once computeFields() has been called this is set to
+     * true and stays true until one of the above situations recurs.
+     * @serial
+     */
+    private transient boolean       areAllFieldsSet;
+
+    /**
+     * True if this calendar allows out-of-range field values during computation
+     * of <code>time</code> from <code>fields[]</code>.
+     * @see #setLenient
+     * @serial
+     */
+    private boolean         lenient = true;
+
+    /**
+     * The <code>TimeZone</code> used by this calendar. </code>Calendar</code>
+     * uses the time zone data to translate between locale and GMT time.
+     * @serial
+     */
+    private TimeZone        zone;
+
+    /**
+     * The first day of the week, with possible values <code>SUNDAY</code>,
+     * <code>MONDAY</code>, etc.  This is a locale-dependent value.
+     * @serial
+     */
+    private int             firstDayOfWeek;
+
+    /**
+     * The number of days required for the first week in a month or year,
+     * with possible values from 1 to 7.  This is a locale-dependent value.
+     * @serial
+     */
+    private int             minimalDaysInFirstWeek;
+
+    /**
+     * First day of the weekend in this calendar's locale.  Must be in
+     * the range SUNDAY...SATURDAY (1..7).  The weekend starts at
+     * weekendOnsetMillis milliseconds after midnight on that day of
+     * the week.  This value is taken from locale resource data.
+     */
+    private int weekendOnset;
+
+    /**
+     * Milliseconds after midnight at which the weekend starts on the
+     * day of the week weekendOnset.  Times that are greater than or
+     * equal to weekendOnsetMillis are considered part of the weekend.
+     * Must be in the range 0..24*60*60*1000-1.  This value is taken
+     * from locale resource data.
+     */
+    private int weekendOnsetMillis;
+
+    /**
+     * Day of the week when the weekend stops in this calendar's
+     * locale.  Must be in the range SUNDAY...SATURDAY (1..7).  The
+     * weekend stops at weekendCeaseMillis milliseconds after midnight
+     * on that day of the week.  This value is taken from locale
+     * resource data.
+     */
+    private int weekendCease;
+
+    /**
+     * Milliseconds after midnight at which the weekend stops on the
+     * day of the week weekendCease.  Times that are greater than or
+     * equal to weekendCeaseMillis are considered not to be the
+     * weekend.  Must be in the range 0..24*60*60*1000-1.  This value
+     * is taken from locale resource data.
+     */
+    private int weekendCeaseMillis;
+
+    /**
+     * Cache to hold the firstDayOfWeek and minimalDaysInFirstWeek
+     * of a Locale.
+     */
+    private static Hashtable cachedLocaleData = new Hashtable(3);
+
+    /**
+     * Value of the time stamp <code>stamp[]</code> indicating that
+     * a field has not been set since the last call to <code>clear()</code>.
+     * @see #INTERNALLY_SET
+     * @see #MINIMUM_USER_STAMP
+     * @stable ICU 2.0
+     */
+    protected static final int UNSET = 0;
+
+    /**
+     * Value of the time stamp <code>stamp[]</code> indicating that a field
+     * has been set via computations from the time or from other fields.
+     * @see #UNSET
+     * @see #MINIMUM_USER_STAMP
+     * @stable ICU 2.0
+     */
+    protected static final int INTERNALLY_SET = 1;
+
+    /**
+     * If the time stamp <code>stamp[]</code> has a value greater than or
+     * equal to <code>MINIMUM_USER_SET</code> then it has been set by the
+     * user via a call to <code>set()</code>.
+     * @see #UNSET
+     * @see #INTERNALLY_SET
+     * @stable ICU 2.0
+     */
+    protected static final int MINIMUM_USER_STAMP = 2;
+
+    /**
+     * The next available value for <code>stamp[]</code>, an internal array.
+     * This actually should not be written out to the stream, and will probably
+     * be removed from the stream in the near future.  In the meantime,
+     * a value of <code>MINIMUM_USER_STAMP</code> should be used.
+     * @serial
+     */
+    private transient int             nextStamp = MINIMUM_USER_STAMP;
+
+    // the internal serial version which says which version was written
+    // - 0 (default) for version up to JDK 1.1.5
+    // - 1 for version from JDK 1.1.6, which writes a correct 'time' value
+    //     as well as compatible values for other fields.  This is a
+    //     transitional format.
+    // - 2 (not implemented yet) a future version, in which fields[],
+    //     areFieldsSet, and isTimeSet become transient, and isSet[] is
+    //     removed. In JDK 1.1.6 we write a format compatible with version 2.
+    // static final int        currentSerialVersion = 1;
+
+    /**
+     * The version of the serialized data on the stream.  Possible values:
+     * <dl>
+     * <dt><b>0</b> or not present on stream</dt>
+     * <dd>
+     * JDK 1.1.5 or earlier.
+     * </dd>
+     * <dt><b>1</b></dt>
+     * <dd>
+     * JDK 1.1.6 or later.  Writes a correct 'time' value
+     * as well as compatible values for other fields.  This is a
+     * transitional format.
+     * </dd>
+     * </dl>
+     * When streaming out this class, the most recent format
+     * and the highest allowable <code>serialVersionOnStream</code>
+     * is written.
+     * @serial
+     * @since JDK1.1.6
+     */
+    // private int             serialVersionOnStream = currentSerialVersion;
+
+    // Proclaim serialization compatibility with JDK 1.1
+    // static final long       serialVersionUID = -1807547505821590642L;
+
+    /**
+     * Bitmask for internalSet() defining which fields may legally be set
+     * by subclasses.  Any attempt to set a field not in this bitmask
+     * results in an exception, because such fields must be set by the base
+     * class.
+     */
+    private transient int internalSetMask;
+
+    /**
+     * The Gregorian year, as computed by computeGregorianFields() and
+     * returned by getGregorianYear().
+     */
+    private transient int gregorianYear;
+
+    /**
+     * The Gregorian month, as computed by computeGregorianFields() and
+     * returned by getGregorianMonth().
+     */
+    private transient int gregorianMonth;
+
+    /**
+     * The Gregorian day of the year, as computed by
+     * computeGregorianFields() and returned by getGregorianDayOfYear().
+     */
+    private transient int gregorianDayOfYear;
+
+    /**
+     * The Gregorian day of the month, as computed by
+     * computeGregorianFields() and returned by getGregorianDayOfMonth().
+     */
+    private transient int gregorianDayOfMonth;
+
+    /**
+     * Constructs a Calendar with the default time zone
+     * and locale.
+     * @see     TimeZone#getDefault
+     * @stable ICU 2.0
+     */
+    protected Calendar()
+    {
+        this(TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    /**
+     * Constructs a calendar with the specified time zone and locale.
+     * @param zone the time zone to use
+     * @param aLocale the locale for the week data
+     * @stable ICU 2.0
+     */
+    protected Calendar(TimeZone zone, Locale aLocale)
+    {
+        this.zone = zone;
+        setWeekCountData(aLocale);
+        setWeekendData(aLocale);
+        initInternal();
+    }
+
+    private void initInternal()
+    {
+        // Allocate fields through the framework method.  Subclasses
+        // may override this to define additional fields.
+        fields = handleCreateFields();
+        if (fields == null || fields.length < BASE_FIELD_COUNT ||
+            fields.length > MAX_FIELD_COUNT) {
+            throw new InternalError("Invalid fields[]");
+        }
+        stamp = new int[fields.length];
+        int mask = (1 << ERA) |
+            (1 << YEAR) |
+            (1 << MONTH) |
+            (1 << DAY_OF_MONTH) |
+            (1 << DAY_OF_YEAR) |
+            (1 << EXTENDED_YEAR);
+        for (int i=BASE_FIELD_COUNT; i<fields.length; ++i) {
+            mask |= (1 << i);
+        }
+        internalSetMask = mask;
+    }
+
+    /**
+     * Gets a calendar using the default time zone and locale.
+     * @return a Calendar.
+     * @stable ICU 2.0
+     */
+    public static synchronized Calendar getInstance()
+    {
+        return getInstance(TimeZone.getDefault(), Locale.getDefault(), null);
+    }
+
+    /**
+     * Gets a calendar using the specified time zone and default locale.
+     * @param zone the time zone to use
+     * @return a Calendar.
+     * @stable ICU 2.0
+     */
+    public static synchronized Calendar getInstance(TimeZone zone)
+    {
+        return getInstance(zone, Locale.getDefault(), null);
+    }
+
+    /**
+     * Gets a calendar using the default time zone and specified locale.
+     * @param aLocale the locale for the week data
+     * @return a Calendar.
+     * @stable ICU 2.0
+     */
+    public static synchronized Calendar getInstance(Locale aLocale)
+    {
+        return getInstance(TimeZone.getDefault(), aLocale, null);
+    }
+
+    /**
+     * Gets a calendar with the specified time zone and locale.
+     * @param zone the time zone to use
+     * @param aLocale the locale for the week data
+     * @return a Calendar.
+     * @stable ICU 2.0
+     */
+    public static synchronized Calendar getInstance(TimeZone zone,
+                                                    Locale aLocale) {
+        return getInstance(zone, aLocale, null);
+    }
+
+    // ==== Factory Stuff ====
+    ///CLOVER:OFF
+    /**
+     * Return a calendar of for the TimeZone and locale.  If factoryName is
+     * not null, looks in the collection of CalendarFactories for a match
+     * and uses that factory to instantiate the calendar.  Otherwise, it
+     * uses the default factory that has been registered for the locale.
+     * @prototype
+     */
+    /* public */ static synchronized Calendar getInstance(TimeZone zone,
+                                                    Locale locale,
+                                                    String factoryName)
+    {
+        CalendarFactory factory = null;
+        if (factoryName != null) {
+            factory = (CalendarFactory)getFactoryMap().get(factoryName);
+        }
+
+        if (factory == null && service != null) {
+            factory = (CalendarFactory)service.get(locale);
+        }
+
+        if (factory == null) {
+            return new GregorianCalendar(zone, locale);
+        } else {
+            return factory.create(zone, locale);
+        }
+    }
+    ///CLOVER:ON
+    /**
+     * Gets the list of locales for which Calendars are installed.
+     * @return the list of locales for which Calendars are installed.
+     * @stable ICU 2.0
+     */
+    public static Locale[] getAvailableLocales()
+    {
+        return service == null
+            ? ICULocaleData.getAvailableLocales()
+            : service.getAvailableLocales();
+    }
+    ///CLOVER:OFF
+    private static Map factoryMap;
+    private static Map getFactoryMap() {
+        if (factoryMap == null) {
+            Map m = new HashMap(5);
+            /*
+            addFactory(m, BuddhistCalendar.factory());
+            addFactory(m, ChineseCalendar.factory());
+            addFactory(m, GregorianCalendar.factory());
+            addFactory(m, HebrewCalendar.factory());
+            addFactory(m, IslamicCalendar.factory());
+            addFactory(m, JapaneseCalendar.factory());
+            */
+            factoryMap = m;
+        }
+        return factoryMap;
+    }
+
+// Never used -- why is this here? Alan 2003-05
+//    private static void addFactory(Map m, CalendarFactory f) {
+//        m.put(f.factoryName(), f);
+//    }
+
+    /**
+     * Return a set of all the registered calendar factory names.
+     * @prototype
+     */
+    /* public */ static Set getCalendarFactoryNames() {
+        return Collections.unmodifiableSet(getFactoryMap().keySet());
+    }
+
+    /**
+     * Register a new CalendarFactory.  getInstance(TimeZone, Locale, String) will
+     * try to locate a registered factories matching the factoryName.  Only registered
+     * factories will be found.
+     * @prototype
+     */
+    private static void registerFactory(CalendarFactory factory) {
+        if (factory == null) {
+            throw new IllegalArgumentException("Factory must not be null");
+        }
+        getFactoryMap().put(factory.factoryName(), factory);
+    }
+
+    /**
+     * Convenience override of register(CalendarFactory, Locale, boolean);
+     * @prototype
+     */
+    /* public */ static Object register(CalendarFactory factory, Locale locale) {
+        return register(factory, locale, true);
+    }
+
+    /**
+     * Registers a default CalendarFactory for the provided locale.
+     * If the factory has not already been registered with
+     * registerFactory, it will be.
+     * @prototype
+     */
+    /* public */ static Object register(CalendarFactory factory, Locale locale, boolean visible) {
+        if (factory == null) {
+            throw new IllegalArgumentException("calendar must not be null");
+        }
+        registerFactory(factory);
+        return getService().registerObject(factory, locale, visible
+                                           ? LocaleKeyFactory.VISIBLE
+                                           : LocaleKeyFactory.INVISIBLE);
+    }
+
+    /**
+     * Unregister the CalendarFactory associated with this key
+     * (obtained from register).
+     * @prototype
+     */
+    /* public */ static boolean unregister(Object registryKey) {
+        return service == null
+            ? false
+            : service.unregisterFactory((Factory)registryKey);
+    }
+
+    private static ICULocaleService service = null;
+    private static ICULocaleService getService() {
+        synchronized (Calendar.class) {
+            if (service == null) {
+                service = new ICULocaleService("Calendar");
+            }
+        }
+        return service;
+    }
+    ///CLOVER:ON
+    // ==== End of factory Stuff ====
+
+    /**
+     * Gets this Calendar's current time.
+     * @return the current time.
+     * @stable ICU 2.0
+     */
+    public final Date getTime() {
+        return new Date( getTimeInMillis() );
+    }
+
+    /**
+     * Sets this Calendar's current time with the given Date.
+     * <p>
+     * Note: Calling <code>setTime()</code> with
+     * <code>Date(Long.MAX_VALUE)</code> or <code>Date(Long.MIN_VALUE)</code>
+     * may yield incorrect field values from <code>get()</code>.
+     * @param date the given Date.
+     * @stable ICU 2.0
+     */
+    public final void setTime(Date date) {
+        setTimeInMillis( date.getTime() );
+    }
+
+    /**
+     * Gets this Calendar's current time as a long.
+     * @return the current time as UTC milliseconds from the epoch.
+     * @stable ICU 2.0
+     */
+    public long getTimeInMillis() {
+        if (!isTimeSet) updateTime();
+        return time;
+    }
+
+    /**
+     * Sets this Calendar's current time from the given long value.
+     * @param date the new time in UTC milliseconds from the epoch.
+     * @stable ICU 2.0
+     */
+    public void setTimeInMillis( long millis ) {
+        if (millis > MAX_MILLIS) {
+            millis = MAX_MILLIS;
+        } else if (millis < MIN_MILLIS) {
+            millis = MIN_MILLIS;
+        }
+        time = millis;
+        isTimeSet = true;
+        computeFields();
+        areFieldsSet = true;
+        areAllFieldsSet = true;
+    }
+
+    /**
+     * Gets the value for a given time field.
+     * @param field the given time field.
+     * @return the value for the given time field.
+     * @stable ICU 2.0
+     */
+    public final int get(int field)
+    {
+        complete();
+        return fields[field];
+    }
+
+    /**
+     * Gets the value for a given time field.  This is an internal method
+     * for subclasses that does <em>not</em> trigger any calculations.
+     * @param field the given time field.
+     * @return the value for the given time field.
+     * @stable ICU 2.0
+     */
+    protected final int internalGet(int field)
+    {
+        return fields[field];
+    }
+
+    /**
+     * Get the value for a given time field, or return the given default
+     * value if the field is not set.  This is an internal method for
+     * subclasses that does <em>not</em> trigger any calculations.
+     * @param field the given time field.
+     * @param defaultValue value to return if field is not set
+     * @return the value for the given time field of defaultValue if the
+     * field is unset
+     * @stable ICU 2.0
+     */
+    protected final int internalGet(int field, int defaultValue) {
+        return (stamp[field] > UNSET) ? fields[field] : defaultValue;
+    }
+
+    /**
+     * Sets the time field with the given value.
+     * @param field the given time field.
+     * @param value the value to be set for the given time field.
+     * @stable ICU 2.0
+     */
+    public final void set(int field, int value)
+    {
+        isTimeSet = false;
+        fields[field] = value;
+        stamp[field] = nextStamp++;
+        areFieldsSet = false;
+    }
+
+    /**
+     * Sets the values for the fields year, month, and date.
+     * Previous values of other fields are retained.  If this is not desired,
+     * call <code>clear</code> first.
+     * @param year the value used to set the YEAR time field.
+     * @param month the value used to set the MONTH time field.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field.
+     * @stable ICU 2.0
+     */
+    public final void set(int year, int month, int date)
+    {
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+    }
+
+    /**
+     * Sets the values for the fields year, month, date, hour, and minute.
+     * Previous values of other fields are retained.  If this is not desired,
+     * call <code>clear</code> first.
+     * @param year the value used to set the YEAR time field.
+     * @param month the value used to set the MONTH time field.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field.
+     * @param hour the value used to set the HOUR_OF_DAY time field.
+     * @param minute the value used to set the MINUTE time field.
+     * @stable ICU 2.0
+     */
+    public final void set(int year, int month, int date, int hour, int minute)
+    {
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+        set(HOUR_OF_DAY, hour);
+        set(MINUTE, minute);
+    }
+
+    /**
+     * Sets the values for the fields year, month, date, hour, minute, and second.
+     * Previous values of other fields are retained.  If this is not desired,
+     * call <code>clear</code> first.
+     * @param year the value used to set the YEAR time field.
+     * @param month the value used to set the MONTH time field.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field.
+     * @param hour the value used to set the HOUR_OF_DAY time field.
+     * @param minute the value used to set the MINUTE time field.
+     * @param second the value used to set the SECOND time field.
+     * @stable ICU 2.0
+     */
+    public final void set(int year, int month, int date, int hour, int minute,
+                          int second)
+    {
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+        set(HOUR_OF_DAY, hour);
+        set(MINUTE, minute);
+        set(SECOND, second);
+    }
+
+    /**
+     * Clears the values of all the time fields.
+     * @stable ICU 2.0
+     */
+    public final void clear()
+    {
+        for (int i=0; i<fields.length; ++i) {
+            fields[i] = stamp[i] = 0; // UNSET == 0
+        }
+        areFieldsSet = false;
+        areAllFieldsSet = false;
+        isTimeSet = false;
+    }
+
+    /**
+     * Clears the value in the given time field.
+     * @param field the time field to be cleared.
+     * @stable ICU 2.0
+     */
+    public final void clear(int field)
+    {
+        fields[field] = 0;
+        stamp[field] = UNSET;
+        areFieldsSet = false;
+        areAllFieldsSet = false;
+        isTimeSet = false;
+    }
+
+    /**
+     * Determines if the given time field has a value set.
+     * @return true if the given time field has a value set; false otherwise.
+     * @stable ICU 2.0
+     */
+    public final boolean isSet(int field)
+    {
+        return stamp[field] != UNSET;
+    }
+
+    /**
+     * Fills in any unset fields in the time field list.
+     * @stable ICU 2.0
+     */
+    protected void complete()
+    {
+        if (!isTimeSet) updateTime();
+        if (!areFieldsSet) {
+            computeFields(); // fills in unset fields
+            areFieldsSet = true;
+            areAllFieldsSet = true;
+        }
+    }
+
+    /**
+     * Compares this calendar to the specified object.
+     * The result is <code>true</code> if and only if the argument is
+     * not <code>null</code> and is a <code>Calendar</code> object that
+     * represents the same calendar as this object.
+     * @param obj the object to compare with.
+     * @return <code>true</code> if the objects are the same;
+     * <code>false</code> otherwise.
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (this.getClass() != obj.getClass()) {
+            return false;
+        }
+
+        Calendar that = (Calendar) obj;
+
+        return isEquivalentTo(that) &&
+            getTimeInMillis() == that.getTime().getTime();
+    }
+
+    /**
+     * Returns true if the given Calendar object is equivalent to this
+     * one.  An equivalent Calendar will behave exactly as this one
+     * does, but it may be set to a different time.  By contrast, for
+     * the equals() method to return true, the other Calendar must
+     * be set to the same time.
+     *
+     * @param other the Calendar to be compared with this Calendar
+     * @draft ICU 2.4
+     */
+    public boolean isEquivalentTo(Calendar other) {
+        return this.getClass() == other.getClass() &&
+            isLenient() == other.isLenient() &&
+            getFirstDayOfWeek() == other.getFirstDayOfWeek() &&
+            getMinimalDaysInFirstWeek() == other.getMinimalDaysInFirstWeek() &&
+            getTimeZone().equals(other.getTimeZone());
+    }
+
+    /**
+     * Returns a hash code for this calendar.
+     * @return a hash code value for this object.
+     * @stable ICU 2.0
+     */
+    public int hashCode() {
+        /* Don't include the time because (a) we don't want the hash value to
+         * move around just because a calendar is set to different times, and
+         * (b) we don't want to trigger a time computation just to get a hash.
+         * Note that it is not necessary for unequal objects to always have
+         * unequal hashes, but equal objects must have equal hashes.  */
+        return (lenient ? 1 : 0)
+            | (firstDayOfWeek << 1)
+            | (minimalDaysInFirstWeek << 4)
+            | (zone.hashCode() << 7);
+    }
+
+    /**
+     * Return the difference in milliseconds between the moment this
+     * calendar is set to and the moment the given calendar or Date object
+     * is set to.
+     */
+    private long compare(Object that) {
+        long thatMs;
+        if (that instanceof Calendar) {
+            thatMs = ((Calendar)that).getTimeInMillis();
+        } else if (that instanceof Date) {
+            thatMs = ((Date)that).getTime();
+        } else {
+            throw new IllegalArgumentException(that + "is not a Calendar or Date");
+        }
+        return getTimeInMillis() - thatMs;
+    }
+
+    /**
+     * Compares the time field records.
+     * Equivalent to comparing result of conversion to UTC.
+     * @param when the Calendar to be compared with this Calendar.
+     * @return true if the current time of this Calendar is before
+     * the time of Calendar when; false otherwise.
+     * @stable ICU 2.0
+     */
+    public boolean before(Object when) {
+        return compare(when) < 0;
+    }
+
+    /**
+     * Compares the time field records.
+     * Equivalent to comparing result of conversion to UTC.
+     * @param when the Calendar to be compared with this Calendar.
+     * @return true if the current time of this Calendar is after
+     * the time of Calendar when; false otherwise.
+     * @stable ICU 2.0
+     */
+    public boolean after(Object when) {
+        return compare(when) > 0;
+    }
+
+    /**
+     * Return the maximum value that this field could have, given the
+     * current date.  For example, with the Gregorian date February 3, 1997
+     * and the {@link #DAY_OF_MONTH DAY_OF_MONTH} field, the actual maximum
+     * is 28; for February 3, 1996 it is 29.
+     *
+     * <p>The actual maximum computation ignores smaller fields and the
+     * current value of like-sized fields.  For example, the actual maximum
+     * of the DAY_OF_YEAR or MONTH depends only on the year and supra-year
+     * fields.  The actual maximum of the DAY_OF_MONTH depends, in
+     * addition, on the MONTH field and any other fields at that
+     * granularity (such as ChineseCalendar.IS_LEAP_MONTH).  The
+     * DAY_OF_WEEK_IN_MONTH field does not depend on the current
+     * DAY_OF_WEEK; it returns the maximum for any day of week in the
+     * current month.  Likewise for the WEEK_OF_MONTH and WEEK_OF_YEAR
+     * fields.
+     *
+     * @param field the field whose maximum is desired
+     * @return the maximum of the given field for the current date of this calendar
+     * @see #getMaximum
+     * @see #getLeastMaximum
+     * @stable ICU 2.0
+     */
+    public int getActualMaximum(int field) {
+        int result;
+
+        switch (field) {
+        case DAY_OF_MONTH:
+            {
+                Calendar cal = (Calendar) clone();
+                cal.prepareGetActual(field, false);
+                result = handleGetMonthLength(cal.get(EXTENDED_YEAR), cal.get(MONTH));
+            }
+            break;
+
+        case DAY_OF_YEAR:
+            {
+                Calendar cal = (Calendar) clone();
+                cal.prepareGetActual(field, false);
+                result = handleGetYearLength(cal.get(EXTENDED_YEAR));
+            }
+            break;
+
+        case DAY_OF_WEEK:
+        case AM_PM:
+        case HOUR:
+        case HOUR_OF_DAY:
+        case MINUTE:
+        case SECOND:
+        case MILLISECOND:
+        case ZONE_OFFSET:
+        case DST_OFFSET:
+        case DOW_LOCAL:
+        case JULIAN_DAY:
+        case MILLISECONDS_IN_DAY:
+            // These fields all have fixed minima/maxima
+            result = getMaximum(field);
+            break;
+
+        default:
+            // For all other fields, do it the hard way....
+            result = getActualHelper(field, getLeastMaximum(field), getMaximum(field));
+            break;
+        }
+        return result;
+    }
+
+    /**
+     * Return the minimum value that this field could have, given the current date.
+     * For most fields, this is the same as {@link #getMinimum getMinimum}
+     * and {@link #getGreatestMinimum getGreatestMinimum}.  However, some fields,
+     * especially those related to week number, are more complicated.
+     * <p>
+     * For example, assume {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
+     * returns 4 and {@link #getFirstDayOfWeek getFirstDayOfWeek} returns SUNDAY.
+     * If the first day of the month is Sunday, Monday, Tuesday, or Wednesday
+     * there will be four or more days in the first week, so it will be week number 1,
+     * and <code>getActualMinimum(WEEK_OF_MONTH)</code> will return 1.  However,
+     * if the first of the month is a Thursday, Friday, or Saturday, there are
+     * <em>not</em> four days in that week, so it is week number 0, and
+     * <code>getActualMinimum(WEEK_OF_MONTH)</code> will return 0.
+     * <p>
+     * @param field the field whose actual minimum value is desired.
+     * @return the minimum of the given field for the current date of this calendar
+     *
+     * @see #getMinimum
+     * @see #getGreatestMinimum
+     * @stable ICU 2.0
+     */
+    public int getActualMinimum(int field) {
+        int result;
+
+        switch (field) {
+        case DAY_OF_WEEK:
+        case AM_PM:
+        case HOUR:
+        case HOUR_OF_DAY:
+        case MINUTE:
+        case SECOND:
+        case MILLISECOND:
+        case ZONE_OFFSET:
+        case DST_OFFSET:
+        case DOW_LOCAL:
+        case JULIAN_DAY:
+        case MILLISECONDS_IN_DAY:
+            // These fields all have fixed minima/maxima
+            result = getMinimum(field);
+            break;
+
+        default:
+            // For all other fields, do it the hard way....
+            result = getActualHelper(field, getGreatestMinimum(field), getMinimum(field));
+            break;
+        }
+        return result;
+    }
+
+    /**
+     * Prepare this calendar for computing the actual minimum or maximum.
+     * This method modifies this calendar's fields; it is called on a
+     * temporary calendar.
+     *
+     * <p>Rationale: The semantics of getActualXxx() is to return the
+     * maximum or minimum value that the given field can take, taking into
+     * account other relevant fields.  In general these other fields are
+     * larger fields.  For example, when computing the actual maximum
+     * DAY_OF_MONTH, the current value of DAY_OF_MONTH itself is ignored,
+     * as is the value of any field smaller.
+     *
+     * <p>The time fields all have fixed minima and maxima, so we don't
+     * need to worry about them.  This also lets us set the
+     * MILLISECONDS_IN_DAY to zero to erase any effects the time fields
+     * might have when computing date fields.
+     *
+     * <p>DAY_OF_WEEK is adjusted specially for the WEEK_OF_MONTH and
+     * WEEK_OF_YEAR fields to ensure that they are computed correctly.
+     * @stable ICU 2.0
+     */
+    protected void prepareGetActual(int field, boolean isMinimum) {
+        set(MILLISECONDS_IN_DAY, 0);
+
+        switch (field) {
+        case YEAR:
+        case YEAR_WOY:
+        case EXTENDED_YEAR:
+            set(DAY_OF_YEAR, getGreatestMinimum(DAY_OF_YEAR));
+            break;
+
+        case MONTH:
+            set(DAY_OF_MONTH, getGreatestMinimum(DAY_OF_MONTH));
+            break;
+
+        case DAY_OF_WEEK_IN_MONTH:
+            // For dowim, the maximum occurs for the DOW of the first of the
+            // month.
+            set(DAY_OF_MONTH, 1);
+            set(DAY_OF_WEEK, get(DAY_OF_WEEK)); // Make this user set
+            break;
+
+        case WEEK_OF_MONTH:
+        case WEEK_OF_YEAR:
+            // If we're counting weeks, set the day of the week to either the
+            // first or last localized DOW.  We know the last week of a month
+            // or year will contain the first day of the week, and that the
+            // first week will contain the last DOW.
+            {
+                int dow = firstDayOfWeek;
+                if (isMinimum) {
+                    dow = (dow + 6) % 7; // set to last DOW
+                    if (dow < SUNDAY) {
+                        dow += 7;
+                    }
+                }
+                set(DAY_OF_WEEK, dow);
+            }
+            break;
+        }
+
+        // Do this last to give it the newest time stamp
+        set(field, getGreatestMinimum(field));
+    }
+
+    private int getActualHelper(int field, int startValue, int endValue) {
+
+        if (startValue == endValue) {
+            // if we know that the maximum value is always the same, just return it
+            return startValue;
+        }
+
+        final int delta = (endValue > startValue) ? 1 : -1;
+
+        // clone the calendar so we don't mess with the real one, and set it to
+        // accept anything for the field values
+        Calendar work = (Calendar) clone();
+        work.setLenient(true);
+        work.prepareGetActual(field, delta < 0);
+
+        // now try each value from the start to the end one by one until
+        // we get a value that normalizes to another value.  The last value that
+        // normalizes to itself is the actual maximum for the current date
+        int result = startValue;
+        do {
+            work.set(field, startValue);
+            if (work.get(field) != startValue) {
+                break;
+            } else {
+                result = startValue;
+                startValue += delta;
+            }
+        } while (result != endValue);
+
+        return result;
+    }
+
+    /**
+     * Rolls (up/down) a single unit of time on the given field.  If the
+     * field is rolled past its maximum allowable value, it will "wrap" back
+     * to its minimum and continue rolling. For
+     * example, to roll the current date up by one day, you can call:
+     * <p>
+     * <code>roll({@link #DATE}, true)</code>
+     * <p>
+     * When rolling on the {@link #YEAR} field, it will roll the year
+     * value in the range between 1 and the value returned by calling
+     * {@link #getMaximum getMaximum}({@link #YEAR}).
+     * <p>
+     * When rolling on certain fields, the values of other fields may conflict and
+     * need to be changed.  For example, when rolling the <code>MONTH</code> field
+     * for the Gregorian date 1/31/96 upward, the <code>DAY_OF_MONTH</code> field
+     * must be adjusted so that the result is 2/29/96 rather than the invalid
+     * 2/31/96.
+     * <p>
+     * <b>Note:</b> Calling <tt>roll(field, true)</tt> N times is <em>not</em>
+     * necessarily equivalent to calling <tt>roll(field, N)</tt>.  For example,
+     * imagine that you start with the date Gregorian date January 31, 1995.  If you call
+     * <tt>roll(Calendar.MONTH, 2)</tt>, the result will be March 31, 1995.
+     * But if you call <tt>roll(Calendar.MONTH, true)</tt>, the result will be
+     * February 28, 1995.  Calling it one more time will give March 28, 1995, which
+     * is usually not the desired result.
+     * <p>
+     * <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
+     * than attempting to perform arithmetic operations directly on the fields
+     * of a <tt>Calendar</tt>.  It is quite possible for <tt>Calendar</tt> subclasses
+     * to have fields with non-linear behavior, for example missing months
+     * or days during non-leap years.  The subclasses' <tt>add</tt> and <tt>roll</tt>
+     * methods will take this into account, while simple arithmetic manipulations
+     * may give invalid results.
+     * <p>
+     * @param field the calendar field to roll.
+     *
+     * @param up    indicates if the value of the specified time field is to be
+     *              rolled up or rolled down. Use <code>true</code> if rolling up,
+     *              <code>false</code> otherwise.
+     *
+     * @exception   IllegalArgumentException if the field is invalid or refers
+     *              to a field that cannot be handled by this method.
+     * @see #roll(int, int)
+     * @see #add
+     * @stable ICU 2.0
+     */
+    public final void roll(int field, boolean up)
+    {
+        roll(field, up ? +1 : -1);
+    }
+
+    /**
+     * Rolls (up/down) a specified amount time on the given field.  For
+     * example, to roll the current date up by three days, you can call
+     * <code>roll(Calendar.DATE, 3)</code>.  If the
+     * field is rolled past its maximum allowable value, it will "wrap" back
+     * to its minimum and continue rolling.
+     * For example, calling <code>roll(Calendar.DATE, 10)</code>
+     * on a Gregorian calendar set to 4/25/96 will result in the date 4/5/96.
+     * <p>
+     * When rolling on certain fields, the values of other fields may conflict and
+     * need to be changed.  For example, when rolling the {@link #MONTH MONTH} field
+     * for the Gregorian date 1/31/96 by +1, the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
+     * must be adjusted so that the result is 2/29/96 rather than the invalid
+     * 2/31/96.
+     * <p>
+     * The <code>com.ibm.icu.util.Calendar</code> implementation of this method is able to roll
+     * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
+     * and {@link #ZONE_OFFSET ZONE_OFFSET}.  Subclasses may, of course, add support for
+     * additional fields in their overrides of <code>roll</code>.
+     * <p>
+     * <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
+     * than attempting to perform arithmetic operations directly on the fields
+     * of a <tt>Calendar</tt>.  It is quite possible for <tt>Calendar</tt> subclasses
+     * to have fields with non-linear behavior, for example missing months
+     * or days during non-leap years.  The subclasses' <tt>add</tt> and <tt>roll</tt>
+     * methods will take this into account, while simple arithmetic manipulations
+     * may give invalid results.
+     * <p>
+     * <b>Subclassing:</b><br>
+     * This implementation of <code>roll</code> assumes that the behavior of the
+     * field is continuous between its minimum and maximum, which are found by
+     * calling {@link #getActualMinimum getActualMinimum} and {@link #getActualMaximum getActualMaximum}.
+     * For most such fields, simple addition, subtraction, and modulus operations
+     * are sufficient to perform the roll.  For week-related fields,
+     * the results of {@link #getFirstDayOfWeek getFirstDayOfWeek} and
+     * {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} are also necessary.
+     * Subclasses can override these two methods if their values differ from the defaults.
+     * <p>
+     * Subclasses that have fields for which the assumption of continuity breaks
+     * down must overide <code>roll</code> to handle those fields specially.
+     * For example, in the Hebrew calendar the month "Adar I"
+     * only occurs in leap years; in other years the calendar jumps from
+     * Shevat (month #4) to Adar (month #6).  The
+     * {@link HebrewCalendar#roll HebrewCalendar.roll} method takes this into account,
+     * so that rolling the month of Shevat by one gives the proper result (Adar) in a
+     * non-leap year.
+     * <p>
+     * @param field     the calendar field to roll.
+     * @param amount    the amount by which the field should be rolled.
+     *
+     * @exception   IllegalArgumentException if the field is invalid or refers
+     *              to a field that cannot be handled by this method.
+     * @see #roll(int, boolean)
+     * @see #add
+     * @stable ICU 2.0
+     */
+    public void roll(int field, int amount) {
+
+        if (amount == 0) {
+            return; // Nothing to do
+        }
+
+        complete();
+
+        switch (field) {
+        case DAY_OF_MONTH:
+        case AM_PM:
+        case MINUTE:
+        case SECOND:
+        case MILLISECOND:
+        case MILLISECONDS_IN_DAY:
+        case ERA:
+            // These are the standard roll instructions.  These work for all
+            // simple cases, that is, cases in which the limits are fixed, such
+            // as the hour, the day of the month, and the era.
+            {
+                int min = getActualMinimum(field);
+                int max = getActualMaximum(field);
+                int gap = max - min + 1;
+
+                int value = internalGet(field) + amount;
+                value = (value - min) % gap;
+                if (value < 0) {
+                    value += gap;
+                }
+                value += min;
+
+                set(field, value);
+                return;
+            }
+
+        case HOUR:
+        case HOUR_OF_DAY:
+            // Rolling the hour is difficult on the ONSET and CEASE days of
+            // daylight savings.  For example, if the change occurs at
+            // 2 AM, we have the following progression:
+            // ONSET: 12 Std -> 1 Std -> 3 Dst -> 4 Dst
+            // CEASE: 12 Dst -> 1 Dst -> 1 Std -> 2 Std
+            // To get around this problem we don't use fields; we manipulate
+            // the time in millis directly.
+            {
+                // Assume min == 0 in calculations below
+                long start = getTimeInMillis();
+                int oldHour = internalGet(field);
+                int max = getMaximum(field);
+                int newHour = (oldHour + amount) % (max + 1);
+                if (newHour < 0) {
+                    newHour += max + 1;
+                }
+                setTimeInMillis(start + ONE_HOUR * (newHour - oldHour));
+                return;
+            }
+
+        case MONTH:
+            // Rolling the month involves both pinning the final value
+            // and adjusting the DAY_OF_MONTH if necessary.  We only adjust the
+            // DAY_OF_MONTH if, after updating the MONTH field, it is illegal.
+            // E.g., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>.
+            {
+                int max = getActualMaximum(MONTH);
+                int mon = (internalGet(MONTH) + amount) % (max+1);
+
+                if (mon < 0) {
+                    mon += (max + 1);
+                }
+                set(MONTH, mon);
+
+                // Keep the day of month in range.  We don't want to spill over
+                // into the next month; e.g., we don't want jan31 + 1 mo -> feb31 ->
+                // mar3.
+                pinField(DAY_OF_MONTH);
+                return;
+            }
+
+        case YEAR:
+        case YEAR_WOY:
+        case EXTENDED_YEAR:
+            // Rolling the year can involve pinning the DAY_OF_MONTH.
+            set(field, internalGet(field) + amount);
+            pinField(MONTH);
+            pinField(DAY_OF_MONTH);
+            return;
+
+        case WEEK_OF_MONTH:
+            {
+                // This is tricky, because during the roll we may have to shift
+                // to a different day of the week.  For example:
+
+                //    s  m  t  w  r  f  s
+                //          1  2  3  4  5
+                //    6  7  8  9 10 11 12
+
+                // When rolling from the 6th or 7th back one week, we go to the
+                // 1st (assuming that the first partial week counts).  The same
+                // thing happens at the end of the month.
+
+                // The other tricky thing is that we have to figure out whether
+                // the first partial week actually counts or not, based on the
+                // minimal first days in the week.  And we have to use the
+                // correct first day of the week to delineate the week
+                // boundaries.
+
+                // Here's our algorithm.  First, we find the real boundaries of
+                // the month.  Then we discard the first partial week if it
+                // doesn't count in this locale.  Then we fill in the ends with
+                // phantom days, so that the first partial week and the last
+                // partial week are full weeks.  We then have a nice square
+                // block of weeks.  We do the usual rolling within this block,
+                // as is done elsewhere in this method.  If we wind up on one of
+                // the phantom days that we added, we recognize this and pin to
+                // the first or the last day of the month.  Easy, eh?
+
+                // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
+                // in this locale.  We have dow in 0..6.
+                int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
+                if (dow < 0) dow += 7;
+
+                // Find the day of the week (normalized for locale) for the first
+                // of the month.
+                int fdm = (dow - internalGet(DAY_OF_MONTH) + 1) % 7;
+                if (fdm < 0) fdm += 7;
+
+                // Get the first day of the first full week of the month,
+                // including phantom days, if any.  Figure out if the first week
+                // counts or not; if it counts, then fill in phantom days.  If
+                // not, advance to the first real full week (skip the partial week).
+                int start;
+                if ((7 - fdm) < getMinimalDaysInFirstWeek())
+                    start = 8 - fdm; // Skip the first partial week
+                else
+                    start = 1 - fdm; // This may be zero or negative
+
+                // Get the day of the week (normalized for locale) for the last
+                // day of the month.
+                int monthLen = getActualMaximum(DAY_OF_MONTH);
+                int ldm = (monthLen - internalGet(DAY_OF_MONTH) + dow) % 7;
+                // We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here.
+
+                // Get the limit day for the blocked-off rectangular month; that
+                // is, the day which is one past the last day of the month,
+                // after the month has already been filled in with phantom days
+                // to fill out the last week.  This day has a normalized DOW of 0.
+                int limit = monthLen + 7 - ldm;
+
+                // Now roll between start and (limit - 1).
+                int gap = limit - start;
+                int day_of_month = (internalGet(DAY_OF_MONTH) + amount*7 -
+                                    start) % gap;
+                if (day_of_month < 0) day_of_month += gap;
+                day_of_month += start;
+
+                // Finally, pin to the real start and end of the month.
+                if (day_of_month < 1) day_of_month = 1;
+                if (day_of_month > monthLen) day_of_month = monthLen;
+
+                // Set the DAY_OF_MONTH.  We rely on the fact that this field
+                // takes precedence over everything else (since all other fields
+                // are also set at this point).  If this fact changes (if the
+                // disambiguation algorithm changes) then we will have to unset
+                // the appropriate fields here so that DAY_OF_MONTH is attended
+                // to.
+                set(DAY_OF_MONTH, day_of_month);
+                return;
+            }
+        case WEEK_OF_YEAR:
+            {
+                // This follows the outline of WEEK_OF_MONTH, except it applies
+                // to the whole year.  Please see the comment for WEEK_OF_MONTH
+                // for general notes.
+
+                // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
+                // in this locale.  We have dow in 0..6.
+                int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
+                if (dow < 0) dow += 7;
+
+                // Find the day of the week (normalized for locale) for the first
+                // of the year.
+                int fdy = (dow - internalGet(DAY_OF_YEAR) + 1) % 7;
+                if (fdy < 0) fdy += 7;
+
+                // Get the first day of the first full week of the year,
+                // including phantom days, if any.  Figure out if the first week
+                // counts or not; if it counts, then fill in phantom days.  If
+                // not, advance to the first real full week (skip the partial week).
+                int start;
+                if ((7 - fdy) < getMinimalDaysInFirstWeek())
+                    start = 8 - fdy; // Skip the first partial week
+                else
+                    start = 1 - fdy; // This may be zero or negative
+
+                // Get the day of the week (normalized for locale) for the last
+                // day of the year.
+                int yearLen = getActualMaximum(DAY_OF_YEAR);
+                int ldy = (yearLen - internalGet(DAY_OF_YEAR) + dow) % 7;
+                // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here.
+
+                // Get the limit day for the blocked-off rectangular year; that
+                // is, the day which is one past the last day of the year,
+                // after the year has already been filled in with phantom days
+                // to fill out the last week.  This day has a normalized DOW of 0.
+                int limit = yearLen + 7 - ldy;
+
+                // Now roll between start and (limit - 1).
+                int gap = limit - start;
+                int day_of_year = (internalGet(DAY_OF_YEAR) + amount*7 -
+                                    start) % gap;
+                if (day_of_year < 0) day_of_year += gap;
+                day_of_year += start;
+
+                // Finally, pin to the real start and end of the month.
+                if (day_of_year < 1) day_of_year = 1;
+                if (day_of_year > yearLen) day_of_year = yearLen;
+
+                // Make sure that the year and day of year are attended to by
+                // clearing other fields which would normally take precedence.
+                // If the disambiguation algorithm is changed, this section will
+                // have to be updated as well.
+                set(DAY_OF_YEAR, day_of_year);
+                clear(MONTH);
+                return;
+            }
+        case DAY_OF_YEAR:
+            {
+                // Roll the day of year using millis.  Compute the millis for
+                // the start of the year, and get the length of the year.
+                long delta = amount * ONE_DAY; // Scale up from days to millis
+                long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY;
+                int yearLength = getActualMaximum(DAY_OF_YEAR);
+                time = (time + delta - min2) % (yearLength*ONE_DAY);
+                if (time < 0) time += yearLength*ONE_DAY;
+                setTimeInMillis(time + min2);
+                return;
+            }
+        case DAY_OF_WEEK:
+        case DOW_LOCAL:
+            {
+                // Roll the day of week using millis.  Compute the millis for
+                // the start of the week, using the first day of week setting.
+                // Restrict the millis to [start, start+7days).
+                long delta = amount * ONE_DAY; // Scale up from days to millis
+                // Compute the number of days before the current day in this
+                // week.  This will be a value 0..6.
+                int leadDays = internalGet(field);
+                leadDays -= (field == DAY_OF_WEEK) ? getFirstDayOfWeek() : 1;
+                if (leadDays < 0) leadDays += 7;
+                long min2 = time - leadDays * ONE_DAY;
+                time = (time + delta - min2) % ONE_WEEK;
+                if (time < 0) time += ONE_WEEK;
+                setTimeInMillis(time + min2);
+                return;
+            }
+        case DAY_OF_WEEK_IN_MONTH:
+            {
+                // Roll the day of week in the month using millis.  Determine
+                // the first day of the week in the month, and then the last,
+                // and then roll within that range.
+                long delta = amount * ONE_WEEK; // Scale up from weeks to millis
+                // Find the number of same days of the week before this one
+                // in this month.
+                int preWeeks = (internalGet(DAY_OF_MONTH) - 1) / 7;
+                // Find the number of same days of the week after this one
+                // in this month.
+                int postWeeks = (getActualMaximum(DAY_OF_MONTH) -
+                                 internalGet(DAY_OF_MONTH)) / 7;
+                // From these compute the min and gap millis for rolling.
+                long min2 = time - preWeeks * ONE_WEEK;
+                long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1!
+                // Roll within this range
+                time = (time + delta - min2) % gap2;
+                if (time < 0) time += gap2;
+                setTimeInMillis(time + min2);
+                return;
+            }
+        case JULIAN_DAY:
+            set(field, internalGet(field) + amount);
+            return;
+        default:
+            // Other fields cannot be rolled by this method
+            throw new IllegalArgumentException("Calendar.roll(" + fieldName(field) +
+                                               ") not supported");
+        }
+    }
+
+    /**
+     * Add a signed amount to a specified field, using this calendar's rules.
+     * For example, to add three days to the current date, you can call
+     * <code>add(Calendar.DATE, 3)</code>.
+     * <p>
+     * When adding to certain fields, the values of other fields may conflict and
+     * need to be changed.  For example, when adding one to the {@link #MONTH MONTH} field
+     * for the Gregorian date 1/31/96, the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
+     * must be adjusted so that the result is 2/29/96 rather than the invalid
+     * 2/31/96.
+     * <p>
+     * The <code>com.ibm.icu.util.Calendar</code> implementation of this method is able to add to
+     * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
+     * and {@link #ZONE_OFFSET ZONE_OFFSET}.  Subclasses may, of course, add support for
+     * additional fields in their overrides of <code>add</code>.
+     * <p>
+     * <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
+     * than attempting to perform arithmetic operations directly on the fields
+     * of a <tt>Calendar</tt>.  It is quite possible for <tt>Calendar</tt> subclasses
+     * to have fields with non-linear behavior, for example missing months
+     * or days during non-leap years.  The subclasses' <tt>add</tt> and <tt>roll</tt>
+     * methods will take this into account, while simple arithmetic manipulations
+     * may give invalid results.
+     * <p>
+     * <b>Subclassing:</b><br>
+     * This implementation of <code>add</code> assumes that the behavior of the
+     * field is continuous between its minimum and maximum, which are found by
+     * calling {@link #getActualMinimum getActualMinimum} and
+     * {@link #getActualMaximum getActualMaximum}.
+     * For such fields, simple arithmetic operations are sufficient to
+     * perform the add.
+     * <p>
+     * Subclasses that have fields for which this assumption of continuity breaks
+     * down must overide <code>add</code> to handle those fields specially.
+     * For example, in the Hebrew calendar the month "Adar I"
+     * only occurs in leap years; in other years the calendar jumps from
+     * Shevat (month #4) to Adar (month #6).  The
+     * {@link HebrewCalendar#add HebrewCalendar.add} method takes this into account,
+     * so that adding one month
+     * to a date in Shevat gives the proper result (Adar) in a non-leap year.
+     * <p>
+     * @param field     the time field.
+     * @param amount    the amount to add to the field.
+     *
+     * @exception   IllegalArgumentException if the field is invalid or refers
+     *              to a field that cannot be handled by this method.
+     * @see #roll(int, int)
+     * @stable ICU 2.0
+     */
+    public void add(int field, int amount) {
+
+        if (amount == 0) {
+            return;   // Do nothing!
+        }
+
+        // We handle most fields in the same way.  The algorithm is to add
+        // a computed amount of millis to the current millis.  The only
+        // wrinkle is with DST -- for some fields, like the DAY_OF_MONTH,
+        // we don't want the HOUR to shift due to changes in DST.  If the
+        // result of the add operation is to move from DST to Standard, or
+        // vice versa, we need to adjust by an hour forward or back,
+        // respectively.  For such fields we set keepHourInvariant to true.
+
+        // We only adjust the DST for fields larger than an hour.  For
+        // fields smaller than an hour, we cannot adjust for DST without
+        // causing problems.  for instance, if you add one hour to April 5,
+        // 1998, 1:00 AM, in PST, the time becomes "2:00 AM PDT" (an
+        // illegal value), but then the adjustment sees the change and
+        // compensates by subtracting an hour.  As a result the time
+        // doesn't advance at all.
+
+        // For some fields larger than a day, such as a MONTH, we pin the
+        // DAY_OF_MONTH.  This allows <March 31>.add(MONTH, 1) to be
+        // <April 30>, rather than <April 31> => <May 1>.
+
+        long delta = amount; // delta in ms
+        boolean keepHourInvariant = true;
+
+        switch (field) {
+        case ERA:
+            set(field, get(field) + amount);
+            pinField(ERA);
+            return;
+
+        case YEAR:
+        case EXTENDED_YEAR:
+        case YEAR_WOY:
+        case MONTH:
+            set(field, get(field) + amount);
+            pinField(DAY_OF_MONTH);
+            return;
+
+        case WEEK_OF_YEAR:
+        case WEEK_OF_MONTH:
+        case DAY_OF_WEEK_IN_MONTH:
+            delta *= ONE_WEEK;
+            break;
+
+        case AM_PM:
+            delta *= 12 * ONE_HOUR;
+            break;
+
+        case DAY_OF_MONTH:
+        case DAY_OF_YEAR:
+        case DAY_OF_WEEK:
+        case DOW_LOCAL:
+        case JULIAN_DAY:
+            delta *= ONE_DAY;
+            break;
+
+        case HOUR_OF_DAY:
+        case HOUR:
+            delta *= ONE_HOUR;
+            keepHourInvariant = false;
+            break;
+
+        case MINUTE:
+            delta *= ONE_MINUTE;
+            keepHourInvariant = false;
+            break;
+
+        case SECOND:
+            delta *= ONE_SECOND;
+            keepHourInvariant = false;
+            break;
+
+        case MILLISECOND:
+        case MILLISECONDS_IN_DAY:
+            keepHourInvariant = false;
+            break;
+
+        default:
+            throw new IllegalArgumentException("Calendar.add(" + fieldName(field) +
+                                               ") not supported");
+        }
+
+        // In order to keep the hour invariant (for fields where this is
+        // appropriate), record the DST_OFFSET before and after the add()
+        // operation.  If it has changed, then adjust the millis to
+        // compensate.
+        int dst = 0;
+        int hour = 0;
+        if (keepHourInvariant) {
+            dst = get(DST_OFFSET);
+            hour = internalGet(HOUR_OF_DAY);
+        }
+
+        setTimeInMillis(getTimeInMillis() + delta);
+
+        if (keepHourInvariant) {
+            dst -= get(DST_OFFSET);
+            if (dst != 0) {
+                // We have done an hour-invariant adjustment but the
+                // DST offset has altered.  We adjust millis to keep
+                // the hour constant.  In cases such as midnight after
+                // a DST change which occurs at midnight, there is the
+                // danger of adjusting into a different day.  To avoid
+                // this we make the adjustment only if it actually
+                // maintains the hour.
+                long t = time;
+                setTimeInMillis(time + dst);
+                if (get(HOUR_OF_DAY) != hour) {
+                    setTimeInMillis(t);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the name of this calendar in the language of the given locale.
+     * @stable ICU 2.0
+     */
+    public String getDisplayName(Locale loc) {
+        return this.getClass().getName();
+    }
+
+    //-------------------------------------------------------------------------
+    // Interface for creating custon DateFormats for different types of Calendars
+    //-------------------------------------------------------------------------
+
+    /**
+     * Return a <code>DateFormat</code> appropriate to this calendar.
+     * Subclasses wishing to specialize this behavior should override
+     * <code>handleGetDateFormat()</code>
+     * @see #handleGetDateFormat
+     * @stable ICU 2.0
+     */
+    public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, Locale loc) {
+        return formatHelper(this, loc, dateStyle, timeStyle);
+    }
+
+    /**
+     * Create a <code>DateFormat</code> appropriate to this calendar.
+     * This is a framework method for subclasses to override.  This method
+     * is responsible for creating the calendar-specific DateFormat and
+     * DateFormatSymbols objects as needed.
+     * @param pattern the pattern, specific to the <code>DateFormat</code>
+     * subclass
+     * @param locale the locale for which the symbols should be drawn
+     * @return a <code>DateFormat</code> appropriate to this calendar
+     * @stable ICU 2.0
+     */
+    protected DateFormat handleGetDateFormat(String pattern, Locale locale) {
+        DateFormatSymbols symbols = new DateFormatSymbols(this, locale);
+        return new SimpleDateFormat(pattern, symbols);
+    }
+
+    static private DateFormat formatHelper(Calendar cal, Locale loc,
+                                            int dateStyle, int timeStyle)
+    {
+        // See if there are any custom resources for this calendar
+        // If not, just use the default DateFormat
+        DateFormat result = null;
+
+        ResourceBundle bundle = DateFormatSymbols.getDateFormatBundle(cal, loc);
+
+        if (bundle != null) {
+
+            try {
+                String[] patterns = bundle.getStringArray("DateTimePatterns");
+
+                String pattern = null;
+                if ((timeStyle >= 0) && (dateStyle >= 0)) {
+                    Object[] dateTimeArgs = { patterns[timeStyle],
+                                              patterns[dateStyle + 4] };
+                    pattern = MessageFormat.format(patterns[8], dateTimeArgs);
+                }
+                else if (timeStyle >= 0) {
+                    pattern = patterns[timeStyle];
+                }
+                else if (dateStyle >= 0) {
+                    pattern = patterns[dateStyle + 4];
+                }
+                else {
+                    throw new IllegalArgumentException("No date or time style specified");
+                }
+                result = cal.handleGetDateFormat(pattern, loc);
+            } catch (MissingResourceException e) {
+                // No custom patterns
+                result = DateFormat.getDateTimeInstance(dateStyle, timeStyle, loc);
+                DateFormatSymbols symbols = new DateFormatSymbols(cal, loc);
+                ((SimpleDateFormat) result).setDateFormatSymbols(symbols); // aliu
+            }
+        } else {
+            result = SimpleDateFormat.getDateTimeInstance(dateStyle, timeStyle, loc);
+        }
+        result.setCalendar(cal);
+        return result;
+    }
+
+    //-------------------------------------------------------------------------
+    // Protected utility methods for use by subclasses.  These are very handy
+    // for implementing add, roll, and computeFields.
+    //-------------------------------------------------------------------------
+
+    /**
+     * Adjust the specified field so that it is within
+     * the allowable range for the date to which this calendar is set.
+     * For example, in a Gregorian calendar pinning the {@link #DAY_OF_MONTH DAY_OF_MONTH}
+     * field for a calendar set to April 31 would cause it to be set
+     * to April 30.
+     * <p>
+     * <b>Subclassing:</b>
+     * <br>
+     * This utility method is intended for use by subclasses that need to implement
+     * their own overrides of {@link #roll roll} and {@link #add add}.
+     * <p>
+     * <b>Note:</b>
+     * <code>pinField</code> is implemented in terms of
+     * {@link #getActualMinimum getActualMinimum}
+     * and {@link #getActualMaximum getActualMaximum}.  If either of those methods uses
+     * a slow, iterative algorithm for a particular field, it would be
+     * unwise to attempt to call <code>pinField</code> for that field.  If you
+     * really do need to do so, you should override this method to do
+     * something more efficient for that field.
+     * <p>
+     * @param field The calendar field whose value should be pinned.
+     *
+     * @see #getActualMinimum
+     * @see #getActualMaximum
+     * @stable ICU 2.0
+     */
+    protected void pinField(int field) {
+        int max = getActualMaximum(field);
+        int min = getActualMinimum(field);
+
+        if (fields[field] > max) {
+            set(field, max);
+        } else if (fields[field] < min) {
+            set(field, min);
+        }
+    }
+
+    /**
+     * Return the week number of a day, within a period. This may be the week number in
+     * a year or the week number in a month. Usually this will be a value >= 1, but if
+     * some initial days of the period are excluded from week 1, because
+     * {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} is > 1, then
+     * the week number will be zero for those
+     * initial days. This method requires the day number and day of week for some
+     * known date in the period in order to determine the day of week
+     * on the desired day.
+     * <p>
+     * <b>Subclassing:</b>
+     * <br>
+     * This method is intended for use by subclasses in implementing their
+     * {@link #computeTime computeTime} and/or {@link #computeFields computeFields} methods.
+     * It is often useful in {@link #getActualMinimum getActualMinimum} and
+     * {@link #getActualMaximum getActualMaximum} as well.
+     * <p>
+     * This variant is handy for computing the week number of some other
+     * day of a period (often the first or last day of the period) when its day
+     * of the week is not known but the day number and day of week for some other
+     * day in the period (e.g. the current date) <em>is</em> known.
+     * <p>
+     * @param desiredDay    The {@link #DAY_OF_YEAR DAY_OF_YEAR} or
+     *              {@link #DAY_OF_MONTH DAY_OF_MONTH} whose week number is desired.
+     *              Should be 1 for the first day of the period.
+     *
+     * @param knownDayOfPeriod   The {@link #DAY_OF_YEAR DAY_OF_YEAR}
+     *              or {@link #DAY_OF_MONTH DAY_OF_MONTH} for a day in the period whose
+     *              {@link #DAY_OF_WEEK DAY_OF_WEEK} is specified by the
+     *              <code>knownDayOfWeek</code> parameter.
+     *              Should be 1 for first day of period.
+     *
+     * @param knownDayOfWeek  The {@link #DAY_OF_WEEK DAY_OF_WEEK} for the day
+     *              corresponding to the <code>knownDayOfPeriod</code> parameter.
+     *              1-based with 1=Sunday.
+     *
+     * @return      The week number (one-based), or zero if the day falls before
+     *              the first week because
+     *              {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
+     *              is more than one.
+     * @stable ICU 2.0
+     */
+    protected int weekNumber(int desiredDay, int dayOfPeriod, int dayOfWeek)
+    {
+        // Determine the day of the week of the first day of the period
+        // in question (either a year or a month).  Zero represents the
+        // first day of the week on this calendar.
+        int periodStartDayOfWeek = (dayOfWeek - getFirstDayOfWeek() - dayOfPeriod + 1) % 7;
+        if (periodStartDayOfWeek < 0) periodStartDayOfWeek += 7;
+
+        // Compute the week number.  Initially, ignore the first week, which
+        // may be fractional (or may not be).  We add periodStartDayOfWeek in
+        // order to fill out the first week, if it is fractional.
+        int weekNo = (desiredDay + periodStartDayOfWeek - 1)/7;
+
+        // If the first week is long enough, then count it.  If
+        // the minimal days in the first week is one, or if the period start
+        // is zero, we always increment weekNo.
+        if ((7 - periodStartDayOfWeek) >= getMinimalDaysInFirstWeek()) ++weekNo;
+
+        return weekNo;
+    }
+
+    /**
+     * Return the week number of a day, within a period. This may be the week number in
+     * a year, or the week number in a month. Usually this will be a value >= 1, but if
+     * some initial days of the period are excluded from week 1, because
+     * {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} is > 1,
+     * then the week number will be zero for those
+     * initial days. This method requires the day of week for the given date in order to
+     * determine the result.
+     * <p>
+     * <b>Subclassing:</b>
+     * <br>
+     * This method is intended for use by subclasses in implementing their
+     * {@link #computeTime computeTime} and/or {@link #computeFields computeFields} methods.
+     * It is often useful in {@link #getActualMinimum getActualMinimum} and
+     * {@link #getActualMaximum getActualMaximum} as well.
+     * <p>
+     * @param dayOfPeriod   The {@link #DAY_OF_YEAR DAY_OF_YEAR} or
+     *                      {@link #DAY_OF_MONTH DAY_OF_MONTH} whose week number is desired.
+     *                      Should be 1 for the first day of the period.
+     *
+     * @param dayofWeek     The {@link #DAY_OF_WEEK DAY_OF_WEEK} for the day
+     *                      corresponding to the <code>dayOfPeriod</code> parameter.
+     *                      1-based with 1=Sunday.
+     *
+     * @return      The week number (one-based), or zero if the day falls before
+     *              the first week because
+     *              {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
+     *              is more than one.
+     * @stable ICU 2.0
+     */
+    protected final int weekNumber(int dayOfPeriod, int dayOfWeek)
+    {
+        return weekNumber(dayOfPeriod, dayOfPeriod, dayOfWeek);
+    }
+
+    //-------------------------------------------------------------------------
+    // Constants
+    //-------------------------------------------------------------------------
+
+    /**
+     * [NEW]
+     * Return the difference between the given time and the time this
+     * calendar object is set to.  If this calendar is set
+     * <em>before</em> the given time, the returned value will be
+     * positive.  If this calendar is set <em>after</em> the given
+     * time, the returned value will be negative.  The
+     * <code>field</code> parameter specifies the units of the return
+     * value.  For example, if <code>fieldDifference(when,
+     * Calendar.MONTH)</code> returns 3, then this calendar is set to
+     * 3 months before <code>when</code>, and possibly some addition
+     * time less than one month.
+     *
+     * <p>As a side effect of this call, this calendar is advanced
+     * toward <code>when</code> by the given amount.  That is, calling
+     * this method has the side effect of calling <code>add(field,
+     * n)</code>, where <code>n</code> is the return value.
+     *
+     * <p>Usage: To use this method, call it first with the largest
+     * field of interest, then with progressively smaller fields.  For
+     * example:
+     *
+     * <pre>
+     * int y = cal.fieldDifference(when, Calendar.YEAR);
+     * int m = cal.fieldDifference(when, Calendar.MONTH);
+     * int d = cal.fieldDifference(when, Calendar.DATE);</pre>
+     *
+     * computes the difference between <code>cal</code> and
+     * <code>when</code> in years, months, and days.
+     *
+     * <p>Note: <code>fieldDifference()</code> is
+     * <em>asymmetrical</em>.  That is, in the following code:
+     *
+     * <pre>
+     * cal.setTime(date1);
+     * int m1 = cal.fieldDifference(date2, Calendar.MONTH);
+     * int d1 = cal.fieldDifference(date2, Calendar.DATE);
+     * cal.setTime(date2);
+     * int m2 = cal.fieldDifference(date1, Calendar.MONTH);
+     * int d2 = cal.fieldDifference(date1, Calendar.DATE);</pre>
+     *
+     * one might expect that <code>m1 == -m2 && d1 == -d2</code>.
+     * However, this is not generally the case, because of
+     * irregularities in the underlying calendar system (e.g., the
+     * Gregorian calendar has a varying number of days per month).
+     *
+     * @param when the date to compare this calendar's time to
+     * @param field the field in which to compute the result
+     * @return the difference, either positive or negative, between
+     * this calendar's time and <code>when</code>, in terms of
+     * <code>field</code>.
+     * @stable ICU 2.0
+     */
+    public int fieldDifference(Date when, int field) {
+        int min = 0;
+        long startMs = getTimeInMillis();
+        long targetMs = when.getTime();
+        // Always add from the start millis.  This accomodates
+        // operations like adding years from February 29, 2000 up to
+        // February 29, 2004.  If 1, 1, 1, 1 is added to the year
+        // field, the DOM gets pinned to 28 and stays there, giving an
+        // incorrect DOM difference of 1.  We have to add 1, reset, 2,
+        // reset, 3, reset, 4.
+        if (startMs < targetMs) {
+            int max = 1;
+            // Find a value that is too large
+            for (;;) {
+                setTimeInMillis(startMs);
+                add(field, max);
+                long ms = getTimeInMillis();
+                if (ms == targetMs) {
+                    return max;
+                } else if (ms > targetMs) {
+                    break;
+                } else {
+                    max <<= 1;
+                    if (max < 0) {
+                        // Field difference too large to fit into int
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            // Do a binary search
+            while ((max - min) > 1) {
+                int t = (min + max) / 2;
+                setTimeInMillis(startMs);
+                add(field, t);
+                long ms = getTimeInMillis();
+                if (ms == targetMs) {
+                    return t;
+                } else if (ms > targetMs) {
+                    max = t;
+                } else {
+                    min = t;
+                }
+            }
+        } else if (startMs > targetMs) {
+            if (false) {
+                // This works, and makes the code smaller, but costs
+                // an extra object creation and an extra couple cycles
+                // of calendar computation.
+                setTimeInMillis(targetMs);
+                min = -fieldDifference(new Date(startMs), field);
+            }
+            int max = -1;
+            // Find a value that is too small
+            for (;;) {
+                setTimeInMillis(startMs);
+                add(field, max);
+                long ms = getTimeInMillis();
+                if (ms == targetMs) {
+                    return max;
+                } else if (ms < targetMs) {
+                    break;
+                } else {
+                    max <<= 1;
+                    if (max == 0) {
+                        // Field difference too large to fit into int
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            // Do a binary search
+            while ((min - max) > 1) {
+                int t = (min + max) / 2;
+                setTimeInMillis(startMs);
+                add(field, t);
+                long ms = getTimeInMillis();
+                if (ms == targetMs) {
+                    return t;
+                } else if (ms < targetMs) {
+                    max = t;
+                } else {
+                    min = t;
+                }
+            }
+        }
+        // Set calendar to end point
+        setTimeInMillis(startMs);
+        add(field, min);
+        return min;
+    }
+
+    /**
+     * Sets the time zone with the given time zone value.
+     * @param value the given time zone.
+     * @stable ICU 2.0
+     */
+    public void setTimeZone(TimeZone value)
+    {
+        zone = value;
+        /* Recompute the fields from the time using the new zone.  This also
+         * works if isTimeSet is false (after a call to set()).  In that case
+         * the time will be computed from the fields using the new zone, then
+         * the fields will get recomputed from that.  Consider the sequence of
+         * calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
+         * Is cal set to 1 o'clock EST or 1 o'clock PST?  Answer: PST.  More
+         * generally, a call to setTimeZone() affects calls to set() BEFORE AND
+         * AFTER it up to the next call to complete().
+         */
+        areFieldsSet = false;
+    }
+
+    /**
+     * Gets the time zone.
+     * @return the time zone object associated with this calendar.
+     * @stable ICU 2.0
+     */
+    public TimeZone getTimeZone()
+    {
+        return zone;
+    }
+
+    /**
+     * Specify whether or not date/time interpretation is to be lenient.  With
+     * lenient interpretation, a date such as "February 942, 1996" will be
+     * treated as being equivalent to the 941st day after February 1, 1996.
+     * With strict interpretation, such dates will cause an exception to be
+     * thrown.
+     *
+     * @see DateFormat#setLenient
+     * @stable ICU 2.0
+     */
+    public void setLenient(boolean lenient)
+    {
+        this.lenient = lenient;
+    }
+
+    /**
+     * Tell whether date/time interpretation is to be lenient.
+     * @stable ICU 2.0
+     */
+    public boolean isLenient()
+    {
+        return lenient;
+    }
+
+    /**
+     * Sets what the first day of the week is; e.g., Sunday in US,
+     * Monday in France.
+     * @param value the given first day of the week.
+     * @stable ICU 2.0
+     */
+    public void setFirstDayOfWeek(int value)
+    {
+        if (firstDayOfWeek != value) {
+            if (value < SUNDAY || value > SATURDAY) {
+                throw new IllegalArgumentException("Invalid day of week");
+            }
+            firstDayOfWeek = value;
+            areFieldsSet = false;
+        }
+    }
+
+    /**
+     * Gets what the first day of the week is; e.g., Sunday in US,
+     * Monday in France.
+     * @return the first day of the week.
+     * @stable ICU 2.0
+     */
+    public int getFirstDayOfWeek()
+    {
+        return firstDayOfWeek;
+    }
+
+    /**
+     * Sets what the minimal days required in the first week of the year are.
+     * For example, if the first week is defined as one that contains the first
+     * day of the first month of a year, call the method with value 1. If it
+     * must be a full week, use value 7.
+     * @param value the given minimal days required in the first week
+     * of the year.
+     * @stable ICU 2.0
+     */
+    public void setMinimalDaysInFirstWeek(int value)
+    {
+        // Values less than 1 have the same effect as 1; values greater
+        // than 7 have the same effect as 7. However, we normalize values
+        // so operator== and so forth work.
+        if (value < 1) {
+            value = 1;
+        } else if (value > 7) {
+            value = 7;
+        }
+        if (minimalDaysInFirstWeek != value) {
+            minimalDaysInFirstWeek = value;
+            areFieldsSet = false;
+        }
+    }
+
+    /**
+     * Gets what the minimal days required in the first week of the year are;
+     * e.g., if the first week is defined as one that contains the first day
+     * of the first month of a year, getMinimalDaysInFirstWeek returns 1. If
+     * the minimal days required must be a full week, getMinimalDaysInFirstWeek
+     * returns 7.
+     * @return the minimal days required in the first week of the year.
+     * @stable ICU 2.0
+     */
+    public int getMinimalDaysInFirstWeek()
+    {
+        return minimalDaysInFirstWeek;
+    }
+
+    private static final int LIMITS[][] = {
+        //    Minimum  Greatest min      Least max   Greatest max
+        {/*                                                      */}, // ERA
+        {/*                                                      */}, // YEAR
+        {/*                                                      */}, // MONTH
+        {/*                                                      */}, // WEEK_OF_YEAR
+        {/*                                                      */}, // WEEK_OF_MONTH
+        {/*                                                      */}, // DAY_OF_MONTH
+        {/*                                                      */}, // DAY_OF_YEAR
+        {           1,            1,             7,             7  }, // DAY_OF_WEEK
+        {/*                                                      */}, // DAY_OF_WEEK_IN_MONTH
+        {           0,            0,             1,             1  }, // AM_PM
+        {           0,            0,            11,            11  }, // HOUR
+        {           0,            0,            23,            23  }, // HOUR_OF_DAY
+        {           0,            0,            59,            59  }, // MINUTE
+        {           0,            0,            59,            59  }, // SECOND
+        {           0,            0,           999,           999  }, // MILLISECOND
+        {-12*ONE_HOUR, -12*ONE_HOUR,   12*ONE_HOUR,   12*ONE_HOUR  }, // ZONE_OFFSET
+        {           0,            0,    1*ONE_HOUR,    1*ONE_HOUR  }, // DST_OFFSET
+        {/*                                                      */}, // YEAR_WOY
+        {           1,            1,             7,             7  }, // DOW_LOCAL
+        {/*                                                      */}, // EXTENDED_YEAR
+        { -0x7F000000,  -0x7F000000,    0x7F000000,    0x7F000000  }, // JULIAN_DAY
+        {           0,            0, 24*ONE_HOUR-1, 24*ONE_HOUR-1  }, // MILLISECONDS_IN_DAY
+    };
+
+    /**
+     * Subclass API for defining limits of different types.
+     * Subclasses must implement this method to return limits for the
+     * following fields:
+     *
+     * <pre>ERA
+     * YEAR
+     * MONTH
+     * WEEK_OF_YEAR
+     * WEEK_OF_MONTH
+     * DAY_OF_MONTH
+     * DAY_OF_YEAR
+     * DAY_OF_WEEK_IN_MONTH
+     * YEAR_WOY
+     * EXTENDED_YEAR</pre>
+     *
+     * @param field one of the above field numbers
+     * @param limitType one of <code>MINIMUM</code>, <code>GREATEST_MINIMUM</code>,
+     * <code>LEAST_MAXIMUM</code>, or <code>MAXIMUM</code>
+     * @stable ICU 2.0
+     */
+    abstract protected int handleGetLimit(int field, int limitType);
+
+    /**
+     * Return a limit for a field.
+     * @param field the field, from 0..</code>getFieldCount()-1</code>
+     * @param limitType the type specifier for the limit
+     * @see #MINIMUM
+     * @see #GREATEST_MINIMUM
+     * @see #LEAST_MAXIMUM
+     * @see #MAXIMUM
+     * @stable ICU 2.0
+     */
+    protected int getLimit(int field, int limitType) {
+        switch (field) {
+        case DAY_OF_WEEK:
+        case AM_PM:
+        case HOUR:
+        case HOUR_OF_DAY:
+        case MINUTE:
+        case SECOND:
+        case MILLISECOND:
+        case ZONE_OFFSET:
+        case DST_OFFSET:
+        case DOW_LOCAL:
+        case JULIAN_DAY:
+        case MILLISECONDS_IN_DAY:
+            return LIMITS[field][limitType];
+        }
+        return handleGetLimit(field, limitType);
+    }
+
+    /**
+     * Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
+     * indicating the minimum value that a field can take (least minimum).
+     * @see #getLimit
+     * @see #handleGetLimit
+     * @stable ICU 2.0
+     */
+    protected static final int MINIMUM = 0;
+
+    /**
+     * Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
+     * indicating the greatest minimum value that a field can take.
+     * @see #getLimit
+     * @see #handleGetLimit
+     * @stable ICU 2.0
+     */
+    protected static final int GREATEST_MINIMUM = 1;
+
+    /**
+     * Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
+     * indicating the least maximum value that a field can take.
+     * @see #getLimit
+     * @see #handleGetLimit
+     * @stable ICU 2.0
+     */
+    protected static final int LEAST_MAXIMUM = 2;
+
+    /**
+     * Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
+     * indicating the maximum value that a field can take (greatest maximum).
+     * @see #getLimit
+     * @see #handleGetLimit
+     * @stable ICU 2.0
+     */
+    protected static final int MAXIMUM = 3;
+
+    /**
+     * Gets the minimum value for the given time field.
+     * e.g., for Gregorian DAY_OF_MONTH, 1.
+     * @param field the given time field.
+     * @return the minimum value for the given time field.
+     * @stable ICU 2.0
+     */
+    public final int getMinimum(int field) {
+        return getLimit(field, MINIMUM);
+    }
+
+    /**
+     * Gets the maximum value for the given time field.
+     * e.g. for Gregorian DAY_OF_MONTH, 31.
+     * @param field the given time field.
+     * @return the maximum value for the given time field.
+     * @stable ICU 2.0
+     */
+    public final int getMaximum(int field) {
+        return getLimit(field, MAXIMUM);
+    }
+
+    /**
+     * Gets the highest minimum value for the given field if varies.
+     * Otherwise same as getMinimum(). For Gregorian, no difference.
+     * @param field the given time field.
+     * @return the highest minimum value for the given time field.
+     * @stable ICU 2.0
+     */
+    public final int getGreatestMinimum(int field) {
+        return getLimit(field, GREATEST_MINIMUM);
+    }
+
+    /**
+     * Gets the lowest maximum value for the given field if varies.
+     * Otherwise same as getMaximum(). e.g., for Gregorian DAY_OF_MONTH, 28.
+     * @param field the given time field.
+     * @return the lowest maximum value for the given time field.
+     * @stable ICU 2.0
+     */
+    public final int getLeastMaximum(int field) {
+        return getLimit(field, LEAST_MAXIMUM);
+    }
+
+    //-------------------------------------------------------------------------
+    // Weekend support -- determining which days of the week are the weekend
+    // in a given locale
+    //-------------------------------------------------------------------------
+
+    /**
+     * Return whether the given day of the week is a weekday, a
+     * weekend day, or a day that transitions from one to the other,
+     * in this calendar system.  If a transition occurs at midnight,
+     * then the days before and after the transition will have the
+     * type WEEKDAY or WEEKEND.  If a transition occurs at a time
+     * other than midnight, then the day of the transition will have
+     * the type WEEKEND_ONSET or WEEKEND_CEASE.  In this case, the
+     * method getWeekendTransition() will return the point of
+     * transition.
+     * @param dayOfWeek either SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
+     * THURSDAY, FRIDAY, or SATURDAY
+     * @return either WEEKDAY, WEEKEND, WEEKEND_ONSET, or
+     * WEEKEND_CEASE
+     * @exception IllegalArgumentException if dayOfWeek is not
+     * between SUNDAY and SATURDAY, inclusive
+     * @see #WEEKDAY
+     * @see #WEEKEND
+     * @see #WEEKEND_ONSET
+     * @see #WEEKEND_CEASE
+     * @see #getWeekendTransition
+     * @see #isWeekend(Date)
+     * @see #isWeekend()
+     * @stable ICU 2.0
+     */
+    public int getDayOfWeekType(int dayOfWeek) {
+        if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) {
+            throw new IllegalArgumentException("Invalid day of week");
+        }
+        if (weekendOnset < weekendCease) {
+            if (dayOfWeek < weekendOnset || dayOfWeek > weekendCease) {
+                return WEEKDAY;
+            }
+        } else {
+            if (dayOfWeek > weekendCease && dayOfWeek < weekendOnset) {
+                return WEEKDAY;
+            }
+        }
+        if (dayOfWeek == weekendOnset) {
+            return (weekendOnsetMillis == 0) ? WEEKEND : WEEKEND_ONSET;
+        }
+        if (dayOfWeek == weekendCease) {
+            return (weekendCeaseMillis == 0) ? WEEKDAY : WEEKEND_CEASE;
+        }
+        return WEEKEND;
+    }
+
+    /**
+     * Return the time during the day at which the weekend begins or end in
+     * this calendar system.  If getDayOfWeekType(dayOfWeek) ==
+     * WEEKEND_ONSET return the time at which the weekend begins.  If
+     * getDayOfWeekType(dayOfWeek) == WEEKEND_CEASE return the time at
+     * which the weekend ends.  If getDayOfWeekType(dayOfWeek) has some
+     * other value, then throw an exception.
+     * @param dayOfWeek either SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
+     * THURSDAY, FRIDAY, or SATURDAY
+     * @return the milliseconds after midnight at which the
+     * weekend begins or ends
+     * @exception IllegalArgumentException if dayOfWeek is not
+     * WEEKEND_ONSET or WEEKEND_CEASE
+     * @see #getDayOfWeekType
+     * @see #isWeekend(Date)
+     * @see #isWeekend()
+     * @stable ICU 2.0
+     */
+    public int getWeekendTransition(int dayOfWeek) {
+        if (dayOfWeek == weekendOnset) {
+            return weekendOnsetMillis;
+        } else if (dayOfWeek == weekendCease) {
+            return weekendCeaseMillis;
+        }
+        throw new IllegalArgumentException("Not weekend transition day");
+    }
+
+    /**
+     * Return true if the given date and time is in the weekend in
+     * this calendar system.  Equivalent to calling setTime() followed
+     * by isWeekend().  Note: This method changes the time this
+     * calendar is set to.
+     * @param date the date and time
+     * @return true if the given date and time is part of the
+     * weekend
+     * @see #getDayOfWeekType
+     * @see #getWeekendTransition
+     * @see #isWeekend()
+     * @stable ICU 2.0
+     */
+    public boolean isWeekend(Date date) {
+        setTime(date);
+        return isWeekend();
+    }
+
+    /**
+     * Return true if this Calendar's current date and time is in the
+     * weekend in this calendar system.
+     * @return true if the given date and time is part of the
+     * weekend
+     * @see #getDayOfWeekType
+     * @see #getWeekendTransition
+     * @see #isWeekend(Date)
+     * @stable ICU 2.0
+     */
+    public boolean isWeekend() {
+        int dow =  get(DAY_OF_WEEK);
+        int dowt = getDayOfWeekType(dow);
+        switch (dowt) {
+        case WEEKDAY:
+            return false;
+        case WEEKEND:
+            return true;
+        default: // That is, WEEKEND_ONSET or WEEKEND_CEASE
+            // Use internalGet() because the above call to get() populated
+            // all fields.
+            // [Note: There should be a better way to get millis in day.
+            //  For ICU4J, submit request for a MILLIS_IN_DAY field
+            //  and a DAY_NUMBER field (could be Julian day #). - aliu]
+            int millisInDay = internalGet(MILLISECOND) + 1000 * (internalGet(SECOND) +
+                60 * (internalGet(MINUTE) + 60 * internalGet(HOUR_OF_DAY)));
+            int transition = getWeekendTransition(dow);
+            return (dowt == WEEKEND_ONSET)
+                ? (millisInDay >= transition)
+                : (millisInDay <  transition);
+        }
+        // (We can never reach this point.)
+    }
+
+    /**
+     * Read the locale weekend data for the given locale.
+     *
+     * This is the initial placement and format of this data -- it may very
+     * well change in the future.  See the locale files themselves for
+     * details.
+     */
+    private void setWeekendData(Locale loc) {
+        ResourceBundle resource =
+            ICULocaleData.getResourceBundle("CalendarData", loc);
+        String[] data = resource.getStringArray("Weekend");
+        weekendOnset       = Integer.parseInt(data[0]);
+        weekendOnsetMillis = Integer.parseInt(data[1]);
+        weekendCease       = Integer.parseInt(data[2]);
+        weekendCeaseMillis = Integer.parseInt(data[3]);
+    }
+
+    //-------------------------------------------------------------------------
+    // End of weekend support
+    //-------------------------------------------------------------------------
+
+    /**
+     * Overrides Cloneable
+     * @stable ICU 2.0
+     */
+    public Object clone()
+    {
+        try {
+            Calendar other = (Calendar) super.clone();
+
+            other.fields = new int[fields.length];
+            other.stamp = new int[fields.length];
+            System.arraycopy(this.fields, 0, other.fields, 0, fields.length);
+            System.arraycopy(this.stamp, 0, other.stamp, 0, fields.length);
+
+            other.zone = (TimeZone) zone.clone();
+            return other;
+        }
+        catch (CloneNotSupportedException e) {
+            // this shouldn't happen, since we are Cloneable
+            throw new InternalError();
+        }
+    }
+
+    /**
+     * Return a string representation of this calendar. This method
+     * is intended to be used only for debugging purposes, and the
+     * format of the returned string may vary between implementations.
+     * The returned string may be empty but may not be <code>null</code>.
+     *
+     * @return  a string representation of this calendar.
+     * @stable ICU 2.0
+     */
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append(getClass().getName());
+        buffer.append("[time=");
+        buffer.append(isTimeSet ? String.valueOf(time) : "?");
+        buffer.append(",areFieldsSet=");
+        buffer.append(areFieldsSet);
+        buffer.append(",areAllFieldsSet=");
+        buffer.append(areAllFieldsSet);
+        buffer.append(",lenient=");
+        buffer.append(lenient);
+        buffer.append(",zone=");
+        buffer.append(zone);
+        buffer.append(",firstDayOfWeek=");
+        buffer.append(firstDayOfWeek);
+        buffer.append(",minimalDaysInFirstWeek=");
+        buffer.append(minimalDaysInFirstWeek);
+        for (int i=0; i<fields.length; ++i) {
+            buffer.append(',').append(FIELD_NAME[i]).append('=');
+            buffer.append(isSet(i) ? String.valueOf(fields[i]) : "?");
+        }
+        buffer.append(']');
+        return buffer.toString();
+    }
+
+    // =======================privates===============================
+
+    /**
+     * Both firstDayOfWeek and minimalDaysInFirstWeek are locale-dependent.
+     * They are used to figure out the week count for a specific date for
+     * a given locale. These must be set when a Calendar is constructed.
+     * @param desiredLocale the given locale.
+     */
+    private void setWeekCountData(Locale desiredLocale)
+    {
+    /* try to get the Locale data from the cache */
+    int[] data = (int[]) cachedLocaleData.get(desiredLocale);
+
+    if (data == null) {  /* cache miss */
+        ResourceBundle resource = ICULocaleData.getLocaleElements(desiredLocale);
+        String[] dateTimePatterns =  resource.getStringArray("DateTimeElements");
+        data = new int[2];
+        data[0] = Integer.parseInt(dateTimePatterns[0]);
+        data[1] = Integer.parseInt(dateTimePatterns[1]);
+        /* cache update */
+        cachedLocaleData.put(desiredLocale, data);
+    }
+    setFirstDayOfWeek(data[0]);
+    setMinimalDaysInFirstWeek(data[1]);
+    }
+
+    /**
+     * Recompute the time and update the status fields isTimeSet
+     * and areFieldsSet.  Callers should check isTimeSet and only
+     * call this method if isTimeSet is false.
+     */
+    private void updateTime() {
+        computeTime();
+        // If we are lenient, we need to recompute the fields to normalize
+        // the values.  Also, if we haven't set all the fields yet (i.e.,
+        // in a newly-created object), we need to fill in the fields. [LIU]
+        if (isLenient() || !areAllFieldsSet) areFieldsSet = false;
+        isTimeSet = true;
+    }
+
+    /**
+     * Save the state of this object to a stream (i.e., serialize it).
+     *
+     * Ideally, <code>Calendar</code> would only write out its state data and
+     * the current time, and not write any field data out, such as
+     * <code>fields[]</code>, <code>isTimeSet</code>, <code>areFieldsSet</code>,
+     * and <code>isSet[]</code>.  <code>nextStamp</code> also should not be part
+     * of the persistent state. Unfortunately, this didn't happen before JDK 1.1
+     * shipped. To be compatible with JDK 1.1, we will always have to write out
+     * the field values and state flags.  However, <code>nextStamp</code> can be
+     * removed from the serialization stream; this will probably happen in the
+     * near future.
+     */
+    private void writeObject(ObjectOutputStream stream)
+         throws IOException
+    {
+        // Try to compute the time correctly, for the future (stream
+        // version 2) in which we don't write out fields[] or isSet[].
+        if (!isTimeSet) {
+            try {
+                updateTime();
+            }
+            catch (IllegalArgumentException e) {}
+        }
+
+        // Write out the 1.1 FCS object.
+        stream.defaultWriteObject();
+    }
+
+    /**
+     * Reconstitute this object from a stream (i.e., deserialize it).
+     */
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException {
+
+        stream.defaultReadObject();
+
+        initInternal();
+
+        isTimeSet = true;
+        areFieldsSet = areAllFieldsSet = false;
+        nextStamp = MINIMUM_USER_STAMP;
+    }
+
+
+    //----------------------------------------------------------------------
+    // Time -> Fields
+    //----------------------------------------------------------------------
+
+    /**
+     * Converts the current millisecond time value <code>time</code> to
+     * field values in <code>fields[]</code>.  This synchronizes the time
+     * field values with a new time that is set for the calendar.  The time
+     * is <em>not</em> recomputed first; to recompute the time, then the
+     * fields, call the <code>complete</code> method.
+     * @see #complete
+     * @stable ICU 2.0
+     */
+    protected void computeFields() {
+        int rawOffset = getTimeZone().getRawOffset();
+        long localMillis = time + rawOffset;
+
+        // Mark fields as set.  Do this before calling handleComputeFields().
+        int mask = internalSetMask;
+        for (int i=0; i<fields.length; ++i) {
+            if ((mask & 1) == 0) {
+                stamp[i] = INTERNALLY_SET;
+            } else {
+                stamp[i] = UNSET;
+            }
+            mask >>= 1;
+        }
+
+        // We used to check for and correct extreme millis values (near
+        // Long.MIN_VALUE or Long.MAX_VALUE) here.  Such values would cause
+        // overflows from positive to negative (or vice versa) and had to
+        // be manually tweaked.  We no longer need to do this because we
+        // have limited the range of supported dates to those that have a
+        // Julian day that fits into an int.  This allows us to implement a
+        // JULIAN_DAY field and also removes some inelegant code. - Liu
+        // 11/6/00
+
+        fields[JULIAN_DAY] = (int) floorDivide(localMillis, ONE_DAY) +
+            EPOCH_JULIAN_DAY;
+
+        // In some cases we will have to call this method again below to
+        // adjust for DST pushing us into the next Julian day.
+        computeGregorianAndDOWFields(fields[JULIAN_DAY]);
+
+        long days = (long) (localMillis / ONE_DAY);
+        int millisInDay = (int) (localMillis - (days * ONE_DAY));
+        if (millisInDay < 0) millisInDay += ONE_DAY;
+
+        // Call getOffset() to get the TimeZone offset.  The millisInDay value
+        // must be _standard_ local zone millis.      
+        // TODO: Consider calling the getOffset(millis) API when we drop JDK 1.3 support - Alan
+        int dstOffset = getTimeZone().getOffset(GregorianCalendar.AD,
+                gregorianYear, gregorianMonth,
+                gregorianDayOfMonth,
+                fields[DAY_OF_WEEK],
+                millisInDay)
+            - rawOffset;
+
+        // Adjust our millisInDay for DST.  dstOffset will be zero if DST
+        // is not in effect at this time of year, or if our zone does not
+        // use DST.
+        millisInDay += dstOffset;
+
+        // If DST has pushed us into the next day, we must call
+        // computeGregorianAndDOWFields() again.  This happens in DST between
+        // 12:00 am and 1:00 am every day.  The first call to
+        // computeGregorianAndDOWFields() will give the wrong day, since the
+        // Standard time is in the previous day.
+        if (millisInDay >= ONE_DAY) {
+            millisInDay -= ONE_DAY; // ASSUME dstOffset < 24:00
+
+            // We don't worry about overflow of JULIAN_DAY because the
+            // allowable range of JULIAN_DAY has slop at the ends (that is,
+            // the max is less that 0x7FFFFFFF and the min is greater than
+            // -0x80000000).
+            computeGregorianAndDOWFields(++fields[JULIAN_DAY]);
+        }
+
+        // Call framework method to have subclass compute its fields.
+        // These must include, at a minimum, MONTH, DAY_OF_MONTH,
+        // EXTENDED_YEAR, YEAR, DAY_OF_YEAR.  This method will call internalSet(),
+        // which will update stamp[].
+        handleComputeFields(fields[JULIAN_DAY]);
+
+        // Compute week-related fields, based on the subclass-computed
+        // fields computed by handleComputeFields().
+        computeWeekFields();
+
+        // Compute time-related fields.  These are indepent of the date and
+        // of the subclass algorithm.  They depend only on the local zone
+        // wall milliseconds in day.
+        fields[MILLISECONDS_IN_DAY] = millisInDay;
+        fields[MILLISECOND] = millisInDay % 1000;
+        millisInDay /= 1000;
+        fields[SECOND] = millisInDay % 60;
+        millisInDay /= 60;
+        fields[MINUTE] = millisInDay % 60;
+        millisInDay /= 60;
+        fields[HOUR_OF_DAY] = millisInDay;
+        fields[AM_PM] = millisInDay / 12; // Assume AM == 0
+        fields[HOUR] = millisInDay % 12;
+        fields[ZONE_OFFSET] = rawOffset;
+        fields[DST_OFFSET] = dstOffset;
+    }
+
+    /**
+     * Compute the Gregorian calendar year, month, and day of month from
+     * the given Julian day.  These values are not stored in fields, but in
+     * member variables gregorianXxx.  Also compute the DAY_OF_WEEK and
+     * DOW_LOCAL fields.
+     */
+    private final void computeGregorianAndDOWFields(int julianDay) {
+        computeGregorianFields(julianDay);
+
+        // Compute day of week: JD 0 = Monday
+        int dow = fields[DAY_OF_WEEK] = julianDayToDayOfWeek(julianDay);
+
+        // Calculate 1-based localized day of week
+        int dowLocal = dow - getFirstDayOfWeek() + 1;
+        if (dowLocal < 1) {
+            dowLocal += 7;
+        }
+        fields[DOW_LOCAL] = dowLocal;
+    }
+
+    /**
+     * Compute the Gregorian calendar year, month, and day of month from the
+     * Julian day.  These values are not stored in fields, but in member
+     * variables gregorianXxx.  They are used for time zone computations and by
+     * subclasses that are Gregorian derivatives.  Subclasses may call this
+     * method to perform a Gregorian calendar millis->fields computation.
+     * To perform a Gregorian calendar fields->millis computation, call
+     * computeGregorianMonthStart().
+     * @see #computeGregorianMonthStart
+     * @stable ICU 2.0
+     */
+    protected final void computeGregorianFields(int julianDay) {
+        int year, month, dayOfMonth, dayOfYear;
+
+        // The Gregorian epoch day is zero for Monday January 1, year 1.
+        long gregorianEpochDay = julianDay - JAN_1_1_JULIAN_DAY;
+
+        // Here we convert from the day number to the multiple radix
+        // representation.  We use 400-year, 100-year, and 4-year cycles.
+        // For example, the 4-year cycle has 4 years + 1 leap day; giving
+        // 1461 == 365*4 + 1 days.
+        int[] rem = new int[1];
+        int n400 = floorDivide(gregorianEpochDay, 146097, rem); // 400-year cycle length
+        int n100 = floorDivide(rem[0], 36524, rem); // 100-year cycle length
+        int n4 = floorDivide(rem[0], 1461, rem); // 4-year cycle length
+        int n1 = floorDivide(rem[0], 365, rem);
+        year = 400*n400 + 100*n100 + 4*n4 + n1;
+        dayOfYear = rem[0]; // zero-based day of year
+        if (n100 == 4 || n1 == 4) {
+            dayOfYear = 365; // Dec 31 at end of 4- or 400-yr cycle
+        } else {
+            ++year;
+        }
+
+        boolean isLeap = ((year&0x3) == 0) && // equiv. to (year%4 == 0)
+            (year%100 != 0 || year%400 == 0);
+
+        int correction = 0;
+        int march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
+        if (dayOfYear >= march1) correction = isLeap ? 1 : 2;
+        month = (12 * (dayOfYear + correction) + 6) / 367; // zero-based month
+        dayOfMonth = dayOfYear -
+            GREGORIAN_MONTH_COUNT[month][isLeap?3:2] + 1; // one-based DOM
+
+        gregorianYear = year;
+        gregorianMonth = month; // 0-based already
+        gregorianDayOfMonth = dayOfMonth; // 1-based already
+        gregorianDayOfYear = dayOfYear + 1; // Convert from 0-based to 1-based
+    }
+
+    /**
+     * Compute the fields WEEK_OF_YEAR, YEAR_WOY, WEEK_OF_MONTH,
+     * DAY_OF_WEEK_IN_MONTH, and DOW_LOCAL from EXTENDED_YEAR, YEAR,
+     * DAY_OF_WEEK, and DAY_OF_YEAR.  The latter fields are computed by the
+     * subclass based on the calendar system.
+     *
+     * <p>The YEAR_WOY field is computed simplistically.  It is equal to YEAR
+     * most of the time, but at the year boundary it may be adjusted to YEAR-1
+     * or YEAR+1 to reflect the overlap of a week into an adjacent year.  In
+     * this case, a simple increment or decrement is performed on YEAR, even
+     * though this may yield an invalid YEAR value.  For instance, if the YEAR
+     * is part of a calendar system with an N-year cycle field CYCLE, then
+     * incrementing the YEAR may involve incrementing CYCLE and setting YEAR
+     * back to 0 or 1.  This is not handled by this code, and in fact cannot be
+     * simply handled without having subclasses define an entire parallel set of
+     * fields for fields larger than or equal to a year.  This additional
+     * complexity is not warranted, since the intention of the YEAR_WOY field is
+     * to support ISO 8601 notation, so it will typically be used with a
+     * proleptic Gregorian calendar, which has no field larger than a year.
+     */
+    private final void computeWeekFields() {
+        int eyear = fields[EXTENDED_YEAR];
+        int year = fields[YEAR];
+        int dayOfWeek = fields[DAY_OF_WEEK];
+        int dayOfYear = fields[DAY_OF_YEAR];
+
+        // WEEK_OF_YEAR start
+        // Compute the week of the year.  For the Gregorian calendar, valid week
+        // numbers run from 1 to 52 or 53, depending on the year, the first day
+        // of the week, and the minimal days in the first week.  For other
+        // calendars, the valid range may be different -- it depends on the year
+        // length.  Days at the start of the year may fall into the last week of
+        // the previous year; days at the end of the year may fall into the
+        // first week of the next year.  ASSUME that the year length is less than
+        // 7000 days.
+        int yearOfWeekOfYear = year;
+        int relDow = (dayOfWeek + 7 - getFirstDayOfWeek()) % 7; // 0..6
+        int relDowJan1 = (dayOfWeek - dayOfYear + 7001 - getFirstDayOfWeek()) % 7; // 0..6
+        int woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53
+        if ((7 - relDowJan1) >= getMinimalDaysInFirstWeek()) {
+            ++woy;
+        }
+
+        // Adjust for weeks at the year end that overlap into the previous or
+        // next calendar year.
+        if (woy == 0) {
+            // We are the last week of the previous year.
+            // Check to see if we are in the last week; if so, we need
+            // to handle the case in which we are the first week of the
+            // next year.
+
+            int prevDoy = dayOfYear + handleGetYearLength(eyear - 1);
+            woy = weekNumber(prevDoy, dayOfWeek);
+            yearOfWeekOfYear--;
+        } else {
+            int lastDoy = handleGetYearLength(eyear);
+            // Fast check: For it to be week 1 of the next year, the DOY
+            // must be on or after L-5, where L is yearLength(), then it
+            // cannot possibly be week 1 of the next year:
+            //          L-5                  L
+            // doy: 359 360 361 362 363 364 365 001
+            // dow:      1   2   3   4   5   6   7
+            if (dayOfYear >= (lastDoy - 5)) {
+                int lastRelDow = (relDow + lastDoy - dayOfYear) % 7;
+                if (lastRelDow < 0) {
+                    lastRelDow += 7;
+                }
+                if (((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) &&
+                    ((dayOfYear + 7 - relDow) > lastDoy)) {
+                    woy = 1;
+                    yearOfWeekOfYear++;
+                }
+            }
+        }
+        fields[WEEK_OF_YEAR] = woy;
+        fields[YEAR_WOY] = yearOfWeekOfYear;
+        // WEEK_OF_YEAR end
+
+        int dayOfMonth = fields[DAY_OF_MONTH];
+        fields[WEEK_OF_MONTH] = weekNumber(dayOfMonth, dayOfWeek);
+        fields[DAY_OF_WEEK_IN_MONTH] = (dayOfMonth-1) / 7 + 1;
+    }
+
+    //----------------------------------------------------------------------
+    // Fields -> Time
+    //----------------------------------------------------------------------
+
+    /**
+     * Value to OR against resolve table field values for remapping.
+     * @see #resolveFields
+     * @stable ICU 2.0
+     */
+    protected static final int RESOLVE_REMAP = 32;
+    // A power of 2 greater than or equal to MAX_FIELD_COUNT
+
+    // Default table for day in year
+    static final int[][][] DATE_PRECEDENCE = {
+        {
+            { DAY_OF_MONTH },
+            { WEEK_OF_YEAR, DAY_OF_WEEK },
+            { WEEK_OF_MONTH, DAY_OF_WEEK },
+            { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
+            { WEEK_OF_YEAR, DOW_LOCAL },
+            { WEEK_OF_MONTH, DOW_LOCAL },
+            { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
+            { DAY_OF_YEAR },
+        },
+        {
+            { WEEK_OF_YEAR },
+            { WEEK_OF_MONTH },
+            { DAY_OF_WEEK_IN_MONTH },
+            { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
+            { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
+        },
+    };
+
+    static final int[][][] DOW_PRECEDENCE = {
+        {
+            { DAY_OF_WEEK },
+            { DOW_LOCAL },
+        },
+    };
+
+    /**
+     * Given a precedence table, return the newest field combination in
+     * the table, or -1 if none is found.
+     *
+     * <p>The precedence table is a 3-dimensional array of integers.  It
+     * may be thought of as an array of groups.  Each group is an array of
+     * lines.  Each line is an array of field numbers.  Within a line, if
+     * all fields are set, then the time stamp of the line is taken to be
+     * the stamp of the most recently set field.  If any field of a line is
+     * unset, then the line fails to match.  Within a group, the line with
+     * the newest time stamp is selected.  The first field of the line is
+     * returned to indicate which line matched.
+     *
+     * <p>In some cases, it may be desirable to map a line to field that
+     * whose stamp is NOT examined.  For example, if the best field is
+     * DAY_OF_WEEK then the DAY_OF_WEEK_IN_MONTH algorithm may be used.  In
+     * order to do this, insert the value <code>REMAP_RESOLVE | F</code> at
+     * the start of the line, where <code>F</code> is the desired return
+     * field value.  This field will NOT be examined; it only determines
+     * the return value if the other fields in the line are the newest.
+     *
+     * <p>If all lines of a group contain at least one unset field, then no
+     * line will match, and the group as a whole will fail to match.  In
+     * that case, the next group will be processed.  If all groups fail to
+     * match, then -1 is returned.
+     * @stable ICU 2.0
+     */
+    protected int resolveFields(int[][][] precedenceTable) {
+        int bestField = -1;
+        for (int g=0; g<precedenceTable.length && bestField < 0; ++g) {
+            int[][] group = precedenceTable[g];
+            int bestStamp = UNSET;
+        linesInGroup:
+            for (int l=0; l<group.length; ++l) {
+                int[] line= group[l];
+                int lineStamp = UNSET;
+                // Skip over first entry if it is negative
+                for (int i=(line[0]>=RESOLVE_REMAP)?1:0; i<line.length; ++i) {
+                    int s = stamp[line[i]];
+                    // If any field is unset then don't use this line
+                    if (s == UNSET) {
+                        continue linesInGroup;
+                    } else {
+                        lineStamp = Math.max(lineStamp, s);
+                    }
+                }
+                // Record new maximum stamp & field no.
+                if (lineStamp > bestStamp) {
+                    bestStamp = lineStamp;
+                    bestField = line[0]; // First field refers to entire line
+                }
+            }
+        }
+        return (bestField>=RESOLVE_REMAP)?(bestField&(RESOLVE_REMAP-1)):bestField;
+    }
+
+    /**
+     * Return the newest stamp of a given range of fields.
+     * @stable ICU 2.0
+     */
+    protected int newestStamp(int first, int last, int bestStampSoFar) {
+        int bestStamp = bestStampSoFar;
+        for (int i=first; i<=last; ++i) {
+            if (stamp[i] > bestStamp) {
+                bestStamp = stamp[i];
+            }
+        }
+        return bestStamp;
+    }
+
+    /**
+     * Return the timestamp of a field.
+     * @stable ICU 2.0
+     */
+    protected final int getStamp(int field) {
+        return stamp[field];
+    }
+
+    /**
+     * Return the field that is newer, either defaultField, or
+     * alternateField.  If neither is newer or neither is set, return defaultField.
+     * @stable ICU 2.0
+     */
+    protected int newerField(int defaultField, int alternateField) {
+        if (stamp[alternateField] > stamp[defaultField]) {
+            return alternateField;
+        }
+        return defaultField;
+    }
+
+    /**
+     * Ensure that each field is within its valid range by calling {@link
+     * #validateField(int)} on each field that has been set.  This method
+     * should only be called if this calendar is not lenient.
+     * @see #isLenient
+     * @see #validateField(int)
+     * @stable ICU 2.0
+     */
+    protected void validateFields() {
+        for (int field = 0; field < fields.length; field++) {
+            if (isSet(field)) {
+                validateField(field);
+            }
+        }
+    }
+
+    /**
+     * Validate a single field of this calendar.  Subclasses should
+     * override this method to validate any calendar-specific fields.
+     * Generic fields can be handled by
+     * <code>Calendar.validateField()</code>.
+     * @see #validateField(int, int, int)
+     * @stable ICU 2.0
+     */
+    protected void validateField(int field) {
+        int y;
+        switch (field) {
+        case DAY_OF_MONTH:
+            y = handleGetExtendedYear();
+            validateField(field, 1, handleGetMonthLength(y, internalGet(MONTH)));
+            break;
+        case DAY_OF_YEAR:
+            y = handleGetExtendedYear();
+            validateField(field, 1, handleGetYearLength(y));
+            break;
+        case DAY_OF_WEEK_IN_MONTH:
+            if (internalGet(field) == 0) {
+                throw new IllegalArgumentException("DAY_OF_WEEK_IN_MONTH cannot be zero");
+            }
+            validateField(field, getMinimum(field), getMaximum(field));
+            break;
+        default:
+            validateField(field, getMinimum(field), getMaximum(field));
+            break;
+        }
+    }
+
+    /**
+     * Validate a single field of this calendar given its minimum and
+     * maximum allowed value.  If the field is out of range, throw a
+     * descriptive <code>IllegalArgumentException</code>.  Subclasses may
+     * use this method in their implementation of {@link
+     * #validateField(int)}.
+     * @stable ICU 2.0
+     */
+    protected final void validateField(int field, int min, int max) {
+        int value = fields[field];
+        if (value < min || value > max) {
+            throw new IllegalArgumentException(fieldName(field) +
+                                               '=' + value + ", valid range=" +
+                                               min + ".." + max);
+        }
+    }
+
+    /**
+     * Converts the current field values in <code>fields[]</code> to the
+     * millisecond time value <code>time</code>.
+     * @stable ICU 2.0
+     */
+   protected void computeTime() {
+        if (!isLenient()) {
+            validateFields();
+        }
+
+        // Compute the Julian day
+        int julianDay = computeJulianDay();
+
+        long millis = julianDayToMillis(julianDay);
+
+        int millisInDay;
+
+        // We only use MILLISECONDS_IN_DAY if it has been set by the user.
+        // This makes it possible for the caller to set the calendar to a
+        // time and call clear(MONTH) to reset the MONTH to January.  This
+        // is legacy behavior.  Without this, clear(MONTH) has no effect,
+        // since the internally set JULIAN_DAY is used.
+        if (stamp[MILLISECONDS_IN_DAY] >= MINIMUM_USER_STAMP &&
+            newestStamp(AM_PM, MILLISECOND, UNSET) <= stamp[MILLISECONDS_IN_DAY]) {
+            millisInDay = internalGet(MILLISECONDS_IN_DAY);
+        } else {
+            millisInDay = computeMillisInDay();
+        }
+
+        // Compute the time zone offset and DST offset.  There are two potential
+        // ambiguities here.  We'll assume a 2:00 am (wall time) switchover time
+        // for discussion purposes here.
+        // 1. The transition into DST.  Here, a designated time of 2:00 am - 2:59 am
+        //    can be in standard or in DST depending.  However, 2:00 am is an invalid
+        //    representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST).
+        //    We assume standard time.
+        // 2. The transition out of DST.  Here, a designated time of 1:00 am - 1:59 am
+        //    can be in standard or DST.  Both are valid representations (the rep
+        //    jumps from 1:59:59 DST to 1:00:00 Std).
+        //    Again, we assume standard time.
+        // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
+        // or DST_OFFSET fields; then we use those fields.
+        if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP ||
+            stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) {
+            millisInDay -= internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET);
+        } else {
+            millisInDay -= computeZoneOffset(millis, millisInDay);
+        }
+
+        time = millis + millisInDay;
+    }
+
+    /**
+     * Compute the milliseconds in the day from the fields.  This is a
+     * value from 0 to 23:59:59.999 inclusive, unless fields are out of
+     * range, in which case it can be an arbitrary value.  This value
+     * reflects local zone wall time.
+     * @stable ICU 2.0
+     */
+    protected int computeMillisInDay() {
+        // Do the time portion of the conversion.
+
+        int millisInDay = 0;
+
+        // Find the best set of fields specifying the time of day.  There
+        // are only two possibilities here; the HOUR_OF_DAY or the
+        // AM_PM and the HOUR.
+        int hourOfDayStamp = stamp[HOUR_OF_DAY];
+        int hourStamp = Math.max(stamp[HOUR], stamp[AM_PM]);
+        int bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
+
+        // Hours
+        if (bestStamp != UNSET) {
+            if (bestStamp == hourOfDayStamp) {
+                // Don't normalize here; let overflow bump into the next period.
+                // This is consistent with how we handle other fields.
+                millisInDay += internalGet(HOUR_OF_DAY);
+            } else {
+                // Don't normalize here; let overflow bump into the next period.
+                // This is consistent with how we handle other fields.
+                millisInDay += internalGet(HOUR);
+                millisInDay += 12 * internalGet(AM_PM); // Default works for unset AM_PM
+            }
+        }
+
+        // We use the fact that unset == 0; we start with millisInDay
+        // == HOUR_OF_DAY.
+        millisInDay *= 60;
+        millisInDay += internalGet(MINUTE); // now have minutes
+        millisInDay *= 60;
+        millisInDay += internalGet(SECOND); // now have seconds
+        millisInDay *= 1000;
+        millisInDay += internalGet(MILLISECOND); // now have millis
+
+        return millisInDay;
+    }
+
+    /**
+     * This method can assume EXTENDED_YEAR has been set.
+     * @param millis milliseconds of the date fields
+     * @param millisInDay milliseconds of the time fields; may be out
+     * or range.
+     * @stable ICU 2.0
+     */
+    protected int computeZoneOffset(long millis, int millisInDay) {
+
+        /* Normalize the millisInDay to 0..ONE_DAY-1.  If the millis is out
+         * of range, then we must call computeGregorianAndDOWFields() to
+         * recompute our fields. */
+        int[] normalizedMillisInDay = new int[1];
+        int days = floorDivide(millis + millisInDay, (int) ONE_DAY,
+                               normalizedMillisInDay);
+
+        int julianDay = millisToJulianDay(days * ONE_DAY);
+
+        computeGregorianAndDOWFields(julianDay);
+	     
+        // TODO: Consider calling the getOffset(millis) API when we drop JDK 1.3 support - Alan
+        return zone.getOffset(GregorianCalendar.AD,
+                      gregorianYear, gregorianMonth, gregorianDayOfMonth,
+                      fields[DAY_OF_WEEK], normalizedMillisInDay[0]);
+
+        // Note: Because we pass in wall millisInDay, rather than
+        // standard millisInDay, we interpret "1:00 am" on the day
+        // of cessation of DST as "1:00 am Std" (assuming the time
+        // of cessation is 2:00 am).
+    }
+
+    /**
+     * Compute the Julian day number as specified by this calendar's fields.
+     * @stable ICU 2.0
+     */
+    protected int computeJulianDay() {
+
+        // We want to see if any of the date fields is newer than the
+        // JULIAN_DAY.  If not, then we use JULIAN_DAY.  If so, then we do
+        // the normal resolution.  We only use JULIAN_DAY if it has been
+        // set by the user.  This makes it possible for the caller to set
+        // the calendar to a time and call clear(MONTH) to reset the MONTH
+        // to January.  This is legacy behavior.  Without this,
+        // clear(MONTH) has no effect, since the internally set JULIAN_DAY
+        // is used.
+        if (stamp[JULIAN_DAY] >= MINIMUM_USER_STAMP) {
+            int bestStamp = newestStamp(ERA, DAY_OF_WEEK_IN_MONTH, UNSET);
+            bestStamp = newestStamp(YEAR_WOY, EXTENDED_YEAR, bestStamp);
+            if (bestStamp <= stamp[JULIAN_DAY]) {
+                return internalGet(JULIAN_DAY);
+            }
+        }
+
+        int bestField = resolveFields(getFieldResolutionTable());
+        if (bestField < 0) {
+            bestField = DAY_OF_MONTH;
+        }
+
+        return handleComputeJulianDay(bestField);
+    }
+
+    /**
+     * Return the field resolution array for this calendar.  Calendars that
+     * define additional fields or change the semantics of existing fields
+     * should override this method to adjust the field resolution semantics
+     * accordingly.  Other subclasses should not override this method.
+     * @see #resolveFields
+     * @stable ICU 2.0
+     */
+    protected int[][][] getFieldResolutionTable() {
+        return DATE_PRECEDENCE;
+    }
+
+    /**
+     * Return the Julian day number of day before the first day of the
+     * given month in the given extended year.  Subclasses should override
+     * this method to implement their calendar system.
+     * @param eyear the extended year
+     * @param month the zero-based month, or 0 if useMonth is false
+     * @param useMonth if false, compute the day before the first day of
+     * the given year, otherwise, compute the day before the first day of
+     * the given month
+     * @param return the Julian day number of the day before the first
+     * day of the given month and year
+     * @stable ICU 2.0
+     */
+    abstract protected int handleComputeMonthStart(int eyear, int month,
+                                                   boolean useMonth);
+
+    /**
+     * Return the extended year defined by the current fields.  This will
+     * use the EXTENDED_YEAR field or the YEAR and supra-year fields (such
+     * as ERA) specific to the calendar system, depending on which set of
+     * fields is newer.
+     * @return the extended year
+     * @stable ICU 2.0
+     */
+    abstract protected int handleGetExtendedYear();
+
+    // (The following method is not called because all existing subclasses
+    // override it.  2003-06-11 ICU 2.6 Alan)
+    ///CLOVER:OFF
+    /**
+     * Return the number of days in the given month of the given extended
+     * year of this calendar system.  Subclasses should override this
+     * method if they can provide a more correct or more efficient
+     * implementation than the default implementation in Calendar.
+     * @stable ICU 2.0
+     */
+    protected int handleGetMonthLength(int extendedYear, int month) {
+        return handleComputeMonthStart(extendedYear, month+1, true) -
+               handleComputeMonthStart(extendedYear, month, true);
+    }
+    ///CLOVER:ON
+
+    /**
+     * Return the number of days in the given extended year of this
+     * calendar system.  Subclasses should override this method if they can
+     * provide a more correct or more efficient implementation than the
+     * default implementation in Calendar.
+     * @stable ICU 2.0
+     */
+    protected int handleGetYearLength(int eyear) {
+        return handleComputeMonthStart(eyear+1, 0, false) -
+               handleComputeMonthStart(eyear, 0, false);
+    }
+
+    /**
+     * Subclasses that use additional fields beyond those defined in
+     * <code>Calendar</code> should override this method to return an
+     * <code>int[]</code> array of the appropriate length.  The length
+     * must be at least <code>BASE_FIELD_COUNT</code> and no more than
+     * <code>MAX_FIELD_COUNT</code>.
+     * @stable ICU 2.0
+     */
+    protected int[] handleCreateFields() {
+        return new int[BASE_FIELD_COUNT];
+    }
+
+    /**
+     * Subclasses may override this.  This method calls
+     * handleGetMonthLength() to obtain the calendar-specific month
+     * length.
+     * @stable ICU 2.0
+     */
+    protected int handleComputeJulianDay(int bestField) {
+
+        boolean useMonth = (bestField == DAY_OF_MONTH ||
+                            bestField == WEEK_OF_MONTH ||
+                            bestField == DAY_OF_WEEK_IN_MONTH);
+
+        int year = handleGetExtendedYear();
+        internalSet(EXTENDED_YEAR, year);
+
+        // Get the Julian day of the day BEFORE the start of this year.
+        // If useMonth is true, get the day before the start of the month.
+        int julianDay = handleComputeMonthStart(year, useMonth ? internalGet(MONTH) : 0, useMonth);
+
+        if (bestField == DAY_OF_MONTH) {
+            return julianDay + internalGet(DAY_OF_MONTH, 1);
+        }
+
+        if (bestField == DAY_OF_YEAR) {
+            return julianDay + internalGet(DAY_OF_YEAR);
+        }
+
+        int firstDayOfWeek = getFirstDayOfWeek(); // Localized fdw
+
+        // At this point julianDay is the 0-based day BEFORE the first day of
+        // January 1, year 1 of the given calendar.  If julianDay == 0, it
+        // specifies (Jan. 1, 1) - 1, in whatever calendar we are using (Julian
+        // or Gregorian).
+
+        // At this point we need to process the WEEK_OF_MONTH or
+        // WEEK_OF_YEAR, which are similar, or the DAY_OF_WEEK_IN_MONTH.
+        // First, perform initial shared computations.  These locate the
+        // first week of the period.
+
+        // Get the 0-based localized DOW of day one of the month or year.
+        // Valid range 0..6.
+        int first = julianDayToDayOfWeek(julianDay + 1) - firstDayOfWeek;
+        if (first < 0) {
+            first += 7;
+        }
+
+        // Get zero-based localized DOW, valid range 0..6.  This is the DOW
+        // we are looking for.
+        int dowLocal = 0;
+        switch (resolveFields(DOW_PRECEDENCE)) {
+        case DAY_OF_WEEK:
+            dowLocal = internalGet(DAY_OF_WEEK) - firstDayOfWeek;
+            break;
+        case DOW_LOCAL:
+            dowLocal = internalGet(DOW_LOCAL) - 1;
+            break;
+        }
+        dowLocal = dowLocal % 7;
+        if (dowLocal < 0) {
+            dowLocal += 7;
+        }
+
+        // Find the first target DOW (dowLocal) in the month or year.
+        // Actually, it may be just before the first of the month or year.
+        // It will be an integer from -5..7.
+        int date = 1 - first + dowLocal;
+
+        if (bestField == DAY_OF_WEEK_IN_MONTH) {
+
+            // Adjust the target DOW to be in the month or year.
+            if (date < 1) {
+                date += 7;
+            }
+
+            // The only trickiness occurs if the day-of-week-in-month is
+            // negative.
+            int dim = internalGet(DAY_OF_WEEK_IN_MONTH, 1);
+            if (dim >= 0) {
+                date += 7*(dim - 1);
+
+            } else {
+                // Move date to the last of this day-of-week in this month,
+                // then back up as needed.  If dim==-1, we don't back up at
+                // all.  If dim==-2, we back up once, etc.  Don't back up
+                // past the first of the given day-of-week in this month.
+                // Note that we handle -2, -3, etc. correctly, even though
+                // values < -1 are technically disallowed.
+                int m = internalGet(MONTH, JANUARY);
+                int monthLength = handleGetMonthLength(year, m);
+                date += ((monthLength - date) / 7 + dim + 1) * 7;
+            }
+        } else {
+            // assert(bestField == WEEK_OF_MONTH || bestField == WEEK_OF_YEAR)
+
+            // Adjust for minimal days in first week
+            if ((7 - first) < getMinimalDaysInFirstWeek()) {
+                date += 7;
+            }
+
+            // Now adjust for the week number.
+            date += 7 * (internalGet(bestField) - 1);
+        }
+
+        return julianDay + date;
+    }
+
+    /**
+     * Compute the Julian day of a month of the Gregorian calendar.
+     * Subclasses may call this method to perform a Gregorian calendar
+     * fields->millis computation.  To perform a Gregorian calendar
+     * millis->fields computation, call computeGregorianFields().
+     * @param year extended Gregorian year
+     * @param month zero-based Gregorian month
+     * @return the Julian day number of the day before the first
+     * day of the given month in the given extended year
+     * @see #computeGregorianFields
+     * @stable ICU 2.0
+     */
+    protected int computeGregorianMonthStart(int year, int month) {
+
+        // If the month is out of range, adjust it into range, and
+        // modify the extended year value accordingly.
+        if (month < 0 || month > 11) {
+            int[] rem = new int[1];
+            year += floorDivide(month, 12, rem);
+            month = rem[0];
+        }
+
+        boolean isLeap = (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
+        int y = year - 1;
+        // This computation is actually ... + (JAN_1_1_JULIAN_DAY - 3) + 2.
+        // Add 2 because Gregorian calendar starts 2 days after Julian
+        // calendar.
+        int julianDay = 365*y + floorDivide(y, 4) - floorDivide(y, 100) +
+            floorDivide(y, 400) + JAN_1_1_JULIAN_DAY - 1;
+
+        // At this point julianDay indicates the day BEFORE the first day
+        // of January 1, <eyear> of the Gregorian calendar.
+        if (month != 0) {
+            julianDay += GREGORIAN_MONTH_COUNT[month][isLeap?3:2];
+        }
+
+        return julianDay;
+    }
+
+    //----------------------------------------------------------------------
+    // Subclass API
+    // For subclasses to override
+    //----------------------------------------------------------------------
+
+    // (The following method is not called because all existing subclasses
+    // override it.  2003-06-11 ICU 2.6 Alan)
+    ///CLOVER:OFF
+    /**
+     * Subclasses may override this method to compute several fields
+     * specific to each calendar system.  These are:
+     *
+     * <ul><li>ERA
+     * <li>YEAR
+     * <li>MONTH
+     * <li>DAY_OF_MONTH
+     * <li>DAY_OF_YEAR
+     * <li>EXTENDED_YEAR</ul>
+     *
+     * Subclasses can refer to the DAY_OF_WEEK and DOW_LOCAL fields, which
+     * will be set when this method is called.  Subclasses can also call
+     * the getGregorianXxx() methods to obtain Gregorian calendar
+     * equivalents for the given Julian day.
+     *
+     * <p>In addition, subclasses should compute any subclass-specific
+     * fields, that is, fields from BASE_FIELD_COUNT to
+     * getFieldCount() - 1.
+     *
+     * <p>The default implementation in <code>Calendar</code> implements
+     * a pure proleptic Gregorian calendar.
+     * @stable ICU 2.0
+     */
+    protected void handleComputeFields(int julianDay) {
+        internalSet(MONTH, getGregorianMonth());
+        internalSet(DAY_OF_MONTH, getGregorianDayOfMonth());
+        internalSet(DAY_OF_YEAR, getGregorianDayOfYear());
+        int eyear = getGregorianYear();
+        internalSet(EXTENDED_YEAR, eyear);
+        int era = GregorianCalendar.AD;
+        if (eyear < 1) {
+            era = GregorianCalendar.BC;
+            eyear = 1 - eyear;
+        }
+        internalSet(ERA, era);
+        internalSet(YEAR, eyear);
+    }
+    ///CLOVER:ON
+
+    //----------------------------------------------------------------------
+    // Subclass API
+    // For subclasses to call
+    //----------------------------------------------------------------------
+
+    /**
+     * Return the extended year on the Gregorian calendar as computed by
+     * <code>computeGregorianFields()</code>.
+     * @see #computeGregorianFields
+     * @stable ICU 2.0
+     */
+    protected final int getGregorianYear() {
+        return gregorianYear;
+    }
+
+    /**
+     * Return the month (0-based) on the Gregorian calendar as computed by
+     * <code>computeGregorianFields()</code>.
+     * @see #computeGregorianFields
+     * @stable ICU 2.0
+     */
+    protected final int getGregorianMonth() {
+        return gregorianMonth;
+    }
+
+    /**
+     * Return the day of year (1-based) on the Gregorian calendar as
+     * computed by <code>computeGregorianFields()</code>.
+     * @see #computeGregorianFields
+     * @stable ICU 2.0
+     */
+    protected final int getGregorianDayOfYear() {
+        return gregorianDayOfYear;
+    }
+
+    /**
+     * Return the day of month (1-based) on the Gregorian calendar as
+     * computed by <code>computeGregorianFields()</code>.
+     * @see #computeGregorianFields
+     * @stable ICU 2.0
+     */
+    protected final int getGregorianDayOfMonth() {
+        return gregorianDayOfMonth;
+    }
+
+    /**
+     * Return the number of fields defined by this calendar.  Valid field
+     * arguments to <code>set()</code> and <code>get()</code> are
+     * <code>0..getFieldCount()-1</code>.
+     * @stable ICU 2.0
+     */
+    public final int getFieldCount() {
+        return fields.length;
+    }
+
+    /**
+     * Set a field to a value.  Subclasses should use this method when
+     * computing fields.  It sets the time stamp in the
+     * <code>stamp[]</code> array to <code>INTERNALLY_SET</code>.  If a
+     * field that may not be set by subclasses is passed in, an
+     * <code>IllegalArgumentException</code> is thrown.  This prevents
+     * subclasses from modifying fields that are intended to be
+     * calendar-system invariant.
+     * @stable ICU 2.0
+     */
+    protected final void internalSet(int field, int value) {
+        if (((1 << field) & internalSetMask) == 0) {
+            throw new InternalError("Subclass cannot set " +
+                                               fieldName(field));
+        }
+        fields[field] = value;
+        stamp[field] = INTERNALLY_SET;
+    }
+
+    private static final int[][] GREGORIAN_MONTH_COUNT = {
+        //len len2   st  st2
+        {  31,  31,   0,   0 }, // Jan
+        {  28,  29,  31,  31 }, // Feb
+        {  31,  31,  59,  60 }, // Mar
+        {  30,  30,  90,  91 }, // Apr
+        {  31,  31, 120, 121 }, // May
+        {  30,  30, 151, 152 }, // Jun
+        {  31,  31, 181, 182 }, // Jul
+        {  31,  31, 212, 213 }, // Aug
+        {  30,  30, 243, 244 }, // Sep
+        {  31,  31, 273, 274 }, // Oct
+        {  30,  30, 304, 305 }, // Nov
+        {  31,  31, 334, 335 }  // Dec
+        // len  length of month
+        // len2 length of month in a leap year
+        // st   days in year before start of month
+        // st2  days in year before month in leap year
+    };
+
+    /**
+     * Determines if the given year is a leap year. Returns true if the
+     * given year is a leap year.
+     * @param year the given year.
+     * @return true if the given year is a leap year; false otherwise.
+     * @stable ICU 2.0
+     */
+    protected static final boolean isGregorianLeapYear(int year) {
+        return (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
+    }
+
+    /**
+     * Return the length of a month of the Gregorian calendar.
+     * @param y the extended year
+     * @param m the 0-based month number
+     * @return the number of days in the given month
+     * @stable ICU 2.0
+     */
+    protected static final int gregorianMonthLength(int y, int m) {
+        return GREGORIAN_MONTH_COUNT[m][isGregorianLeapYear(y)?1:0];
+    }
+
+    /**
+     * Return the length of a previous month of the Gregorian calendar.
+     * @param y the extended year
+     * @param m the 0-based month number
+     * @return the number of days in the month previous to the given month
+     * @stable ICU 2.0
+     */
+    protected static final int gregorianPreviousMonthLength(int y, int m) {
+        return (m > 0) ? gregorianMonthLength(y, m-1) : 31;
+    }
+
+    /**
+     * Divide two long integers, returning the floor of the quotient.
+     * <p>
+     * Unlike the built-in division, this is mathematically well-behaved.
+     * E.g., <code>-1/4</code> => 0
+     * but <code>floorDivide(-1,4)</code> => -1.
+     * @param numerator the numerator
+     * @param denominator a divisor which must be > 0
+     * @return the floor of the quotient.
+     * @stable ICU 2.0
+     */
+    protected static final long floorDivide(long numerator, long denominator) {
+        // We do this computation in order to handle
+        // a numerator of Long.MIN_VALUE correctly
+        return (numerator >= 0) ?
+            numerator / denominator :
+            ((numerator + 1) / denominator) - 1;
+    }
+
+    /**
+     * Divide two integers, returning the floor of the quotient.
+     * <p>
+     * Unlike the built-in division, this is mathematically well-behaved.
+     * E.g., <code>-1/4</code> => 0
+     * but <code>floorDivide(-1,4)</code> => -1.
+     * @param numerator the numerator
+     * @param denominator a divisor which must be > 0
+     * @return the floor of the quotient.
+     * @stable ICU 2.0
+     */
+    protected static final int floorDivide(int numerator, int denominator) {
+        // We do this computation in order to handle
+        // a numerator of Integer.MIN_VALUE correctly
+        return (numerator >= 0) ?
+            numerator / denominator :
+            ((numerator + 1) / denominator) - 1;
+    }
+
+    /**
+     * Divide two integers, returning the floor of the quotient, and
+     * the modulus remainder.
+     * <p>
+     * Unlike the built-in division, this is mathematically well-behaved.
+     * E.g., <code>-1/4</code> => 0 and <code>-1%4</code> => -1,
+     * but <code>floorDivide(-1,4)</code> => -1 with <code>remainder[0]</code> => 3.
+     * @param numerator the numerator
+     * @param denominator a divisor which must be > 0
+     * @param remainder an array of at least one element in which the value
+     * <code>numerator mod denominator</code> is returned. Unlike <code>numerator
+     * % denominator</code>, this will always be non-negative.
+     * @return the floor of the quotient.
+     * @stable ICU 2.0
+     */
+    protected static final int floorDivide(int numerator, int denominator, int[] remainder) {
+        if (numerator >= 0) {
+            remainder[0] = numerator % denominator;
+            return numerator / denominator;
+        }
+    int quotient = ((numerator + 1) / denominator) - 1;
+        remainder[0] = numerator - (quotient * denominator);
+        return quotient;
+    }
+
+    /**
+     * Divide two integers, returning the floor of the quotient, and
+     * the modulus remainder.
+     * <p>
+     * Unlike the built-in division, this is mathematically well-behaved.
+     * E.g., <code>-1/4</code> => 0 and <code>-1%4</code> => -1,
+     * but <code>floorDivide(-1,4)</code> => -1 with <code>remainder[0]</code> => 3.
+     * @param numerator the numerator
+     * @param denominator a divisor which must be > 0
+     * @param remainder an array of at least one element in which the value
+     * <code>numerator mod denominator</code> is returned. Unlike <code>numerator
+     * % denominator</code>, this will always be non-negative.
+     * @return the floor of the quotient.
+     * @stable ICU 2.0
+     */
+    protected static final int floorDivide(long numerator, int denominator, int[] remainder) {
+        if (numerator >= 0) {
+            remainder[0] = (int)(numerator % denominator);
+            return (int)(numerator / denominator);
+        }
+        int quotient = (int)(((numerator + 1) / denominator) - 1);
+        remainder[0] = (int)(numerator - (quotient * denominator));
+        return quotient;
+    }
+
+    private static final String[] FIELD_NAME = {
+        "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
+        "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
+        "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
+        "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
+        "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
+        "JULIAN_DAY", "MILLISECONDS_IN_DAY",
+    };
+
+    /**
+     * Return a string name for a field, for debugging and exceptions.
+     * @stable ICU 2.0
+     */
+    protected String fieldName(int field) {
+        try {
+            return FIELD_NAME[field];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return "Field " + field;
+        }
+    }
+
+    /**
+     * Converts time as milliseconds to Julian day.
+     * @param millis the given milliseconds.
+     * @return the Julian day number.
+     * @stable ICU 2.0
+     */
+    protected static final int millisToJulianDay(long millis) {
+        return (int) (EPOCH_JULIAN_DAY + floorDivide(millis, ONE_DAY));
+    }
+
+    /**
+     * Converts Julian day to time as milliseconds.
+     * @param julian the given Julian day number.
+     * @return time as milliseconds.
+     * @stable ICU 2.0
+     */
+    protected static final long julianDayToMillis(int julian) {
+        return (julian - EPOCH_JULIAN_DAY) * ONE_DAY;
+    }
+
+    /**
+     * Return the day of week, from SUNDAY to SATURDAY, given a Julian day.
+     * @stable ICU 2.0
+     */
+    protected static final int julianDayToDayOfWeek(int julian) {
+        // If julian is negative, then julian%7 will be negative, so we adjust
+        // accordingly.  Julian day 0 is Monday.
+        int dayOfWeek = (julian + MONDAY) % 7;
+        if (dayOfWeek < SUNDAY) {
+            dayOfWeek += 7;
+        }
+        return dayOfWeek;
+    }
+
+    /**
+     * Return the current milliseconds without recomputing.
+     * @stable ICU 2.0
+     */
+    protected final long internalGetTimeInMillis() {
+        return time;
+    }
+}
\ No newline at end of file
diff --git a/src/com/ibm/icu/util/CalendarFactory.java b/src/com/ibm/icu/util/CalendarFactory.java
new file mode 100644
index 0000000..a38b34d
--- /dev/null
+++ b/src/com/ibm/icu/util/CalendarFactory.java
@@ -0,0 +1,24 @@
+/**
+*******************************************************************************
+* Copyright (C) 2002, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/CalendarFactory.java,v $ 
+* $Date: 2003/09/04 01:00:30 $ 
+* $Revision: 1.4 $
+*
+*******************************************************************************
+*/
+package com.ibm.icu.util;
+
+import java.util.Locale;
+import java.util.TimeZone;
+/**
+ * @prototype
+ */
+interface CalendarFactory {
+    public Calendar create(TimeZone tz, Locale loc);
+    public String factoryName();
+}
+        
diff --git a/src/com/ibm/icu/util/ChineseCalendar.java b/src/com/ibm/icu/util/ChineseCalendar.java
new file mode 100755
index 0000000..3b7025a
--- /dev/null
+++ b/src/com/ibm/icu/util/ChineseCalendar.java
@@ -0,0 +1,820 @@
+/*********************************************************************
+ * Copyright (C) 2000-2003, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *********************************************************************
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/ChineseCalendar.java,v $
+ * $Date: 2003/09/04 01:00:30 $
+ * $Revision: 1.17 $
+ */
+package com.ibm.icu.util;
+import com.ibm.icu.text.*;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
+ * that implements a traditional Chinese calendar.  The traditional Chinese
+ * calendar is a lunisolar calendar: Each month starts on a new moon, and
+ * the months are numbered according to solar events, specifically, to
+ * guarantee that month 11 always contains the winter solstice.  In order
+ * to accomplish this, leap months are inserted in certain years.  Leap
+ * months are numbered the same as the month they follow.  The decision of
+ * which month is a leap month depends on the relative movements of the sun
+ * and moon.
+ *
+ * <p>This class defines one addition field beyond those defined by
+ * <code>Calendar</code>: The <code>IS_LEAP_MONTH</code> field takes the
+ * value of 0 for normal months, or 1 for leap months.
+ *
+ * <p>All astronomical computations are performed with respect to a time
+ * zone of GMT+8:00 and a longitude of 120 degrees east.  Although some
+ * calendars implement a historically more accurate convention of using
+ * Beijing's local longitude (116 degrees 25 minutes east) and time zone
+ * (GMT+7:45:40) for dates before 1929, we do not implement this here.
+ *
+ * <p>Years are counted in two different ways in the Chinese calendar.  The
+ * first method is by sequential numbering from the 61st year of the reign
+ * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
+ * calendar.  The second method uses 60-year cycles from the same starting
+ * point, which is designated year 1 of cycle 1.  In this class, the
+ * <code>EXTENDED_YEAR</code> field contains the sequential year count.
+ * The <code>ERA</code> field contains the cycle number, and the
+ * <code>YEAR</code> field contains the year of the cycle, a value between
+ * 1 and 60.
+ *
+ * <p>There is some variation in what is considered the starting point of
+ * the calendar, with some sources starting in the first year of the reign
+ * of Huang Di, rather than the 61st.  This gives continuous year numbers
+ * 60 years greater and cycle numbers one greater than what this class
+ * implements.
+ *
+ * <p>Because <code>ChineseCalendar</code> defines an additional field and
+ * redefines the way the <code>ERA</code> field is used, it requires a new
+ * format class, <code>ChineseDateFormat</code>.  As always, use the
+ * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to
+ * obtain a formatter for this calendar.
+ *
+ * <p>References:<ul>
+ * 
+ * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
+ * Cambridge University Press, 1997</li>
+ * 
+ * <li>Helmer Aslaksen's
+ * <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
+ * Chinese Calendar page</a></li>
+ *
+ * <li>The <a href="http://www.tondering.dk/claus/calendar.html">
+ * Calendar FAQ</a></li>
+ *
+ * </ul>
+ * @see com.ibm.icu.text.ChineseDateFormat
+ * @author Alan Liu
+ * @draft ICU 2.4
+ */
+public class ChineseCalendar extends Calendar {
+
+    //------------------------------------------------------------------
+    // Developer Notes
+    // 
+    // Time is represented as a scalar in two ways in this class.  One is
+    // the usual UTC epoch millis, that is, milliseconds after January 1,
+    // 1970 Gregorian, 0:00:00.000 UTC.  The other is in terms of 'local
+    // days.'  This is the number of days after January 1, 1970 Gregorian,
+    // local to Beijing, China (since all computations of the Chinese
+    // calendar are done in Beijing).  That is, 0 represents January 1,
+    // 1970 0:00 Asia/Shanghai.  Conversion of local days to and from
+    // standard epoch milliseconds is accomplished by the daysToMillis()
+    // and millisToDays() methods.
+    // 
+    // Several methods use caches to improve performance.  Caches are at
+    // the object, not class level, under the assumption that typical
+    // usage will be to have one instance of ChineseCalendar at a time.
+ 
+    /**
+     * We have one instance per object, and we don't synchronize it because
+     * Calendar doesn't support multithreaded execution in the first place.
+     */
+    private transient CalendarAstronomer astro = new CalendarAstronomer();
+
+    /**
+     * Cache that maps Gregorian year to local days of winter solstice.
+     * @see #winterSolstice
+     */
+    private transient CalendarCache winterSolsticeCache = new CalendarCache();
+
+    /**
+     * Cache that maps Gregorian year to local days of Chinese new year.
+     * @see #newYear
+     */
+    private transient CalendarCache newYearCache = new CalendarCache();
+
+    /**
+     * True if the current year is a leap year.  Updated with each time to
+     * fields resolution.
+     * @see #computeChineseFields
+     */
+    private transient boolean isLeapYear;
+
+    //------------------------------------------------------------------
+    // Constructors
+    //------------------------------------------------------------------
+
+    /**
+     * Construct a Chinese calendar with the default time zone and locale.
+     * @draft ICU 2.4
+     */
+    public ChineseCalendar() {
+        super();
+    }
+
+    /**
+     * Construct a Chinese calendar with the given time zone and locale.
+     * @param zone time zone for this calendar
+     * @param locale locale for this calendar
+     * @draft ICU 2.4
+     */
+    public ChineseCalendar(TimeZone zone, Locale locale) {
+        super(zone, locale);
+    }
+
+    //------------------------------------------------------------------
+    // Public constants
+    //------------------------------------------------------------------
+
+    /**
+     * Field indicating whether or not the current month is a leap month.
+     * Should have a value of 0 for non-leap months, and 1 for leap months.
+     * @draft ICU 2.4
+     */
+    public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
+
+    /**
+     * Count of fields in this class.
+     */
+    private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;
+
+    //------------------------------------------------------------------
+    // Calendar framework
+    //------------------------------------------------------------------
+
+    /**
+     * Override Calendar to allocate our additional field.
+     * @draft ICU 2.4
+     */
+    protected int[] handleCreateFields() {
+        return new int[FIELD_COUNT];
+    }
+
+    /**
+     * Array defining the limits of field values for this class.  Field
+     * limits which are invariant with respect to calendar system and
+     * defined by Calendar are left blank.
+     *
+     * Notes:
+     *
+     * ERA 5000000 / 60 = 83333.
+     *
+     * MONTH There are 12 or 13 lunar months in a year.  However, we always
+     * number them 0..11, with an intercalated, identically numbered leap
+     * month, when necessary.
+     *
+     * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days.  In
+     * a leap year there are 383, 384, or 385 days.
+     *
+     * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
+     * year, and the first 6 are the last week of the previous year.  Then
+     * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
+     * 353.  So the least maximum is 50.  The maximum occurs if there are
+     * 385 days in the year, and WOY 1 extends 6 days into the prior year.
+     * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
+     * + 6 = 385.  The 6 days of the last week will fall into WOY 1 of the
+     * next year.  Maximum is 55.
+     *
+     * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
+     * that leaves 3 full weeks and 1 day at the end.  The least maximum is
+     * thus 5.  In a 30 days month, if the previous 6 days belong WOM 1 of
+     * this month, we have 4 full weeks and 1 days at the end (which
+     * technically will be WOM 1 of the next month, but will be reported by
+     * time->fields and hence by getActualMaximum as WOM 6 of this month).
+     * Maximum is 6.
+     *
+     * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
+     * plus 1 or 2 days at the end, so the maximum is always 5.
+     */
+    private static final int LIMITS[][] = {
+        // Minimum  Greatest    Least  Maximum
+        //           Minimum  Maximum
+        {        1,        1,   83333,   83333 }, // ERA
+        {        1,        1,      70,      70 }, // YEAR
+        {        0,        0,      11,      11 }, // MONTH
+        {        1,        1,      50,      55 }, // WEEK_OF_YEAR
+        {        1,        1,       5,       6 }, // WEEK_OF_MONTH
+        {        1,        1,      29,      30 }, // DAY_OF_MONTH
+        {        1,        1,     353,     385 }, // DAY_OF_YEAR
+        {/*                                  */}, // DAY_OF_WEEK
+        {       -1,       -1,       5,       5 }, // DAY_OF_WEEK_IN_MONTH
+        {/*                                  */}, // AM_PM
+        {/*                                  */}, // HOUR
+        {/*                                  */}, // HOUR_OF_DAY
+        {/*                                  */}, // MINUTE
+        {/*                                  */}, // SECOND
+        {/*                                  */}, // MILLISECOND
+        {/*                                  */}, // ZONE_OFFSET
+        {/*                                  */}, // DST_OFFSET
+        { -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
+        {/*                                  */}, // DOW_LOCAL
+        { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
+        {/*                                  */}, // JULIAN_DAY
+        {/*                                  */}, // MILLISECONDS_IN_DAY
+        {        0,        0,       1,       1 }, // IS_LEAP_MONTH
+    };
+
+    /**
+     * Override Calendar to return the limit value for the given field.
+     * @draft ICU 2.4
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        return LIMITS[field][limitType];
+    }
+
+    /**
+     * Implement abstract Calendar method to return the extended year
+     * defined by the current fields.  This will use either the ERA and
+     * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
+     * field as the continuous year count, depending on which is newer.
+     * @draft ICU 2.4
+     */
+    protected int handleGetExtendedYear() {
+        int year;
+        if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
+            year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
+        } else {
+            int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
+            year = cycle * 60 + internalGet(YEAR, 1);
+        }
+        return year;
+    }
+
+    /**
+     * Override Calendar method to return the number of days in the given
+     * extended year and month.
+     *
+     * <p>Note: This method also reads the IS_LEAP_MONTH field to determine
+     * whether or not the given month is a leap month.
+     * @draft ICU 2.4
+     */
+    protected int handleGetMonthLength(int extendedYear, int month) {
+        int thisStart = handleComputeMonthStart(extendedYear, month, true) -
+            EPOCH_JULIAN_DAY + 1; // Julian day -> local days
+        int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);
+        return nextStart - thisStart;
+    }
+
+    /**
+     * Framework method to create a calendar-specific DateFormat object
+     * using the the given pattern.  This method is responsible for
+     * creating the calendar- specific DateFormat and DateFormatSymbols
+     * objects as needed.
+     * @draft ICU 2.4
+     */
+    protected DateFormat handleGetDateFormat(String pattern, Locale locale) {
+        return new ChineseDateFormat(pattern, locale);
+    }
+
+    /**
+     * Field resolution table that incorporates IS_LEAP_MONTH.
+     */
+    static final int[][][] CHINESE_DATE_PRECEDENCE = {
+        {
+            { DAY_OF_MONTH },
+            { WEEK_OF_YEAR, DAY_OF_WEEK },
+            { WEEK_OF_MONTH, DAY_OF_WEEK },
+            { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
+            { WEEK_OF_YEAR, DOW_LOCAL },
+            { WEEK_OF_MONTH, DOW_LOCAL },
+            { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
+            { DAY_OF_YEAR },
+            { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH },
+        },
+        {
+            { WEEK_OF_YEAR },
+            { WEEK_OF_MONTH },
+            { DAY_OF_WEEK_IN_MONTH },
+            { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
+            { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
+        },
+    };
+
+    /**
+     * Override Calendar to add IS_LEAP_MONTH to the field resolution
+     * table.
+     * @draft ICU 2.4
+     */
+    protected int[][][] getFieldResolutionTable() {
+        return CHINESE_DATE_PRECEDENCE;
+    }
+
+    /**
+     * Adjust this calendar to be delta months before or after a given
+     * start position, pinning the day of month if necessary.  The start
+     * position is given as a local days number for the start of the month
+     * and a day-of-month.  Used by add() and roll().
+     * @param newMoon the local days of the first day of the month of the
+     * start position (days after January 1, 1970 0:00 Asia/Shanghai)
+     * @param dom the 1-based day-of-month of the start position
+     * @param delta the number of months to move forward or backward from
+     * the start position
+     */
+    private void offsetMonth(int newMoon, int dom, int delta) {
+        // Move to the middle of the month before our target month.
+        newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5));
+
+        // Search forward to the target month's new moon
+        newMoon = newMoonNear(newMoon, true);
+
+        // Find the target dom
+        int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;
+
+        // Pin the dom.  In this calendar all months are 29 or 30 days
+        // so pinning just means handling dom 30.
+        if (dom > 29) {
+            set(JULIAN_DAY, jd-1);
+            // TODO Fix this.  We really shouldn't ever have to
+            // explicitly call complete().  This is either a bug in
+            // this method, in ChineseCalendar, or in
+            // Calendar.getActualMaximum().  I suspect the last.
+            complete();
+            if (getActualMaximum(DAY_OF_MONTH) >= dom) {
+                set(JULIAN_DAY, jd);
+            }
+        } else {
+            set(JULIAN_DAY, jd);
+        }
+    }
+
+    /**
+     * Override Calendar to handle leap months properly.
+     * @draft ICU 2.4
+     */
+    public void add(int field, int amount) {
+        switch (field) {
+        case MONTH:
+            if (amount != 0) {
+                int dom = get(DAY_OF_MONTH);
+                int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
+                int moon = day - dom + 1; // New moon 
+                offsetMonth(moon, dom, amount);
+            }
+            break;
+        default:
+            super.add(field, amount);
+            break;
+        }
+    }
+
+    /**
+     * Override Calendar to handle leap months properly.
+     * @draft ICU 2.4
+     */
+    public void roll(int field, int amount) {
+        switch (field) {
+        case MONTH:
+            if (amount != 0) {
+                int dom = get(DAY_OF_MONTH);
+                int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
+                int moon = day - dom + 1; // New moon (start of this month)
+
+                // Note throughout the following:  Months 12 and 1 are never
+                // followed by a leap month (D&R p. 185).
+
+                // Compute the adjusted month number m.  This is zero-based
+                // value from 0..11 in a non-leap year, and from 0..12 in a
+                // leap year.
+                int m = get(MONTH); // 0-based month
+                if (isLeapYear) { // (member variable)
+                    if (get(IS_LEAP_MONTH) == 1) {
+                        ++m;
+                    } else {
+                        // Check for a prior leap month.  (In the
+                        // following, month 0 is the first month of the
+                        // year.)  Month 0 is never followed by a leap
+                        // month, and we know month m is not a leap month.
+                        // moon1 will be the start of month 0 if there is
+                        // no leap month between month 0 and month m;
+                        // otherwise it will be the start of month 1.
+                        int moon1 = moon -
+                            (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));
+                        moon1 = newMoonNear(moon1, true);
+                        if (isLeapMonthBetween(moon1, moon)) {
+                            ++m;
+                        }
+                    }
+                }
+
+                // Now do the standard roll computation on m, with the
+                // allowed range of 0..n-1, where n is 12 or 13.
+                int n = isLeapYear ? 13 : 12; // Months in this year
+                int newM = (m + amount) % n;
+                if (newM < 0) {
+                    newM += n;
+                }
+
+                if (newM != m) {
+                    offsetMonth(moon, dom, newM - m);
+                }
+            }
+            break;
+        default:
+            super.roll(field, amount);
+            break;
+        }
+    }
+
+    //------------------------------------------------------------------
+    // Support methods and constants
+    //------------------------------------------------------------------
+   
+    /**
+     * The start year of the Chinese calendar, the 61st year of the reign
+     * of Huang Di.  Some sources use the first year of his reign,
+     * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
+     * values one greater.
+     */
+    private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
+
+    /**
+     * The offset from GMT in milliseconds at which we perform astronomical
+     * computations.  Some sources use a different historically accurate
+     * offset of GMT+7:45:40 for years before 1929; we do not do this.
+     */
+    private static final long CHINA_OFFSET = 8*ONE_HOUR;
+
+    /**
+     * Value to be added or subtracted from the local days of a new moon to
+     * get close to the next or prior new moon, but not cross it.  Must be
+     * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
+     */
+    private static final int SYNODIC_GAP = 25;
+
+    /**
+     * Convert local days to UTC epoch milliseconds.
+     * @param days days after January 1, 1970 0:00 Asia/Shanghai
+     * @return milliseconds after January 1, 1970 0:00 GMT
+     */
+    private static final long daysToMillis(int days) {
+        return (days * ONE_DAY) - CHINA_OFFSET;
+    }
+
+    /**
+     * Convert UTC epoch milliseconds to local days.
+     * @param millis milliseconds after January 1, 1970 0:00 GMT
+     * @return days after January 1, 1970 0:00 Asia/Shanghai
+     */
+    private static final int millisToDays(long millis) {
+        return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
+    }
+
+    //------------------------------------------------------------------
+    // Astronomical computations
+    //------------------------------------------------------------------
+    
+    /**
+     * Return the major solar term on or after December 15 of the given
+     * Gregorian year, that is, the winter solstice of the given year.
+     * Computations are relative to Asia/Shanghai time zone.
+     * @param gyear a Gregorian year
+     * @return days after January 1, 1970 0:00 Asia/Shanghai of the
+     * winter solstice of the given year
+     */
+    private int winterSolstice(int gyear) {
+
+        long cacheValue = winterSolsticeCache.get(gyear);
+
+        if (cacheValue == CalendarCache.EMPTY) {
+            // In books December 15 is used, but it fails for some years
+            // using our algorithms, e.g.: 1298 1391 1492 1553 1560.  That
+            // is, winterSolstice(1298) starts search at Dec 14 08:00:00
+            // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
+            long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) +
+                                   1 - EPOCH_JULIAN_DAY);
+            astro.setTime(ms);
+            
+            // Winter solstice is 270 degrees solar longitude aka Dongzhi
+            long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,
+                                              true);
+            cacheValue = millisToDays(solarLong);
+            winterSolsticeCache.put(gyear, cacheValue);
+        }
+        return (int) cacheValue;
+    }
+
+    /**
+     * Return the closest new moon to the given date, searching either
+     * forward or backward in time.
+     * @param days days after January 1, 1970 0:00 Asia/Shanghai
+     * @param after if true, search for a new moon on or after the given
+     * date; otherwise, search for a new moon before it
+     * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
+     * new moon after or before <code>days</code>
+     */
+    private int newMoonNear(int days, boolean after) {
+        
+        astro.setTime(daysToMillis(days));
+        long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);
+        
+        return millisToDays(newMoon);
+    }
+
+    /**
+     * Return the nearest integer number of synodic months between
+     * two dates.
+     * @param day1 days after January 1, 1970 0:00 Asia/Shanghai
+     * @param day2 days after January 1, 1970 0:00 Asia/Shanghai
+     * @return the nearest integer number of months between day1 and day2
+     */
+    private int synodicMonthsBetween(int day1, int day2) {
+        return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);
+    }
+
+    /**
+     * Return the major solar term on or before a given date.  This
+     * will be an integer from 1..12, with 1 corresponding to 330 degrees,
+     * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
+     * @param days days after January 1, 1970 0:00 Asia/Shanghai
+     */
+    private int majorSolarTerm(int days) {
+        
+        astro.setTime(daysToMillis(days));
+
+        // Compute (floor(solarLongitude / (pi/6)) + 2) % 12
+        int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;
+        if (term < 1) {
+            term += 12;
+        }
+        return term;
+    }
+
+    /**
+     * Return true if the given month lacks a major solar term.
+     * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
+     * moon
+     */
+    private boolean hasNoMajorSolarTerm(int newMoon) {
+        
+        int mst = majorSolarTerm(newMoon);
+        int nmn = newMoonNear(newMoon + SYNODIC_GAP, true);
+        int mstt = majorSolarTerm(nmn);
+        return mst == mstt;
+        /*
+        return majorSolarTerm(newMoon) ==
+            majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
+        */
+    }
+
+    //------------------------------------------------------------------
+    // Time to fields
+    //------------------------------------------------------------------
+    
+    /**
+     * Return true if there is a leap month on or after month newMoon1 and
+     * at or before month newMoon2.
+     * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
+     * new moon
+     * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
+     * new moon
+     */
+    private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
+
+        // This is only needed to debug the timeOfAngle divergence bug.
+        // Remove this later. Liu 11/9/00
+        // DEBUG
+        if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
+            throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +
+                                               ", " + newMoon2 +
+                                               "): Invalid parameters");
+        }
+
+        return (newMoon2 >= newMoon1) &&
+            (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||
+             hasNoMajorSolarTerm(newMoon2));
+    }
+
+    /**
+     * Override Calendar to compute several fields specific to the Chinese
+     * calendar system.  These are:
+     *
+     * <ul><li>ERA
+     * <li>YEAR
+     * <li>MONTH
+     * <li>DAY_OF_MONTH
+     * <li>DAY_OF_YEAR
+     * <li>EXTENDED_YEAR</ul>
+     * 
+     * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
+     * method is called.  The getGregorianXxx() methods return Gregorian
+     * calendar equivalents for the given Julian day.
+     *
+     * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
+     * @draft ICU 2.4
+     */
+    protected void handleComputeFields(int julianDay) {
+
+        computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
+                             getGregorianYear(), getGregorianMonth(),
+                             true); // set all fields
+    }
+
+    /**
+     * Compute fields for the Chinese calendar system.  This method can
+     * either set all relevant fields, as required by
+     * <code>handleComputeFields()</code>, or it can just set the MONTH and
+     * IS_LEAP_MONTH fields, as required by
+     * <code>handleComputeMonthStart()</code>.
+     *
+     * <p>As a side effect, this method sets {@link #isLeapYear}.
+     * @param days days after January 1, 1970 0:00 Asia/Shanghai of the
+     * date to compute fields for
+     * @param gyear the Gregorian year of the given date
+     * @param gmonth the Gregorian month of the given date
+     * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
+     * DAY_OF_MONTH, and DAY_OF_YEAR fields.  In either case set the MONTH
+     * and IS_LEAP_MONTH fields.
+     */
+    private void computeChineseFields(int days, int gyear, int gmonth,
+                                      boolean setAllFields) {
+
+        // Find the winter solstices before and after the target date.
+        // These define the boundaries of this Chinese year, specifically,
+        // the position of month 11, which always contains the solstice.
+        // We want solsticeBefore <= date < solsticeAfter.
+        int solsticeBefore;
+        int solsticeAfter = winterSolstice(gyear);
+        if (days < solsticeAfter) {
+            solsticeBefore = winterSolstice(gyear - 1);
+        } else {
+            solsticeBefore = solsticeAfter;
+            solsticeAfter = winterSolstice(gyear + 1);
+        }
+
+        // Find the start of the month after month 11.  This will be either
+        // the prior month 12 or leap month 11 (very rare).  Also find the
+        // start of the following month 11.
+        int firstMoon = newMoonNear(solsticeBefore + 1, true);
+        int lastMoon = newMoonNear(solsticeAfter + 1, false);
+        int thisMoon = newMoonNear(days + 1, false); // Start of this month
+        // Note: isLeapYear is a member variable
+        isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
+
+        int month = synodicMonthsBetween(firstMoon, thisMoon);
+        if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {
+            month--;
+        }
+        if (month < 1) {
+            month += 12;
+        }
+
+        boolean isLeapMonth = isLeapYear &&
+            hasNoMajorSolarTerm(thisMoon) &&
+            !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));
+
+        internalSet(MONTH, month-1); // Convert from 1-based to 0-based
+        internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);
+
+        if (setAllFields) {
+
+            int year = gyear - CHINESE_EPOCH_YEAR;
+            if (month < 11 ||
+                gmonth >= JULY) {
+                year++;
+            }
+            int dayOfMonth = days - thisMoon + 1;
+
+            internalSet(EXTENDED_YEAR, year);
+
+            // 0->0,60  1->1,1  60->1,60  61->2,1  etc.
+            int[] yearOfCycle = new int[1];
+            int cycle = floorDivide(year-1, 60, yearOfCycle);
+            internalSet(ERA, cycle+1);
+            internalSet(YEAR, yearOfCycle[0]+1);
+
+            internalSet(DAY_OF_MONTH, dayOfMonth);
+
+            // Days will be before the first new year we compute if this
+            // date is in month 11, leap 11, 12.  There is never a leap 12.
+            // New year computations are cached so this should be cheap in
+            // the long run.
+            int newYear = newYear(gyear);
+            if (days < newYear) {
+                newYear = newYear(gyear-1);
+            }
+            internalSet(DAY_OF_YEAR, days - newYear + 1);
+        }
+    }
+
+    //------------------------------------------------------------------
+    // Fields to time
+    //------------------------------------------------------------------
+    
+    /**
+     * Return the Chinese new year of the given Gregorian year.
+     * @param gyear a Gregorian year
+     * @return days after January 1, 1970 0:00 Asia/Shanghai of the
+     * Chinese new year of the given year (this will be a new moon)
+     */
+    private int newYear(int gyear) {
+
+        long cacheValue = newYearCache.get(gyear);
+
+        if (cacheValue == CalendarCache.EMPTY) {
+
+            int solsticeBefore= winterSolstice(gyear - 1);
+            int solsticeAfter = winterSolstice(gyear);
+            int newMoon1 = newMoonNear(solsticeBefore + 1, true);
+            int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
+            int newMoon11 = newMoonNear(solsticeAfter + 1, false);
+            
+            if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
+                (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
+                cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
+            } else {
+                cacheValue = newMoon2;
+            }
+
+            newYearCache.put(gyear, cacheValue);
+        }
+        return (int) cacheValue;
+    }
+
+    /**
+     * Return the Julian day number of day before the first day of the
+     * given month in the given extended year.
+     * 
+     * <p>Note: This method reads the IS_LEAP_MONTH field to determine
+     * whether the given month is a leap month.
+     * @param eyear the extended year
+     * @param month the zero-based month.  The month is also determined
+     * by reading the IS_LEAP_MONTH field.
+     * @param return the Julian day number of the day before the first
+     * day of the given month and year
+     * @draft ICU 2.4
+     */
+    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
+
+        // If the month is out of range, adjust it into range, and
+        // modify the extended year value accordingly.
+        if (month < 0 || month > 11) {
+            int[] rem = new int[1];
+            eyear += floorDivide(month, 12, rem);
+            month = rem[0];
+        }
+
+        int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
+        int newYear = newYear(gyear);
+        int newMoon = newMoonNear(newYear + month * 29, true);
+        
+        int julianDay = newMoon + EPOCH_JULIAN_DAY;
+
+        // Save fields for later restoration
+        int saveMonth = internalGet(MONTH);
+        int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);
+
+        // Ignore IS_LEAP_MONTH field if useMonth is false
+        int isLeapMonth = useMonth ? saveIsLeapMonth : 0;
+
+        computeGregorianFields(julianDay);
+        
+        // This will modify the MONTH and IS_LEAP_MONTH fields (only)
+        computeChineseFields(newMoon, getGregorianYear(),
+                             getGregorianMonth(), false);        
+
+        if (month != internalGet(MONTH) ||
+            isLeapMonth != internalGet(IS_LEAP_MONTH)) {
+            newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
+            julianDay = newMoon + EPOCH_JULIAN_DAY;
+        }
+
+        internalSet(MONTH, saveMonth);
+        internalSet(IS_LEAP_MONTH, saveIsLeapMonth);
+
+        return julianDay - 1;
+    }
+
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new ChineseCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Chinese";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
diff --git a/src/com/ibm/icu/util/EasterHoliday.java b/src/com/ibm/icu/util/EasterHoliday.java
new file mode 100755
index 0000000..9625def
--- /dev/null
+++ b/src/com/ibm/icu/util/EasterHoliday.java
@@ -0,0 +1,290 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/EasterHoliday.java,v $ 
+ * $Date: 2003/09/04 01:00:30 $ 
+ * $Revision: 1.10 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.SimpleTimeZone;
+
+/**
+ * A Holiday subclass which represents holidays that occur
+ * a fixed number of days before or after Easter.  Supports both the
+ * Western and Orthodox methods for calculating Easter.
+ * @draft ICU 2.2
+ */
+public class EasterHoliday extends Holiday
+{
+    /**
+     * Construct a holiday that falls on Easter Sunday every year
+     *
+     * @param name The name of the holiday
+     * @draft ICU 2.2
+     */
+    public EasterHoliday(String name)
+    {
+        super(name, new EasterRule(0, false));
+    }
+
+    /**
+     * Construct a holiday that falls a specified number of days before
+     * or after Easter Sunday each year.
+     *
+     * @param daysAfter The number of days before (-) or after (+) Easter
+     * @param name      The name of the holiday
+     * @draft ICU 2.2
+     */
+    public EasterHoliday(int daysAfter, String name)
+    {
+        super(name, new EasterRule(daysAfter, false));
+    }
+
+    /**
+     * Construct a holiday that falls a specified number of days before
+     * or after Easter Sunday each year, using either the Western
+     * or Orthodox calendar.
+     *
+     * @param daysAfter The number of days before (-) or after (+) Easter
+     * @param orthodox  Use the Orthodox calendar?
+     * @param name      The name of the holiday
+     * @draft ICU 2.2
+     */
+    public EasterHoliday(int daysAfter, boolean orthodox, String name)
+    {
+        super(name, new EasterRule(daysAfter, orthodox));
+    }
+
+    /**
+     * Shrove Tuesday, aka Mardi Gras, 48 days before Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday SHROVE_TUESDAY  = new EasterHoliday(-48,    "Shrove Tuesday");
+
+    /**
+     * Ash Wednesday, start of Lent, 47 days before Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday ASH_WEDNESDAY   = new EasterHoliday(-47,    "Ash Wednesday");
+
+    /**
+     * Palm Sunday, 7 days before Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday PALM_SUNDAY     = new EasterHoliday( -7,    "Palm Sunday");
+
+    /**
+     * Maundy Thursday, 3 days before Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday MAUNDY_THURSDAY = new EasterHoliday( -3,    "Maundy Thursday");
+
+    /**
+     * Good Friday, 2 days before Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday GOOD_FRIDAY     = new EasterHoliday( -2,    "Good Friday");
+
+    /**
+     * Easter Sunday
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday EASTER_SUNDAY   = new EasterHoliday(  0,    "Easter Sunday");
+
+    /**
+     * Easter Monday, 1 day after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday EASTER_MONDAY   = new EasterHoliday(  1,    "Easter Monday");
+
+    /**
+     * Ascension, 39 days after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday ASCENSION       = new EasterHoliday( 39,    "Ascension");
+
+    /**
+     * Pentecost (aka Whit Sunday), 49 days after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday PENTECOST       = new EasterHoliday( 49,    "Pentecost");
+
+    /**
+     * Whit Sunday (aka Pentecost), 49 days after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday WHIT_SUNDAY     = new EasterHoliday( 49,    "Whit Sunday");
+
+    /**
+     * Whit Monday, 50 days after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday WHIT_MONDAY     = new EasterHoliday( 50,    "Whit Monday");
+
+    /**
+     * Corpus Christi, 60 days after Easter
+     * @draft ICU 2.2
+     */
+    static public final EasterHoliday CORPUS_CHRISTI  = new EasterHoliday( 60,    "Corpus Christi");
+}
+
+class EasterRule implements DateRule {
+    public EasterRule(int daysAfterEaster, boolean isOrthodox) {
+        this.daysAfterEaster = daysAfterEaster;
+        if (isOrthodox) {
+            orthodox.setGregorianChange(new Date(Long.MAX_VALUE));
+            calendar = orthodox;
+        }
+    }
+
+    /**
+     * Return the first occurrance of this rule on or after the given date
+     */
+    public Date firstAfter(Date start)
+    {
+        if (startDate != null && start.before(startDate)) {
+            start = startDate;
+        }
+        return doFirstBetween(start, null);
+    }
+
+    /**
+     * Return the first occurrance of this rule on or after
+     * the given start date and before the given end date.
+     */
+    public Date firstBetween(Date start, Date end)
+    {
+        // Pin to the min/max dates for this rule
+        if (startDate != null && start.before(startDate)) {
+            start = startDate;
+        }
+        return doFirstBetween(start, end);
+    }
+
+    /**
+     * Return true if the given Date is on the same day as Easter
+     */
+    public boolean isOn(Date date)
+    {
+        if (startDate != null && date.before(startDate)) {
+            return false;
+        }
+
+        synchronized(calendar) {
+            calendar.setTime(date);
+            int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
+
+            calendar.setTime(computeInYear(calendar.getTime(), calendar));
+
+            return calendar.get(Calendar.DAY_OF_YEAR) == dayOfYear;
+        }
+    }
+
+    /**
+     * Return true if Easter occurs between the two dates given
+     */
+    public boolean isBetween(Date start, Date end)
+    {
+        return firstBetween(start, end) != null; // TODO: optimize?
+    }
+
+    private Date doFirstBetween(Date start, Date end)
+    {
+        //System.out.println("doFirstBetween: start   = " + start.toString());
+        //System.out.println("doFirstBetween: end     = " + end.toString());
+
+        synchronized(calendar) {
+            // Figure out when this holiday lands in the given year
+            Date result = computeInYear(start, calendar);
+
+         //System.out.println("                result  = " + result.toString());
+
+            // We might have gotten a date that's in the same year as "start", but
+            // earlier in the year.  If so, go to next year
+            if (result.before(start))
+            {
+                calendar.setTime(start);
+                calendar.get(Calendar.YEAR);    // JDK 1.1.2 bug workaround
+                calendar.add(Calendar.YEAR, 1);
+
+                //System.out.println("                Result before start, going to next year: "
+                //                        + calendar.getTime().toString());
+
+                result = computeInYear(calendar.getTime(), calendar);
+                //System.out.println("                result  = " + result.toString());
+            }
+
+            if (end != null && result.after(end)) {
+                //System.out.println("Result after end, returning null");
+                return null;
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Compute the month and date on which this holiday falls in the year
+     * containing the date "date".  First figure out which date Easter
+     * lands on in this year, and then add the offset for this holiday to get
+     * the right date.
+     * <p>
+     * The algorithm here is taken from the
+     * <a href="http://www.faqs.org/faqs/calendars/faq/">Calendar FAQ</a>.
+     */
+    private Date computeInYear(Date date, GregorianCalendar cal)
+    {
+        if (cal == null) cal = calendar;
+
+        synchronized(cal) {
+            cal.setTime(date);
+
+            int year = cal.get(Calendar.YEAR);
+            int g = year % 19;  // "Golden Number" of year - 1
+            int i = 0;          // # of days from 3/21 to the Paschal full moon
+            int j = 0;          // Weekday (0-based) of Paschal full moon
+
+            if (cal.getTime().after( cal.getGregorianChange()))
+            {
+                // We're past the Gregorian switchover, so use the Gregorian rules.
+                int c = year / 100;
+                int h = (c - c/4 - (8*c+13)/25 + 19*g + 15) % 30;
+                i = h - (h/28)*(1 - (h/28)*(29/(h+1))*((21-g)/11));
+                j = (year + year/4 + i + 2 - c + c/4) % 7;
+            }
+            else
+            {
+                // Use the old Julian rules.
+                i = (19*g + 15) % 30;
+                j = (year + year/4 + i) % 7;
+            }
+            int l = i - j;
+            int m = 3 + (l+40)/44;              // 1-based month in which Easter falls
+            int d = l + 28 - 31*(m/4);          // Date of Easter within that month
+
+            cal.clear();
+            cal.set(Calendar.ERA, GregorianCalendar.AD);
+            cal.set(Calendar.YEAR, year);
+            cal.set(Calendar.MONTH, m-1);       // 0-based
+            cal.set(Calendar.DATE, d);
+            cal.getTime();                      // JDK 1.1.2 bug workaround
+            cal.add(Calendar.DATE, daysAfterEaster);
+
+            return cal.getTime();
+        }
+    }
+
+    static GregorianCalendar gregorian = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
+    static GregorianCalendar orthodox = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
+
+    int               daysAfterEaster;
+    Date              startDate = null;
+    GregorianCalendar calendar = gregorian;
+}
diff --git a/src/com/ibm/icu/util/GregorianCalendar.java b/src/com/ibm/icu/util/GregorianCalendar.java
new file mode 100755
index 0000000..e5f57bf
--- /dev/null
+++ b/src/com/ibm/icu/util/GregorianCalendar.java
@@ -0,0 +1,913 @@
+/*
+*   Copyright (C) 1996-2003, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*/
+package com.ibm.icu.util;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <code>GregorianCalendar</code> is a concrete subclass of
+ * {@link Calendar}
+ * and provides the standard calendar used by most of the world.
+ *
+ * <p>
+ * The standard (Gregorian) calendar has 2 eras, BC and AD.
+ *
+ * <p>
+ * This implementation handles a single discontinuity, which corresponds by
+ * default to the date the Gregorian calendar was instituted (October 15, 1582
+ * in some countries, later in others).  The cutover date may be changed by the
+ * caller by calling <code>setGregorianChange()</code>.
+ *
+ * <p>
+ * Historically, in those countries which adopted the Gregorian calendar first,
+ * October 4, 1582 was thus followed by October 15, 1582. This calendar models
+ * this correctly.  Before the Gregorian cutover, <code>GregorianCalendar</code>
+ * implements the Julian calendar.  The only difference between the Gregorian
+ * and the Julian calendar is the leap year rule. The Julian calendar specifies
+ * leap years every four years, whereas the Gregorian calendar omits century
+ * years which are not divisible by 400.
+ *
+ * <p>
+ * <code>GregorianCalendar</code> implements <em>proleptic</em> Gregorian and
+ * Julian calendars. That is, dates are computed by extrapolating the current
+ * rules indefinitely far backward and forward in time. As a result,
+ * <code>GregorianCalendar</code> may be used for all years to generate
+ * meaningful and consistent results. However, dates obtained using
+ * <code>GregorianCalendar</code> are historically accurate only from March 1, 4
+ * AD onward, when modern Julian calendar rules were adopted.  Before this date,
+ * leap year rules were applied irregularly, and before 45 BC the Julian
+ * calendar did not even exist.
+ *
+ * <p>
+ * Prior to the institution of the Gregorian calendar, New Year's Day was
+ * March 25. To avoid confusion, this calendar always uses January 1. A manual
+ * 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.
+ *
+ * <p>Values calculated for the <code>WEEK_OF_MONTH</code> field range from 0 or
+ * 1 to 4 or 5.  Week 1 of a month (the days with <code>WEEK_OF_MONTH =
+ * 1</code>) is the earliest set of at least
+ * <code>getMinimalDaysInFirstWeek()</code> contiguous days in that month,
+ * ending on the day before <code>getFirstDayOfWeek()</code>.  Unlike
+ * week 1 of a year, week 1 of a month may be shorter than 7 days, need
+ * not start on <code>getFirstDayOfWeek()</code>, and will not include days of
+ * the previous month.  Days of a month before week 1 have a
+ * <code>WEEK_OF_MONTH</code> of 0.
+ *
+ * <p>For example, if <code>getFirstDayOfWeek()</code> is <code>SUNDAY</code>
+ * and <code>getMinimalDaysInFirstWeek()</code> is 4, then the first week of
+ * January 1998 is Sunday, January 4 through Saturday, January 10.  These days
+ * have a <code>WEEK_OF_MONTH</code> of 1.  Thursday, January 1 through
+ * Saturday, January 3 have a <code>WEEK_OF_MONTH</code> of 0.  If
+ * <code>getMinimalDaysInFirstWeek()</code> is changed to 3, then January 1
+ * through January 3 have a <code>WEEK_OF_MONTH</code> of 1.
+ *
+ * <p>
+ * <strong>Example:</strong>
+ * <blockquote>
+ * <pre>
+ * // get the supported ids for GMT-08:00 (Pacific Standard Time)
+ * String[] ids = TimeZone.getAvailableIDs(-8 * 60 * 60 * 1000);
+ * // if no ids were returned, something is wrong. get out.
+ * if (ids.length == 0)
+ *     System.exit(0);
+ *
+ *  // begin output
+ * System.out.println("Current Time");
+ *
+ * // create a Pacific Standard Time time zone
+ * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, ids[0]);
+ *
+ * // set up rules for daylight savings time
+ * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
+ * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
+ *
+ * // create a GregorianCalendar with the Pacific Daylight time zone
+ * // and the current date and time
+ * Calendar calendar = new GregorianCalendar(pdt);
+ * Date trialTime = new Date();
+ * calendar.setTime(trialTime);
+ *
+ * // print out a bunch of interesting things
+ * System.out.println("ERA: " + calendar.get(Calendar.ERA));
+ * System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
+ * System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
+ * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
+ * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
+ * System.out.println("DATE: " + calendar.get(Calendar.DATE));
+ * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
+ * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
+ * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
+ * System.out.println("DAY_OF_WEEK_IN_MONTH: "
+ *                    + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
+ * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
+ * System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
+ * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
+ * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
+ * System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
+ * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
+ * System.out.println("ZONE_OFFSET: "
+ *                    + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000)));
+ * System.out.println("DST_OFFSET: "
+ *                    + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000)));
+
+ * System.out.println("Current Time, with hour reset to 3");
+ * calendar.clear(Calendar.HOUR_OF_DAY); // so doesn't override
+ * calendar.set(Calendar.HOUR, 3);
+ * System.out.println("ERA: " + calendar.get(Calendar.ERA));
+ * System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
+ * System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
+ * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
+ * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
+ * System.out.println("DATE: " + calendar.get(Calendar.DATE));
+ * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
+ * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
+ * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
+ * System.out.println("DAY_OF_WEEK_IN_MONTH: "
+ *                    + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
+ * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
+ * System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
+ * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
+ * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
+ * System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
+ * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
+ * System.out.println("ZONE_OFFSET: "
+ *        + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000))); // in hours
+ * System.out.println("DST_OFFSET: "
+ *        + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000))); // in hours
+ * </pre>
+ * </blockquote>
+ *
+ * @see          Calendar
+ * @see          TimeZone
+ * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu
+ * @stable ICU 2.0
+ */
+public class GregorianCalendar extends Calendar {
+    /*
+     * Implementation Notes
+     *
+     * The Julian day number, as used here, is a modified number which has its
+     * onset at midnight, rather than noon.
+     *
+     * The epoch is the number of days or milliseconds from some defined
+     * starting point. The epoch for java.util.Date is used here; that is,
+     * milliseconds from January 1, 1970 (Gregorian), midnight UTC.  Other
+     * epochs which are used are January 1, year 1 (Gregorian), which is day 1
+     * of the Gregorian calendar, and December 30, year 0 (Gregorian), which is
+     * day 1 of the Julian calendar.
+     *
+     * We implement the proleptic Julian and Gregorian calendars.  This means we
+     * implement the modern definition of the calendar even though the
+     * historical usage differs.  For example, if the Gregorian change is set
+     * to new Date(Long.MIN_VALUE), we have a pure Gregorian calendar which
+     * labels dates preceding the invention of the Gregorian calendar in 1582 as
+     * if the calendar existed then.
+     *
+     * Likewise, with the Julian calendar, we assume a consistent 4-year leap
+     * rule, even though the historical pattern of leap years is irregular,
+     * being every 3 years from 45 BC through 9 BC, then every 4 years from 8 AD
+     * onwards, with no leap years in-between.  Thus date computations and
+     * functions such as isLeapYear() are not intended to be historically
+     * accurate.
+     *
+     * Given that milliseconds are a long, day numbers such as Julian day
+     * numbers, Gregorian or Julian calendar days, or epoch days, are also
+     * longs. Years can fit into an int.
+     */
+
+//////////////////
+// Class Variables
+//////////////////
+
+    /**
+     * Value of the <code>ERA</code> field indicating
+     * the period before the common era (before Christ), also known as BCE.
+     * The sequence of years at the transition from <code>BC</code> to <code>AD</code> is
+     * ..., 2 BC, 1 BC, 1 AD, 2 AD,...
+     * @see Calendar#ERA
+     * @stable ICU 2.0
+     */
+    public static final int BC = 0;
+
+    /**
+     * Value of the <code>ERA</code> field indicating
+     * the common era (Anno Domini), also known as CE.
+     * The sequence of years at the transition from <code>BC</code> to <code>AD</code> is
+     * ..., 2 BC, 1 BC, 1 AD, 2 AD,...
+     * @see Calendar#ERA
+     * @stable ICU 2.0
+     */
+    public static final int AD = 1;
+
+    private static final int EPOCH_YEAR = 1970;
+
+    private static final int[][] MONTH_COUNT = {
+        //len len2   st  st2
+        {  31,  31,   0,   0 }, // Jan
+        {  28,  29,  31,  31 }, // Feb
+        {  31,  31,  59,  60 }, // Mar
+        {  30,  30,  90,  91 }, // Apr
+        {  31,  31, 120, 121 }, // May
+        {  30,  30, 151, 152 }, // Jun
+        {  31,  31, 181, 182 }, // Jul
+        {  31,  31, 212, 213 }, // Aug
+        {  30,  30, 243, 244 }, // Sep
+        {  31,  31, 273, 274 }, // Oct
+        {  30,  30, 304, 305 }, // Nov
+        {  31,  31, 334, 335 }  // Dec
+        // len  length of month
+        // len2 length of month in a leap year
+        // st   days in year before start of month
+        // st2  days in year before month in leap year
+    };
+    
+    /**
+     * Old year limits were least max 292269054, max 292278994.
+     */
+    private static final int LIMITS[][] = {
+        // Minimum  Greatest    Least  Maximum
+        //           Minimum  Maximum
+        {        0,        0,       1,       1 }, // ERA
+        {        1,        1, 5828963, 5838270 }, // YEAR
+        {        0,        0,      11,      11 }, // MONTH
+        {        1,        1,      52,      53 }, // WEEK_OF_YEAR
+        {        0,        0,       4,       6 }, // WEEK_OF_MONTH
+        {        1,        1,      28,      31 }, // DAY_OF_MONTH
+        {        1,        1,     365,     366 }, // DAY_OF_YEAR
+        {/*                                  */}, // DAY_OF_WEEK
+        {       -1,       -1,       4,       6 }, // DAY_OF_WEEK_IN_MONTH
+        {/*                                  */}, // AM_PM
+        {/*                                  */}, // HOUR
+        {/*                                  */}, // HOUR_OF_DAY
+        {/*                                  */}, // MINUTE
+        {/*                                  */}, // SECOND
+        {/*                                  */}, // MILLISECOND
+        {/*                                  */}, // ZONE_OFFSET
+        {/*                                  */}, // DST_OFFSET
+        { -5838270, -5838270, 5828964, 5838271 }, // YEAR_WOY
+        {/*                                  */}, // DOW_LOCAL
+        { -5838269, -5838269, 5828963, 5838270 }, // EXTENDED_YEAR
+        {/*                                  */}, // JULIAN_DAY
+        {/*                                  */}, // MILLISECONDS_IN_DAY
+    };
+
+    /**
+     * @stable ICU 2.0
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        return LIMITS[field][limitType];
+    }
+
+/////////////////////
+// Instance Variables
+/////////////////////
+
+    /**
+     * The point at which the Gregorian calendar rules are used, measured in
+     * milliseconds from the standard epoch.  Default is October 15, 1582
+     * (Gregorian) 00:00:00 UTC or -12219292800000L.  For this value, October 4,
+     * 1582 (Julian) is followed by October 15, 1582 (Gregorian).  This
+     * corresponds to Julian day number 2299161.
+     * @serial
+     */
+    private long gregorianCutover = -12219292800000L;
+
+    /**
+     * Julian day number of the Gregorian cutover.
+     */
+    private transient int cutoverJulianDay = 2299161;
+    
+    /**
+     * The year of the gregorianCutover, with 0 representing
+     * 1 BC, -1 representing 2 BC, etc.
+     */
+    private transient int gregorianCutoverYear = 1582;
+
+    /**
+     * Used by handleComputeJulianDay() and handleComputeMonthStart().
+     * @stable ICU 2.0
+     */
+    transient protected boolean isGregorian;
+
+    /**
+     * Used by handleComputeJulianDay() and handleComputeMonthStart().
+     * @stable ICU 2.0
+     */
+    transient protected boolean invertGregorian;
+
+///////////////
+// Constructors
+///////////////
+
+    /**
+     * Constructs a default GregorianCalendar using the current time
+     * in the default time zone with the default locale.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar() {
+        this(TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    /**
+     * Constructs a GregorianCalendar based on the current time
+     * in the given time zone with the default locale.
+     * @param zone the given time zone.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(TimeZone zone) {
+        this(zone, Locale.getDefault());
+    }
+
+    /**
+     * Constructs a GregorianCalendar based on the current time
+     * in the default time zone with the given locale.
+     * @param aLocale the given locale.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(Locale aLocale) {
+        this(TimeZone.getDefault(), aLocale);
+    }
+
+    /**
+     * Constructs a GregorianCalendar based on the current time
+     * in the given time zone with the given locale.
+     * @param zone the given time zone.
+     * @param aLocale the given locale.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(TimeZone zone, Locale aLocale) {
+        super(zone, aLocale);
+        setTimeInMillis(System.currentTimeMillis());
+    }
+
+    /**
+     * Constructs a GregorianCalendar with the given date set
+     * in the default time zone with the default locale.
+     * @param year the value used to set the YEAR time field in the calendar.
+     * @param month the value used to set the MONTH time field in the calendar.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field in the calendar.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(int year, int month, int date) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        set(ERA, AD);
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+    }
+
+    /**
+     * Constructs a GregorianCalendar with the given date
+     * and time set for the default time zone with the default locale.
+     * @param year the value used to set the YEAR time field in the calendar.
+     * @param month the value used to set the MONTH time field in the calendar.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field in the calendar.
+     * @param hour the value used to set the HOUR_OF_DAY time field
+     * in the calendar.
+     * @param minute the value used to set the MINUTE time field
+     * in the calendar.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(int year, int month, int date, int hour,
+                             int minute) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        set(ERA, AD);
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+        set(HOUR_OF_DAY, hour);
+        set(MINUTE, minute);
+    }
+
+    /**
+     * Constructs a GregorianCalendar with the given date
+     * and time set for the default time zone with the default locale.
+     * @param year the value used to set the YEAR time field in the calendar.
+     * @param month the value used to set the MONTH time field in the calendar.
+     * Month value is 0-based. e.g., 0 for January.
+     * @param date the value used to set the DATE time field in the calendar.
+     * @param hour the value used to set the HOUR_OF_DAY time field
+     * in the calendar.
+     * @param minute the value used to set the MINUTE time field
+     * in the calendar.
+     * @param second the value used to set the SECOND time field
+     * in the calendar.
+     * @stable ICU 2.0
+     */
+    public GregorianCalendar(int year, int month, int date, int hour,
+                             int minute, int second) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        set(ERA, AD);
+        set(YEAR, year);
+        set(MONTH, month);
+        set(DATE, date);
+        set(HOUR_OF_DAY, hour);
+        set(MINUTE, minute);
+        set(SECOND, second);
+    }
+
+/////////////////
+// Public methods
+/////////////////
+
+    /**
+     * Sets the GregorianCalendar change date. This is the point when the switch
+     * from Julian dates to Gregorian dates occurred. Default is October 15,
+     * 1582. Previous to this, dates will be in the Julian calendar.
+     * <p>
+     * To obtain a pure Julian calendar, set the change date to
+     * <code>Date(Long.MAX_VALUE)</code>.  To obtain a pure Gregorian calendar,
+     * set the change date to <code>Date(Long.MIN_VALUE)</code>.
+     *
+     * @param date the given Gregorian cutover date.
+     * @stable ICU 2.0
+     */
+    public void setGregorianChange(Date date) {
+        gregorianCutover = date.getTime();
+
+        // If the cutover has an extreme value, then create a pure
+        // Gregorian or pure Julian calendar by giving the cutover year and
+        // JD extreme values.
+        if (gregorianCutover <= MIN_MILLIS) {
+            gregorianCutoverYear = cutoverJulianDay = Integer.MIN_VALUE;
+        } else if (gregorianCutover >= MAX_MILLIS) {
+            gregorianCutoverYear = cutoverJulianDay = Integer.MAX_VALUE;
+        } else {
+            // Precompute two internal variables which we use to do the actual
+            // cutover computations.  These are the Julian day of the cutover
+            // and the cutover year.
+            cutoverJulianDay = (int) floorDivide(gregorianCutover, ONE_DAY);
+            
+            // Convert cutover millis to extended year
+            GregorianCalendar cal = new GregorianCalendar(getTimeZone());
+            cal.setTime(date);
+            gregorianCutoverYear = cal.get(EXTENDED_YEAR);
+        }
+    }
+
+    /**
+     * Gets the Gregorian Calendar change date.  This is the point when the
+     * switch from Julian dates to Gregorian dates occurred. Default is
+     * October 15, 1582. Previous to this, dates will be in the Julian
+     * calendar.
+     * @return the Gregorian cutover date for this calendar.
+     * @stable ICU 2.0
+     */
+    public final Date getGregorianChange() {
+        return new Date(gregorianCutover);
+    }
+
+    /**
+     * Determines if the given year is a leap year. Returns true if the
+     * given year is a leap year.
+     * @param year the given year.
+     * @return true if the given year is a leap year; false otherwise.
+     * @stable ICU 2.0
+     */
+    public boolean isLeapYear(int year) {
+        return year >= gregorianCutoverYear ?
+            ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))) : // Gregorian
+            (year%4 == 0); // Julian
+    }
+
+    /**
+     * Returns true if the given Calendar object is equivalent to this
+     * one.  Calendar override.
+     *
+     * @param other the Calendar to be compared with this Calendar   
+     * @draft ICU 2.4
+     */
+    public boolean isEquivalentTo(Calendar other) {
+        return super.isEquivalentTo(other) &&
+            gregorianCutover == ((GregorianCalendar)other).gregorianCutover;
+    }
+
+    /**
+     * Override hashCode.
+     * Generates the hash code for the GregorianCalendar object
+     * @stable ICU 2.0
+     */
+    public int hashCode() {
+        return super.hashCode() ^ (int)gregorianCutover;
+    }
+
+    /**
+     * Roll a field by a signed amount.
+     * @stable ICU 2.0
+     */
+    public void roll(int field, int amount) {
+
+        switch (field) {
+        case WEEK_OF_YEAR:
+            {
+                // Unlike WEEK_OF_MONTH, WEEK_OF_YEAR never shifts the day of the
+                // week.  Also, rolling the week of the year can have seemingly
+                // strange effects simply because the year of the week of year
+                // may be different from the calendar year.  For example, the
+                // date Dec 28, 1997 is the first day of week 1 of 1998 (if
+                // weeks start on Sunday and the minimal days in first week is
+                // <= 3).
+                int woy = get(WEEK_OF_YEAR);
+                // Get the ISO year, which matches the week of year.  This
+                // may be one year before or after the calendar year.
+                int isoYear = get(YEAR_WOY);
+                int isoDoy = internalGet(DAY_OF_YEAR);
+                if (internalGet(MONTH) == Calendar.JANUARY) {
+                    if (woy >= 52) {
+                        isoDoy += handleGetYearLength(isoYear);
+                    }
+                } else {
+                    if (woy == 1) {
+                        isoDoy -= handleGetYearLength(isoYear - 1);
+                    }
+                }
+                woy += amount;
+                // Do fast checks to avoid unnecessary computation:
+                if (woy < 1 || woy > 52) {
+                    // Determine the last week of the ISO year.
+                    // We do this using the standard formula we use
+                    // everywhere in this file.  If we can see that the
+                    // days at the end of the year are going to fall into
+                    // week 1 of the next year, we drop the last week by
+                    // subtracting 7 from the last day of the year.
+                    int lastDoy = handleGetYearLength(isoYear);
+                    int lastRelDow = (lastDoy - isoDoy + internalGet(DAY_OF_WEEK) -
+                                      getFirstDayOfWeek()) % 7;
+                    if (lastRelDow < 0) lastRelDow += 7;
+                    if ((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) lastDoy -= 7;
+                    int lastWoy = weekNumber(lastDoy, lastRelDow + 1);
+                    woy = ((woy + lastWoy - 1) % lastWoy) + 1;
+                }
+                set(WEEK_OF_YEAR, woy);
+                set(YEAR, isoYear); // Why not YEAR_WOY? - Alan 11/6/00
+                return;
+            }
+
+        default:
+            super.roll(field, amount);
+            return;
+        }
+    }
+
+    /**
+     * Return the minimum value that this field could have, given the current date.
+     * For the Gregorian calendar, this is the same as getMinimum() and getGreatestMinimum().
+     * @stable ICU 2.0
+     */
+    public int getActualMinimum(int field) {
+        return getMinimum(field);
+    }
+
+    /**
+     * Return the maximum value that this field could have, given the current date.
+     * For example, with the date "Feb 3, 1997" and the DAY_OF_MONTH field, the actual
+     * maximum would be 28; for "Feb 3, 1996" it s 29.  Similarly for a Hebrew calendar,
+     * for some years the actual maximum for MONTH is 12, and for others 13.
+     * @stable ICU 2.0
+     */
+    public int getActualMaximum(int field) {
+        /* It is a known limitation that the code here (and in getActualMinimum)
+         * won't behave properly at the extreme limits of GregorianCalendar's
+         * representable range (except for the code that handles the YEAR
+         * field).  That's because the ends of the representable range are at
+         * odd spots in the year.  For calendars with the default Gregorian
+         * cutover, these limits are Sun Dec 02 16:47:04 GMT 292269055 BC to Sun
+         * Aug 17 07:12:55 GMT 292278994 AD, somewhat different for non-GMT
+         * zones.  As a result, if the calendar is set to Aug 1 292278994 AD,
+         * the actual maximum of DAY_OF_MONTH is 17, not 30.  If the date is Mar
+         * 31 in that year, the actual maximum month might be Jul, whereas is
+         * the date is Mar 15, the actual maximum might be Aug -- depending on
+         * the precise semantics that are desired.  Similar considerations
+         * affect all fields.  Nonetheless, this effect is sufficiently arcane
+         * that we permit it, rather than complicating the code to handle such
+         * intricacies. - liu 8/20/98
+
+         * UPDATE: No longer true, since we have pulled in the limit values on
+         * the year. - Liu 11/6/00 */
+
+        switch (field) {
+
+        case YEAR:
+            /* The year computation is no different, in principle, from the
+             * others, however, the range of possible maxima is large.  In
+             * addition, the way we know we've exceeded the range is different.
+             * For these reasons, we use the special case code below to handle
+             * this field.
+             *
+             * The actual maxima for YEAR depend on the type of calendar:
+             *
+             *     Gregorian = May 17, 292275056 BC - Aug 17, 292278994 AD
+             *     Julian    = Dec  2, 292269055 BC - Jan  3, 292272993 AD
+             *     Hybrid    = Dec  2, 292269055 BC - Aug 17, 292278994 AD
+             *
+             * We know we've exceeded the maximum when either the month, date,
+             * time, or era changes in response to setting the year.  We don't
+             * check for month, date, and time here because the year and era are
+             * sufficient to detect an invalid year setting.  NOTE: If code is
+             * added to check the month and date in the future for some reason,
+             * Feb 29 must be allowed to shift to Mar 1 when setting the year.
+             */
+            {
+                Calendar cal = (Calendar) clone();
+                cal.setLenient(true);
+                
+                int era = cal.get(ERA);
+                Date d = cal.getTime();
+
+                /* Perform a binary search, with the invariant that lowGood is a
+                 * valid year, and highBad is an out of range year.
+                 */
+                int lowGood = LIMITS[YEAR][1];
+                int highBad = LIMITS[YEAR][2]+1;
+                while ((lowGood + 1) < highBad) {
+                    int y = (lowGood + highBad) / 2;
+                    cal.set(YEAR, y);
+                    if (cal.get(YEAR) == y && cal.get(ERA) == era) {
+                        lowGood = y;
+                    } else {
+                        highBad = y;
+                        cal.setTime(d); // Restore original fields
+                    }
+                }
+                
+                return lowGood;
+            }
+
+        default:
+            return super.getActualMaximum(field);
+        }
+    }
+
+//////////////////////
+// Proposed public API
+//////////////////////
+
+    /**
+     * Return true if the current time for this Calendar is in Daylignt
+     * Savings Time.
+     *
+     * Note -- MAKE THIS PUBLIC AT THE NEXT API CHANGE.  POSSIBLY DEPRECATE
+     * AND REMOVE TimeZone.inDaylightTime().
+     */
+    boolean inDaylightTime() {
+        if (!getTimeZone().useDaylightTime()) return false;
+        complete(); // Force update of DST_OFFSET field
+        return internalGet(DST_OFFSET) != 0;
+    }
+
+
+/////////////////////
+// Calendar framework
+/////////////////////
+
+    /**
+     * @stable ICU 2.0
+     */
+    protected int handleGetMonthLength(int extendedYear, int month) {
+        return MONTH_COUNT[month][isLeapYear(extendedYear)?1:0];
+    }
+
+    /**
+     * @stable ICU 2.0
+     */
+    protected int handleGetYearLength(int eyear) {
+        return isLeapYear(eyear) ? 366 : 365;
+    }
+
+/////////////////////////////
+// Time => Fields computation
+/////////////////////////////
+
+    /**
+     * Override Calendar to compute several fields specific to the hybrid
+     * Gregorian-Julian calendar system.  These are:
+     *
+     * <ul><li>ERA
+     * <li>YEAR
+     * <li>MONTH
+     * <li>DAY_OF_MONTH
+     * <li>DAY_OF_YEAR
+     * <li>EXTENDED_YEAR</ul>
+     * @stable ICU 2.0
+     */
+    protected void handleComputeFields(int julianDay) {
+        int eyear, month, dayOfMonth, dayOfYear;
+
+        if (julianDay >= cutoverJulianDay) {
+            month = getGregorianMonth();
+            dayOfMonth = getGregorianDayOfMonth();
+            dayOfYear = getGregorianDayOfYear();
+            eyear = getGregorianYear();
+        } else {
+            // The Julian epoch day (not the same as Julian Day)
+            // is zero on Saturday December 30, 0 (Gregorian).
+            long julianEpochDay = julianDay - (JAN_1_1_JULIAN_DAY - 2);
+            eyear = (int) floorDivide(4*julianEpochDay + 1464, 1461);
+            
+            // Compute the Julian calendar day number for January 1, eyear
+            long january1 = 365*(eyear-1) + floorDivide(eyear-1, 4);
+            dayOfYear = (int)(julianEpochDay - january1); // 0-based
+            
+            // Julian leap years occurred historically every 4 years starting
+            // with 8 AD.  Before 8 AD the spacing is irregular; every 3 years
+            // from 45 BC to 9 BC, and then none until 8 AD.  However, we don't
+            // implement this historical detail; instead, we implement the
+            // computatinally cleaner proleptic calendar, which assumes
+            // consistent 4-year cycles throughout time.
+            boolean isLeap = ((eyear&0x3) == 0); // equiv. to (eyear%4 == 0)
+            
+            // Common Julian/Gregorian calculation
+            int correction = 0;
+            int march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
+            if (dayOfYear >= march1) {
+                correction = isLeap ? 1 : 2;
+            }
+            month = (12 * (dayOfYear + correction) + 6) / 367; // zero-based month
+            dayOfMonth = dayOfYear - MONTH_COUNT[month][isLeap?3:2] + 1; // one-based DOM
+            ++dayOfYear;
+        }
+        internalSet(MONTH, month);
+        internalSet(DAY_OF_MONTH, dayOfMonth);
+        internalSet(DAY_OF_YEAR, dayOfYear);
+        internalSet(EXTENDED_YEAR, eyear);
+        int era = AD;
+        if (eyear < 1) {
+            era = BC;
+            eyear = 1 - eyear;
+        }
+        internalSet(ERA, era);
+        internalSet(YEAR, eyear);
+    }
+
+/////////////////////////////
+// Fields => Time computation
+/////////////////////////////
+
+    /**
+     * @stable ICU 2.0
+     */
+    protected int handleGetExtendedYear() {
+        int year;
+        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
+            year = internalGet(EXTENDED_YEAR, EPOCH_YEAR);
+        } else {
+            // The year defaults to the epoch start, the era to AD
+            int era = internalGet(ERA, AD);
+            if (era == BC) {
+                year = 1 - internalGet(YEAR, 1); // Convert to extended year
+            } else {
+                year = internalGet(YEAR, EPOCH_YEAR);
+            }
+        }
+        return year;
+    }
+
+    /**
+     * Override Calendar to improve performance.  This method tries to use
+     * the EXTENDED_YEAR, MONTH, DATE, fields if they are set, instead of
+     * computing them.  If they are not set, this method defers to the
+     * default implemenation.
+     * @param millis milliseconds of the date fields
+     * @param millisInDay milliseconds of the time fields; may be out
+     * or range.
+     * @stable ICU 2.0
+     */
+    protected int computeZoneOffset(long millis, int millisInDay) {
+
+        // Normalize the millisInDay to 0..ONE_DAY-1.  If the millis is out
+        // of range, then we defer to the base class implementation which
+        // will recompute the correct values.
+        int[] normalizedMillisInDay = new int[1];
+        int days = floorDivide(millis + millisInDay, (int) ONE_DAY, normalizedMillisInDay);
+
+        // We need to have the month, the day, and the day of the week.  If
+        // the fields are not set or if we're lenient (so fields may be out
+        // of range) then we defer to the base class which will normalize
+        // the year, month, and day of month.
+        if (isLenient() || !isSet(MONTH) || !isSet(DAY_OF_MONTH)
+            || millisInDay != normalizedMillisInDay[0]) {
+            return super.computeZoneOffset(millis, millisInDay);
+        }
+
+        int julianDay = millisToJulianDay(days * ONE_DAY);
+
+        // It's tempting to try to use DAY_OF_WEEK here, if it
+        // is set, but we CAN'T.  Even if it's set, it might have
+        // been set wrong by the user.  We should rely only on
+        // the Julian day number, which has been computed correctly
+        // using the disambiguation algorithm above. [LIU]
+        int year = internalGet(EXTENDED_YEAR);
+        int month = internalGet(MONTH);
+	    // TODO: consider calling the getOffset(millis) API when we drop JDK 1.3 support - Alan
+        return getTimeZone().getOffset(AD, year, month,
+                              internalGet(DATE),
+                              julianDayToDayOfWeek(julianDay),
+                              normalizedMillisInDay[0]);
+
+        // Note: Because we pass in wall millisInDay, rather than
+        // standard millisInDay, we interpret "1:00 am" on the day
+        // of cessation of DST as "1:00 am Std" (assuming the time
+        // of cessation is 2:00 am).
+    }
+
+    /**
+     * @stable ICU 2.0
+     */
+    protected int handleComputeJulianDay(int bestField) {
+
+        invertGregorian = false;
+
+        int jd = super.handleComputeJulianDay(bestField);
+
+        // The following check handles portions of the cutover year BEFORE the
+        // cutover itself happens.
+        if (isGregorian != (jd >= cutoverJulianDay)) {
+            invertGregorian = true;
+            jd = super.handleComputeJulianDay(bestField);
+        }
+        
+        return jd;
+    }
+
+    /**
+     * Return JD of start of given month/year
+     * @stable ICU 2.0
+     */
+    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
+
+        // If the month is out of range, adjust it into range, and
+        // modify the extended year value accordingly.
+        if (month < 0 || month > 11) {
+            int[] rem = new int[1];
+            eyear += floorDivide(month, 12, rem);
+            month = rem[0];
+        }
+
+        boolean isLeap = eyear%4 == 0;
+        int y = eyear - 1;
+        int julianDay = 365*y + floorDivide(y, 4) + (JAN_1_1_JULIAN_DAY - 3);
+
+        isGregorian = (eyear >= gregorianCutoverYear);
+        if (invertGregorian) {
+            isGregorian = !isGregorian;
+        }
+        if (isGregorian) {
+            isLeap = isLeap && ((eyear%100 != 0) || (eyear%400 == 0));
+            // Add 2 because Gregorian calendar starts 2 days after
+            // Julian calendar
+            julianDay += floorDivide(y, 400) - floorDivide(y, 100) + 2;
+        }
+
+        // At this point julianDay indicates the day BEFORE the first
+        // day of January 1, <eyear> of either the Julian or Gregorian
+        // calendar.
+
+        if (month != 0) {
+            julianDay += MONTH_COUNT[month][isLeap?3:2];
+        }
+
+        return julianDay;
+    }
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new GregorianCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Gregorian";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
\ No newline at end of file
diff --git a/src/com/ibm/icu/util/HebrewCalendar.java b/src/com/ibm/icu/util/HebrewCalendar.java
new file mode 100755
index 0000000..251e1d7
--- /dev/null
+++ b/src/com/ibm/icu/util/HebrewCalendar.java
@@ -0,0 +1,828 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/HebrewCalendar.java,v $ 
+ * $Date: 2003/09/04 01:00:58 $ 
+ * $Revision: 1.19 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <code>HebrewCalendar</code> is a subclass of <code>Calendar</code>
+ * that that implements the traditional Hebrew calendar.
+ * This is the civil calendar in Israel and the liturgical calendar
+ * of the Jewish faith worldwide.
+ * <p>
+ * The Hebrew calendar is lunisolar and thus has a number of interesting
+ * properties that distinguish it from the Gregorian.  Months start
+ * on the day of (an arithmetic approximation of) each new moon.  Since the
+ * solar year (approximately 365.24 days) is not an even multiple of
+ * the lunar month (approximately 29.53 days) an extra "leap month" is
+ * inserted in 7 out of every 19 years.  To make matters even more
+ * interesting, the start of a year can be delayed by up to three days
+ * in order to prevent certain holidays from falling on the Sabbath and
+ * to prevent certain illegal year lengths.  Finally, the lengths of certain
+ * months can vary depending on the number of days in the year.
+ * <p>
+ * The leap month is known as "Adar 1" and is inserted between the
+ * months of Shevat and Adar in leap years.  Since the leap month does
+ * not come at the end of the year, calculations involving
+ * month numbers are particularly complex.  Users of this class should
+ * make sure to use the {@link #roll roll} and {@link #add add} methods
+ * rather than attempting to perform date arithmetic by manipulating
+ * the fields directly.
+ * <p>
+ * <b>Note:</b> In the traditional Hebrew calendar, days start at sunset.
+ * However, in order to keep the time fields in this class
+ * synchronized with those of the other calendars and with local clock time,
+ * we treat days and months as beginning at midnight,
+ * roughly 6 hours after the corresponding sunset.
+ * <p>
+ * If you are interested in more information on the rules behind the Hebrew
+ * calendar, see one of the following references:
+ * <ul>
+ * <li>"<a href="http://www.amazon.com/exec/obidos/ASIN/0521564743">Calendrical Calculations</a>",
+ *      by Nachum Dershowitz & Edward Reingold, Cambridge University Press, 1997, pages 85-91.
+ *
+ * <li>Hebrew Calendar Science and Myths,
+ *      <a href="http://www.geocities.com/Athens/1584/">
+ *      http://www.geocities.com/Athens/1584/</a>
+ *
+ * <li>The Calendar FAQ,
+ *      <a href="http://www.faqs.org/faqs/calendars/faq/">
+ *      http://www.faqs.org/faqs/calendars/faq/</a>
+ * </ul>
+ * <p>
+ * @see com.ibm.icu.util.GregorianCalendar
+ *
+ * @author Laura Werner
+ * @author Alan Liu
+ * @draft ICU 2.4
+ */
+public class HebrewCalendar extends Calendar {
+
+    private static String copyright = "Copyright \u00a9 1997-1998 IBM Corp. All Rights Reserved.";
+
+    //-------------------------------------------------------------------------
+    // Tons o' Constants...
+    //-------------------------------------------------------------------------
+
+
+    /** 
+     * Constant for Tishri, the 1st month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int TISHRI = 0;
+
+    /**
+     * Constant for Heshvan, the 2nd month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int HESHVAN = 1;
+
+    /**
+     * Constant for Kislev, the 3rd month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int KISLEV = 2;
+
+    /**
+     * Constant for Tevet, the 4th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int TEVET = 3;
+
+    /**
+     * Constant for Shevat, the 5th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int SHEVAT = 4;
+
+    /**
+     * Constant for Adar I, the 6th month of the Hebrew year
+     * (present in leap years only). In non-leap years, the calendar
+     * jumps from Shevat (5th month) to Adar (7th month).
+     * @draft ICU 2.4
+     */
+    public static final int ADAR_1 = 5;
+
+    /** 
+     * Constant for the Adar, the 7th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int ADAR = 6;
+
+    /**
+     * Constant for Nisan, the 8th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int NISAN = 7;
+
+    /**
+     * Constant for Iyar, the 9th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int IYAR = 8;
+
+    /**
+     * Constant for Sivan, the 10th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int SIVAN = 9;
+
+    /**
+     * Constant for Tammuz, the 11th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int TAMUZ = 10;
+
+    /**
+     * Constant for Av, the 12th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int AV = 11;
+
+    /**
+     * Constant for Elul, the 13th month of the Hebrew year. 
+     * @draft ICU 2.4 
+     */
+    public static final int ELUL = 12;
+
+    /**
+     * The absolute date, in milliseconds since 1/1/1970 AD, Gregorian,
+     * of the start of the Hebrew calendar.  In order to keep this calendar's
+     * time of day in sync with that of the Gregorian calendar, we use
+     * midnight, rather than sunset the day before.
+     */
+    //private static final long EPOCH_MILLIS = -180799862400000L; // 1/1/1 HY
+
+    private static final int LIMITS[][] = {
+        // Minimum  Greatest    Least  Maximum
+        //           Minimum  Maximum
+        {        0,        0,       0,       0 }, // ERA
+        {        1,        1, 5000000, 5000000 }, // YEAR
+        {        0,        0,      12,      12 }, // MONTH
+        {        1,        1,      51,      56 }, // WEEK_OF_YEAR
+        {        0,        0,       5,       6 }, // WEEK_OF_MONTH
+        {        1,        1,      29,      30 }, // DAY_OF_MONTH
+        {        1,        1,     353,     385 }, // DAY_OF_YEAR
+        {/*                                  */}, // DAY_OF_WEEK
+        {       -1,       -1,       4,       6 }, // DAY_OF_WEEK_IN_MONTH
+        {/*                                  */}, // AM_PM
+        {/*                                  */}, // HOUR
+        {/*                                  */}, // HOUR_OF_DAY
+        {/*                                  */}, // MINUTE
+        {/*                                  */}, // SECOND
+        {/*                                  */}, // MILLISECOND
+        {/*                                  */}, // ZONE_OFFSET
+        {/*                                  */}, // DST_OFFSET
+        { -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
+        {/*                                  */}, // DOW_LOCAL
+        { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
+        {/*                                  */}, // JULIAN_DAY
+        {/*                                  */}, // MILLISECONDS_IN_DAY
+    };
+
+    /**
+     * The lengths of the Hebrew months.  This is complicated, because there
+     * are three different types of years, or six if you count leap years.
+     * Due to the rules for postponing the start of the year to avoid having
+     * certain holidays fall on the sabbath, the year can end up being three
+     * different lengths, called "deficient", "normal", and "complete".
+     */
+    private static final int MONTH_LENGTH[][] = {
+        // Deficient  Normal     Complete
+        {   30,         30,         30     },           //Tishri
+        {   29,         29,         30     },           //Heshvan
+        {   29,         30,         30     },           //Kislev
+        {   29,         29,         29     },           //Tevet
+        {   30,         30,         30     },           //Shevat
+        {   30,         30,         30     },           //Adar I (leap years only)
+        {   29,         29,         29     },           //Adar
+        {   30,         30,         30     },           //Nisan
+        {   29,         29,         29     },           //Iyar
+        {   30,         30,         30     },           //Sivan
+        {   29,         29,         29     },           //Tammuz
+        {   30,         30,         30     },           //Av
+        {   29,         29,         29     },           //Elul
+    };
+
+    /**
+     * The cumulative # of days to the end of each month in a non-leap year
+     * Although this can be calculated from the MONTH_LENGTH table,
+     * keeping it around separately makes some calculations a lot faster
+     */
+    private static final int MONTH_START[][] = {
+        // Deficient  Normal     Complete
+        {    0,          0,          0  },          // (placeholder)
+        {   30,         30,         30  },          // Tishri
+        {   59,         59,         60  },          // Heshvan
+        {   88,         89,         90  },          // Kislev
+        {  117,        118,        119  },          // Tevet
+        {  147,        148,        149  },          // Shevat
+        {  147,        148,        149  },          // (Adar I placeholder)
+        {  176,        177,        178  },          // Adar
+        {  206,        207,        208  },          // Nisan
+        {  235,        236,        237  },          // Iyar
+        {  265,        266,        267  },          // Sivan
+        {  294,        295,        296  },          // Tammuz
+        {  324,        325,        326  },          // Av
+        {  353,        354,        355  },          // Elul
+    };
+
+    /**
+     * The cumulative # of days to the end of each month in a leap year
+     */
+    private static final int LEAP_MONTH_START[][] = {
+        // Deficient  Normal     Complete
+        {    0,          0,          0  },          // (placeholder)
+        {   30,         30,         30  },          // Tishri
+        {   59,         59,         60  },          // Heshvan
+        {   88,         89,         90  },          // Kislev
+        {  117,        118,        119  },          // Tevet
+        {  147,        148,        149  },          // Shevat
+        {  177,        178,        179  },          // Adar I
+        {  206,        207,        208  },          // Adar II
+        {  236,        237,        238  },          // Nisan
+        {  265,        266,        267  },          // Iyar
+        {  295,        296,        297  },          // Sivan
+        {  324,        325,        326  },          // Tammuz
+        {  354,        355,        356  },          // Av
+        {  383,        384,        385  },          // Elul
+    };
+
+    //-------------------------------------------------------------------------
+    // Data Members...
+    //-------------------------------------------------------------------------
+
+    private static CalendarCache cache = new CalendarCache();
+    
+    //-------------------------------------------------------------------------
+    // Constructors...
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constructs a default <code>HebrewCalendar</code> using the current time
+     * in the default time zone with the default locale.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar() {
+        this(TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> based on the current time
+     * in the given time zone with the default locale.
+     *
+     * @param zone The time zone for the new calendar.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(TimeZone zone) {
+        this(zone, Locale.getDefault());
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> based on the current time
+     * in the default time zone with the given locale.
+     *
+     * @param aLocale The locale for the new calendar.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(Locale aLocale) {
+        this(TimeZone.getDefault(), aLocale);
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> based on the current time
+     * in the given time zone with the given locale.
+     *
+     * @param zone The time zone for the new calendar.
+     *
+     * @param aLocale The locale for the new calendar.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(TimeZone zone, Locale aLocale) {
+        super(zone, aLocale);
+        setTimeInMillis(System.currentTimeMillis());
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.
+     *                  The value is 0-based. e.g., 0 for Tishri.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} time field.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(int year, int month, int date) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.set(YEAR, year);
+        this.set(MONTH, month);
+        this.set(DATE, date);
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param date      The date to which the new calendar is set.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(Date date) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.setTime(date);
+    }
+
+    /**
+     * Constructs a <code>HebrewCalendar</code> with the given date
+     * and time set for the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.
+     *                  The value is 0-based. e.g., 0 for Tishri.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} time field.
+     *
+     * @param hour      The value used to set the calendar's {@link #HOUR_OF_DAY HOUR_OF_DAY} time field.
+     *
+     * @param minute    The value used to set the calendar's {@link #MINUTE MINUTE} time field.
+     *
+     * @param second    The value used to set the calendar's {@link #SECOND SECOND} time field.
+     * @draft ICU 2.4
+     */
+    public HebrewCalendar(int year, int month, int date, int hour,
+                             int minute, int second)
+    {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.set(YEAR, year);
+        this.set(MONTH, month);
+        this.set(DATE, date);
+        this.set(HOUR_OF_DAY, hour);
+        this.set(MINUTE, minute);
+        this.set(SECOND, second);
+    }
+
+    //-------------------------------------------------------------------------
+    // Rolling and adding functions overridden from Calendar
+    //
+    // These methods call through to the default implementation in IBMCalendar
+    // for most of the fields and only handle the unusual ones themselves.
+    //-------------------------------------------------------------------------
+
+    /**
+     * Add a signed amount to a specified field, using this calendar's rules.
+     * For example, to add three days to the current date, you can call
+     * <code>add(Calendar.DATE, 3)</code>. 
+     * <p>
+     * When adding to certain fields, the values of other fields may conflict and
+     * need to be changed.  For example, when adding one to the {@link #MONTH MONTH} field
+     * for the date "30 Av 5758", the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
+     * must be adjusted so that the result is "29 Elul 5758" rather than the invalid
+     * "30 Elul 5758".
+     * <p>
+     * This method is able to add to
+     * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
+     * and {@link #ZONE_OFFSET ZONE_OFFSET}.
+     * <p>
+     * <b>Note:</b> You should always use {@link #roll roll} and add rather
+     * than attempting to perform arithmetic operations directly on the fields
+     * of a <tt>HebrewCalendar</tt>.  Since the {@link #MONTH MONTH} field behaves
+     * discontinuously in non-leap years, simple arithmetic can give invalid results.
+     * <p>
+     * @param field     the time field.
+     * @param amount    the amount to add to the field.
+     *
+     * @exception   IllegalArgumentException if the field is invalid or refers
+     *              to a field that cannot be handled by this method.
+     * @draft ICU 2.4
+     */
+    public void add(int field, int amount)
+    {
+        switch (field) {
+        case MONTH: 
+            {
+                // We can't just do a set(MONTH, get(MONTH) + amount).  The
+                // reason is ADAR_1.  Suppose amount is +2 and we land in
+                // ADAR_1 -- then we have to bump to ADAR_2 aka ADAR.  But
+                // if amount is -2 and we land in ADAR_1, then we have to
+                // bump the other way -- down to SHEVAT.  - Alan 11/00
+                int month = get(MONTH);
+                int year = get(YEAR);
+                boolean acrossAdar1;
+                if (amount > 0) {
+                    acrossAdar1 = (month < ADAR_1); // started before ADAR_1?
+                    month += amount;
+                    for (;;) {
+                        if (acrossAdar1 && month>=ADAR_1 && !isLeapYear(year)) {
+                            ++month;
+                        }
+                        if (month <= ELUL) {
+                            break;
+                        }
+                        month -= ELUL+1;
+                        ++year;
+                        acrossAdar1 = true;
+                    }
+                } else {
+                    acrossAdar1 = (month > ADAR_1); // started after ADAR_1?
+                    month += amount;
+                    for (;;) {
+                        if (acrossAdar1 && month<=ADAR_1 && !isLeapYear(year)) {
+                            --month;
+                        }
+                        if (month >= 0) {
+                            break;
+                        }
+                        month += ELUL+1;
+                        --year;
+                        acrossAdar1 = true;
+                    }
+                }
+                set(MONTH, month);
+                set(YEAR, year);
+                pinField(DAY_OF_MONTH);
+                break;
+            }
+            
+        default:
+            super.add(field, amount);
+            break;
+        }
+    }
+
+    /**
+     * Rolls (up/down) a specified amount time on the given field.  For
+     * example, to roll the current date up by three days, you can call
+     * <code>roll(Calendar.DATE, 3)</code>.  If the
+     * field is rolled past its maximum allowable value, it will "wrap" back
+     * to its minimum and continue rolling.  
+     * For example, calling <code>roll(Calendar.DATE, 10)</code>
+     * on a Hebrew calendar set to "25 Av 5758" will result in the date "5 Av 5758".
+     * <p>
+     * When rolling certain fields, the values of other fields may conflict and
+     * need to be changed.  For example, when rolling the {@link #MONTH MONTH} field
+     * upward by one for the date "30 Av 5758", the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
+     * must be adjusted so that the result is "29 Elul 5758" rather than the invalid
+     * "30 Elul".
+     * <p>
+     * This method is able to roll
+     * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
+     * and {@link #ZONE_OFFSET ZONE_OFFSET}.  Subclasses may, of course, add support for
+     * additional fields in their overrides of <code>roll</code>.
+     * <p>
+     * <b>Note:</b> You should always use roll and {@link #add add} rather
+     * than attempting to perform arithmetic operations directly on the fields
+     * of a <tt>HebrewCalendar</tt>.  Since the {@link #MONTH MONTH} field behaves
+     * discontinuously in non-leap years, simple arithmetic can give invalid results.
+     * <p>
+     * @param field     the time field.
+     * @param amount    the amount by which the field should be rolled.
+     *
+     * @exception   IllegalArgumentException if the field is invalid or refers
+     *              to a field that cannot be handled by this method.
+     * @draft ICU 2.4
+     */
+    public void roll(int field, int amount)
+    {
+        switch (field) {
+        case MONTH:
+            {
+                int month = get(MONTH);
+                int year = get(YEAR);
+                
+                boolean leapYear = isLeapYear(year);
+                int yearLength = monthsInYear(year);
+                int newMonth = month + (amount % yearLength);
+                //
+                // If it's not a leap year and we're rolling past the missing month
+                // of ADAR_1, we need to roll an extra month to make up for it.
+                //
+                if (!leapYear) {
+                    if (amount > 0 && month < ADAR_1 && newMonth >= ADAR_1) {
+                        newMonth++;
+                    } else if (amount < 0 && month > ADAR_1 && newMonth <= ADAR_1) {
+                        newMonth--;
+                    }
+                }
+                set(MONTH, (newMonth + 13) % 13);
+                pinField(DAY_OF_MONTH);
+                return;
+            }
+        default:
+            super.roll(field, amount);
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // Support methods
+    //-------------------------------------------------------------------------
+
+    // Hebrew date calculations are performed in terms of days, hours, and
+    // "parts" (or halakim), which are 1/1080 of an hour, or 3 1/3 seconds.
+    private static final long HOUR_PARTS = 1080;
+    private static final long DAY_PARTS  = 24*HOUR_PARTS;
+    
+    // An approximate value for the length of a lunar month.
+    // It is used to calculate the approximate year and month of a given
+    // absolute date.
+    static private final int  MONTH_DAYS = 29;
+    static private final long MONTH_FRACT = 12*HOUR_PARTS + 793;
+    static private final long MONTH_PARTS = MONTH_DAYS*DAY_PARTS + MONTH_FRACT;
+    
+    // The time of the new moon (in parts) on 1 Tishri, year 1 (the epoch)
+    // counting from noon on the day before.  BAHARAD is an abbreviation of
+    // Bet (Monday), Hey (5 hours from sunset), Resh-Daled (204).
+    static private final long BAHARAD = 11*HOUR_PARTS + 204;
+
+    /**
+     * Finds the day # of the first day in the given Hebrew year.
+     * To do this, we want to calculate the time of the Tishri 1 new moon
+     * in that year.
+     * <p>
+     * The algorithm here is similar to ones described in a number of
+     * references, including:
+     * <ul>
+     * <li>"Calendrical Calculations", by Nachum Dershowitz & Edward Reingold,
+     *     Cambridge University Press, 1997, pages 85-91.
+     *
+     * <li>Hebrew Calendar Science and Myths,
+     *     <a href="http://www.geocities.com/Athens/1584/">
+     *     http://www.geocities.com/Athens/1584/</a>
+     *
+     * <li>The Calendar FAQ,
+     *      <a href="http://www.faqs.org/faqs/calendars/faq/">
+     *      http://www.faqs.org/faqs/calendars/faq/</a>
+     * </ul>
+     */
+    private static long startOfYear(int year)
+    {
+        long day = cache.get(year);
+        
+        if (day == CalendarCache.EMPTY) {
+            int months = (235 * year - 234) / 19;           // # of months before year
+
+            long frac = months * MONTH_FRACT + BAHARAD;     // Fractional part of day #
+            day  = months * 29 + (frac / DAY_PARTS);        // Whole # part of calculation
+            frac = frac % DAY_PARTS;                        // Time of day
+
+            int wd = (int)(day % 7);                        // Day of week (0 == Monday)
+
+            if (wd == 2 || wd == 4 || wd == 6) {
+                // If the 1st is on Sun, Wed, or Fri, postpone to the next day
+                day += 1;
+                wd = (int)(day % 7);
+            }
+            if (wd == 1 && frac > 15*HOUR_PARTS+204 && !isLeapYear(year) ) {
+                // If the new moon falls after 3:11:20am (15h204p from the previous noon)
+                // on a Tuesday and it is not a leap year, postpone by 2 days.
+                // This prevents 356-day years.
+                day += 2;
+            }
+            else if (wd == 0 && frac > 21*HOUR_PARTS+589 && isLeapYear(year-1) ) {
+                // If the new moon falls after 9:32:43 1/3am (21h589p from yesterday noon)
+                // on a Monday and *last* year was a leap year, postpone by 1 day.
+                // Prevents 382-day years.
+                day += 1;
+            }
+            cache.put(year, day);
+        }
+        return day;
+    }
+
+    /**
+     * Find the day of the week for a given day
+     *
+     * @param day   The # of days since the start of the Hebrew calendar,
+     *              1-based (i.e. 1/1/1 AM is day 1).
+     */
+    ///CLOVER:OFF
+    private static int absoluteDayToDayOfWeek(long day)
+    {
+        // We know that 1/1/1 AM is a Monday, which makes the math easy...
+        return (int)(day % 7) + 1;
+    }
+    ///CLOVER:ON
+
+    /**
+     * Returns the the type of a given year.
+     *  0   "Deficient" year with 353 or 383 days
+     *  1   "Normal"    year with 354 or 384 days
+     *  2   "Complete"  year with 355 or 385 days
+     */
+    private final int yearType(int year)
+    {
+        int yearLength = handleGetYearLength(year);
+
+        if (yearLength > 380) {
+           yearLength -= 30;        // Subtract length of leap month.
+        }
+
+        int type = 0;
+
+        switch (yearLength) {
+            case 353:
+                type = 0; break;
+            case 354:
+                type = 1; break;
+            case 355:
+                type = 2; break;
+            default:
+                throw new RuntimeException("Illegal year length " + yearLength + " in year " + year);
+
+        }
+        return type;
+    }
+
+    /**
+     * Determine whether a given Hebrew year is a leap year
+     *
+     * The rule here is that if (year % 19) == 0, 3, 6, 8, 11, 14, or 17.
+     * The formula below performs the same test, believe it or not.
+     */
+    private static final boolean isLeapYear(int year) {
+        //return (year * 12 + 17) % 19 >= 12;
+        int x = (year*12 + 17) % 19;
+        return x >= ((x < 0) ? -7 : 12);
+    }
+
+    private static int monthsInYear(int year) {
+        return isLeapYear(year) ? 13 : 12;
+    }
+
+    //-------------------------------------------------------------------------
+    // Calendar framework
+    //-------------------------------------------------------------------------
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        return LIMITS[field][limitType];
+    }
+
+    /**
+     * Returns the length of the given month in the given year
+     * @draft ICU 2.4
+     */
+    protected int handleGetMonthLength(int extendedYear, int month) {
+
+        switch (month) {
+            case HESHVAN:
+            case KISLEV:
+                // These two month lengths can vary
+                return MONTH_LENGTH[month][yearType(extendedYear)];
+                
+            default:
+                // The rest are a fixed length
+                return MONTH_LENGTH[month][0];
+        }
+    }
+
+    /**
+     * Returns the number of days in the given Hebrew year
+     * @draft ICU 2.4
+     */
+    protected int handleGetYearLength(int eyear) {
+        return (int)(startOfYear(eyear+1) - startOfYear(eyear));
+    }
+
+    //-------------------------------------------------------------------------
+    // Functions for converting from milliseconds to field values
+    //-------------------------------------------------------------------------
+
+    /**
+     * Subclasses may override this method to compute several fields
+     * specific to each calendar system.  These are:
+     *
+     * <ul><li>ERA
+     * <li>YEAR
+     * <li>MONTH
+     * <li>DAY_OF_MONTH
+     * <li>DAY_OF_YEAR
+     * <li>EXTENDED_YEAR</ul>
+     * 
+     * Subclasses can refer to the DAY_OF_WEEK and DOW_LOCAL fields,
+     * which will be set when this method is called.  Subclasses can
+     * also call the getGregorianXxx() methods to obtain Gregorian
+     * calendar equivalents for the given Julian day.
+     *
+     * <p>In addition, subclasses should compute any subclass-specific
+     * fields, that is, fields from BASE_FIELD_COUNT to
+     * getFieldCount() - 1.
+     * @draft ICU 2.4
+     */
+    protected void handleComputeFields(int julianDay) {
+        long d = julianDay - 347997;
+        long m = (d * DAY_PARTS) / MONTH_PARTS;         // Months (approx)
+        int year = (int)((19 * m + 234) / 235) + 1;     // Years (approx)
+        long ys  = startOfYear(year);                   // 1st day of year
+        int dayOfYear = (int)(d - ys);
+
+        // Because of the postponement rules, it's possible to guess wrong.  Fix it.
+        while (dayOfYear < 1) {
+            year--;
+            ys  = startOfYear(year);
+            dayOfYear = (int)(d - ys);
+        }
+
+        // Now figure out which month we're in, and the date within that month
+        int yearType = yearType(year);
+        int monthStart[][] = isLeapYear(year) ? LEAP_MONTH_START : MONTH_START;
+
+        int month = 0;
+        while (dayOfYear > monthStart[month][yearType]) {
+            month++;
+        }
+        month--;
+        int dayOfMonth = dayOfYear - monthStart[month][yearType];
+
+        internalSet(ERA, 0);
+        internalSet(YEAR, year);
+        internalSet(EXTENDED_YEAR, year);
+        internalSet(MONTH, month);
+        internalSet(DAY_OF_MONTH, dayOfMonth);
+        internalSet(DAY_OF_YEAR, dayOfYear);       
+    }
+
+    //-------------------------------------------------------------------------
+    // Functions for converting from field values to milliseconds
+    //-------------------------------------------------------------------------
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleGetExtendedYear() {
+        int year;
+        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
+            year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
+        } else {
+            year = internalGet(YEAR, 1); // Default to year 1
+        }
+        return year;
+    }
+
+    /**
+     * Return JD of start of given month/year.
+     * @draft ICU 2.4
+     */
+    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
+
+        // Resolve out-of-range months.  This is necessary in order to
+        // obtain the correct year.  We correct to
+        // a 12- or 13-month year (add/subtract 12 or 13, depending
+        // on the year) but since we _always_ number from 0..12, and
+        // the leap year determines whether or not month 5 (Adar 1)
+        // is present, we allow 0..12 in any given year.
+        while (month < 0) {
+            month += monthsInYear(--eyear);
+        }
+        // Careful: allow 0..12 in all years
+        while (month > 12) {
+            month -= monthsInYear(eyear++);
+        }
+
+        long day = startOfYear(eyear);
+
+        if (month != 0) {
+            if (isLeapYear(eyear)) {
+                day += LEAP_MONTH_START[month][yearType(eyear)];
+            } else {
+                day += MONTH_START[month][yearType(eyear)];
+            }
+        }
+
+        return (int) (day + 347997);
+    }
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new HebrewCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Hebrew";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
diff --git a/src/com/ibm/icu/util/IslamicCalendar.java b/src/com/ibm/icu/util/IslamicCalendar.java
new file mode 100755
index 0000000..4f926c4
--- /dev/null
+++ b/src/com/ibm/icu/util/IslamicCalendar.java
@@ -0,0 +1,630 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2003, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/IslamicCalendar.java,v $ 
+ * $Date: 2003/09/04 01:00:58 $ 
+ * $Revision: 1.20 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <code>IslamicCalendar</code> is a subclass of <code>Calendar</code>
+ * that that implements the Islamic civil and religious calendars.  It
+ * is used as the civil calendar in most of the Arab world and the
+ * liturgical calendar of the Islamic faith worldwide.  This calendar
+ * is also known as the "Hijri" calendar, since it starts at the time
+ * of Mohammed's emigration (or "hijra") to Medinah on Thursday, 
+ * July 15, 622 AD (Julian).
+ * <p>
+ * The Islamic calendar is strictly lunar, and thus an Islamic year of twelve
+ * lunar months does not correspond to the solar year used by most other
+ * calendar systems, including the Gregorian.  An Islamic year is, on average,
+ * about 354 days long, so each successive Islamic year starts about 11 days
+ * earlier in the corresponding Gregorian year.
+ * <p>
+ * Each month of the calendar starts when the new moon's crescent is visible
+ * at sunset.  However, in order to keep the time fields in this class
+ * synchronized with those of the other calendars and with local clock time,
+ * we treat days and months as beginning at midnight,
+ * roughly 6 hours after the corresponding sunset.
+ * <p>
+ * There are two main variants of the Islamic calendar in existence.  The first
+ * is the <em>civil</em> calendar, which uses a fixed cycle of alternating 29-
+ * and 30-day months, with a leap day added to the last month of 11 out of
+ * every 30 years.  This calendar is easily calculated and thus predictable in
+ * advance, so it is used as the civil calendar in a number of Arab countries.
+ * This is the default behavior of a newly-created <code>IslamicCalendar</code>
+ * object.
+ * <p>
+ * The Islamic <em>religious</em> calendar, however, is based on the <em>observation</em>
+ * of the crescent moon.  It is thus affected by the position at which the
+ * observations are made, seasonal variations in the time of sunset, the
+ * eccentricities of the moon's orbit, and even the weather at the observation
+ * site.  This makes it impossible to calculate in advance, and it causes the
+ * start of a month in the religious calendar to differ from the civil calendar
+ * by up to three days.
+ * <p>
+ * Using astronomical calculations for the position of the sun and moon, the
+ * moon's illumination, and other factors, it is possible to determine the start
+ * of a lunar month with a fairly high degree of certainty.  However, these
+ * calculations are extremely complicated and thus slow, so most algorithms,
+ * including the one used here, are only approximations of the true astronical
+ * calculations.  At present, the approximations used in this class are fairly
+ * simplistic; they will be improved in later versions of the code.
+ * <p>
+ * The {@link #setCivil setCivil} method determines
+ * which approach is used to determine the start of a month.  By default, the
+ * fixed-cycle civil calendar is used.  However, if <code>setCivil(false)</code>
+ * is called, an approximation of the true lunar calendar will be used.
+ *
+ * @see com.ibm.icu.util.GregorianCalendar
+ *
+ * @author Laura Werner
+ * @author Alan Liu
+ * @draft ICU 2.4
+ */
+public class IslamicCalendar extends Calendar {
+
+    private static String copyright = "Copyright \u00a9 1997-1998 IBM Corp. All Rights Reserved.";
+
+    //-------------------------------------------------------------------------
+    // Constants...
+    //-------------------------------------------------------------------------
+    
+    /**
+     * Constant for Muharram, the 1st month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int MUHARRAM = 0;
+
+    /**
+     * Constant for Safar, the 2nd month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int SAFAR = 1;
+
+    /**
+     * Constant for Rabi' al-awwal (or Rabi' I), the 3rd month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int RABI_1 = 2;
+
+    /**
+     * Constant for Rabi' al-thani or (Rabi' II), the 4th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int RABI_2 = 3;
+
+    /**
+     * Constant for Jumada al-awwal or (Jumada I), the 5th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int JUMADA_1 = 4;
+
+    /**
+     * Constant for Jumada al-thani or (Jumada II), the 6th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int JUMADA_2 = 5;
+
+    /**
+     * Constant for Rajab, the 7th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int RAJAB = 6;
+
+    /**
+     * Constant for Sha'ban, the 8th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int SHABAN = 7;
+
+    /**
+     * Constant for Ramadan, the 9th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int RAMADAN = 8;
+
+    /**
+     * Constant for Shawwal, the 10th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int SHAWWAL = 9;
+
+    /**
+     * Constant for Dhu al-Qi'dah, the 11th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int DHU_AL_QIDAH = 10;
+
+    /**
+     * Constant for Dhu al-Hijjah, the 12th month of the Islamic year. 
+     * @draft ICU 2.4 
+     */
+    public static final int DHU_AL_HIJJAH = 11;
+
+
+    private static final long HIJRA_MILLIS = -42521587200000L;    // 7/16/622 AD 00:00
+
+    //-------------------------------------------------------------------------
+    // Constructors...
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constructs a default <code>IslamicCalendar</code> using the current time
+     * in the default time zone with the default locale.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar()
+    {
+        this(TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> based on the current time
+     * in the given time zone with the default locale.
+     * @param zone the given time zone.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(TimeZone zone)
+    {
+        this(zone, Locale.getDefault());
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> based on the current time
+     * in the default time zone with the given locale.
+     *
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(Locale aLocale)
+    {
+        this(TimeZone.getDefault(), aLocale);
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> based on the current time
+     * in the given time zone with the given locale.
+     *
+     * @param zone the given time zone.
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(TimeZone zone, Locale aLocale)
+    {
+        super(zone, aLocale);
+        setTimeInMillis(System.currentTimeMillis());
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param date      The date to which the new calendar is set.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(Date date) {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.setTime(date);
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
+     * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
+     *              Note that the month value is 0-based. e.g., 0 for Muharram.
+     * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(int year, int month, int date)
+    {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.set(Calendar.YEAR, year);
+        this.set(Calendar.MONTH, month);
+        this.set(Calendar.DATE, date);
+    }
+
+    /**
+     * Constructs an <code>IslamicCalendar</code> with the given date
+     * and time set for the default time zone with the default locale.
+     *
+     * @param year  the value used to set the {@link #YEAR YEAR} time field in the calendar.
+     * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
+     *              Note that the month value is 0-based. e.g., 0 for Muharram.
+     * @param date  the value used to set the {@link #DATE DATE} time field in the calendar.
+     * @param hour  the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
+     *              in the calendar.
+     * @param minute the value used to set the {@link #MINUTE MINUTE} time field
+     *              in the calendar.
+     * @param second the value used to set the {@link #SECOND SECOND} time field
+     *              in the calendar.
+     * @draft ICU 2.4
+     */
+    public IslamicCalendar(int year, int month, int date, int hour,
+                             int minute, int second)
+    {
+        super(TimeZone.getDefault(), Locale.getDefault());
+        this.set(Calendar.YEAR, year);
+        this.set(Calendar.MONTH, month);
+        this.set(Calendar.DATE, date);
+        this.set(Calendar.HOUR_OF_DAY, hour);
+        this.set(Calendar.MINUTE, minute);
+        this.set(Calendar.SECOND, second);
+    }
+
+    /**
+     * Determines whether this object uses the fixed-cycle Islamic civil calendar
+     * or an approximation of the religious, astronomical calendar.
+     *
+     * @param beCivil   <code>true</code> to use the civil calendar,
+     *                  <code>false</code> to use the astronomical calendar.
+     * @draft ICU 2.4
+     */
+    public void setCivil(boolean beCivil)
+    {
+        if (civil != beCivil) {
+            // The fields of the calendar will become invalid, because the calendar
+            // rules are different
+            long m = getTimeInMillis();
+            civil = beCivil;
+            clear();
+            setTimeInMillis(m);
+        }
+    }
+    
+    /**
+     * Returns <code>true</code> if this object is using the fixed-cycle civil
+     * calendar, or <code>false</code> if using the religious, astronomical
+     * calendar.
+     * @draft ICU 2.4
+     */
+    public boolean isCivil() {
+        return civil;
+    }
+    
+    //-------------------------------------------------------------------------
+    // Minimum / Maximum access functions
+    //-------------------------------------------------------------------------
+
+    private static final int LIMITS[][] = {
+        // Minimum  Greatest    Least  Maximum
+        //           Minimum  Maximum
+        {        0,        0,       0,       0 }, // ERA
+        {        1,        1, 5000000, 5000000 }, // YEAR
+        {        0,        0,      11,      11 }, // MONTH
+        {        1,        1,      51,      52 }, // WEEK_OF_YEAR
+        {        0,        0,       5,       6 }, // WEEK_OF_MONTH
+        {        1,        1,      29,      30 }, // DAY_OF_MONTH
+        {        1,        1,     354,     355 }, // DAY_OF_YEAR
+        {/*                                  */}, // DAY_OF_WEEK
+        {       -1,       -1,       4,       5 }, // DAY_OF_WEEK_IN_MONTH
+        {/*                                  */}, // AM_PM
+        {/*                                  */}, // HOUR
+        {/*                                  */}, // HOUR_OF_DAY
+        {/*                                  */}, // MINUTE
+        {/*                                  */}, // SECOND
+        {/*                                  */}, // MILLISECOND
+        {/*                                  */}, // ZONE_OFFSET
+        {/*                                  */}, // DST_OFFSET
+        { -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
+        {/*                                  */}, // DOW_LOCAL
+        { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
+        {/*                                  */}, // JULIAN_DAY
+        {/*                                  */}, // MILLISECONDS_IN_DAY
+    };
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        return LIMITS[field][limitType];
+    }
+
+    //-------------------------------------------------------------------------
+    // Assorted calculation utilities
+    //
+
+// Unused code - Alan 2003-05
+//    /**
+//     * Find the day of the week for a given day
+//     *
+//     * @param day   The # of days since the start of the Islamic calendar.
+//     */
+//    // private and uncalled, perhaps not used yet?
+//    ///CLOVER:OFF
+//    private static final int absoluteDayToDayOfWeek(long day)
+//    {
+//        // Calculate the day of the week.
+//        // This relies on the fact that the epoch was a Thursday.
+//        int dayOfWeek = (int)(day + THURSDAY) % 7 + SUNDAY;
+//        if (dayOfWeek < 0) {
+//            dayOfWeek += 7;
+//        }
+//        return dayOfWeek;
+//    }
+//    ///CLOVER:ON
+
+    /**
+     * Determine whether a year is a leap year in the Islamic civil calendar
+     */
+    private final static boolean civilLeapYear(int year)
+    {
+        return (14 + 11 * year) % 30 < 11;
+        
+    }
+    
+    /**
+     * Return the day # on which the given year starts.  Days are counted
+     * from the Hijri epoch, origin 0.
+     */
+    private long yearStart(int year) {
+        if (civil) {
+            return (year-1)*354 + (long)Math.floor((3+11*year)/30.0);
+        } else {
+            return trueMonthStart(12*(year-1));
+        }
+    }
+    
+    /**
+     * Return the day # on which the given month starts.  Days are counted
+     * from the Hijri epoch, origin 0.
+     *
+     * @param year  The hijri year
+     * @param year  The hijri month, 0-based
+     */
+    private long monthStart(int year, int month) {
+        if (civil) {
+            return (long)Math.ceil(29.5*month)
+                    + (year-1)*354 + (long)Math.floor((3+11*year)/30.0);
+        } else {
+            return trueMonthStart(12*(year-1) + month);
+        }
+    }
+    
+    /**
+     * Find the day number on which a particular month of the true/lunar
+     * Islamic calendar starts.
+     *
+     * @param month The month in question, origin 0 from the Hijri epoch
+     *
+     * @return The day number on which the given month starts.
+     */
+    private static final long trueMonthStart(long month)
+    {
+        long start = cache.get(month);
+
+        if (start == CalendarCache.EMPTY)
+        {
+            // Make a guess at when the month started, using the average length
+            long origin = HIJRA_MILLIS 
+                        + (long)Math.floor(month * CalendarAstronomer.SYNODIC_MONTH - 1) * ONE_DAY;
+
+            double age = moonAge(origin);
+
+            if (moonAge(origin) >= 0) {
+                // The month has already started
+                do {
+                    origin -= ONE_DAY;
+                    age = moonAge(origin);
+                } while (age >= 0);
+            }
+            else {
+                // Preceding month has not ended yet.
+                do {
+                    origin += ONE_DAY;
+                    age = moonAge(origin);
+                } while (age < 0);
+            }
+
+            start = (origin - HIJRA_MILLIS) / ONE_DAY + 1;
+            
+            cache.put(month, start);
+        }
+        return start;
+    }
+
+    /**
+     * Return the "age" of the moon at the given time; this is the difference
+     * in ecliptic latitude between the moon and the sun.  This method simply
+     * calls CalendarAstronomer.moonAge, converts to degrees, 
+     * and adjusts the resultto be in the range [-180, 180].
+     *
+     * @param time  The time at which the moon's age is desired,
+     *              in millis since 1/1/1970.
+     */
+    static final double moonAge(long time)
+    {
+        double age = 0;
+        
+        synchronized(astro) {
+            astro.setTime(time);
+            age = astro.getMoonAge();
+        }
+        // Convert to degrees and normalize...
+        age = age * 180 / Math.PI;
+        if (age > 180) {
+            age = age - 360;
+        }
+
+        return age;
+    }
+
+    //-------------------------------------------------------------------------
+    // Internal data....
+    //
+    
+    // And an Astronomer object for the moon age calculations
+    private static CalendarAstronomer astro = new CalendarAstronomer();
+    
+    private static CalendarCache cache = new CalendarCache();
+    
+    /**
+     * <code>true</code> if this object uses the fixed-cycle Islamic civil calendar,
+     * and <code>false</code> if it approximates the true religious calendar using
+     * astronomical calculations for the time of the new moon.
+     *
+     * @serial
+     */
+    private boolean civil = true;
+
+    //----------------------------------------------------------------------
+    // Calendar framework
+    //----------------------------------------------------------------------
+
+    /**
+     * Return the length (in days) of the given month.
+     *
+     * @param year  The hijri year
+     * @param year  The hijri month, 0-based
+     * @draft ICU 2.4
+     */
+    protected int handleGetMonthLength(int extendedYear, int month) {
+
+        int length = 0;
+        
+        if (civil) {
+            length = 29 + (month+1) % 2;
+            if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) {
+                length++;
+            }
+        } else {
+            month = 12*(extendedYear-1) + month;
+            length = (int)( trueMonthStart(month+1) - trueMonthStart(month) );
+        }
+        return length;
+    }
+
+    /**
+     * Return the number of days in the given Islamic year
+     * @draft ICU 2.4
+     */
+    protected int handleGetYearLength(int extendedYear) {
+        if (civil) {
+            return 354 + (civilLeapYear(extendedYear) ? 1 : 0);
+        } else {
+            int month = 12*(extendedYear-1);
+            return (int)(trueMonthStart(month + 12) - trueMonthStart(month));
+        }
+    }
+    
+    //-------------------------------------------------------------------------
+    // Functions for converting from field values to milliseconds....
+    //-------------------------------------------------------------------------
+
+    // Return JD of start of given month/year
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
+        return (int) monthStart(eyear, month) + 1948439;
+    }    
+
+    //-------------------------------------------------------------------------
+    // Functions for converting from milliseconds to field values
+    //-------------------------------------------------------------------------
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleGetExtendedYear() {
+        int year;
+        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
+            year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
+        } else {
+            year = internalGet(YEAR, 1); // Default to year 1
+        }
+        return year;
+    }
+
+    /**
+     * Override Calendar to compute several fields specific to the Islamic
+     * calendar system.  These are:
+     *
+     * <ul><li>ERA
+     * <li>YEAR
+     * <li>MONTH
+     * <li>DAY_OF_MONTH
+     * <li>DAY_OF_YEAR
+     * <li>EXTENDED_YEAR</ul>
+     * 
+     * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
+     * method is called. The getGregorianXxx() methods return Gregorian
+     * calendar equivalents for the given Julian day.
+     * @draft ICU 2.4
+     */
+    protected void handleComputeFields(int julianDay) {
+        int year, month, dayOfMonth, dayOfYear;
+        long monthStart;
+        long days = julianDay - 1948440;
+
+        if (civil) {
+            // Use the civil calendar approximation, which is just arithmetic
+            year  = (int)Math.floor( (30 * days + 10646) / 10631.0 );
+            month = (int)Math.ceil((days - 29 - yearStart(year)) / 29.5 );
+            month = Math.min(month, 11);
+            monthStart = monthStart(year, month);
+        } else {
+            // Guess at the number of elapsed full months since the epoch
+            int months = (int)Math.floor(days / CalendarAstronomer.SYNODIC_MONTH);
+
+            monthStart = (long)Math.floor(months * CalendarAstronomer.SYNODIC_MONTH - 1);
+
+            if ( days - monthStart >= 28 && moonAge(internalGetTimeInMillis()) > 0) {
+                // If we're near the end of the month, assume next month and search backwards
+                months++;
+            }
+
+            // Find out the last time that the new moon was actually visible at this longitude
+            // This returns midnight the night that the moon was visible at sunset.
+            while ((monthStart = trueMonthStart(months)) > days) {
+                // If it was after the date in question, back up a month and try again
+                months--;
+            }
+
+            year = months / 12 + 1;
+            month = months % 12;
+        }
+
+        dayOfMonth = (int)(days - monthStart(year, month)) + 1;
+
+        // Now figure out the day of the year.
+        dayOfYear = (int)(days - monthStart(year, 0) + 1);
+
+        internalSet(ERA, 0);
+        internalSet(YEAR, year);
+        internalSet(EXTENDED_YEAR, year);
+        internalSet(MONTH, month);
+        internalSet(DAY_OF_MONTH, dayOfMonth);
+        internalSet(DAY_OF_YEAR, dayOfYear);       
+    }    
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new IslamicCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Islamic";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
diff --git a/src/com/ibm/icu/util/JapaneseCalendar.java b/src/com/ibm/icu/util/JapaneseCalendar.java
new file mode 100755
index 0000000..8d77c48
--- /dev/null
+++ b/src/com/ibm/icu/util/JapaneseCalendar.java
@@ -0,0 +1,588 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2000, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/JapaneseCalendar.java,v $ 
+ * $Date: 2003/09/04 01:00:59 $ 
+ * $Revision: 1.14 $
+ *
+ *****************************************************************************************
+ */
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <code>JapaneseCalendar</code> is a subclass of <code>GregorianCalendar</code>
+ * that numbers years and eras based on the reigns of the Japanese emperors.
+ * The Japanese calendar is identical to the Gregorian calendar in all respects
+ * except for the year and era.  The ascension of each  emperor to the throne
+ * begins a new era, and the years of that era are numbered starting with the
+ * year of ascension as year 1.
+ * <p>
+ * Note that in the year of an imperial ascension, there are two possible sets
+ * of year and era values: that for the old era and for the new.  For example, a
+ * new era began on January 7, 1989 AD.  Strictly speaking, the first six days
+ * of that year were in the Showa era, e.g. "January 6, 64 Showa", while the rest
+ * of the year was in the Heisei era, e.g. "January 7, 1 Heisei".  This class
+ * handles this distinction correctly when computing dates.  However, in lenient
+ * mode either form of date is acceptable as input. 
+ * <p>
+ * In modern times, eras have started on January 8, 1868 AD, Gregorian (Meiji),
+ * July 30, 1912 (Taisho), December 25, 1926 (Showa), and January 7, 1989 (Heisei).  Constants
+ * for these eras, suitable for use in the <code>ERA</code> field, are provided
+ * in this class.  Note that the <em>number</em> used for each era is more or
+ * less arbitrary.  Currently, the era starting in 1053 AD is era #0; however this
+ * may change in the future as we add more historical data.  Use the predefined
+ * constants rather than using actual, absolute numbers.
+ * <p>
+ *
+ * @see com.ibm.icu.util.GregorianCalendar
+ *
+ * @author Laura Werner
+ * @author Alan Liu
+ * @draft ICU 2.4
+ */
+public class JapaneseCalendar extends GregorianCalendar {
+    
+    private static String copyright = "Copyright \u00a9 1998 IBM Corp. All Rights Reserved.";
+
+    //-------------------------------------------------------------------------
+    // Constructors...
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constructs a default <code>JapaneseCalendar</code> using the current time
+     * in the default time zone with the default locale.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar() {
+        super();
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> based on the current time
+     * in the given time zone with the default locale.
+     * @param zone the given time zone.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(TimeZone zone) {
+        super(zone);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> based on the current time
+     * in the default time zone with the given locale.
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(Locale aLocale) {
+        super(aLocale);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> based on the current time
+     * in the given time zone with the given locale.
+     *
+     * @param zone the given time zone.
+     *
+     * @param aLocale the given locale.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(TimeZone zone, Locale aLocale) {
+        super(zone, aLocale);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param date      The date to which the new calendar is set.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(Date date) {
+        this();
+        setTime(date);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param era       The imperial era used to set the calendar's {@link #ERA ERA} field.
+     *                  Eras are numbered starting with the Tenki era, which
+     *                  began in 1053 AD Gregorian, as era zero.  Recent
+     *                  eras can be specified using the constants
+     *                  {@link #MEIJI} (which started in 1868 AD),
+     *                  {@link #TAISHO} (1912 AD),
+     *                  {@link #SHOWA} (1926 AD), and
+     *                  {@link #HEISEI} (1989 AD).
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} field,
+     *                  in terms of the era.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} field.
+     *                  The value is 0-based. e.g., 0 for January.
+     *
+     * @param date      The value used to set the calendar's DATE field.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(int era, int year, int month, int date) {
+        super(year, month, date);
+        set(ERA, era);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> with the given date set
+     * in the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} field,
+     *                  in the era Heisei, the most current at the time this
+     *                  class was last updated.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} field.
+     *                  The value is 0-based. e.g., 0 for January.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} field.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(int year, int month, int date) {
+        super(year, month, date);
+        set(ERA, CURRENT_ERA);
+    }
+
+    /**
+     * Constructs a <code>JapaneseCalendar</code> with the given date
+     * and time set for the default time zone with the default locale.
+     *
+     * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field,
+     *                  in the era Heisei, the most current at the time of this
+     *                  writing.
+     *
+     * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.
+     *                  The value is 0-based. e.g., 0 for January.
+     *
+     * @param date      The value used to set the calendar's {@link #DATE DATE} time field.
+     *
+     * @param hour      The value used to set the calendar's {@link #HOUR_OF_DAY HOUR_OF_DAY} time field.
+     *
+     * @param minute    The value used to set the calendar's {@link #MINUTE MINUTE} time field.
+     *
+     * @param second    The value used to set the calendar's {@link #SECOND SECOND} time field.
+     * @draft ICU 2.4
+     */
+    public JapaneseCalendar(int year, int month, int date, int hour,
+                             int minute, int second)
+    {
+        super(year, month, date, hour, minute, second);
+        set(ERA, CURRENT_ERA);
+    }
+
+    //-------------------------------------------------------------------------
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected int handleGetExtendedYear() {
+        int year;
+        // TODO reimplement this to be faster?
+        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR &&
+            newerField(EXTENDED_YEAR, ERA) == EXTENDED_YEAR) {
+            year = internalGet(EXTENDED_YEAR, 1);
+        } else {
+            // Subtract one because year starts at 1
+            year = internalGet(YEAR) + ERAS[internalGet(ERA) * 3] - 1;
+        }
+        return year;
+    }
+
+    /**
+     * @draft ICU 2.4
+     */
+    protected void handleComputeFields(int julianDay) {
+        super.handleComputeFields(julianDay);
+        int year = internalGet(EXTENDED_YEAR);
+
+        int low = 0;
+
+        // Short circuit for recent years.  Most modern computations will
+        // occur in the current era and won't require the binary search.
+        // Note that if the year is == the current era year, then we use
+        // the binary search to handle the month/dom comparison.
+        if (year > ERAS[ERAS.length - 3]) {
+            low = CURRENT_ERA;
+        } else {
+            // Binary search
+            int high = ERAS.length / 3;
+        
+            while (low < high - 1) {
+                int i = (low + high) / 2;
+                int diff = year - ERAS[i*3];
+
+                // If years are the same, then compare the months, and if those
+                // are the same, compare days of month.  In the ERAS array
+                // months are 1-based for easier maintenance.
+                if (diff == 0) {
+                    diff = internalGet(MONTH) - (ERAS[i*3 + 1] - 1);
+                    if (diff == 0) {
+                        diff = internalGet(DAY_OF_MONTH) - ERAS[i*3 + 2];
+                    }
+                }
+                if (diff >= 0) {
+                    low = i;
+                } else {
+                    high = i;
+                }
+            }
+        }
+
+        // Now we've found the last era that starts before this date, so
+        // adjust the year to count from the start of that era.  Note that
+        // all dates before the first era will fall into the first era by
+        // the algorithm.
+        internalSet(ERA, low);
+        internalSet(YEAR, year - ERAS[low*3] + 1);
+    }
+
+    private static final int[] ERAS = {
+    //  Gregorian date of each emperor's ascension
+    //  Years are AD, months are 1-based.
+    //  Year  Month Day
+         645,    6, 19,     // Taika
+         650,    2, 15,     // Hakuchi
+         672,    1,  1,     // Hakuho
+         686,    7, 20,     // Shucho
+         701,    3, 21,     // Taiho
+         704,    5, 10,     // Keiun
+         708,    1, 11,     // Wado
+         715,    9,  2,     // Reiki
+         717,   11, 17,     // Yoro
+         724,    2,  4,     // Jinki
+         729,    8,  5,     // Tempyo
+         749,    4, 14,     // Tempyo-kampo
+         749,    7,  2,     // Tempyo-shoho
+         757,    8, 18,     // Tempyo-hoji
+         765,    1,  7,     // Tempho-jingo
+         767,    8, 16,     // Jingo-keiun
+         770,   10,  1,     // Hoki
+         781,    1,  1,     // Ten-o
+         782,    8, 19,     // Enryaku
+         806,    5, 18,     // Daido
+         810,    9, 19,     // Konin
+         824,    1,  5,     // Tencho
+         834,    1,  3,     // Showa
+         848,    6, 13,     // Kajo
+         851,    4, 28,     // Ninju
+         854,   11, 30,     // Saiko
+         857,    2, 21,     // Tennan
+         859,    4, 15,     // Jogan
+         877,    4, 16,     // Genkei
+         885,    2, 21,     // Ninna
+         889,    4, 27,     // Kampyo
+         898,    4, 26,     // Shotai
+         901,    7, 15,     // Engi
+         923,    4, 11,     // Encho
+         931,    4, 26,     // Shohei
+         938,    5, 22,     // Tengyo
+         947,    4, 22,     // Tenryaku
+         957,   10, 27,     // Tentoku
+         961,    2, 16,     // Owa
+         964,    7, 10,     // Koho
+         968,    8, 13,     // Anna
+         970,    3, 25,     // Tenroku
+         973,   12, 20,     // Ten-en
+         976,    7, 13,     // Jogen
+         978,   11, 29,     // Tengen
+         983,    4, 15,     // Eikan
+         985,    4, 27,     // Kanna
+         987,    4,  5,     // Ei-en
+         989,    8,  8,     // Eiso
+         990,   11,  7,     // Shoryaku
+         995,    2, 22,     // Chotoku
+         999,    1, 13,     // Choho
+        1004,    7, 20,     // Kanko
+        1012,   12, 25,     // Chowa
+        1017,    4, 23,     // Kannin
+        1021,    2,  2,     // Jian
+        1024,    7, 13,     // Manju
+        1028,    7, 25,     // Chogen
+        1037,    4, 21,     // Choryaku
+        1040,   11, 10,     // Chokyu
+        1044,   11, 24,     // Kantoku
+        1046,    4, 14,     // Eisho
+        1053,    1, 11,     // Tengi
+        1058,    8, 29,     // Kohei
+        1065,    8,  2,     // Jiryaku
+        1069,    4, 13,     // Enkyu
+        1074,    8, 23,     // Shoho
+        1077,   11, 17,     // Shoryaku
+        1081,    2, 10,     // Eiho
+        1084,    2,  7,     // Otoku
+        1087,    4,  7,     // Kanji
+        1094,   12, 15,     // Kaho
+        1096,   12, 17,     // Eicho
+        1097,   11, 21,     // Shotoku
+        1099,    8, 28,     // Kowa
+        1104,    2, 10,     // Choji
+        1106,    4,  9,     // Kasho
+        1108,    8,  3,     // Tennin
+        1110,    7, 13,     // Ten-ei
+        1113,    7, 13,     // Eikyu
+        1118,    4,  3,     // Gen-ei
+        1120,    4, 10,     // Hoan
+        1124,    4,  3,     // Tenji
+        1126,    1, 22,     // Daiji
+        1131,    1, 29,     // Tensho
+        1132,    8, 11,     // Chosho
+        1135,    4, 27,     // Hoen
+        1141,    7, 10,     // Eiji
+        1142,    4, 28,     // Koji
+        1144,    2, 23,     // Tenyo
+        1145,    7, 22,     // Kyuan
+        1151,    1, 26,     // Ninpei
+        1154,   10, 28,     // Kyuju
+        1156,    4, 27,     // Hogen
+        1159,    4, 20,     // Heiji
+        1160,    1, 10,     // Eiryaku
+        1161,    9,  4,     // Oho
+        1163,    3, 29,     // Chokan
+        1165,    6,  5,     // Eiman
+        1166,    8, 27,     // Nin-an
+        1169,    4,  8,     // Kao
+        1171,    4, 21,     // Shoan
+        1175,    7, 28,     // Angen
+        1177,    8,  4,     // Jisho
+        1181,    7, 14,     // Yowa
+        1182,    5, 27,     // Juei
+        1184,    4, 16,     // Genryuku
+        1185,    8, 14,     // Bunji
+        1190,    4, 11,     // Kenkyu
+        1199,    4, 27,     // Shoji
+        1201,    2, 13,     // Kennin
+        1204,    2, 20,     // Genkyu
+        1206,    4, 27,     // Ken-ei
+        1207,   10, 25,     // Shogen
+        1211,    3,  9,     // Kenryaku
+        1213,   12,  6,     // Kenpo
+        1219,    4, 12,     // Shokyu
+        1222,    4, 13,     // Joo
+        1224,   11, 20,     // Gennin
+        1225,    4, 20,     // Karoku
+        1227,   12, 10,     // Antei
+        1229,    3,  5,     // Kanki
+        1232,    4,  2,     // Joei
+        1233,    4, 15,     // Tempuku
+        1234,   11,  5,     // Bunryaku
+        1235,    9, 19,     // Katei
+        1238,   11, 23,     // Ryakunin
+        1239,    2,  7,     // En-o
+        1240,    7, 16,     // Ninji
+        1243,    2, 26,     // Kangen
+        1247,    2, 28,     // Hoji
+        1249,    3, 18,     // Kencho
+        1256,   10,  5,     // Kogen
+        1257,    3, 14,     // Shoka
+        1259,    3, 26,     // Shogen
+        1260,    4, 13,     // Bun-o
+        1261,    2, 20,     // Kocho
+        1264,    2, 28,     // Bun-ei
+        1275,    4, 25,     // Kenji
+        1278,    2, 29,     // Koan
+        1288,    4, 28,     // Shoo
+        1293,    8, 55,     // Einin
+        1299,    4, 25,     // Shoan
+        1302,   11, 21,     // Kengen
+        1303,    8,  5,     // Kagen
+        1306,   12, 14,     // Tokuji
+        1308,   10,  9,     // Enkei
+        1311,    4, 28,     // Ocho
+        1312,    3, 20,     // Showa
+        1317,    2,  3,     // Bunpo
+        1319,    4, 28,     // Geno
+        1321,    2, 23,     // Genkyo
+        1324,   12,  9,     // Shochu
+        1326,    4, 26,     // Kareki
+        1329,    8, 29,     // Gentoku
+        1331,    8,  9,     // Genko
+        1334,    1, 29,     // Kemmu
+        1336,    2, 29,     // Engen
+        1340,    4, 28,     // Kokoku
+        1346,   12,  8,     // Shohei
+        1370,    7, 24,     // Kentoku
+        1372,    4,  1,     // Bunch\u0169
+        1375,    5, 27,     // Tenju
+        1381,    2, 10,     // Kowa
+        1384,    4, 28,     // Gench\u0169
+        1384,    2, 27,     // Meitoku
+        1379,    3, 22,     // Koryaku
+        1387,    8, 23,     // Kakei
+        1389,    2,  9,     // Koo
+        1390,    3, 26,     // Meitoku
+        1394,    7,  5,     // Oei
+        1428,    4, 27,     // Shocho
+        1429,    9,  5,     // Eikyo
+        1441,    2, 17,     // Kakitsu
+        1444,    2,  5,     // Bun-an
+        1449,    7, 28,     // Hotoku
+        1452,    7, 25,     // Kyotoku
+        1455,    7, 25,     // Kosho
+        1457,    9, 28,     // Choroku
+        1460,   12, 21,     // Kansho
+        1466,    2, 28,     // Bunsho
+        1467,    3,  3,     // Onin
+        1469,    4, 28,     // Bunmei
+        1487,    7, 29,     // Chokyo
+        1489,    8, 21,     // Entoku
+        1492,    7, 19,     // Meio
+        1501,    2, 29,     // Bunki
+        1504,    2, 30,     // Eisho
+        1521,    8, 23,     // Taiei
+        1528,    8, 20,     // Kyoroku
+        1532,    7, 29,     // Tenmon
+        1555,   10, 23,     // Koji
+        1558,    2, 28,     // Eiroku
+        1570,    4, 23,     // Genki
+        1573,    7, 28,     // Tensho
+        1592,   12,  8,     // Bunroku
+        1596,   10, 27,     // Keicho
+        1615,    7, 13,     // Genwa
+        1624,    2, 30,     // Kan-ei
+        1644,   12, 16,     // Shoho
+        1648,    2, 15,     // Keian
+        1652,    9, 18,     // Shoo
+        1655,    4, 13,     // Meiryaku
+        1658,    7, 23,     // Manji
+        1661,    4, 25,     // Kanbun
+        1673,    9, 21,     // Enpo
+        1681,    9, 29,     // Tenwa
+        1684,    2, 21,     // Jokyo
+        1688,    9, 30,     // Genroku
+        1704,    3, 13,     // Hoei
+        1711,    4, 25,     // Shotoku
+        1716,    6, 22,     // Kyoho
+        1736,    4, 28,     // Genbun
+        1741,    2, 27,     // Kanpo
+        1744,    2, 21,     // Enkyo
+        1748,    7, 12,     // Kan-en
+        1751,   10, 27,     // Horyaku
+        1764,    6,  2,     // Meiwa
+        1772,   11, 16,     // An-ei
+        1781,    4,  2,     // Tenmei
+        1789,    1, 25,     // Kansei
+        1801,    2,  5,     // Kyowa
+        1804,    2, 11,     // Bunka
+        1818,    4, 22,     // Bunsei
+        1830,   12, 10,     // Tenpo
+        1844,   12,  2,     // Koka
+        1848,    2, 28,     // Kaei
+        1854,   11, 27,     // Ansei
+        1860,    3, 18,     // Man-en
+        1861,    2, 19,     // Bunkyu
+        1864,    2, 20,     // Genji
+        1865,    4,  7,     // Keio
+        1868,    9,  8,     // Meiji
+        1912,    7, 30,     // Taisho
+        1926,   12, 25,     // Showa
+        1989,    1,  8,     // Heisei
+    };
+
+    //-------------------------------------------------------------------------
+    // Public constants for some of the recent eras that folks might use...
+    //-------------------------------------------------------------------------
+
+    // Constant for the current era.  This must be regularly updated.
+    /**
+     * @draft ICU 2.4
+     */
+    static public final int CURRENT_ERA = (ERAS.length / 3) - 1;
+    
+    /** 
+     * Constant for the era starting on Sept. 8, 1868 AD.
+     * @draft ICU 2.4 
+     */
+    static public final int MEIJI = CURRENT_ERA - 3;
+
+    /** 
+     * Constant for the era starting on July 30, 1912 AD. 
+     * @draft ICU 2.4 
+     */
+    static public final int TAISHO = CURRENT_ERA - 2;
+    
+    /** 
+     * Constant for the era starting on Dec. 25, 1926 AD. 
+     * @draft ICU 2.4 
+     */
+    static public final int SHOWA = CURRENT_ERA - 1;
+
+    /** 
+     * Constant for the era starting on Jan. 7, 1989 AD. 
+     * @draft ICU 2.4 
+     */
+    static public final int HEISEI = CURRENT_ERA;
+
+    /**
+     * Partial limits table for limits that differ from GregorianCalendar's.
+     * The YEAR max limits are filled in the first time they are needed.
+     */
+    private static int LIMITS[][] = {
+        // Minimum  Greatest        Least      Maximum
+        //           Minimum      Maximum
+        {        0,        0, CURRENT_ERA, CURRENT_ERA }, // ERA
+        {        1,        1,           0,           0 }, // YEAR
+    };
+
+    private static boolean YEAR_LIMIT_KNOWN = false;
+
+    /**
+     * Override GregorianCalendar.  We should really handle YEAR_WOY and
+     * EXTENDED_YEAR here too to implement the 1..5000000 range, but it's
+     * not critical.
+     * @draft ICU 2.4
+     */
+    protected int handleGetLimit(int field, int limitType) {
+        switch (field) {
+        case ERA:
+            return LIMITS[field][limitType];
+        case YEAR:
+            if (!YEAR_LIMIT_KNOWN) {
+                int min = ERAS[3] - ERAS[0];
+                int max = min;
+                for (int i=6; i<ERAS.length; i+=3) {
+                    int d = ERAS[i] - ERAS[i-3];
+                    if (d < min) {
+                        min = d;
+                    } else if (d > max) {
+                        max = d;
+                    }
+                }
+                LIMITS[field][LEAST_MAXIMUM] = min;
+                LIMITS[field][MAXIMUM] = max;
+            }
+            return LIMITS[field][limitType];
+        default:
+            return super.handleGetLimit(field, limitType);
+        }
+    }
+
+    /*
+    private static CalendarFactory factory;
+    public static CalendarFactory factory() {
+        if (factory == null) {
+            factory = new CalendarFactory() {
+                public Calendar create(TimeZone tz, Locale loc) {
+                    return new JapaneseCalendar(tz, loc);
+                }
+
+                public String factoryName() {
+                    return "Japanese";
+                }
+            };
+        }
+        return factory;
+    }
+    */
+}
diff --git a/src/com/ibm/icu/util/SimpleDateRule.java b/src/com/ibm/icu/util/SimpleDateRule.java
new file mode 100755
index 0000000..d69f7f8
--- /dev/null
+++ b/src/com/ibm/icu/util/SimpleDateRule.java
@@ -0,0 +1,238 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2000, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ *
+ * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/SimpleDateRule.java,v $ 
+ * $Date: 2003/09/04 01:00:59 $ 
+ * $Revision: 1.9 $
+ *
+ *****************************************************************************************
+ */
+
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.SimpleTimeZone;
+
+/**
+ * Simple implementation of DateRule.
+ * @draft ICU 2.2
+ */
+public class SimpleDateRule implements DateRule
+{
+    /**
+     * Construct a rule for a fixed date within a month
+     *
+     * @param month         The month in which this rule occurs (0-based).
+     * @param dayOfMonth    The date in that month (1-based).
+     * @draft ICU 2.2
+     */
+    public SimpleDateRule(int month, int dayOfMonth)
+    {
+        this.month      = month;
+        this.dayOfMonth = dayOfMonth;
+        this.dayOfWeek  = 0;
+    }
+
+    /**
+     * Construct a rule for a weekday within a month, e.g. the first Monday.
+     *
+     * @param month         The month in which this rule occurs (0-based).
+     * @param dayOfMonth    A date within that month (1-based).
+     * @param dayOfWeek     The day of the week on which this rule occurs.
+     * @param after         If true, this rule selects the first dayOfWeek
+     *                      on or after dayOfMonth.  If false, the rule selects
+     *                      the first dayOfWeek on or before dayOfMonth.
+     * @draft ICU 2.2
+     */
+    public SimpleDateRule(int month, int dayOfMonth, int dayOfWeek, boolean after)
+    {
+        this.month      = month;
+        this.dayOfMonth = dayOfMonth;
+        this.dayOfWeek  = after ? dayOfWeek : -dayOfWeek;
+    }
+
+    /**
+     * Return the first occurrance of the event represented by this rule
+     * that is on or after the given start date.
+     *
+     * @param start Only occurrances on or after this date are returned.
+     *
+     * @return      The date on which this event occurs, or null if it
+     *              does not occur on or after the start date.
+     *
+     * @see #firstBetween
+     * @draft ICU 2.2
+     */
+    public Date firstAfter(Date start)
+    {
+        if (startDate != null && start.before(startDate)) {
+            start = startDate;
+        }
+        return doFirstBetween(start, endDate);
+    }
+
+    /**
+     * Return the first occurrance of the event represented by this rule
+     * that is on or after the given start date and before the given
+     * end date.
+     *
+     * @param start Only occurrances on or after this date are returned.
+     * @param end   Only occurrances before this date are returned.
+     *
+     * @return      The date on which this event occurs, or null if it
+     *              does not occur between the start and end dates.
+     *
+     * @see #firstAfter
+     * @draft ICU 2.2
+     */
+    public Date firstBetween(Date start, Date end)
+    {
+        // Pin to the min/max dates for this rule
+        if (startDate != null && start.before(startDate)) {
+            start = startDate;
+        }
+        if (endDate != null && end.after(endDate)) {
+            end = endDate;
+        }
+        return doFirstBetween(start, end);
+    }
+
+    /**
+     * Checks whether this event occurs on the given date.  This does
+     * <em>not</em> take time of day into account; instead it checks
+     * whether this event and the given date are on the same day.
+     * This is useful for applications such as determining whether a given
+     * day is a holiday.
+     *
+     * @param date  The date to check.
+     * @return      true if this event occurs on the given date.
+     * @draft ICU 2.2
+     *
+     */
+    public boolean isOn(Date date)
+    {
+        if (startDate != null && date.before(startDate)) {
+            return false;
+        }
+        if (endDate != null && date.after(endDate)) {
+            return false;
+        }
+
+        Calendar c = calendar;
+
+        synchronized(c) {
+            c.setTime(date);
+
+            int dayOfYear = c.get(Calendar.DAY_OF_YEAR);
+
+            c.setTime(computeInYear(c.get(Calendar.YEAR), c));
+
+            //System.out.println("  isOn: dayOfYear = " + dayOfYear);
+            //System.out.println("        holiday   = " + c.get(Calendar.DAY_OF_YEAR));
+
+            return c.get(Calendar.DAY_OF_YEAR) == dayOfYear;
+        }
+    }
+
+    /**
+     * Check whether this event occurs at least once between the two
+     * dates given.
+     * @draft ICU 2.2
+     */
+    public boolean isBetween(Date start, Date end)
+    {
+        return firstBetween(start, end) != null; // TODO: optimize?
+    }
+
+    private Date doFirstBetween(Date start, Date end)
+    {
+        Calendar c = calendar;
+
+        synchronized(c) {
+            c.setTime(start);
+
+            int year = c.get(Calendar.YEAR);
+            int month = c.get(Calendar.MONTH);
+
+            // If the rule is earlier in the year than the start date
+            // we have to go to the next year.
+            if (month > this.month) {
+                year++;
+            }
+
+            // Figure out when the rule lands in the given year
+            Date result = computeInYear(year, c);
+
+            // If the rule is in the same month as the start date, it's possible
+            // to get a result that's before the start.  If so, go to next year.
+            if (month == this.month && result.before(start)) {
+                result = computeInYear(year+1, c);
+            }
+
+            if (end != null && result.after(end)) {
+                return null;
+            }
+            return result;
+        }
+    }
+
+    private Date computeInYear(int year, Calendar c)
+    {
+        if (c == null) c = calendar;
+
+        synchronized(c) {
+            c.clear();
+            c.set(Calendar.ERA, c.getMaximum(Calendar.ERA));
+            c.set(Calendar.YEAR, year);
+            c.set(Calendar.MONTH, month);
+            c.set(Calendar.DATE, dayOfMonth);
+
+            //System.out.println("     computeInYear: start at " + c.getTime().toString());
+
+            if (dayOfWeek != 0) {
+                c.setTime(c.getTime());        // JDK 1.1.2 workaround
+                int weekday = c.get(Calendar.DAY_OF_WEEK);
+
+                //System.out.println("                    weekday = " + weekday);
+                //System.out.println("                    dayOfYear = " + c.get(Calendar.DAY_OF_YEAR));
+
+                int delta = 0;
+                if (dayOfWeek > 0) {
+                    // We want the first occurrance of the given day of the week
+                    // on or after the specified date in the month.
+                    delta = (dayOfWeek - weekday + 7) % 7;
+                }
+                else if (dayOfWeek < 0) {
+                    // We want the first occurrance of the (-dayOfWeek)
+                    // on or before the specified date in the month.
+                    delta = -((dayOfWeek + weekday + 7) % 7);
+                }
+                //System.out.println("                    adding " + delta + " days");
+                c.add(Calendar.DATE, delta);
+            }
+
+            return c.getTime();
+        }
+    }
+
+    /**
+     * @draft ICU 2.2
+     */
+    public void setCalendar(Calendar c) {
+        calendar = c;
+    }
+
+    static GregorianCalendar gCalendar = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
+
+    Calendar calendar = gCalendar;
+
+    private int     month;
+    private int     dayOfMonth;
+    private int     dayOfWeek;
+
+    private Date    startDate = null;
+    private Date    endDate = null;
+};