1 /* 2 ****************************************************************************** 3 * Copyright (C) 2003-2008, International Business Machines Corporation 4 * and others. All Rights Reserved. 5 ****************************************************************************** 6 * 7 * File PERSNCAL.CPP 8 * 9 * Modification History: 10 * 11 * Date Name Description 12 * 9/23/2003 mehran posted to icu-design 13 ***************************************************************************** 14 */ 15 16 #include "persncal.h" 17 18 #if !UCONFIG_NO_FORMATTING 19 20 #include "umutex.h" 21 #include <float.h> 22 23 static const int8_t monthDays[] = { 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 }; 24 25 static int32_t 26 jalali_to_julian(int year, int month, int day) 27 { 28 int32_t daysNo=0; 29 int i; 30 31 year = year -475+2820; 32 month -= 1; 33 34 daysNo=(year/2820)*1029983; 35 year=year % 2820; 36 37 daysNo+=(year/128)* 46751; 38 if((year/128)>21) 39 { 40 daysNo-=46751; 41 year=(year%128)+128; 42 } 43 else 44 year=year%128; 45 46 if(year>=29) 47 { 48 year-=29; 49 daysNo+=10592; 50 } 51 52 if(year>=66) 53 { 54 year-=66; 55 daysNo+=24106; 56 } 57 else if( year>=33) 58 { 59 daysNo+=(year/33)* 12053; 60 year=year%33; 61 } 62 63 if (year >= 5) 64 { 65 daysNo += 1826; 66 year -=5; 67 } 68 else if (year == 4) 69 { 70 daysNo += 1460; 71 year -=4; 72 } 73 74 daysNo += 1461 * (year/4); 75 year %= 4; 76 daysNo += 365 * year; 77 78 for (i = 0; i < month; i++) { 79 daysNo += monthDays[i]; 80 } 81 82 daysNo += day; 83 84 return daysNo-856493; 85 } 86 87 static void julian_to_jalali (int32_t daysNo, int *h_y, int *h_m, int *h_d) 88 { 89 int year=0, month=0, day=0,scalarDays=0; 90 int i; 91 92 daysNo+=856493; 93 scalarDays=daysNo; 94 year=(daysNo/1029983)*2820; 95 daysNo=daysNo%1029983; 96 97 if((daysNo/46751)<=21) 98 { 99 year+=(daysNo/46751)* 128; 100 daysNo=daysNo%46751; 101 } 102 else 103 { 104 year+=(daysNo/46751)* 128; 105 daysNo=daysNo%46751; 106 year-=128; 107 daysNo+=46751; 108 } 109 110 if (daysNo >= 10592) 111 { 112 year+= 29; 113 daysNo -= 10592; 114 } 115 116 if(daysNo>=24106) 117 { 118 daysNo-=24106; 119 year+=66; 120 } 121 122 if(daysNo>=12053) 123 { 124 daysNo-=12053; 125 year+=33; 126 } 127 128 129 if (daysNo >= 1826) 130 { 131 year+= 5; 132 daysNo -= 1826; 133 } 134 else if (daysNo > 1095) 135 { 136 year+= 3; 137 daysNo -= 1095; 138 139 } 140 141 year +=(4 * (daysNo/1461)); 142 daysNo %= 1461; 143 144 if (daysNo == 0) 145 { 146 year -= 1; 147 daysNo = 366; 148 } 149 else 150 { 151 year += daysNo/365; 152 daysNo = daysNo % 365; 153 if (daysNo == 0) 154 { 155 year -= 1; 156 daysNo = 365; 157 } 158 159 } 160 161 for (i = 0; i < 11 && daysNo > monthDays[i]; ++i) { 162 daysNo -= monthDays[i]; 163 } 164 165 month = i + 1; 166 167 day = daysNo; 168 169 *h_d = day; 170 *h_m = month; 171 *h_y = year-2345; 172 } 173 174 U_NAMESPACE_BEGIN 175 176 // Implementation of the PersianCalendar class 177 178 //------------------------------------------------------------------------- 179 // Constructors... 180 //------------------------------------------------------------------------- 181 182 const char *PersianCalendar::getType() const { 183 return "persian"; 184 } 185 186 Calendar* PersianCalendar::clone() const { 187 return new PersianCalendar(*this); 188 } 189 190 PersianCalendar::PersianCalendar(const Locale& aLocale, UErrorCode& success) 191 : Calendar(TimeZone::createDefault(), aLocale, success) 192 { 193 setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 194 } 195 196 PersianCalendar::PersianCalendar(const PersianCalendar& other) : Calendar(other) { 197 } 198 199 PersianCalendar::~PersianCalendar() 200 { 201 } 202 203 //------------------------------------------------------------------------- 204 // Minimum / Maximum access functions 205 //------------------------------------------------------------------------- 206 207 static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { 208 // Minimum Greatest Least Maximum 209 // Minimum Maximum 210 { 0, 0, 0, 0}, // ERA 211 { -5000000, -5000000, 5000000, 5000000}, // YEAR 212 { 0, 0, 11, 11}, // MONTH 213 { 1, 1, 52, 53}, // WEEK_OF_YEAR 214 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 215 { 1, 1, 29, 31}, // DAY_OF_MONTH 216 { 1, 1, 365, 366}, // DAY_OF_YEAR 217 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 218 { 1, 1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 219 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 220 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 221 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 222 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 223 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 224 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 225 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 226 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 227 { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY 228 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 229 { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR 230 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 231 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 232 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH 233 }; 234 static const int32_t MONTH_COUNT[12][4] = { 235 //len len2 st st2 236 { 31, 31, 0, 0 }, // Farvardin 237 { 31, 31, 31, 31 }, // Ordibehesht 238 { 31, 31, 62, 62 }, // Khordad 239 { 31, 31, 93, 93 }, // Tir 240 { 31, 31, 124, 124 }, // Mordad 241 { 31, 31, 155, 155 }, // Shahrivar 242 { 30, 30, 186, 186 }, // Mehr 243 { 30, 30, 216, 216 }, // Aban 244 { 30, 30, 246, 246 }, // Azar 245 { 30, 30, 276, 276 }, // Dey 246 { 30, 30, 306, 306 }, // Bahman 247 { 29, 30, 336, 336 } // Esfand 248 // len length of month 249 // len2 length of month in a leap year 250 // st days in year before start of month 251 // st2 days in year before month in leap year 252 }; 253 254 int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 255 return LIMITS[field][limitType]; 256 } 257 258 //------------------------------------------------------------------------- 259 // Assorted calculation utilities 260 // 261 262 /** 263 * Determine whether a year is a leap year in the Persian calendar 264 */ 265 UBool PersianCalendar::isLeapYear(int32_t year) 266 { 267 return jalali_to_julian(year+1,1,1)-jalali_to_julian(year,1,1) == 366; 268 } 269 270 /** 271 * Return the day # on which the given year starts. Days are counted 272 * from the Hijri epoch, origin 0. 273 */ 274 int32_t PersianCalendar::yearStart(int32_t year) { 275 return handleComputeMonthStart(year,1,FALSE); 276 } 277 278 /** 279 * Return the day # on which the given month starts. Days are counted 280 * from the Hijri epoch, origin 0. 281 * 282 * @param year The hijri shamsi year 283 * @param year The hijri shamsi month, 0-based 284 */ 285 int32_t PersianCalendar::monthStart(int32_t year, int32_t month) const { 286 return handleComputeMonthStart(year,month,FALSE); 287 } 288 289 //---------------------------------------------------------------------- 290 // Calendar framework 291 //---------------------------------------------------------------------- 292 293 /** 294 * Return the length (in days) of the given month. 295 * 296 * @param year The hijri shamsi year 297 * @param year The hijri shamsi month, 0-based 298 */ 299 int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { 300 return MONTH_COUNT[month][PersianCalendar::isLeapYear(extendedYear)?1:0]; 301 } 302 303 /** 304 * Return the number of days in the given Persian year 305 */ 306 int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear) const { 307 return 365 + (PersianCalendar::isLeapYear(extendedYear) ? 1 : 0); 308 } 309 310 //------------------------------------------------------------------------- 311 // Functions for converting from field values to milliseconds.... 312 //------------------------------------------------------------------------- 313 314 // Return JD of start of given month/year 315 int32_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth) const { 316 // If the month is out of range, adjust it into range, and 317 // modify the extended year value accordingly. 318 if (month < 0 || month > 11) { 319 eyear += month / 12; 320 month = month % 12; 321 } 322 return jalali_to_julian(eyear,(useMonth?month+1:1),1)-1+1947955; 323 } 324 325 //------------------------------------------------------------------------- 326 // Functions for converting from milliseconds to field values 327 //------------------------------------------------------------------------- 328 329 int32_t PersianCalendar::handleGetExtendedYear() { 330 int32_t year; 331 if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { 332 year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 333 } else { 334 year = internalGet(UCAL_YEAR, 1); // Default to year 1 335 } 336 return year; 337 } 338 339 /** 340 * Override Calendar to compute several fields specific to the Persian 341 * calendar system. These are: 342 * 343 * <ul><li>ERA 344 * <li>YEAR 345 * <li>MONTH 346 * <li>DAY_OF_MONTH 347 * <li>DAY_OF_YEAR 348 * <li>EXTENDED_YEAR</ul> 349 * 350 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 351 * method is called. The getGregorianXxx() methods return Gregorian 352 * calendar equivalents for the given Julian day. 353 */ 354 void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { 355 int jy,jm,jd; 356 julian_to_jalali(julianDay-1947955,&jy,&jm,&jd); 357 internalSet(UCAL_ERA, 0); 358 internalSet(UCAL_YEAR, jy); 359 internalSet(UCAL_EXTENDED_YEAR, jy); 360 internalSet(UCAL_MONTH, jm-1); 361 internalSet(UCAL_DAY_OF_MONTH, jd); 362 internalSet(UCAL_DAY_OF_YEAR, jd + MONTH_COUNT[jm-1][2]); 363 } 364 365 UBool 366 PersianCalendar::inDaylightTime(UErrorCode& status) const 367 { 368 // copied from GregorianCalendar 369 if (U_FAILURE(status) || !getTimeZone().useDaylightTime()) 370 return FALSE; 371 372 // Force an update of the state of the Calendar. 373 ((PersianCalendar*)this)->complete(status); // cast away const 374 375 return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); 376 } 377 378 // default century 379 const UDate PersianCalendar::fgSystemDefaultCentury = DBL_MIN; 380 const int32_t PersianCalendar::fgSystemDefaultCenturyYear = -1; 381 382 UDate PersianCalendar::fgSystemDefaultCenturyStart = DBL_MIN; 383 int32_t PersianCalendar::fgSystemDefaultCenturyStartYear = -1; 384 385 UBool PersianCalendar::haveDefaultCentury() const 386 { 387 return TRUE; 388 } 389 390 UDate PersianCalendar::defaultCenturyStart() const 391 { 392 return internalGetDefaultCenturyStart(); 393 } 394 395 int32_t PersianCalendar::defaultCenturyStartYear() const 396 { 397 return internalGetDefaultCenturyStartYear(); 398 } 399 400 UDate 401 PersianCalendar::internalGetDefaultCenturyStart() const 402 { 403 // lazy-evaluate systemDefaultCenturyStart 404 UBool needsUpdate; 405 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 406 407 if (needsUpdate) { 408 initializeSystemDefaultCentury(); 409 } 410 411 // use defaultCenturyStart unless it's the flag value; 412 // then use systemDefaultCenturyStart 413 414 return fgSystemDefaultCenturyStart; 415 } 416 417 int32_t 418 PersianCalendar::internalGetDefaultCenturyStartYear() const 419 { 420 // lazy-evaluate systemDefaultCenturyStartYear 421 UBool needsUpdate; 422 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 423 424 if (needsUpdate) { 425 initializeSystemDefaultCentury(); 426 } 427 428 // use defaultCenturyStart unless it's the flag value; 429 // then use systemDefaultCenturyStartYear 430 431 return fgSystemDefaultCenturyStartYear; 432 } 433 434 void 435 PersianCalendar::initializeSystemDefaultCentury() 436 { 437 // initialize systemDefaultCentury and systemDefaultCenturyYear based 438 // on the current time. They'll be set to 80 years before 439 // the current time. 440 UErrorCode status = U_ZERO_ERROR; 441 PersianCalendar calendar(Locale("@calendar=persian"),status); 442 if (U_SUCCESS(status)) 443 { 444 calendar.setTime(Calendar::getNow(), status); 445 calendar.add(UCAL_YEAR, -80, status); 446 UDate newStart = calendar.getTime(status); 447 int32_t newYear = calendar.get(UCAL_YEAR, status); 448 umtx_lock(NULL); 449 if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) 450 { 451 fgSystemDefaultCenturyStartYear = newYear; 452 fgSystemDefaultCenturyStart = newStart; 453 } 454 umtx_unlock(NULL); 455 } 456 // We have no recourse upon failure unless we want to propagate the failure 457 // out. 458 } 459 460 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar) 461 462 U_NAMESPACE_END 463 464 #endif 465 466