]> granicus.if.org Git - icu/commitdiff
ICU-8965 Add Persian Calendar support
authorRoozbeh Pournader <roozbeh@unicode.org>
Thu, 4 Oct 2012 00:42:02 +0000 (00:42 +0000)
committerRoozbeh Pournader <roozbeh@unicode.org>
Thu, 4 Oct 2012 00:42:02 +0000 (00:42 +0000)
X-SVN-Rev: 32500

.gitattributes
icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java
icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java [new file with mode: 0644]
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CalendarTests.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java

index b217ed3a383baedfc457b21e5d2f50d89bd6f16c..127dee56a8b1d6d0c1f79557d158dcc6d53d4491 100644 (file)
@@ -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
index 6791d59d95d6f405b0d230b3a93a32dcab3b508c..3f8224e3e7795516ca4a6c62019cb2d799cf9094 100644 (file)
@@ -1747,7 +1747,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
     private static final int CALTYPE_JAPANESE = 1;
     private static final int CALTYPE_BUDDHIST = 2;
     private static final int CALTYPE_ROC = 3;
-    private static final int CALTYPE_PERSIAN = 4;  // not yet implemented
+    private static final int CALTYPE_PERSIAN = 4;
     private static final int CALTYPE_ISLAMIC_CIVIL = 5;
     private static final int CALTYPE_ISLAMIC = 6;
     private static final int CALTYPE_HEBREW = 7;
@@ -1874,8 +1874,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
             cal = new TaiwanCalendar(zone, locale);
             break;
         case CALTYPE_PERSIAN:
-            // Not yet implemented in ICU4J
-            cal = new GregorianCalendar(zone, locale);
+            cal = new PersianCalendar(zone, locale);
             break;
         case CALTYPE_ISLAMIC_CIVIL:
             cal = new IslamicCalendar(zone, locale);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java
new file mode 100644 (file)
index 0000000..fb46c19
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2012, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ */
+
+package com.ibm.icu.util;
+
+import java.util.Date;
+import java.util.Locale;
+
+import com.ibm.icu.util.ULocale.Category;
+
+/**
+ * <code>PersianCalendar</code> is a subclass of <code>Calendar</code> that
+ * that implements the Persian calendar.  It is used as the main civil
+ * calendar in Iran and Afghanistan, and by Iranians and Afghans worldwide.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p> 
+ * 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.
+ * <p>
+ * This class should not be subclassed.</p>
+ * <p>
+ * PersianCalendar usually should be instantiated using
+ * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a
+ * <code>ULocale</code> with the tag <code>"@calendar=persian"</code>.</p>
+ *
+ * @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 <code>PersianCalendar</code> using the current time
+     * in the default time zone with the default <code>FORMAT</code> locale.
+     * @see Category#FORMAT
+     *
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    public PersianCalendar()
+    {
+        this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
+    }
+
+    /**
+     * Constructs a <code>PersianCalendar</code> based on the current time
+     * in the given time zone with the default <code>FORMAT</code> 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 <code>PersianCalendar</code> 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 <code>PersianCalendar</code> 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 <code>PersianCalendar</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.
+     *
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    public PersianCalendar(TimeZone zone, Locale aLocale)
+    {
+        super(zone, aLocale);
+        setTimeInMillis(System.currentTimeMillis());
+    }
+
+    /**
+     * Constructs a <code>PersianCalendar</code> 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 <code>PersianCalendar</code> with the given date set
+     * in the default time zone with the default <code>FORMAT</code> 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 <code>PersianCalendar</code> with the given date set
+     * in the default time zone with the default <code>FORMAT</code> 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 <code>PersianCalendar</code> with the given date
+     * and time set for the default time zone with the default <code>FORMAT</code> 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:
+     *
+     * <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.
+     *
+     * @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";
+    }
+}
index 588096978fe22d5e447853101fa73524c7e3ecb9..0e31f7ce3f7129ec942f09a3885f2a72063cc193 100644 (file)
@@ -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();
index bd69d6f423008f25cad3156b83ab4e2d1d59c80f..e8c52dd229b9ad3fe9be007794f9badbdcc8ddd0 100644 (file)
@@ -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());