1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_i18npool.hxx"
30 
31 #include "calendar_gregorian.hxx"
32 #include "localedata.hxx"
33 #include <com/sun/star/i18n/AmPmValue.hpp>
34 #include <com/sun/star/i18n/Months.hpp>
35 #include <com/sun/star/i18n/Weekdays.hpp>
36 #include <com/sun/star/i18n/reservedWords.hpp>
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
38 #include <comphelper/processfactory.hxx>
39 
40 #include <stdio.h>
41 #include <string.h>
42 
43 #define erDUMP_ICU_CALENDAR 0
44 #define erDUMP_I18N_CALENDAR 0
45 #if erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
46 // If both are used, DUMP_ICU_CAL_MSG() must be used before DUMP_I18N_CAL_MSG()
47 // to obtain internally set values from ICU, else Calendar::get() calls in
48 // DUMP_I18N_CAL_MSG() recalculate!
49 
50 // These pieces of macro are shamelessly borrowed from icu's olsontz.cpp, the
51 // double parens'ed approach to pass multiple parameters as one macro parameter
52 // is appealing.
53 static void debug_cal_loc(const char *f, int32_t l)
54 {
55     fprintf(stderr, "%s:%d: ", f, l);
56 }
57 # include <stdarg.h>
58 static void debug_cal_msg(const char *pat, ...)
59 {
60     va_list ap;
61     va_start(ap, pat);
62     vfprintf(stderr, pat, ap);
63 }
64 
65 #if erDUMP_ICU_CALENDAR
66 // Make icu with
67 // DEFS = -DU_DEBUG_CALSVC -DUCAL_DEBUG_DUMP
68 // in icu/$(INPATH)/misc/build/icu/source/icudefs.mk
69 // May need some patches to fix unmaintained things there.
70 extern void ucal_dump( const icu::Calendar & );
71 static void debug_icu_cal_dump( const ::icu::Calendar & r )
72 {
73     ucal_dump(r);
74     fflush(stderr);
75     // set a breakpoint here to pause display between dumps
76 }
77 // must use double parens, i.e.:  DUMP_ICU_CAL_MSG(("four is: %d",4));
78 #define DUMP_ICU_CAL_MSG(x) {debug_cal_loc(__FILE__,__LINE__);debug_cal_msg x;debug_icu_cal_dump(*body);}
79 #else   // erDUMP_ICU_CALENDAR
80 #define DUMP_ICU_CAL_MSG(x)
81 #endif  // erDUMP_ICU_CALENDAR
82 
83 #if erDUMP_I18N_CALENDAR
84 static void debug_cal_millis_to_time( long nMillis, long & h, long & m, long & s, long & f )
85 {
86     int sign = (nMillis < 0 ? -1 : 1);
87     nMillis = ::std::abs(nMillis);
88     h = sign * nMillis / (60 * 60 * 1000);
89     nMillis -= sign * h * (60 * 60 * 1000);
90     m = nMillis / (60 * 1000);
91     nMillis -= m * (60 * 1000);
92     s = nMillis / (1000);
93     nMillis -= s * (1000);
94     f = nMillis;
95 }
96 static void debug_i18n_cal_dump( const ::icu::Calendar & r )
97 {
98     UErrorCode status;
99     long nMillis, h, m, s, f;
100     fprintf( stderr, " %04ld", (long)r.get( UCAL_YEAR, status = U_ZERO_ERROR));
101     fprintf( stderr, "-%02ld", (long)r.get( UCAL_MONTH, status = U_ZERO_ERROR)+1);
102     fprintf( stderr, "-%02ld", (long)r.get( UCAL_DATE, status = U_ZERO_ERROR));
103     fprintf( stderr, " %02ld", (long)r.get( UCAL_HOUR_OF_DAY, status = U_ZERO_ERROR));
104     fprintf( stderr, ":%02ld", (long)r.get( UCAL_MINUTE, status = U_ZERO_ERROR));
105     fprintf( stderr, ":%02ld", (long)r.get( UCAL_SECOND, status = U_ZERO_ERROR));
106     fprintf( stderr, "  zone: %ld", (long)(nMillis = r.get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR)));
107     fprintf( stderr, " (%f min)", (double)nMillis / 60000);
108     debug_cal_millis_to_time( nMillis, h, m, s, f);
109     fprintf( stderr, " (%ld:%02ld:%02ld.%ld)", h, m, s, f);
110     fprintf( stderr, "  DST: %ld", (long)(nMillis = r.get( UCAL_DST_OFFSET, status = U_ZERO_ERROR)));
111     fprintf( stderr, " (%f min)", (double)nMillis / 60000);
112     debug_cal_millis_to_time( nMillis, h, m, s, f);
113     fprintf( stderr, " (%ld:%02ld:%02ld.%ld)", h, m, s, f);
114     fprintf( stderr, "\n");
115     fflush(stderr);
116 }
117 // must use double parens, i.e.:  DUMP_I18N_CAL_MSG(("four is: %d",4));
118 #define DUMP_I18N_CAL_MSG(x) {debug_cal_loc(__FILE__,__LINE__);debug_cal_msg x;debug_i18n_cal_dump(*body);}
119 #else   // erDUMP_I18N_CALENDAR
120 #define DUMP_I18N_CAL_MSG(x)
121 #endif  // erDUMP_I18N_CALENDAR
122 
123 #else   // erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
124 #define DUMP_ICU_CAL_MSG(x)
125 #define DUMP_I18N_CAL_MSG(x)
126 #endif  // erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
127 
128 
129 using namespace ::com::sun::star::uno;
130 using namespace ::com::sun::star::lang;
131 using namespace ::com::sun::star::i18n;
132 using namespace ::rtl;
133 
134 #define ERROR RuntimeException()
135 
136 Calendar_gregorian::Calendar_gregorian()
137 {
138     init(NULL);
139 }
140 Calendar_gregorian::Calendar_gregorian(Era *_earArray)
141 {
142     init(_earArray);
143 }
144 void SAL_CALL
145 Calendar_gregorian::init(Era *_eraArray)
146 {
147         cCalendar = "com.sun.star.i18n.Calendar_gregorian";
148 
149         // #i102356# With icu::Calendar::createInstance(UErrorCode) in a Thai
150         // th_TH system locale we accidentally used a Buddhist calendar. Though
151         // the ICU documentation says that should be the case only for
152         // th_TH_TRADITIONAL (and ja_JP_TRADITIONAL Gengou), a plain th_TH
153         // already triggers that behavior, ja_JP does not. Strange enough,
154         // passing a th_TH locale to the calendar creation doesn't trigger
155         // this.
156         // See also http://userguide.icu-project.org/datetime/calendar
157 
158         // Whatever ICU offers as the default calendar for a locale, ensure we
159         // have a Gregorian calendar as requested.
160 
161         /* XXX: with the current implementation the aLocale member variable is
162          * not set prior to loading a calendar from locale data. This
163          * creates an empty (root) locale for ICU, but at least the correct
164          * calendar is used. The language part must not be NULL (respectively
165          * not all, language and country and variant), otherwise the current
166          * default locale would be used again and the calendar keyword ignored.
167          * */
168         icu::Locale aIcuLocale( "", NULL, NULL, "calendar=gregorian");
169 
170         UErrorCode status;
171         body = icu::Calendar::createInstance( aIcuLocale, status = U_ZERO_ERROR);
172         if (!body || !U_SUCCESS(status)) throw ERROR;
173 
174 #if 0
175         {
176             icu::Locale loc;
177             loc = body->getLocale( ULOC_ACTUAL_LOCALE, status = U_ZERO_ERROR);
178             fprintf( stderr, "\nICU calendar actual locale: %s\n", loc.getName());
179             loc = body->getLocale( ULOC_VALID_LOCALE, status = U_ZERO_ERROR);
180             fprintf( stderr,   "ICU calendar valid  locale: %s\n", loc.getName());
181         }
182 #endif
183 
184         eraArray=_eraArray;
185 }
186 
187 Calendar_gregorian::~Calendar_gregorian()
188 {
189         delete body;
190 }
191 
192 Calendar_hanja::Calendar_hanja()
193 {
194         cCalendar = "com.sun.star.i18n.Calendar_hanja";
195 }
196 
197 OUString SAL_CALL
198 Calendar_hanja::getDisplayName( sal_Int16 displayIndex, sal_Int16 idx, sal_Int16 nameType ) throw(RuntimeException)
199 {
200         if ( displayIndex == CalendarDisplayIndex::AM_PM ) {
201             // Am/Pm string for Korean Hanja calendar will refer to Japanese locale
202             com::sun::star::lang::Locale jaLocale =
203                 com::sun::star::lang::Locale(OUString::createFromAscii("ja"), OUString(), OUString());
204             if (idx == 0) return LocaleData().getLocaleItem(jaLocale).timeAM;
205             else if (idx == 1) return LocaleData().getLocaleItem(jaLocale).timePM;
206             else throw ERROR;
207         }
208         else
209             return Calendar_gregorian::getDisplayName( displayIndex, idx, nameType );
210 }
211 
212 void SAL_CALL
213 Calendar_hanja::loadCalendar( const OUString& /*uniqueID*/, const com::sun::star::lang::Locale& rLocale ) throw(RuntimeException)
214 {
215         // Since this class could be called by service name 'hanja_yoil', we have to
216         // rename uniqueID to get right calendar defined in locale data.
217         Calendar_gregorian::loadCalendar(OUString::createFromAscii("hanja"), rLocale);
218 }
219 
220 static Era gengou_eraArray[] = {
221     {1868,  1,  1},
222     {1912,  7, 30},
223     {1926, 12, 25},
224     {1989,  1,  8},
225     {0, 0,  0}
226 };
227 Calendar_gengou::Calendar_gengou() : Calendar_gregorian(gengou_eraArray)
228 {
229         cCalendar = "com.sun.star.i18n.Calendar_gengou";
230 }
231 
232 static Era ROC_eraArray[] = {
233     {1912, 1, 1},
234     {0, 0,  0}
235 };
236 Calendar_ROC::Calendar_ROC() : Calendar_gregorian(ROC_eraArray)
237 {
238         cCalendar = "com.sun.star.i18n.Calendar_ROC";
239 }
240 
241 static Era buddhist_eraArray[] = {
242     {-542, 1, 1},
243     {0, 0,  0}
244 };
245 Calendar_buddhist::Calendar_buddhist() : Calendar_gregorian(buddhist_eraArray)
246 {
247         cCalendar = "com.sun.star.i18n.Calendar_buddhist";
248 }
249 
250 void SAL_CALL
251 Calendar_gregorian::loadCalendar( const OUString& uniqueID, const com::sun::star::lang::Locale& rLocale ) throw(RuntimeException)
252 {
253         // init. fieldValue[]
254         getValue();
255 
256         aLocale = rLocale;
257         Sequence< Calendar> xC = LocaleData().getAllCalendars(rLocale);
258         for (sal_Int32 i = 0; i < xC.getLength(); i++)
259         {
260             if (uniqueID == xC[i].Name)
261             {
262                 aCalendar = xC[i];
263                 // setup minimalDaysInFirstWeek
264                 setMinimumNumberOfDaysForFirstWeek(
265                         aCalendar.MinimumNumberOfDaysForFirstWeek);
266                 // setup first day of week
267                 for (sal_Int16 day = sal::static_int_cast<sal_Int16>(
268                             aCalendar.Days.getLength()-1); day>=0; day--)
269                 {
270                     if (aCalendar.StartOfWeek == aCalendar.Days[day].ID)
271                     {
272                         setFirstDayOfWeek( day);
273                         return;
274                     }
275                 }
276             }
277         }
278         // Calendar is not for the locale
279         throw ERROR;
280 }
281 
282 
283 com::sun::star::i18n::Calendar SAL_CALL
284 Calendar_gregorian::getLoadedCalendar() throw(RuntimeException)
285 {
286         return aCalendar;
287 }
288 
289 OUString SAL_CALL
290 Calendar_gregorian::getUniqueID() throw(RuntimeException)
291 {
292         return aCalendar.Name;
293 }
294 
295 void SAL_CALL
296 Calendar_gregorian::setDateTime( double timeInDays ) throw(RuntimeException)
297 {
298         UErrorCode status;
299         body->setTime(timeInDays * U_MILLIS_PER_DAY, status = U_ZERO_ERROR);
300         if ( !U_SUCCESS(status) ) throw ERROR;
301         getValue();
302 }
303 
304 double SAL_CALL
305 Calendar_gregorian::getDateTime() throw(RuntimeException)
306 {
307         if (fieldSet) {
308             setValue();
309             getValue();
310         }
311         UErrorCode status;
312         double r = body->getTime(status = U_ZERO_ERROR);
313         if ( !U_SUCCESS(status) ) throw ERROR;
314         return r / U_MILLIS_PER_DAY;
315 }
316 
317 // map field value from gregorian calendar to other calendar, it can be overwritten by derived class.
318 // By using eraArray, it can take care Japanese and Taiwan ROC calendar.
319 void Calendar_gregorian::mapFromGregorian() throw(RuntimeException)
320 {
321         if (eraArray) {
322             sal_Int16 e, y, m, d;
323 
324             e = fieldValue[CalendarFieldIndex::ERA];
325             y = fieldValue[CalendarFieldIndex::YEAR];
326             m = fieldValue[CalendarFieldIndex::MONTH] + 1;
327             d = fieldValue[CalendarFieldIndex::DAY_OF_MONTH];
328 
329             // since the year is reversed for first era, it is reversed again here for Era compare.
330             if (e == 0)
331                 y = 1 - y;
332 
333             for (e = 0; eraArray[e].year; e++)
334                 if ((y != eraArray[e].year) ? y < eraArray[e].year :
335                     (m != eraArray[e].month) ? m < eraArray[e].month : d < eraArray[e].day)
336                     break;
337 
338             fieldValue[CalendarFieldIndex::ERA] = e;
339             fieldValue[CalendarFieldIndex::YEAR] =
340                 sal::static_int_cast<sal_Int16>( (e == 0) ? (eraArray[0].year - y) : (y - eraArray[e-1].year + 1) );
341         }
342 }
343 
344 #define FIELDS  ((1 << CalendarFieldIndex::ERA) | (1 << CalendarFieldIndex::YEAR))
345 // map field value from other calendar to gregorian calendar, it can be overwritten by derived class.
346 // By using eraArray, it can take care Japanese and Taiwan ROC calendar.
347 void Calendar_gregorian::mapToGregorian() throw(RuntimeException)
348 {
349         if (eraArray && (fieldSet & FIELDS)) {
350             sal_Int16 y, e = fieldValue[CalendarFieldIndex::ERA];
351             if (e == 0)
352                 y = sal::static_int_cast<sal_Int16>( eraArray[0].year - fieldValue[CalendarFieldIndex::YEAR] );
353             else
354                 y = sal::static_int_cast<sal_Int16>( eraArray[e-1].year + fieldValue[CalendarFieldIndex::YEAR] - 1 );
355 
356             fieldSetValue[CalendarFieldIndex::ERA] = y <= 0 ? 0 : 1;
357             fieldSetValue[CalendarFieldIndex::YEAR] = (y <= 0 ? 1 - y : y);
358             fieldSet |= FIELDS;
359         }
360 }
361 
362 static UCalendarDateFields fieldNameConverter(sal_Int16 fieldIndex) throw(RuntimeException)
363 {
364         UCalendarDateFields f;
365 
366         switch (fieldIndex) {
367             case CalendarFieldIndex::AM_PM:             f = UCAL_AM_PM; break;
368             case CalendarFieldIndex::DAY_OF_MONTH:      f = UCAL_DATE; break;
369             case CalendarFieldIndex::DAY_OF_WEEK:       f = UCAL_DAY_OF_WEEK; break;
370             case CalendarFieldIndex::DAY_OF_YEAR:       f = UCAL_DAY_OF_YEAR; break;
371             case CalendarFieldIndex::DST_OFFSET:        f = UCAL_DST_OFFSET; break;
372             case CalendarFieldIndex::ZONE_OFFSET:       f = UCAL_ZONE_OFFSET; break;
373             case CalendarFieldIndex::HOUR:              f = UCAL_HOUR_OF_DAY; break;
374             case CalendarFieldIndex::MINUTE:            f = UCAL_MINUTE; break;
375             case CalendarFieldIndex::SECOND:            f = UCAL_SECOND; break;
376             case CalendarFieldIndex::MILLISECOND:       f = UCAL_MILLISECOND; break;
377             case CalendarFieldIndex::WEEK_OF_MONTH:     f = UCAL_WEEK_OF_MONTH; break;
378             case CalendarFieldIndex::WEEK_OF_YEAR:      f = UCAL_WEEK_OF_YEAR; break;
379             case CalendarFieldIndex::YEAR:              f = UCAL_YEAR; break;
380             case CalendarFieldIndex::MONTH:             f = UCAL_MONTH; break;
381             case CalendarFieldIndex::ERA:               f = UCAL_ERA; break;
382             default: throw ERROR;
383         }
384         return f;
385 }
386 
387 void SAL_CALL
388 Calendar_gregorian::setValue( sal_Int16 fieldIndex, sal_Int16 value ) throw(RuntimeException)
389 {
390     if (fieldIndex < 0 || FIELD_INDEX_COUNT <= fieldIndex)
391         throw ERROR;
392     fieldSet |= (1 << fieldIndex);
393     fieldValue[fieldIndex] = value;
394 }
395 
396 bool Calendar_gregorian::getCombinedOffset( sal_Int32 & o_nOffset,
397         sal_Int16 nParentFieldIndex, sal_Int16 nChildFieldIndex ) const
398 {
399     o_nOffset = 0;
400     bool bFieldsSet = false;
401     if (fieldSet & (1 << nParentFieldIndex))
402     {
403         bFieldsSet = true;
404         o_nOffset = static_cast<sal_Int32>( fieldValue[nParentFieldIndex]) * 60000;
405     }
406     if (fieldSet & (1 << nChildFieldIndex))
407     {
408         bFieldsSet = true;
409         if (o_nOffset < 0)
410             o_nOffset -= static_cast<sal_uInt16>( fieldValue[nChildFieldIndex]);
411         else
412             o_nOffset += static_cast<sal_uInt16>( fieldValue[nChildFieldIndex]);
413     }
414     return bFieldsSet;
415 }
416 
417 bool Calendar_gregorian::getZoneOffset( sal_Int32 & o_nOffset ) const
418 {
419     return getCombinedOffset( o_nOffset, CalendarFieldIndex::ZONE_OFFSET,
420             CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS);
421 }
422 
423 bool Calendar_gregorian::getDSTOffset( sal_Int32 & o_nOffset ) const
424 {
425     return getCombinedOffset( o_nOffset, CalendarFieldIndex::DST_OFFSET,
426             CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS);
427 }
428 
429 void Calendar_gregorian::submitFields() throw(com::sun::star::uno::RuntimeException)
430 {
431     for (sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++)
432     {
433         if (fieldSet & (1 << fieldIndex))
434         {
435             switch (fieldIndex)
436             {
437                 default:
438                     body->set(fieldNameConverter(fieldIndex), fieldSetValue[fieldIndex]);
439                     break;
440                 case CalendarFieldIndex::ZONE_OFFSET:
441                 case CalendarFieldIndex::DST_OFFSET:
442                 case CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS:
443                 case CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS:
444                     break;  // nothing, extra handling
445             }
446         }
447     }
448     sal_Int32 nZoneOffset, nDSTOffset;
449     if (getZoneOffset( nZoneOffset))
450         body->set( fieldNameConverter( CalendarFieldIndex::ZONE_OFFSET), nZoneOffset);
451     if (getDSTOffset( nDSTOffset))
452         body->set( fieldNameConverter( CalendarFieldIndex::DST_OFFSET), nDSTOffset);
453 }
454 
455 void Calendar_gregorian::submitValues( sal_Int32 nYear,
456         sal_Int32 nMonth, sal_Int32 nDay, sal_Int32 nHour, sal_Int32 nMinute,
457         sal_Int32 nSecond, sal_Int32 nMilliSecond, sal_Int32 nZone, sal_Int32 nDST )
458             throw(com::sun::star::uno::RuntimeException)
459 {
460     submitFields();
461     if (nYear >= 0)
462         body->set( UCAL_YEAR, nYear);
463     if (nMonth >= 0)
464         body->set( UCAL_MONTH, nMonth);
465     if (nDay >= 0)
466         body->set( UCAL_DATE, nDay);
467     if (nHour >= 0)
468         body->set( UCAL_HOUR_OF_DAY, nHour);
469     if (nMinute >= 0)
470         body->set( UCAL_MINUTE, nMinute);
471     if (nSecond >= 0)
472         body->set( UCAL_SECOND, nSecond);
473     if (nMilliSecond >= 0)
474         body->set( UCAL_MILLISECOND, nMilliSecond);
475     if (nZone != 0)
476         body->set( UCAL_ZONE_OFFSET, nZone);
477     if (nDST != 0)
478         body->set( UCAL_DST_OFFSET, nDST);
479 }
480 
481 static void lcl_setCombinedOffsetFieldValues( sal_Int32 nValue,
482         sal_Int16 rFieldSetValue[], sal_Int16 rFieldValue[],
483         sal_Int16 nParentFieldIndex, sal_Int16 nChildFieldIndex )
484 {
485     sal_Int32 nTrunc = nValue / 60000;
486     rFieldSetValue[nParentFieldIndex] = rFieldValue[nParentFieldIndex] =
487         static_cast<sal_Int16>( nTrunc);
488     sal_uInt16 nMillis = static_cast<sal_uInt16>( abs( nValue - nTrunc * 60000));
489     rFieldSetValue[nChildFieldIndex] = rFieldValue[nChildFieldIndex] =
490         static_cast<sal_Int16>( nMillis);
491 }
492 
493 void Calendar_gregorian::setValue() throw(RuntimeException)
494 {
495         // Correct DST glitch, see also localtime/gmtime conversion pitfalls at
496         // http://www.erack.de/download/timetest.c
497         //
498         // #i24082# in order to make the DST correction work in all
499         // circumstances, the time values have to be always resubmitted,
500         // regardless whether specified by the caller or not. It is not
501         // sufficient to rely on the ICU internal values previously set, as the
502         // following may happen:
503         // - Let 2004-03-28T02:00 be the onsetRule.
504         // - On 2004-03-29 (calendar initialized with 2004-03-29T00:00 DST) set
505         //   a date of 2004-03-28 => calendar results in 2004-03-27T23:00 no DST.
506         // - Correcting this with simply "2004-03-28 no DST" and no time
507         //   specified results in 2004-03-29T00:00, the ICU internal 23:00 time
508         //   being adjusted to 24:00 in this case, switching one day further.
509         // => submit 2004-03-28T00:00 no DST.
510 
511         // This got even weirder since ICU incorporated also historical data,
512         // even the timezone may differ for different dates! It is necessary to
513         // let ICU choose the corresponding OlsonTimeZone transitions and adapt
514         // values.
515         // #i86094# gives examples where that went wrong:
516         // TZ=Europe/Moscow date <= 1919-07-01
517         //      zone +2:30:48 (!) instead of +3h, DST +2h instead of +1h
518         // TZ=America/St_Johns date <= 1935-03-30
519         //      zone -3:30:52 (!) instead of -3:30
520 
521         // Copy fields before calling submitFields() directly or indirectly below.
522         memcpy(fieldSetValue, fieldValue, sizeof(fieldSetValue));
523         // Possibly setup ERA and YEAR in fieldSetValue.
524         mapToGregorian();
525 
526         DUMP_ICU_CAL_MSG(("%s\n","setValue() before any submission"));
527         DUMP_I18N_CAL_MSG(("%s\n","setValue() before any submission"));
528 
529         bool bNeedZone = !(fieldSet & (1 << CalendarFieldIndex::ZONE_OFFSET));
530         bool bNeedDST  = !(fieldSet & (1 << CalendarFieldIndex::DST_OFFSET));
531         sal_Int32 nZone1, nDST1, nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone0, nDST0;
532         nZone1 = nDST1 = nZone0 = nDST0 = 0;
533         nYear = nMonth = nDay = nHour = nMinute = nSecond = nMilliSecond = -1;
534         if ( bNeedZone || bNeedDST )
535         {
536             UErrorCode status;
537             if ( !(fieldSet & (1 << CalendarFieldIndex::YEAR)) )
538             {
539                 nYear = body->get( UCAL_YEAR, status = U_ZERO_ERROR);
540                 if ( !U_SUCCESS(status) )
541                     nYear = -1;
542             }
543             if ( !(fieldSet & (1 << CalendarFieldIndex::MONTH)) )
544             {
545                 nMonth = body->get( UCAL_MONTH, status = U_ZERO_ERROR);
546                 if ( !U_SUCCESS(status) )
547                     nMonth = -1;
548             }
549             if ( !(fieldSet & (1 << CalendarFieldIndex::DAY_OF_MONTH)) )
550             {
551                 nDay = body->get( UCAL_DATE, status = U_ZERO_ERROR);
552                 if ( !U_SUCCESS(status) )
553                     nDay = -1;
554             }
555             if ( !(fieldSet & (1 << CalendarFieldIndex::HOUR)) )
556             {
557                 nHour = body->get( UCAL_HOUR_OF_DAY, status = U_ZERO_ERROR);
558                 if ( !U_SUCCESS(status) )
559                     nHour = -1;
560             }
561             if ( !(fieldSet & (1 << CalendarFieldIndex::MINUTE)) )
562             {
563                 nMinute = body->get( UCAL_MINUTE, status = U_ZERO_ERROR);
564                 if ( !U_SUCCESS(status) )
565                     nMinute = -1;
566             }
567             if ( !(fieldSet & (1 << CalendarFieldIndex::SECOND)) )
568             {
569                 nSecond = body->get( UCAL_SECOND, status = U_ZERO_ERROR);
570                 if ( !U_SUCCESS(status) )
571                     nSecond = -1;
572             }
573             if ( !(fieldSet & (1 << CalendarFieldIndex::MILLISECOND)) )
574             {
575                 nMilliSecond = body->get( UCAL_MILLISECOND, status = U_ZERO_ERROR);
576                 if ( !U_SUCCESS(status) )
577                     nMilliSecond = -1;
578             }
579             if ( !(fieldSet & (1 << CalendarFieldIndex::ZONE_OFFSET)) )
580             {
581                 nZone0 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR);
582                 if ( !U_SUCCESS(status) )
583                     nZone0 = 0;
584             }
585             if ( !(fieldSet & (1 << CalendarFieldIndex::DST_OFFSET)) )
586             {
587                 nDST0 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR);
588                 if ( !U_SUCCESS(status) )
589                     nDST0 = 0;
590             }
591 
592             // Submit values to obtain a time zone and DST corresponding to the date/time.
593             submitValues( nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone0, nDST0);
594 
595             DUMP_ICU_CAL_MSG(("%s\n","setValue() in bNeedZone||bNeedDST after submitValues()"));
596             DUMP_I18N_CAL_MSG(("%s\n","setValue() in bNeedZone||bNeedDST after submitValues()"));
597             nZone1 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR);
598             if ( !U_SUCCESS(status) )
599                 nZone1 = 0;
600             nDST1 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR);
601             if ( !U_SUCCESS(status) )
602                 nDST1 = 0;
603         }
604 
605         // The original submission, may lead to a different zone/DST and
606         // different date.
607         submitFields();
608         DUMP_ICU_CAL_MSG(("%s\n","setValue() after original submission"));
609         DUMP_I18N_CAL_MSG(("%s\n","setValue() after original submission"));
610 
611         if ( bNeedZone || bNeedDST )
612         {
613             UErrorCode status;
614             sal_Int32 nZone2 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR);
615             if ( !U_SUCCESS(status) )
616                 nZone2 = nZone1;
617             sal_Int32 nDST2 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR);
618             if ( !U_SUCCESS(status) )
619                 nDST2 = nDST1;
620             if ( nZone0 != nZone1 || nZone2 != nZone1 || nDST0 != nDST1 || nDST2 != nDST1 )
621             {
622                 // Due to different DSTs, resulting date values may differ if
623                 // DST is onset at 00:00 and the very onsetRule date was
624                 // submitted with DST off => date-1 23:00, for example, which
625                 // is not what we want.
626                 // Resubmit all values, this time including DST => date 01:00
627                 // Similar for zone differences.
628                 // If already the first full submission with nZone0 and nDST0
629                 // lead to date-1 23:00, the original submission was based on
630                 // that date if it wasn't a full date (nDST0 set, nDST1 not
631                 // set, nDST2==nDST1). If it was January 1st without year we're
632                 // even off by one year now. Resubmit all values including new
633                 // DST => date 00:00.
634 
635                 // Set field values accordingly in case they were used.
636                 if (!bNeedZone)
637                     lcl_setCombinedOffsetFieldValues( nZone2, fieldSetValue,
638                             fieldValue, CalendarFieldIndex::ZONE_OFFSET,
639                             CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS);
640                 if (!bNeedDST)
641                     lcl_setCombinedOffsetFieldValues( nDST2, fieldSetValue,
642                             fieldValue, CalendarFieldIndex::DST_OFFSET,
643                             CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS);
644                 submitValues( nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone2, nDST2);
645                 DUMP_ICU_CAL_MSG(("%s\n","setValue() after Zone/DST glitch resubmit"));
646                 DUMP_I18N_CAL_MSG(("%s\n","setValue() after Zone/DST glitch resubmit"));
647 
648                 // Time zone transition => resubmit.
649                 // TZ=America/St_Johns date <= 1935-03-30
650                 //      -3:30:52 (!) instead of -3:30
651                 //      if first submission included time zone -3:30 that would be wrong.
652                 bool bResubmit = false;
653                 sal_Int32 nZone3 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR);
654                 if ( !U_SUCCESS(status) )
655                     nZone3 = nZone2;
656                 if (nZone3 != nZone2)
657                 {
658                     bResubmit = true;
659                     if (!bNeedZone)
660                         lcl_setCombinedOffsetFieldValues( nZone3, fieldSetValue,
661                                 fieldValue, CalendarFieldIndex::ZONE_OFFSET,
662                                 CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS);
663                 }
664 
665                 // If the DST onset rule says to switch from 00:00 to 01:00 and
666                 // we tried to set onsetDay 00:00 with DST, the result was
667                 // onsetDay-1 23:00 and no DST, which is not what we want. So
668                 // once again without DST, resulting in onsetDay 01:00 and DST.
669                 // Yes, this seems to be weird, but logically correct.
670                 // It doesn't even have to be on an onsetDay as the DST is
671                 // factored in all days by ICU and there seems to be some
672                 // unknown behavior.
673                 // TZ=Asia/Tehran 1999-03-22 exposes this, for example.
674                 sal_Int32 nDST3 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR);
675                 if ( !U_SUCCESS(status) )
676                     nDST3 = nDST2;
677                 if (nDST2 != nDST3 && !nDST3)
678                 {
679                     bResubmit = true;
680                     if (!bNeedDST)
681                     {
682                         fieldSetValue[CalendarFieldIndex::DST_OFFSET] =
683                             fieldValue[CalendarFieldIndex::DST_OFFSET] = 0;
684                         fieldSetValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] =
685                             fieldValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] = 0;
686                     }
687                 }
688                 if (bResubmit)
689                 {
690                     submitValues( nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone3, nDST3);
691                     DUMP_ICU_CAL_MSG(("%s\n","setValue() after Zone/DST glitch 2nd resubmit"));
692                     DUMP_I18N_CAL_MSG(("%s\n","setValue() after Zone/DST glitch 2nd resubmit"));
693                 }
694             }
695         }
696 #if erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
697         {
698             // force icu::Calendar to recalculate
699             UErrorCode status;
700             sal_Int32 nTmp = body->get( UCAL_DATE, status = U_ZERO_ERROR);
701             DUMP_ICU_CAL_MSG(("%s: %d\n","setValue() result day",nTmp));
702             DUMP_I18N_CAL_MSG(("%s: %d\n","setValue() result day",nTmp));
703         }
704 #endif
705 }
706 
707 void Calendar_gregorian::getValue() throw(RuntimeException)
708 {
709     DUMP_ICU_CAL_MSG(("%s\n","getValue()"));
710     DUMP_I18N_CAL_MSG(("%s\n","getValue()"));
711     for (sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++)
712     {
713         if (fieldIndex == CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS ||
714                 fieldIndex == CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS)
715             continue;   // not ICU fields
716 
717         UErrorCode status; sal_Int32 value = body->get( fieldNameConverter(
718                     fieldIndex), status = U_ZERO_ERROR);
719         if ( !U_SUCCESS(status) ) throw ERROR;
720 
721         // Convert millisecond to minute for ZONE and DST and set remainder in
722         // second field.
723         if (fieldIndex == CalendarFieldIndex::ZONE_OFFSET)
724         {
725             sal_Int32 nMinutes = value / 60000;
726             sal_Int16 nMillis = static_cast<sal_Int16>( static_cast<sal_uInt16>(
727                         abs( value - nMinutes * 60000)));
728             fieldValue[CalendarFieldIndex::ZONE_OFFSET] = static_cast<sal_Int16>( nMinutes);
729             fieldValue[CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS] = nMillis;
730         }
731         else if (fieldIndex == CalendarFieldIndex::DST_OFFSET)
732         {
733             sal_Int32 nMinutes = value / 60000;
734             sal_Int16 nMillis = static_cast<sal_Int16>( static_cast<sal_uInt16>(
735                         abs( value - nMinutes * 60000)));
736             fieldValue[CalendarFieldIndex::DST_OFFSET] = static_cast<sal_Int16>( nMinutes);
737             fieldValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] = nMillis;
738         }
739         else
740             fieldValue[fieldIndex] = (sal_Int16) value;
741 
742         // offset 1 since the value for week start day SunDay is different between Calendar and Weekdays.
743         if ( fieldIndex == CalendarFieldIndex::DAY_OF_WEEK )
744             fieldValue[fieldIndex]--;   // UCAL_SUNDAY:/* == 1 */ ==> Weekdays::SUNDAY /* ==0 */
745     }
746     mapFromGregorian();
747     fieldSet = 0;
748 }
749 
750 sal_Int16 SAL_CALL
751 Calendar_gregorian::getValue( sal_Int16 fieldIndex ) throw(RuntimeException)
752 {
753     if (fieldIndex < 0 || FIELD_INDEX_COUNT <= fieldIndex)
754         throw ERROR;
755 
756     if (fieldSet)  {
757         setValue();
758         getValue();
759     }
760 
761     return fieldValue[fieldIndex];
762 }
763 
764 void SAL_CALL
765 Calendar_gregorian::addValue( sal_Int16 fieldIndex, sal_Int32 value ) throw(RuntimeException)
766 {
767         // since ZONE and DST could not be add, we don't need to convert value here
768         UErrorCode status;
769         body->add(fieldNameConverter(fieldIndex), value, status = U_ZERO_ERROR);
770         if ( !U_SUCCESS(status) ) throw ERROR;
771         getValue();
772 }
773 
774 sal_Bool SAL_CALL
775 Calendar_gregorian::isValid() throw(RuntimeException)
776 {
777         if (fieldSet) {
778             sal_Int32 tmp = fieldSet;
779             setValue();
780             memcpy(fieldSetValue, fieldValue, sizeof(fieldSetValue));
781             getValue();
782             for ( sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++ ) {
783                 // compare only with fields that are set and reset fieldSet[]
784                 if (tmp & (1 << fieldIndex)) {
785                     if (fieldSetValue[fieldIndex] != fieldValue[fieldIndex])
786                         return sal_False;
787                 }
788             }
789         }
790         return true;
791 }
792 
793 // NativeNumberMode has different meaning between Number and Calendar for Asian locales.
794 // Here is the mapping table
795 // calendar(q/y/m/d)    zh_CN           zh_TW           ja              ko
796 // NatNum1              NatNum1/1/7/7   NatNum1/1/7/7   NatNum1/1/4/4   NatNum1/1/7/7
797 // NatNum2              NatNum2/2/8/8   NatNum2/2/8/8   NatNum2/2/5/5   NatNum2/2/8/8
798 // NatNum3              NatNum3/3/3/3   NatNum3/3/3/3   NatNum3/3/3/3   NatNum3/3/3/3
799 // NatNum4                                                              NatNum9/9/11/11
800 
801 static sal_Int16 SAL_CALL NatNumForCalendar(const com::sun::star::lang::Locale& aLocale,
802         sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, sal_Int16 value )
803 {
804     sal_Bool isShort = ((nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR ||
805         nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR) && value >= 100) ||
806         nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER ||
807         nCalendarDisplayCode == CalendarDisplayCode::LONG_QUARTER;
808     sal_Bool isChinese = aLocale.Language.equalsAscii("zh");
809     sal_Bool isJapanese = aLocale.Language.equalsAscii("ja");
810     sal_Bool isKorean = aLocale.Language.equalsAscii("ko");
811 
812     if (isChinese || isJapanese || isKorean) {
813         switch (nNativeNumberMode) {
814             case NativeNumberMode::NATNUM1:
815                 if (!isShort)
816                     nNativeNumberMode = isJapanese ? NativeNumberMode::NATNUM4 : NativeNumberMode::NATNUM7;
817                 break;
818             case NativeNumberMode::NATNUM2:
819                 if (!isShort)
820                     nNativeNumberMode = isJapanese ? NativeNumberMode::NATNUM5 : NativeNumberMode::NATNUM8;
821                 break;
822             case NativeNumberMode::NATNUM3:
823                 break;
824             case NativeNumberMode::NATNUM4:
825                 if (isKorean)
826                     return isShort ? NativeNumberMode::NATNUM9 : NativeNumberMode::NATNUM11;
827                 // fall through
828             default: return 0;
829         }
830     }
831     return nNativeNumberMode;
832 }
833 
834 static sal_Int32 SAL_CALL DisplayCode2FieldIndex(sal_Int32 nCalendarDisplayCode)
835 {
836     switch( nCalendarDisplayCode ) {
837         case CalendarDisplayCode::SHORT_DAY:
838         case CalendarDisplayCode::LONG_DAY:
839             return CalendarFieldIndex::DAY_OF_MONTH;
840         case CalendarDisplayCode::SHORT_DAY_NAME:
841         case CalendarDisplayCode::LONG_DAY_NAME:
842             return CalendarFieldIndex::DAY_OF_WEEK;
843         case CalendarDisplayCode::SHORT_QUARTER:
844         case CalendarDisplayCode::LONG_QUARTER:
845         case CalendarDisplayCode::SHORT_MONTH:
846         case CalendarDisplayCode::LONG_MONTH:
847         case CalendarDisplayCode::SHORT_MONTH_NAME:
848         case CalendarDisplayCode::LONG_MONTH_NAME:
849             return CalendarFieldIndex::MONTH;
850         case CalendarDisplayCode::SHORT_YEAR:
851         case CalendarDisplayCode::LONG_YEAR:
852             return CalendarFieldIndex::YEAR;
853         case CalendarDisplayCode::SHORT_ERA:
854         case CalendarDisplayCode::LONG_ERA:
855             return CalendarFieldIndex::ERA;
856         case CalendarDisplayCode::SHORT_YEAR_AND_ERA:
857         case CalendarDisplayCode::LONG_YEAR_AND_ERA:
858             return CalendarFieldIndex::YEAR;
859         default:
860             return 0;
861     }
862 }
863 
864 sal_Int16 SAL_CALL
865 Calendar_gregorian::getFirstDayOfWeek() throw(RuntimeException)
866 {
867     // UCAL_SUNDAY == 1, Weekdays::SUNDAY == 0 => offset -1
868     // Check for underflow just in case we're called "out of sync".
869     return ::std::max( sal::static_int_cast<sal_Int16>(0),
870             sal::static_int_cast<sal_Int16>( static_cast<sal_Int16>(
871                     body->getFirstDayOfWeek()) - 1));
872 }
873 
874 void SAL_CALL
875 Calendar_gregorian::setFirstDayOfWeek( sal_Int16 day )
876 throw(RuntimeException)
877 {
878     // Weekdays::SUNDAY == 0, UCAL_SUNDAY == 1 => offset +1
879     body->setFirstDayOfWeek( static_cast<UCalendarDaysOfWeek>( day + 1));
880 }
881 
882 void SAL_CALL
883 Calendar_gregorian::setMinimumNumberOfDaysForFirstWeek( sal_Int16 days ) throw(RuntimeException)
884 {
885         aCalendar.MinimumNumberOfDaysForFirstWeek = days;
886         body->setMinimalDaysInFirstWeek( static_cast<uint8_t>( days));
887 }
888 
889 sal_Int16 SAL_CALL
890 Calendar_gregorian::getMinimumNumberOfDaysForFirstWeek() throw(RuntimeException)
891 {
892         return aCalendar.MinimumNumberOfDaysForFirstWeek;
893 }
894 
895 sal_Int16 SAL_CALL
896 Calendar_gregorian::getNumberOfMonthsInYear() throw(RuntimeException)
897 {
898         return (sal_Int16) aCalendar.Months.getLength();
899 }
900 
901 
902 sal_Int16 SAL_CALL
903 Calendar_gregorian::getNumberOfDaysInWeek() throw(RuntimeException)
904 {
905         return (sal_Int16) aCalendar.Days.getLength();
906 }
907 
908 
909 Sequence< CalendarItem > SAL_CALL
910 Calendar_gregorian::getMonths() throw(RuntimeException)
911 {
912         return aCalendar.Months;
913 }
914 
915 
916 Sequence< CalendarItem > SAL_CALL
917 Calendar_gregorian::getDays() throw(RuntimeException)
918 {
919         return aCalendar.Days;
920 }
921 
922 OUString SAL_CALL
923 Calendar_gregorian::getDisplayName( sal_Int16 displayIndex, sal_Int16 idx, sal_Int16 nameType ) throw(RuntimeException)
924 {
925         OUString aStr;
926 
927         switch( displayIndex ) {
928             case CalendarDisplayIndex::AM_PM:/* ==0 */
929                 if (idx == 0) aStr = LocaleData().getLocaleItem(aLocale).timeAM;
930                 else if (idx == 1) aStr = LocaleData().getLocaleItem(aLocale).timePM;
931                 else throw ERROR;
932                 break;
933             case CalendarDisplayIndex::DAY:
934                 if( idx >= aCalendar.Days.getLength() ) throw ERROR;
935                 if (nameType == 0) aStr = aCalendar.Days[idx].AbbrevName;
936                 else if (nameType == 1) aStr = aCalendar.Days[idx].FullName;
937                 else throw ERROR;
938                 break;
939             case CalendarDisplayIndex::MONTH:
940                 if( idx >= aCalendar.Months.getLength() ) throw ERROR;
941                 if (nameType == 0) aStr = aCalendar.Months[idx].AbbrevName;
942                 else if (nameType == 1) aStr = aCalendar.Months[idx].FullName;
943                 else throw ERROR;
944                 break;
945             case CalendarDisplayIndex::ERA:
946                 if( idx >= aCalendar.Eras.getLength() ) throw ERROR;
947                 if (nameType == 0) aStr = aCalendar.Eras[idx].AbbrevName;
948                 else if (nameType == 1) aStr = aCalendar.Eras[idx].FullName;
949                 else throw ERROR;
950                 break;
951             case CalendarDisplayIndex::YEAR:
952                 break;
953             default:
954                 throw ERROR;
955         }
956         return aStr;
957 }
958 
959 // Methods in XExtendedCalendar
960 OUString SAL_CALL
961 Calendar_gregorian::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode )
962         throw (RuntimeException)
963 {
964     sal_Int16 value = getValue(sal::static_int_cast<sal_Int16>( DisplayCode2FieldIndex(nCalendarDisplayCode) ));
965     OUString aOUStr;
966 
967     if (nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER ||
968             nCalendarDisplayCode == CalendarDisplayCode::LONG_QUARTER) {
969         Sequence< OUString> xR = LocaleData().getReservedWord(aLocale);
970         sal_Int16 quarter = value / 3;
971         // Since this base class method may be called by derived calendar
972         // classes where a year consists of more than 12 months we need a check
973         // to not run out of bounds of reserved quarter words. Perhaps a more
974         // clean way (instead of dividing by 3) would be to first get the
975         // number of months, divide by 4 and then use that result to divide the
976         // actual month value.
977         if ( quarter > 3 )
978             quarter = 3;
979         quarter = sal::static_int_cast<sal_Int16>( quarter +
980             ((nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER) ?
981             reservedWords::QUARTER1_ABBREVIATION : reservedWords::QUARTER1_WORD) );
982         aOUStr = xR[quarter];
983     } else {
984         // The "#100211# - checked" comments serve for detection of "use of
985         // sprintf is safe here" conditions. An sprintf encountered without
986         // having that comment triggers alarm ;-)
987         sal_Char aStr[10];
988         switch( nCalendarDisplayCode ) {
989             case CalendarDisplayCode::SHORT_MONTH:
990                 value += 1;     // month is zero based
991                 // fall thru
992             case CalendarDisplayCode::SHORT_DAY:
993                 sprintf(aStr, "%d", value);     // #100211# - checked
994                 break;
995             case CalendarDisplayCode::LONG_YEAR:
996                 if (aCalendar.Name.equalsAscii("gengou"))
997                     sprintf(aStr, "%02d", value);     // #100211# - checked
998                 else
999                     sprintf(aStr, "%d", value);     // #100211# - checked
1000                 break;
1001             case CalendarDisplayCode::LONG_MONTH:
1002                 value += 1;     // month is zero based
1003                 sprintf(aStr, "%02d", value);   // #100211# - checked
1004                 break;
1005             case CalendarDisplayCode::SHORT_YEAR:
1006                 // Take last 2 digits, or only one if value<10, for example,
1007                 // in case of the Gengou calendar.
1008                 // #i116701# For values in non-Gregorian era years use all
1009                 // digits.
1010                 if (value < 100 || eraArray)
1011                     sprintf(aStr, "%d", value); // #100211# - checked
1012                 else
1013                     sprintf(aStr, "%02d", value % 100); // #100211# - checked
1014                 break;
1015             case CalendarDisplayCode::LONG_DAY:
1016                 sprintf(aStr, "%02d", value);   // #100211# - checked
1017                 break;
1018 
1019             case CalendarDisplayCode::SHORT_DAY_NAME:
1020                 return getDisplayName(CalendarDisplayIndex::DAY, value, 0);
1021             case CalendarDisplayCode::LONG_DAY_NAME:
1022                 return getDisplayName(CalendarDisplayIndex::DAY, value, 1);
1023             case CalendarDisplayCode::SHORT_MONTH_NAME:
1024                 return getDisplayName(CalendarDisplayIndex::MONTH, value, 0);
1025             case CalendarDisplayCode::LONG_MONTH_NAME:
1026                 return getDisplayName(CalendarDisplayIndex::MONTH, value, 1);
1027             case CalendarDisplayCode::SHORT_ERA:
1028                 return getDisplayName(CalendarDisplayIndex::ERA, value, 0);
1029             case CalendarDisplayCode::LONG_ERA:
1030                 return getDisplayName(CalendarDisplayIndex::ERA, value, 1);
1031 
1032             case CalendarDisplayCode::SHORT_YEAR_AND_ERA:
1033                 return  getDisplayString( CalendarDisplayCode::SHORT_ERA, nNativeNumberMode ) +
1034                     getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNativeNumberMode );
1035 
1036             case CalendarDisplayCode::LONG_YEAR_AND_ERA:
1037                 return  getDisplayString( CalendarDisplayCode::LONG_ERA, nNativeNumberMode ) +
1038                     getDisplayString( CalendarDisplayCode::LONG_YEAR, nNativeNumberMode );
1039 
1040             default:
1041                 throw ERROR;
1042         }
1043         aOUStr = OUString::createFromAscii(aStr);
1044     }
1045     if (nNativeNumberMode > 0) {
1046         // For Japanese calendar, first year calls GAN, see bug 111668 for detail.
1047 		if (eraArray == gengou_eraArray && value == 1
1048 			&& (nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR ||
1049 				nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR)
1050 			&& (nNativeNumberMode == NativeNumberMode::NATNUM1 ||
1051 				nNativeNumberMode == NativeNumberMode::NATNUM2)) {
1052 			static sal_Unicode gan = 0x5143;
1053 			return OUString(&gan, 1);
1054 		}
1055         sal_Int16 nNatNum = NatNumForCalendar(aLocale, nCalendarDisplayCode, nNativeNumberMode, value);
1056         if (nNatNum > 0)
1057             return aNatNum.getNativeNumberString(aOUStr, aLocale, nNatNum);
1058     }
1059     return aOUStr;
1060 }
1061 
1062 // Methods in XExtendedCalendar
1063 OUString SAL_CALL
1064 Calendar_buddhist::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode )
1065         throw (RuntimeException)
1066 {
1067     // make year and era in different order for year before and after 0.
1068     if ((nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR_AND_ERA ||
1069                 nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR_AND_ERA) &&
1070             getValue(CalendarFieldIndex::ERA) == 0) {
1071         if (nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR_AND_ERA)
1072             return  getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNativeNumberMode ) +
1073                 getDisplayString( CalendarDisplayCode::SHORT_ERA, nNativeNumberMode );
1074         else
1075             return  getDisplayString( CalendarDisplayCode::LONG_YEAR, nNativeNumberMode ) +
1076                 getDisplayString( CalendarDisplayCode::LONG_ERA, nNativeNumberMode );
1077     }
1078     return Calendar_gregorian::getDisplayString(nCalendarDisplayCode, nNativeNumberMode);
1079 }
1080 
1081 OUString SAL_CALL
1082 Calendar_gregorian::getImplementationName(void) throw( RuntimeException )
1083 {
1084         return OUString::createFromAscii(cCalendar);
1085 }
1086 
1087 sal_Bool SAL_CALL
1088 Calendar_gregorian::supportsService(const rtl::OUString& rServiceName) throw( RuntimeException )
1089 {
1090         return !rServiceName.compareToAscii(cCalendar);
1091 }
1092 
1093 Sequence< OUString > SAL_CALL
1094 Calendar_gregorian::getSupportedServiceNames(void) throw( RuntimeException )
1095 {
1096         Sequence< OUString > aRet(1);
1097         aRet[0] = OUString::createFromAscii(cCalendar);
1098         return aRet;
1099 }
1100 
1101