Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* |
michael@0 | 2 | ****************************************************************************** |
michael@0 | 3 | * Copyright (C) 2003-2013, International Business Machines Corporation |
michael@0 | 4 | * and others. All Rights Reserved. |
michael@0 | 5 | ****************************************************************************** |
michael@0 | 6 | * |
michael@0 | 7 | * File PERSNCAL.CPP |
michael@0 | 8 | * |
michael@0 | 9 | * Modification History: |
michael@0 | 10 | * |
michael@0 | 11 | * Date Name Description |
michael@0 | 12 | * 9/23/2003 mehran posted to icu-design |
michael@0 | 13 | * 10/1/2012 roozbeh Fixed algorithm and heavily refactored and rewrote |
michael@0 | 14 | * based on the implementation of Gregorian |
michael@0 | 15 | ***************************************************************************** |
michael@0 | 16 | */ |
michael@0 | 17 | |
michael@0 | 18 | #include "persncal.h" |
michael@0 | 19 | |
michael@0 | 20 | #if !UCONFIG_NO_FORMATTING |
michael@0 | 21 | |
michael@0 | 22 | #include "umutex.h" |
michael@0 | 23 | #include "gregoimp.h" // Math |
michael@0 | 24 | #include <float.h> |
michael@0 | 25 | |
michael@0 | 26 | static const int16_t kPersianNumDays[] |
michael@0 | 27 | = {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year |
michael@0 | 28 | static const int8_t kPersianMonthLength[] |
michael@0 | 29 | = {31,31,31,31,31,31,30,30,30,30,30,29}; // 0-based |
michael@0 | 30 | static const int8_t kPersianLeapMonthLength[] |
michael@0 | 31 | = {31,31,31,31,31,31,30,30,30,30,30,30}; // 0-based |
michael@0 | 32 | |
michael@0 | 33 | static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = { |
michael@0 | 34 | // Minimum Greatest Least Maximum |
michael@0 | 35 | // Minimum Maximum |
michael@0 | 36 | { 0, 0, 0, 0}, // ERA |
michael@0 | 37 | { -5000000, -5000000, 5000000, 5000000}, // YEAR |
michael@0 | 38 | { 0, 0, 11, 11}, // MONTH |
michael@0 | 39 | { 1, 1, 52, 53}, // WEEK_OF_YEAR |
michael@0 | 40 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH |
michael@0 | 41 | { 1, 1, 29, 31}, // DAY_OF_MONTH |
michael@0 | 42 | { 1, 1, 365, 366}, // DAY_OF_YEAR |
michael@0 | 43 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK |
michael@0 | 44 | { 1, 1, 5, 5}, // DAY_OF_WEEK_IN_MONTH |
michael@0 | 45 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM |
michael@0 | 46 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR |
michael@0 | 47 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY |
michael@0 | 48 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE |
michael@0 | 49 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND |
michael@0 | 50 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND |
michael@0 | 51 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET |
michael@0 | 52 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET |
michael@0 | 53 | { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY |
michael@0 | 54 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL |
michael@0 | 55 | { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR |
michael@0 | 56 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY |
michael@0 | 57 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY |
michael@0 | 58 | {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH |
michael@0 | 59 | }; |
michael@0 | 60 | |
michael@0 | 61 | U_NAMESPACE_BEGIN |
michael@0 | 62 | |
michael@0 | 63 | static const int32_t PERSIAN_EPOCH = 1948320; |
michael@0 | 64 | |
michael@0 | 65 | // Implementation of the PersianCalendar class |
michael@0 | 66 | |
michael@0 | 67 | //------------------------------------------------------------------------- |
michael@0 | 68 | // Constructors... |
michael@0 | 69 | //------------------------------------------------------------------------- |
michael@0 | 70 | |
michael@0 | 71 | const char *PersianCalendar::getType() const { |
michael@0 | 72 | return "persian"; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | Calendar* PersianCalendar::clone() const { |
michael@0 | 76 | return new PersianCalendar(*this); |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | PersianCalendar::PersianCalendar(const Locale& aLocale, UErrorCode& success) |
michael@0 | 80 | : Calendar(TimeZone::createDefault(), aLocale, success) |
michael@0 | 81 | { |
michael@0 | 82 | setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | PersianCalendar::PersianCalendar(const PersianCalendar& other) : Calendar(other) { |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | PersianCalendar::~PersianCalendar() |
michael@0 | 89 | { |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | //------------------------------------------------------------------------- |
michael@0 | 93 | // Minimum / Maximum access functions |
michael@0 | 94 | //------------------------------------------------------------------------- |
michael@0 | 95 | |
michael@0 | 96 | |
michael@0 | 97 | int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { |
michael@0 | 98 | return kPersianCalendarLimits[field][limitType]; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | //------------------------------------------------------------------------- |
michael@0 | 102 | // Assorted calculation utilities |
michael@0 | 103 | // |
michael@0 | 104 | |
michael@0 | 105 | /** |
michael@0 | 106 | * Determine whether a year is a leap year in the Persian calendar |
michael@0 | 107 | */ |
michael@0 | 108 | UBool PersianCalendar::isLeapYear(int32_t year) |
michael@0 | 109 | { |
michael@0 | 110 | int32_t remainder; |
michael@0 | 111 | ClockMath::floorDivide(25 * year + 11, 33, remainder); |
michael@0 | 112 | return (remainder < 8); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | /** |
michael@0 | 116 | * Return the day # on which the given year starts. Days are counted |
michael@0 | 117 | * from the Persian epoch, origin 0. |
michael@0 | 118 | */ |
michael@0 | 119 | int32_t PersianCalendar::yearStart(int32_t year) { |
michael@0 | 120 | return handleComputeMonthStart(year,0,FALSE); |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * Return the day # on which the given month starts. Days are counted |
michael@0 | 125 | * from the Persian epoch, origin 0. |
michael@0 | 126 | * |
michael@0 | 127 | * @param year The Persian year |
michael@0 | 128 | * @param year The Persian month, 0-based |
michael@0 | 129 | */ |
michael@0 | 130 | int32_t PersianCalendar::monthStart(int32_t year, int32_t month) const { |
michael@0 | 131 | return handleComputeMonthStart(year,month,TRUE); |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | //---------------------------------------------------------------------- |
michael@0 | 135 | // Calendar framework |
michael@0 | 136 | //---------------------------------------------------------------------- |
michael@0 | 137 | |
michael@0 | 138 | /** |
michael@0 | 139 | * Return the length (in days) of the given month. |
michael@0 | 140 | * |
michael@0 | 141 | * @param year The Persian year |
michael@0 | 142 | * @param year The Persian month, 0-based |
michael@0 | 143 | */ |
michael@0 | 144 | int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { |
michael@0 | 145 | // If the month is out of range, adjust it into range, and |
michael@0 | 146 | // modify the extended year value accordingly. |
michael@0 | 147 | if (month < 0 || month > 11) { |
michael@0 | 148 | extendedYear += ClockMath::floorDivide(month, 12, month); |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | return isLeapYear(extendedYear) ? kPersianLeapMonthLength[month] : kPersianMonthLength[month]; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | /** |
michael@0 | 155 | * Return the number of days in the given Persian year |
michael@0 | 156 | */ |
michael@0 | 157 | int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear) const { |
michael@0 | 158 | return isLeapYear(extendedYear) ? 366 : 365; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | //------------------------------------------------------------------------- |
michael@0 | 162 | // Functions for converting from field values to milliseconds.... |
michael@0 | 163 | //------------------------------------------------------------------------- |
michael@0 | 164 | |
michael@0 | 165 | // Return JD of start of given month/year |
michael@0 | 166 | int32_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/) const { |
michael@0 | 167 | // If the month is out of range, adjust it into range, and |
michael@0 | 168 | // modify the extended year value accordingly. |
michael@0 | 169 | if (month < 0 || month > 11) { |
michael@0 | 170 | eyear += ClockMath::floorDivide(month, 12, month); |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | int32_t julianDay = PERSIAN_EPOCH - 1 + 365 * (eyear - 1) + ClockMath::floorDivide(8 * eyear + 21, 33); |
michael@0 | 174 | |
michael@0 | 175 | if (month != 0) { |
michael@0 | 176 | julianDay += kPersianNumDays[month]; |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | return julianDay; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | //------------------------------------------------------------------------- |
michael@0 | 183 | // Functions for converting from milliseconds to field values |
michael@0 | 184 | //------------------------------------------------------------------------- |
michael@0 | 185 | |
michael@0 | 186 | int32_t PersianCalendar::handleGetExtendedYear() { |
michael@0 | 187 | int32_t year; |
michael@0 | 188 | if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { |
michael@0 | 189 | year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 |
michael@0 | 190 | } else { |
michael@0 | 191 | year = internalGet(UCAL_YEAR, 1); // Default to year 1 |
michael@0 | 192 | } |
michael@0 | 193 | return year; |
michael@0 | 194 | } |
michael@0 | 195 | |
michael@0 | 196 | /** |
michael@0 | 197 | * Override Calendar to compute several fields specific to the Persian |
michael@0 | 198 | * calendar system. These are: |
michael@0 | 199 | * |
michael@0 | 200 | * <ul><li>ERA |
michael@0 | 201 | * <li>YEAR |
michael@0 | 202 | * <li>MONTH |
michael@0 | 203 | * <li>DAY_OF_MONTH |
michael@0 | 204 | * <li>DAY_OF_YEAR |
michael@0 | 205 | * <li>EXTENDED_YEAR</ul> |
michael@0 | 206 | * |
michael@0 | 207 | * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this |
michael@0 | 208 | * method is called. |
michael@0 | 209 | */ |
michael@0 | 210 | void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { |
michael@0 | 211 | int32_t year, month, dayOfMonth, dayOfYear; |
michael@0 | 212 | |
michael@0 | 213 | int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH; |
michael@0 | 214 | year = 1 + ClockMath::floorDivide(33 * daysSinceEpoch + 3, 12053); |
michael@0 | 215 | |
michael@0 | 216 | int32_t farvardin1 = 365 * (year - 1) + ClockMath::floorDivide(8 * year + 21, 33); |
michael@0 | 217 | dayOfYear = (daysSinceEpoch - farvardin1); // 0-based |
michael@0 | 218 | if (dayOfYear < 216) { // Compute 0-based month |
michael@0 | 219 | month = dayOfYear / 31; |
michael@0 | 220 | } else { |
michael@0 | 221 | month = (dayOfYear - 6) / 30; |
michael@0 | 222 | } |
michael@0 | 223 | dayOfMonth = dayOfYear - kPersianNumDays[month] + 1; |
michael@0 | 224 | ++dayOfYear; // Make it 1-based now |
michael@0 | 225 | |
michael@0 | 226 | internalSet(UCAL_ERA, 0); |
michael@0 | 227 | internalSet(UCAL_YEAR, year); |
michael@0 | 228 | internalSet(UCAL_EXTENDED_YEAR, year); |
michael@0 | 229 | internalSet(UCAL_MONTH, month); |
michael@0 | 230 | internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); |
michael@0 | 231 | internalSet(UCAL_DAY_OF_YEAR, dayOfYear); |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | UBool |
michael@0 | 235 | PersianCalendar::inDaylightTime(UErrorCode& status) const |
michael@0 | 236 | { |
michael@0 | 237 | // copied from GregorianCalendar |
michael@0 | 238 | if (U_FAILURE(status) || !getTimeZone().useDaylightTime()) |
michael@0 | 239 | return FALSE; |
michael@0 | 240 | |
michael@0 | 241 | // Force an update of the state of the Calendar. |
michael@0 | 242 | ((PersianCalendar*)this)->complete(status); // cast away const |
michael@0 | 243 | |
michael@0 | 244 | return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | // default century |
michael@0 | 248 | |
michael@0 | 249 | static UDate gSystemDefaultCenturyStart = DBL_MIN; |
michael@0 | 250 | static int32_t gSystemDefaultCenturyStartYear = -1; |
michael@0 | 251 | static icu::UInitOnce gSystemDefaultCenturyInit = U_INITONCE_INITIALIZER; |
michael@0 | 252 | |
michael@0 | 253 | UBool PersianCalendar::haveDefaultCentury() const |
michael@0 | 254 | { |
michael@0 | 255 | return TRUE; |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | static void U_CALLCONV initializeSystemDefaultCentury() { |
michael@0 | 259 | // initialize systemDefaultCentury and systemDefaultCenturyYear based |
michael@0 | 260 | // on the current time. They'll be set to 80 years before |
michael@0 | 261 | // the current time. |
michael@0 | 262 | UErrorCode status = U_ZERO_ERROR; |
michael@0 | 263 | PersianCalendar calendar(Locale("@calendar=persian"),status); |
michael@0 | 264 | if (U_SUCCESS(status)) |
michael@0 | 265 | { |
michael@0 | 266 | calendar.setTime(Calendar::getNow(), status); |
michael@0 | 267 | calendar.add(UCAL_YEAR, -80, status); |
michael@0 | 268 | |
michael@0 | 269 | gSystemDefaultCenturyStart = calendar.getTime(status); |
michael@0 | 270 | gSystemDefaultCenturyStartYear = calendar.get(UCAL_YEAR, status); |
michael@0 | 271 | } |
michael@0 | 272 | // We have no recourse upon failure unless we want to propagate the failure |
michael@0 | 273 | // out. |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | UDate PersianCalendar::defaultCenturyStart() const { |
michael@0 | 277 | // lazy-evaluate systemDefaultCenturyStart |
michael@0 | 278 | umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury); |
michael@0 | 279 | return gSystemDefaultCenturyStart; |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | int32_t PersianCalendar::defaultCenturyStartYear() const { |
michael@0 | 283 | // lazy-evaluate systemDefaultCenturyStartYear |
michael@0 | 284 | umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury); |
michael@0 | 285 | return gSystemDefaultCenturyStartYear; |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar) |
michael@0 | 289 | |
michael@0 | 290 | U_NAMESPACE_END |
michael@0 | 291 | |
michael@0 | 292 | #endif |
michael@0 | 293 |