1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 ******************************************************************************
5 * Copyright (C) 2003-2013, International Business Machines Corporation
6 * and others. All Rights Reserved.
7 ******************************************************************************
11 * Modification History:
13 * Date Name Description
14 * 9/23/2003 mehran posted to icu-design
15 * 10/1/2012 roozbeh Fixed algorithm and heavily refactored and rewrote
16 * based on the implementation of Gregorian
17 *****************************************************************************
22 #if !UCONFIG_NO_FORMATTING
25 #include "gregoimp.h" // Math
28 static const int16_t kPersianNumDays
[]
29 = {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year
30 static const int8_t kPersianMonthLength
[]
31 = {31,31,31,31,31,31,30,30,30,30,30,29}; // 0-based
32 static const int8_t kPersianLeapMonthLength
[]
33 = {31,31,31,31,31,31,30,30,30,30,30,30}; // 0-based
35 static const int32_t kPersianCalendarLimits
[UCAL_FIELD_COUNT
][4] = {
36 // Minimum Greatest Least Maximum
39 { -5000000, -5000000, 5000000, 5000000}, // YEAR
40 { 0, 0, 11, 11}, // MONTH
41 { 1, 1, 52, 53}, // WEEK_OF_YEAR
42 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH
43 { 1, 1, 29, 31}, // DAY_OF_MONTH
44 { 1, 1, 365, 366}, // DAY_OF_YEAR
45 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
46 { 1, 1, 5, 5}, // DAY_OF_WEEK_IN_MONTH
47 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
48 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
49 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
50 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
51 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
52 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
53 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
54 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
55 { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY
56 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
57 { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR
58 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
59 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY
60 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH
65 static const int32_t PERSIAN_EPOCH
= 1948320;
67 // Implementation of the PersianCalendar class
69 //-------------------------------------------------------------------------
71 //-------------------------------------------------------------------------
73 const char *PersianCalendar::getType() const {
77 Calendar
* PersianCalendar::clone() const {
78 return new PersianCalendar(*this);
81 PersianCalendar::PersianCalendar(const Locale
& aLocale
, UErrorCode
& success
)
82 : Calendar(TimeZone::createDefault(), aLocale
, success
)
84 setTimeInMillis(getNow(), success
); // Call this again now that the vtable is set up properly.
87 PersianCalendar::PersianCalendar(const PersianCalendar
& other
) : Calendar(other
) {
90 PersianCalendar::~PersianCalendar()
94 //-------------------------------------------------------------------------
95 // Minimum / Maximum access functions
96 //-------------------------------------------------------------------------
99 int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field
, ELimitType limitType
) const {
100 return kPersianCalendarLimits
[field
][limitType
];
103 //-------------------------------------------------------------------------
104 // Assorted calculation utilities
108 * Determine whether a year is a leap year in the Persian calendar
110 UBool
PersianCalendar::isLeapYear(int32_t year
)
113 ClockMath::floorDivide(25 * year
+ 11, 33, remainder
);
114 return (remainder
< 8);
118 * Return the day # on which the given year starts. Days are counted
119 * from the Persian epoch, origin 0.
121 int32_t PersianCalendar::yearStart(int32_t year
) {
122 return handleComputeMonthStart(year
,0,FALSE
);
126 * Return the day # on which the given month starts. Days are counted
127 * from the Persian epoch, origin 0.
129 * @param year The Persian year
130 * @param year The Persian month, 0-based
132 int32_t PersianCalendar::monthStart(int32_t year
, int32_t month
) const {
133 return handleComputeMonthStart(year
,month
,TRUE
);
136 //----------------------------------------------------------------------
137 // Calendar framework
138 //----------------------------------------------------------------------
141 * Return the length (in days) of the given month.
143 * @param year The Persian year
144 * @param year The Persian month, 0-based
146 int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear
, int32_t month
) const {
147 // If the month is out of range, adjust it into range, and
148 // modify the extended year value accordingly.
149 if (month
< 0 || month
> 11) {
150 extendedYear
+= ClockMath::floorDivide(month
, 12, month
);
153 return isLeapYear(extendedYear
) ? kPersianLeapMonthLength
[month
] : kPersianMonthLength
[month
];
157 * Return the number of days in the given Persian year
159 int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear
) const {
160 return isLeapYear(extendedYear
) ? 366 : 365;
163 //-------------------------------------------------------------------------
164 // Functions for converting from field values to milliseconds....
165 //-------------------------------------------------------------------------
167 // Return JD of start of given month/year
168 int32_t PersianCalendar::handleComputeMonthStart(int32_t eyear
, int32_t month
, UBool
/*useMonth*/) const {
169 // If the month is out of range, adjust it into range, and
170 // modify the extended year value accordingly.
171 if (month
< 0 || month
> 11) {
172 eyear
+= ClockMath::floorDivide(month
, 12, month
);
175 int32_t julianDay
= PERSIAN_EPOCH
- 1 + 365 * (eyear
- 1) + ClockMath::floorDivide(8 * eyear
+ 21, 33);
178 julianDay
+= kPersianNumDays
[month
];
184 //-------------------------------------------------------------------------
185 // Functions for converting from milliseconds to field values
186 //-------------------------------------------------------------------------
188 int32_t PersianCalendar::handleGetExtendedYear() {
190 if (newerField(UCAL_EXTENDED_YEAR
, UCAL_YEAR
) == UCAL_EXTENDED_YEAR
) {
191 year
= internalGet(UCAL_EXTENDED_YEAR
, 1); // Default to year 1
193 year
= internalGet(UCAL_YEAR
, 1); // Default to year 1
199 * Override Calendar to compute several fields specific to the Persian
200 * calendar system. These are:
207 * <li>EXTENDED_YEAR</ul>
209 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
212 void PersianCalendar::handleComputeFields(int32_t julianDay
, UErrorCode
&/*status*/) {
213 int32_t year
, month
, dayOfMonth
, dayOfYear
;
215 int32_t daysSinceEpoch
= julianDay
- PERSIAN_EPOCH
;
216 year
= 1 + (int32_t)ClockMath::floorDivide(33 * (int64_t)daysSinceEpoch
+ 3, (int64_t)12053);
218 int32_t farvardin1
= 365 * (year
- 1) + ClockMath::floorDivide(8 * year
+ 21, 33);
219 dayOfYear
= (daysSinceEpoch
- farvardin1
); // 0-based
220 if (dayOfYear
< 216) { // Compute 0-based month
221 month
= dayOfYear
/ 31;
223 month
= (dayOfYear
- 6) / 30;
225 dayOfMonth
= dayOfYear
- kPersianNumDays
[month
] + 1;
226 ++dayOfYear
; // Make it 1-based now
228 internalSet(UCAL_ERA
, 0);
229 internalSet(UCAL_YEAR
, year
);
230 internalSet(UCAL_EXTENDED_YEAR
, year
);
231 internalSet(UCAL_MONTH
, month
);
232 internalSet(UCAL_DAY_OF_MONTH
, dayOfMonth
);
233 internalSet(UCAL_DAY_OF_YEAR
, dayOfYear
);
237 PersianCalendar::inDaylightTime(UErrorCode
& status
) const
239 // copied from GregorianCalendar
240 if (U_FAILURE(status
) || !getTimeZone().useDaylightTime())
243 // Force an update of the state of the Calendar.
244 ((PersianCalendar
*)this)->complete(status
); // cast away const
246 return (UBool
)(U_SUCCESS(status
) ? (internalGet(UCAL_DST_OFFSET
) != 0) : FALSE
);
251 static UDate gSystemDefaultCenturyStart
= DBL_MIN
;
252 static int32_t gSystemDefaultCenturyStartYear
= -1;
253 static icu::UInitOnce gSystemDefaultCenturyInit
= U_INITONCE_INITIALIZER
;
255 UBool
PersianCalendar::haveDefaultCentury() const
260 static void U_CALLCONV
initializeSystemDefaultCentury() {
261 // initialize systemDefaultCentury and systemDefaultCenturyYear based
262 // on the current time. They'll be set to 80 years before
264 UErrorCode status
= U_ZERO_ERROR
;
265 PersianCalendar
calendar(Locale("@calendar=persian"),status
);
266 if (U_SUCCESS(status
))
268 calendar
.setTime(Calendar::getNow(), status
);
269 calendar
.add(UCAL_YEAR
, -80, status
);
271 gSystemDefaultCenturyStart
= calendar
.getTime(status
);
272 gSystemDefaultCenturyStartYear
= calendar
.get(UCAL_YEAR
, status
);
274 // We have no recourse upon failure unless we want to propagate the failure
278 UDate
PersianCalendar::defaultCenturyStart() const {
279 // lazy-evaluate systemDefaultCenturyStart
280 umtx_initOnce(gSystemDefaultCenturyInit
, &initializeSystemDefaultCentury
);
281 return gSystemDefaultCenturyStart
;
284 int32_t PersianCalendar::defaultCenturyStartYear() const {
285 // lazy-evaluate systemDefaultCenturyStartYear
286 umtx_initOnce(gSystemDefaultCenturyInit
, &initializeSystemDefaultCentury
);
287 return gSystemDefaultCenturyStartYear
;
290 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar
)