From 8158ae5725c95cf5248bee9a9cb6801b5e04ebeb Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Thu, 4 Oct 2012 00:42:02 +0000 Subject: [PATCH] ICU-8965 Add Persian Calendar support X-SVN-Rev: 32500 --- .gitattributes | 1 + .../core/src/com/ibm/icu/util/Calendar.java | 5 +- .../src/com/ibm/icu/util/PersianCalendar.java | 434 ++++++++++++++++++ .../dev/test/serializable/CalendarTests.java | 17 + .../test/serializable/SerializableTest.java | 1 + 5 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java diff --git a/.gitattributes b/.gitattributes index b217ed3a383..127dee56a8b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -248,6 +248,7 @@ icu4j/main/classes/core/.project -text icu4j/main/classes/core/.settings/org.eclipse.core.resources.prefs -text icu4j/main/classes/core/.settings/org.eclipse.jdt.core.prefs -text icu4j/main/classes/core/manifest.stub -text +icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java -text icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text icu4j/main/classes/currdata/.settings/org.eclipse.core.resources.prefs -text icu4j/main/classes/currdata/.settings/org.eclipse.jdt.core.prefs -text diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java index 6791d59d95d..3f8224e3e77 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java @@ -1747,7 +1747,7 @@ public abstract class Calendar implements Serializable, Cloneable, ComparablePersianCalendar is a subclass of Calendar that + * that implements the Persian calendar. It is used as the main civil + * calendar in Iran and Afghanistan, and by Iranians and Afghans worldwide. + *

+ * The Persian calendar is solar, and is similar to the Gregorian calendar + * in various ways, except its leap year rule, which is determined + * astronomically. The Persian year starts around the March equinox. + *

+ * The modern Persian calendar (used in Iran since 1925 CE and in + * Afghanistan since 1957 CE), has the lengths of the months fixed. The + * first six months are 31 days each, the next five months are 30 days each, + * and the final month is 29 days in non-leap years and 30 days in leap + * ones. Historically, the lengths of the month differed in different + * years, but they were finally fixed at the times mentioned above. Partial + * information is available about the historical lengths. + *

+ * The official rule for determination of the beginning of the Persian year + * is locale dependent, but at the same time, it has not specified a locale. + * Iranians around the world traditionally follow the calendar authorities + * of Iran, which haven't officially specified the locale. Some + * calendarists use some point in Tehran as the locale, while others have + * tried the more neutral 52.5 degrees east meridian. It is not clear which + * locale should be used for the Persian calendar of Afghanistan, but it is + * expected that for about one year in every twenty-four years, the Afghan + * calendar may become different from the Iranian one. + *

+ * The exact locale to be used for the Iranian calendar starts to make a + * difference at around 2090 CE. The specific arithmetic method implemented + * here, commonly known as the 33-year cycle rule, matches the astronomical + * calendar at least for the whole period that the calendar has been both + * well-defined and official, from 1925 to around 2090 CE. The other + * commonly known algorithm, the 2820-year cycle, has been incorrectly + * designed to follow the tropical year instead of the spring equinoctial + * year, and fails to match the astronomical one as early as 2025 CE. + *

+ * This class should not be subclassed.

+ *

+ * PersianCalendar usually should be instantiated using + * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a + * ULocale with the tag "@calendar=persian".

