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,
+ * < 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 < 24:00 on Jan 1, 1970 < 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) < 12:01 am, and 12:00 pm (noon) < 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 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>=></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>=></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 "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} 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;
+};