2 ******************************************************************************
3 * Copyright (C) 2003-2012, International Business Machines Corporation
4 * and others. All Rights Reserved.
5 ******************************************************************************
9 * Modification History:
11 * Date Name Description
12 * 9/23/2003 mehran posted to icu-design
13 * 10/1/2012 roozbeh Fixed algorithm and heavily refactored and rewrote
14 * based on the implementation of Gregorian
15 *****************************************************************************
20 #if !UCONFIG_NO_FORMATTING
23 #include "gregoimp.h" // Math
26 static const int16_t kPersianNumDays
[]
27 = {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year
28 static const int8_t kPersianMonthLength
[]
29 = {31,31,31,31,31,31,30,30,30,30,30,29}; // 0-based
30 static const int8_t kPersianLeapMonthLength
[]
31 = {31,31,31,31,31,31,30,30,30,30,30,30}; // 0-based
33 static const int32_t kPersianCalendarLimits
[UCAL_FIELD_COUNT
][4] = {
34 // Minimum Greatest Least Maximum
37 { -5000000, -5000000, 5000000, 5000000}, // YEAR
38 { 0, 0, 11, 11}, // MONTH
39 { 1, 1, 52, 53}, // WEEK_OF_YEAR
40 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH
41 { 1, 1, 29, 31}, // DAY_OF_MONTH
42 { 1, 1, 365, 366}, // DAY_OF_YEAR
43 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
44 { 1, 1, 5, 5}, // DAY_OF_WEEK_IN_MONTH
45 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
46 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
47 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
48 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
49 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
50 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
51 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
52 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
53 { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY
54 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
55 { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR
56 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
57 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY
58 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH
63 static const int32_t PERSIAN_EPOCH
= 1948320;
65 // Implementation of the PersianCalendar class
67 //-------------------------------------------------------------------------
69 //-------------------------------------------------------------------------
71 const char *PersianCalendar::getType() const {
75 Calendar
* PersianCalendar::clone() const {
76 return new PersianCalendar(*this);
79 PersianCalendar::PersianCalendar(const Locale
& aLocale
, UErrorCode
& success
)
80 : Calendar(TimeZone::createDefault(), aLocale
, success
)
82 setTimeInMillis(getNow(), success
); // Call this again now that the vtable is set up properly.
85 PersianCalendar::PersianCalendar(const PersianCalendar
& other
) : Calendar(other
) {
88 PersianCalendar::~PersianCalendar()
92 //-------------------------------------------------------------------------
93 // Minimum / Maximum access functions
94 //-------------------------------------------------------------------------
97 int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field
, ELimitType limitType
) const {
98 return kPersianCalendarLimits
[field
][limitType
];
101 //-------------------------------------------------------------------------
102 // Assorted calculation utilities
106 * Determine whether a year is a leap year in the Persian calendar
108 UBool
PersianCalendar::isLeapYear(int32_t year
)
111 ClockMath::floorDivide(25 * year
+ 11, 33, remainder
);
112 return (remainder
< 8);
116 * Return the day # on which the given year starts. Days are counted
117 * from the Persian epoch, origin 0.
119 int32_t PersianCalendar::yearStart(int32_t year
) {
120 return handleComputeMonthStart(year
,0,FALSE
);
124 * Return the day # on which the given month starts. Days are counted
125 * from the Persian epoch, origin 0.
127 * @param year The Persian year
128 * @param year The Persian month, 0-based
130 int32_t PersianCalendar::monthStart(int32_t year
, int32_t month
) const {
131 return handleComputeMonthStart(year
,month
,TRUE
);
134 //----------------------------------------------------------------------
135 // Calendar framework
136 //----------------------------------------------------------------------
139 * Return the length (in days) of the given month.
141 * @param year The Persian year
142 * @param year The Persian month, 0-based
144 int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear
, int32_t month
) const {
145 // If the month is out of range, adjust it into range, and
146 // modify the extended year value accordingly.
147 if (month
< 0 || month
> 11) {
148 extendedYear
+= ClockMath::floorDivide(month
, 12, month
);
151 return isLeapYear(extendedYear
) ? kPersianLeapMonthLength
[month
] : kPersianMonthLength
[month
];
155 * Return the number of days in the given Persian year
157 int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear
) const {
158 return isLeapYear(extendedYear
) ? 366 : 365;
161 //-------------------------------------------------------------------------
162 // Functions for converting from field values to milliseconds....
163 //-------------------------------------------------------------------------
165 // Return JD of start of given month/year
166 int32_t PersianCalendar::handleComputeMonthStart(int32_t eyear
, int32_t month
, UBool
/*useMonth*/) const {
167 // If the month is out of range, adjust it into range, and
168 // modify the extended year value accordingly.
169 if (month
< 0 || month
> 11) {
170 eyear
+= ClockMath::floorDivide(month
, 12, month
);
173 int32_t julianDay
= PERSIAN_EPOCH
- 1 + 365 * (eyear
- 1) + ClockMath::floorDivide(8 * eyear
+ 21, 33);
176 julianDay
+= kPersianNumDays
[month
];
182 //-------------------------------------------------------------------------
183 // Functions for converting from milliseconds to field values
184 //-------------------------------------------------------------------------
186 int32_t PersianCalendar::handleGetExtendedYear() {
188 if (newerField(UCAL_EXTENDED_YEAR
, UCAL_YEAR
) == UCAL_EXTENDED_YEAR
) {
189 year
= internalGet(UCAL_EXTENDED_YEAR
, 1); // Default to year 1
191 year
= internalGet(UCAL_YEAR
, 1); // Default to year 1
197 * Override Calendar to compute several fields specific to the Persian
198 * calendar system. These are:
205 * <li>EXTENDED_YEAR</ul>
207 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
210 void PersianCalendar::handleComputeFields(int32_t julianDay
, UErrorCode
&/*status*/) {
211 int32_t year
, month
, dayOfMonth
, dayOfYear
;
213 int32_t daysSinceEpoch
= julianDay
- PERSIAN_EPOCH
;
214 year
= 1 + ClockMath::floorDivide(33 * daysSinceEpoch
+ 3, 12053);
216 int32_t farvardin1
= 365 * (year
- 1) + ClockMath::floorDivide(8 * year
+ 21, 33);
217 dayOfYear
= (daysSinceEpoch
- farvardin1
); // 0-based
218 if (dayOfYear
< 216) { // Compute 0-based month
219 month
= dayOfYear
/ 31;
221 month
= (dayOfYear
- 6) / 30;
223 dayOfMonth
= dayOfYear
- kPersianNumDays
[month
] + 1;
224 ++dayOfYear
; // Make it 1-based now
226 internalSet(UCAL_ERA
, 0);
227 internalSet(UCAL_YEAR
, year
);
228 internalSet(UCAL_EXTENDED_YEAR
, year
);
229 internalSet(UCAL_MONTH
, month
);
230 internalSet(UCAL_DAY_OF_MONTH
, dayOfMonth
);
231 internalSet(UCAL_DAY_OF_YEAR
, dayOfYear
);
235 PersianCalendar::inDaylightTime(UErrorCode
& status
) const
237 // copied from GregorianCalendar
238 if (U_FAILURE(status
) || !getTimeZone().useDaylightTime())
241 // Force an update of the state of the Calendar.
242 ((PersianCalendar
*)this)->complete(status
); // cast away const
244 return (UBool
)(U_SUCCESS(status
) ? (internalGet(UCAL_DST_OFFSET
) != 0) : FALSE
);
248 const UDate
PersianCalendar::fgSystemDefaultCentury
= DBL_MIN
;
249 const int32_t PersianCalendar::fgSystemDefaultCenturyYear
= -1;
251 UDate
PersianCalendar::fgSystemDefaultCenturyStart
= DBL_MIN
;
252 int32_t PersianCalendar::fgSystemDefaultCenturyStartYear
= -1;
254 UBool
PersianCalendar::haveDefaultCentury() const
259 UDate
PersianCalendar::defaultCenturyStart() const
261 return internalGetDefaultCenturyStart();
264 int32_t PersianCalendar::defaultCenturyStartYear() const
266 return internalGetDefaultCenturyStartYear();
270 PersianCalendar::internalGetDefaultCenturyStart() const
272 // lazy-evaluate systemDefaultCenturyStart
274 UMTX_CHECK(NULL
, (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
), needsUpdate
);
277 initializeSystemDefaultCentury();
280 // use defaultCenturyStart unless it's the flag value;
281 // then use systemDefaultCenturyStart
283 return fgSystemDefaultCenturyStart
;
287 PersianCalendar::internalGetDefaultCenturyStartYear() const
289 // lazy-evaluate systemDefaultCenturyStartYear
291 UMTX_CHECK(NULL
, (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
), needsUpdate
);
294 initializeSystemDefaultCentury();
297 // use defaultCenturyStart unless it's the flag value;
298 // then use systemDefaultCenturyStartYear
300 return fgSystemDefaultCenturyStartYear
;
304 PersianCalendar::initializeSystemDefaultCentury()
306 // initialize systemDefaultCentury and systemDefaultCenturyYear based
307 // on the current time. They'll be set to 80 years before
309 UErrorCode status
= U_ZERO_ERROR
;
310 PersianCalendar
calendar(Locale("@calendar=persian"),status
);
311 if (U_SUCCESS(status
))
313 calendar
.setTime(Calendar::getNow(), status
);
314 calendar
.add(UCAL_YEAR
, -80, status
);
315 UDate newStart
= calendar
.getTime(status
);
316 int32_t newYear
= calendar
.get(UCAL_YEAR
, status
);
318 if (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
)
320 fgSystemDefaultCenturyStartYear
= newYear
;
321 fgSystemDefaultCenturyStart
= newStart
;
325 // We have no recourse upon failure unless we want to propagate the failure
329 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar
)