persncal.cpp (11341B)
1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ****************************************************************************** 5 * Copyright (C) 2003-2013, International Business Machines Corporation 6 * and others. All Rights Reserved. 7 ****************************************************************************** 8 * 9 * File PERSNCAL.CPP 10 * 11 * Modification History: 12 * 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 ***************************************************************************** 18 */ 19 20 #include "persncal.h" 21 22 #if !UCONFIG_NO_FORMATTING 23 24 #include "uassert.h" 25 #include "umutex.h" 26 #include "gregoimp.h" // Math 27 #include <float.h> 28 #include "cmemory.h" 29 #include "ucln_in.h" 30 #include "unicode/uniset.h" 31 32 static const int16_t kPersianNumDays[] 33 = {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year 34 static const int8_t kPersianMonthLength[] 35 = {31,31,31,31,31,31,30,30,30,30,30,29}; // 0-based 36 static const int8_t kPersianLeapMonthLength[] 37 = {31,31,31,31,31,31,30,30,30,30,30,30}; // 0-based 38 39 static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = { 40 // Minimum Greatest Least Maximum 41 // Minimum Maximum 42 { 0, 0, 0, 0}, // ERA 43 { -5000000, -5000000, 5000000, 5000000}, // YEAR 44 { 0, 0, 11, 11}, // MONTH 45 { 1, 1, 52, 53}, // WEEK_OF_YEAR 46 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 47 { 1, 1, 29, 31}, // DAY_OF_MONTH 48 { 1, 1, 365, 366}, // DAY_OF_YEAR 49 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 50 { 1, 1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 51 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 52 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 53 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 54 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 55 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 56 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 57 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 58 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 59 { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY 60 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 61 { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR 62 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 63 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 64 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH 65 { 0, 0, 11, 11}, // ORDINAL_MONTH 66 }; 67 68 namespace { // anonymous 69 70 static const icu::UnicodeSet *gLeapCorrection = nullptr; 71 static icu::UInitOnce gCorrectionInitOnce {}; 72 static int32_t gMinCorrection; 73 } // namespace 74 U_CDECL_BEGIN 75 static UBool calendar_persian_cleanup() { 76 if (gLeapCorrection) { 77 delete gLeapCorrection; 78 gLeapCorrection = nullptr; 79 } 80 gCorrectionInitOnce.reset(); 81 return true; 82 } 83 U_CDECL_END 84 85 namespace { // anonymous 86 static void U_CALLCONV initLeapCorrection() { 87 static int16_t nonLeapYears[] = { 88 1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059, 89 2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356, 90 2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620, 91 2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847, 92 2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987, 93 }; 94 gMinCorrection = nonLeapYears[0]; 95 icu::UnicodeSet prefab; 96 for (auto year : nonLeapYears) { 97 prefab.add(year); 98 } 99 gLeapCorrection = prefab.cloneAsThawed()->freeze(); 100 ucln_i18n_registerCleanup(UCLN_I18N_PERSIAN_CALENDAR, calendar_persian_cleanup); 101 } 102 const icu::UnicodeSet* getLeapCorrection() { 103 umtx_initOnce(gCorrectionInitOnce, &initLeapCorrection); 104 return gLeapCorrection; 105 } 106 } // namespace anonymous 107 U_NAMESPACE_BEGIN 108 109 static const int32_t PERSIAN_EPOCH = 1948320; 110 111 // Implementation of the PersianCalendar class 112 113 //------------------------------------------------------------------------- 114 // Constructors... 115 //------------------------------------------------------------------------- 116 117 const char *PersianCalendar::getType() const { 118 return "persian"; 119 } 120 121 PersianCalendar* PersianCalendar::clone() const { 122 return new PersianCalendar(*this); 123 } 124 125 PersianCalendar::PersianCalendar(const Locale& aLocale, UErrorCode& success) 126 : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success) 127 { 128 } 129 130 PersianCalendar::PersianCalendar(const PersianCalendar& other) : Calendar(other) { 131 } 132 133 PersianCalendar::~PersianCalendar() 134 { 135 } 136 137 //------------------------------------------------------------------------- 138 // Minimum / Maximum access functions 139 //------------------------------------------------------------------------- 140 141 142 int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 143 return kPersianCalendarLimits[field][limitType]; 144 } 145 146 //------------------------------------------------------------------------- 147 // Assorted calculation utilities 148 // 149 150 /** 151 * Determine whether a year is a leap year in the Persian calendar 152 */ 153 UBool PersianCalendar::isLeapYear(int32_t year) 154 { 155 if (year >= gMinCorrection && getLeapCorrection()->contains(year)) { 156 return false; 157 } 158 if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) { 159 return true; 160 } 161 int64_t y = static_cast<int64_t>(year) * 25LL + 11LL; 162 bool res = (y % 33L < 8); 163 return res; 164 } 165 166 /** 167 * Return the day # on which the given year starts. Days are counted 168 * from the Persian epoch, origin 0. 169 */ 170 int32_t PersianCalendar::yearStart(int32_t year, UErrorCode& status) { 171 return handleComputeMonthStart(year,0,false, status); 172 } 173 174 /** 175 * Return the day # on which the given month starts. Days are counted 176 * from the Persian epoch, origin 0. 177 * 178 * @param year The Persian year 179 * @param year The Persian month, 0-based 180 */ 181 int32_t PersianCalendar::monthStart(int32_t year, int32_t month, UErrorCode& status) const { 182 return handleComputeMonthStart(year,month,true, status); 183 } 184 185 //---------------------------------------------------------------------- 186 // Calendar framework 187 //---------------------------------------------------------------------- 188 189 /** 190 * Return the length (in days) of the given month. 191 * 192 * @param year The Persian year 193 * @param year The Persian month, 0-based 194 */ 195 int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& /*status*/) const { 196 // If the month is out of range, adjust it into range, and 197 // modify the extended year value accordingly. 198 if (month < 0 || month > 11) { 199 extendedYear += ClockMath::floorDivide(month, 12, &month); 200 } 201 202 return isLeapYear(extendedYear) ? kPersianLeapMonthLength[month] : kPersianMonthLength[month]; 203 } 204 205 /** 206 * Return the number of days in the given Persian year 207 */ 208 int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const { 209 if (U_FAILURE(status)) return 0; 210 return isLeapYear(extendedYear) ? 366 : 365; 211 } 212 213 //------------------------------------------------------------------------- 214 // Functions for converting from field values to milliseconds.... 215 //------------------------------------------------------------------------- 216 217 static int64_t firstJulianOfYear(int64_t year) { 218 int64_t julianDay = 365LL * (year - 1LL) + ClockMath::floorDivide(8LL * year + 21, 33); 219 if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) { 220 julianDay--; 221 } 222 return julianDay; 223 } 224 225 226 // Return JD of start of given month/year 227 int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/, UErrorCode& status) const { 228 if (U_FAILURE(status)) { 229 return 0; 230 } 231 // If the month is out of range, adjust it into range, and 232 // modify the extended year value accordingly. 233 if (month < 0 || month > 11) { 234 if (uprv_add32_overflow(eyear, ClockMath::floorDivide(month, 12, &month), &eyear)) { 235 status = U_ILLEGAL_ARGUMENT_ERROR; 236 return 0; 237 } 238 } 239 240 int64_t julianDay = PERSIAN_EPOCH - 1LL + firstJulianOfYear(eyear); 241 242 if (month != 0) { 243 julianDay += kPersianNumDays[month]; 244 } 245 246 return julianDay; 247 } 248 249 //------------------------------------------------------------------------- 250 // Functions for converting from milliseconds to field values 251 //------------------------------------------------------------------------- 252 253 int32_t PersianCalendar::handleGetExtendedYear(UErrorCode& status) { 254 if (U_FAILURE(status)) { 255 return 0; 256 } 257 if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { 258 return internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 259 } 260 return internalGet(UCAL_YEAR, 1); // Default to year 1 261 } 262 263 /** 264 * Override Calendar to compute several fields specific to the Persian 265 * calendar system. These are: 266 * 267 * <ul><li>ERA 268 * <li>YEAR 269 * <li>MONTH 270 * <li>DAY_OF_MONTH 271 * <li>DAY_OF_YEAR 272 * <li>EXTENDED_YEAR</ul> 273 * 274 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 275 * method is called. 276 */ 277 void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) { 278 int64_t daysSinceEpoch = julianDay; 279 daysSinceEpoch -= PERSIAN_EPOCH; 280 281 int64_t year = ClockMath::floorDivideInt64( 282 33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL; 283 if (year > INT32_MAX || year < INT32_MIN) { 284 status = U_ILLEGAL_ARGUMENT_ERROR; 285 return; 286 } 287 288 int64_t farvardin1 = firstJulianOfYear(year); 289 290 int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based 291 U_ASSERT(dayOfYear >= 0); 292 U_ASSERT(dayOfYear < 366); 293 294 if (dayOfYear == 365 && year >= gMinCorrection && getLeapCorrection()->contains(year)) { 295 year++; 296 dayOfYear = 0; 297 } 298 int32_t month; 299 if (dayOfYear < 216) { // Compute 0-based month 300 month = dayOfYear / 31; 301 } else { 302 month = (dayOfYear - 6) / 30; 303 } 304 U_ASSERT(month >= 0); 305 U_ASSERT(month < 12); 306 307 ++dayOfYear; // Make it 1-based now 308 int32_t dayOfMonth = dayOfYear - kPersianNumDays[month]; 309 U_ASSERT(dayOfMonth > 0); 310 U_ASSERT(dayOfMonth <= 31); 311 312 313 internalSet(UCAL_ERA, 0); 314 internalSet(UCAL_YEAR, year); 315 internalSet(UCAL_EXTENDED_YEAR, year); 316 internalSet(UCAL_MONTH, month); 317 internalSet(UCAL_ORDINAL_MONTH, month); 318 internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); 319 internalSet(UCAL_DAY_OF_YEAR, dayOfYear); 320 } 321 322 IMPL_SYSTEM_DEFAULT_CENTURY(PersianCalendar, "@calendar=persian") 323 324 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar) 325 int32_t 326 PersianCalendar::getRelatedYearDifference() const { 327 constexpr int32_t kPersianCalendarRelatedYearDifference = 622; 328 return kPersianCalendarRelatedYearDifference; 329 } 330 331 U_NAMESPACE_END 332 333 #endif