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
23static const int8_t monthDays[] = { 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 };
24
25static int32_t
26jalali_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
87static 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
174U_NAMESPACE_BEGIN
175
176// Implementation of the PersianCalendar class
177
178//-------------------------------------------------------------------------
179// Constructors...
180//-------------------------------------------------------------------------
181
182const char *PersianCalendar::getType() const {
183    return "persian";
184}
185
186Calendar* PersianCalendar::clone() const {
187    return new PersianCalendar(*this);
188}
189
190PersianCalendar::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
196PersianCalendar::PersianCalendar(const PersianCalendar& other) : Calendar(other) {
197}
198
199PersianCalendar::~PersianCalendar()
200{
201}
202
203//-------------------------------------------------------------------------
204// Minimum / Maximum access functions
205//-------------------------------------------------------------------------
206
207static 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};
234static 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
254int32_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 */
265UBool 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 */
274int32_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 */
285int32_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 */
299int32_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 */
306int32_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
315int32_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
329int32_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 */
354void 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
365UBool
366PersianCalendar::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
379const UDate     PersianCalendar::fgSystemDefaultCentury        = DBL_MIN;
380const int32_t   PersianCalendar::fgSystemDefaultCenturyYear    = -1;
381
382UDate           PersianCalendar::fgSystemDefaultCenturyStart       = DBL_MIN;
383int32_t         PersianCalendar::fgSystemDefaultCenturyStartYear   = -1;
384
385UBool PersianCalendar::haveDefaultCentury() const
386{
387    return TRUE;
388}
389
390UDate PersianCalendar::defaultCenturyStart() const
391{
392    return internalGetDefaultCenturyStart();
393}
394
395int32_t PersianCalendar::defaultCenturyStartYear() const
396{
397    return internalGetDefaultCenturyStartYear();
398}
399
400UDate
401PersianCalendar::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
417int32_t
418PersianCalendar::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
434void
435PersianCalendar::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
460UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar)
461
462U_NAMESPACE_END
463
464#endif
465
466