+ * + * @see com.ibm.icu.util.GregorianCalendar + * @see com.ibm.icu.util.Calendar + * + * @author Roozbeh Pournader + * + * @internal + * @deprecated This API is ICU internal only. + */ +public class PersianCalendar extends Calendar { + private static final long serialVersionUID = -6727306982975111643L; + + //------------------------------------------------------------------------- + // Constants... + //------------------------------------------------------------------------- + + private static final int[][] MONTH_COUNT = { + //len len2 st + { 31, 31, 0 }, // Farvardin + { 31, 31, 31 }, // Ordibehesht + { 31, 31, 62 }, // Khordad + { 31, 31, 93 }, // Tir + { 31, 31, 124 }, // Mordad + { 31, 31, 155 }, // Shahrivar + { 30, 30, 186 }, // Mehr + { 30, 30, 216 }, // Aban + { 30, 30, 246 }, // Azar + { 30, 30, 276 }, // Dey + { 30, 30, 306 }, // Bahman + { 29, 30, 336 } // Esfand + // len length of month + // len2 length of month in a leap year + // st days in year before start of month + }; + + private static final int PERSIAN_EPOCH = 1948320; + + //------------------------------------------------------------------------- + // Constructors... + //------------------------------------------------------------------------- + + /** + * Constructs a default PersianCalendar using the current time + * in the default time zone with the default FORMAT locale. + * @see Category#FORMAT + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar() + { + this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); + } + + /** + * Constructs a PersianCalendar based on the current time + * in the given time zone with the default FORMAT locale. + * @param zone the given time zone. + * @see Category#FORMAT + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(TimeZone zone) + { + this(zone, ULocale.getDefault(Category.FORMAT)); + } + + /** + * Constructs a PersianCalendar based on the current time + * in the default time zone with the given locale. + * + * @param aLocale the given locale. + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(Locale aLocale) + { + this(TimeZone.getDefault(), aLocale); + } + + /** + * Constructs a PersianCalendar based on the current time + * in the default time zone with the given locale. + * + * @param locale the given ulocale. + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(ULocale locale) + { + this(TimeZone.getDefault(), locale); + } + + /** + * Constructs a PersianCalendar 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. + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(TimeZone zone, Locale aLocale) + { + super(zone, aLocale); + setTimeInMillis(System.currentTimeMillis()); + } + + /** + * Constructs a PersianCalendar based on the current time + * in the given time zone with the given locale. + * + * @param zone the given time zone. + * @param locale the given ulocale. + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(TimeZone zone, ULocale locale) + { + super(zone, locale); + setTimeInMillis(System.currentTimeMillis()); + } + + /** + * Constructs a PersianCalendar with the given date set + * in the default time zone with the default FORMAT locale. + * + * @param date The date to which the new calendar is set. + * @see Category#FORMAT + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(Date date) { + super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); + this.setTime(date); + } + + /** + * Constructs a PersianCalendar with the given date set + * in the default time zone with the default FORMAT 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 Farvardin. + * @param date the value used to set the {@link #DATE DATE} time field in the calendar. + * @see Category#FORMAT + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(int year, int month, int date) + { + super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); + this.set(Calendar.YEAR, year); + this.set(Calendar.MONTH, month); + this.set(Calendar.DATE, date); + } + + /** + * Constructs a PersianCalendar with the given date + * and time set for the default time zone with the default FORMAT 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 Farvardin. + * @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. + * @see Category#FORMAT + * + * @internal + * @deprecated This API is ICU internal only. + */ + public PersianCalendar(int year, int month, int date, int hour, + int minute, int second) + { + super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); + 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); + } + + //------------------------------------------------------------------------- + // Minimum / Maximum access functions + //------------------------------------------------------------------------- + + private static final int LIMITS[][] = { + // Minimum Greatest Least Maximum + // Minimum Maximum + { 0, 0, 0, 0}, // ERA + { -5000000, -5000000, 5000000, 5000000}, // YEAR + { 0, 0, 11, 11}, // MONTH + { 1, 1, 52, 53}, // WEEK_OF_YEAR + {/* */}, // WEEK_OF_MONTH + { 1, 1, 29, 31}, // DAY_OF_MONTH + { 1, 1, 365, 366}, // 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 + { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY + {/* */}, // DOW_LOCAL + { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR + {/* */}, // JULIAN_DAY + {/* */}, // MILLISECONDS_IN_DAY + }; + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + protected int handleGetLimit(int field, int limitType) { + return LIMITS[field][limitType]; + } + + //------------------------------------------------------------------------- + // Assorted calculation utilities + // + + /** + * Determine whether a year is a leap year in the Persian calendar + */ + private final static boolean isLeapYear(int year) + { + int[] remainder = new int[1]; + floorDivide(25 * year + 11, 33, remainder); + return remainder[0] < 8; + + } + + //---------------------------------------------------------------------- + // Calendar framework + //---------------------------------------------------------------------- + + /** + * Return the length (in days) of the given month. + * + * @param extendedYear The Persian year + * @param month The Persian month, 0-based + * + * @internal + * @deprecated This API is ICU internal only. + */ + protected int handleGetMonthLength(int extendedYear, 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]; + extendedYear += floorDivide(month, 12, rem); + month = rem[0]; + } + + return MONTH_COUNT[month][isLeapYear(extendedYear)?1:0]; + } + + /** + * Return the number of days in the given Persian year + * + * @internal + * @deprecated This API is ICU internal only. + */ + protected int handleGetYearLength(int extendedYear) { + return isLeapYear(extendedYear) ? 366 : 365; + } + + //------------------------------------------------------------------------- + // Functions for converting from field values to milliseconds.... + //------------------------------------------------------------------------- + + /** + * Return JD of start of given month/year + * + * @internal + * @deprecated This API is ICU internal only. + */ + 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 julianDay = PERSIAN_EPOCH - 1 + 365 * (eyear - 1) + floorDivide(8 * eyear + 21, 33); + if (month != 0) { + julianDay += MONTH_COUNT[month][2]; + } + return julianDay; + } + + //------------------------------------------------------------------------- + // Functions for converting from milliseconds to field values + //------------------------------------------------------------------------- + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + 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 Persian + * calendar system. These are: + * + * + * + * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this + * method is called. + * + * @internal + * @deprecated This API is ICU internal only. + */ + protected void handleComputeFields(int julianDay) { + int year, month, dayOfMonth, dayOfYear; + + long daysSinceEpoch = julianDay - PERSIAN_EPOCH; + year = 1 + (int) floorDivide(33 * daysSinceEpoch + 3, 12053); + + long farvardin1 = 365 * (year - 1) + floorDivide(8 * year + 21, 33); + dayOfYear = (int)(daysSinceEpoch - farvardin1); // 0-based + if (dayOfYear < 216) { // Compute 0-based month + month = dayOfYear / 31; + } else { + month = (dayOfYear - 6) / 30; + } + dayOfMonth = dayOfYear - MONTH_COUNT[month][2] + 1; + ++dayOfYear; // Make it 1-based now + + internalSet(ERA, 0); + internalSet(YEAR, year); + internalSet(EXTENDED_YEAR, year); + internalSet(MONTH, month); + internalSet(DAY_OF_MONTH, dayOfMonth); + internalSet(DAY_OF_YEAR, dayOfYear); + } + + /** + * {@inheritDoc} + * + * @internal + * @deprecated This API is ICU internal only. + */ + public String getType() { + return "persian"; + } +} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CalendarTests.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CalendarTests.java index 588096978fe..0e31f7ce3f7 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CalendarTests.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CalendarTests.java @@ -21,6 +21,7 @@ import com.ibm.icu.util.HebrewCalendar; import com.ibm.icu.util.IndianCalendar; import com.ibm.icu.util.IslamicCalendar; import com.ibm.icu.util.JapaneseCalendar; +import com.ibm.icu.util.PersianCalendar; import com.ibm.icu.util.TaiwanCalendar; import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.ULocale; @@ -223,6 +224,22 @@ public class CalendarTests } } + static class PersianCalendarHandler extends CalendarHandler + { + public Object[] getTestObjects() + { + Locale locales[] = SerializableTest.getLocales(); + TimeZone kst = TimeZone.getTimeZone("Asia/Tehran"); + PersianCalendar calendars[] = new PersianCalendar[locales.length]; + + for (int i = 0; i < locales.length; i += 1) { + calendars[i] = new PersianCalendar(kst, ULocale.forLocale(locales[i])); + } + + return calendars; + } + } + static class TaiwanCalendarHandler extends CalendarHandler { public Object[] getTestObjects() { Locale locales[] = SerializableTest.getLocales(); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java index bd69d6f4230..e8c52dd229b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java @@ -689,6 +689,7 @@ public class SerializableTest extends TestFmwk.TestGroup map.put("com.ibm.icu.util.IndianCalendar", new CalendarTests.IndianCalendarHandler()); map.put("com.ibm.icu.util.IslamicCalendar", new CalendarTests.IslamicCalendarHandler()); map.put("com.ibm.icu.util.JapaneseCalendar", new CalendarTests.JapaneseCalendarHandler()); + map.put("com.ibm.icu.util.PersianCalendar", new CalendarTests.PersianCalendarHandler()); map.put("com.ibm.icu.util.TaiwanCalendar", new CalendarTests.TaiwanCalendarHandler()); map.put("com.ibm.icu.text.ArabicShapingException", new ExceptionTests.ArabicShapingExceptionHandler()); -- 2.40.0