1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <algorithm>
21 
22 #include <calendar_gregorian.hxx>
23 #include <localedata.hxx>
24 #include <nativenumbersupplier.hxx>
25 #include <com/sun/star/i18n/CalendarDisplayCode.hpp>
26 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
27 #include <com/sun/star/i18n/NativeNumberMode.hpp>
28 #include <com/sun/star/i18n/reservedWords.hpp>
29 #include <cppuhelper/supportsservice.hxx>
30 #include <rtl/math.hxx>
31 #include <sal/log.hxx>
32 
33 #include <stdio.h>
34 #include <string.h>
35 
36 #define erDUMP_ICU_CALENDAR 0
37 #define erDUMP_I18N_CALENDAR 0
38 #if erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
39 // If both are used, DUMP_ICU_CAL_MSG() must be used before DUMP_I18N_CAL_MSG()
40 // to obtain internally set values from ICU, else Calendar::get() calls in
41 // DUMP_I18N_CAL_MSG() recalculate!
42 
43 // These pieces of macro are shamelessly borrowed from icu's olsontz.cpp, the
44 // double parens'ed approach to pass multiple parameters as one macro parameter
45 // is appealing.
46 static void debug_cal_loc(const char *f, int32_t l)
47 {
48     fprintf(stderr, "%s:%d: ", f, l);
49 }
50 # include <stdarg.h>
51 static void debug_cal_msg(const char *pat, ...)
52 {
53     va_list ap;
54     va_start(ap, pat);
55     vfprintf(stderr, pat, ap);
56     va_end(ap);
57 }
58 
59 #if erDUMP_ICU_CALENDAR
60 // Make icu with
61 // DEFS = -DU_DEBUG_CALSVC -DUCAL_DEBUG_DUMP
62 // in workdir/UnpackedTarball/icu/source/icudefs.mk
63 // May need some patches to fix unmaintained things there.
64 extern void ucal_dump( const icu::Calendar & );
65 static void debug_icu_cal_dump( const ::icu::Calendar & r )
66 {
67     ucal_dump(r);
68     fflush(stderr);
69     // set a breakpoint here to pause display between dumps
70 }
71 // must use double parens, i.e.:  DUMP_ICU_CAL_MSG(("four is: %d",4));
72 #define DUMP_ICU_CAL_MSG(x) {debug_cal_loc(__FILE__,__LINE__);debug_cal_msg x;debug_icu_cal_dump(*body);}
73 #else   // erDUMP_ICU_CALENDAR
74 #define DUMP_ICU_CAL_MSG(x)
75 #endif  // erDUMP_ICU_CALENDAR
76 
77 #if erDUMP_I18N_CALENDAR
78 static void debug_cal_millis_to_time( long nMillis, long & h, long & m, long & s, long & f )
79 {
80     int sign = (nMillis < 0 ? -1 : 1);
81     nMillis = ::std::abs(nMillis);
82     h = sign * nMillis / (60 * 60 * 1000);
83     nMillis -= sign * h * (60 * 60 * 1000);
84     m = nMillis / (60 * 1000);
85     nMillis -= m * (60 * 1000);
86     s = nMillis / (1000);
87     nMillis -= s * (1000);
88     f = nMillis;
89 }
90 static void debug_i18n_cal_dump( const ::icu::Calendar & r )
91 {
92     UErrorCode status;
93     long nMillis, h, m, s, f;
94     fprintf( stderr, " %04ld", (long)r.get( UCAL_YEAR, status = U_ZERO_ERROR));
95     fprintf( stderr, "-%02ld", (long)r.get( UCAL_MONTH, status = U_ZERO_ERROR)+1);
96     fprintf( stderr, "-%02ld", (long)r.get( UCAL_DATE, status = U_ZERO_ERROR));
97     fprintf( stderr, " %02ld", (long)r.get( UCAL_HOUR_OF_DAY, status = U_ZERO_ERROR));
98     fprintf( stderr, ":%02ld", (long)r.get( UCAL_MINUTE, status = U_ZERO_ERROR));
99     fprintf( stderr, ":%02ld", (long)r.get( UCAL_SECOND, status = U_ZERO_ERROR));
100     fprintf( stderr, "  zone: %ld", (long)(nMillis = r.get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR)));
101     fprintf( stderr, " (%f min)", (double)nMillis / 60000);
102     debug_cal_millis_to_time( nMillis, h, m, s, f);
103     fprintf( stderr, " (%ld:%02ld:%02ld.%ld)", h, m, s, f);
104     fprintf( stderr, "  DST: %ld", (long)(nMillis = r.get( UCAL_DST_OFFSET, status = U_ZERO_ERROR)));
105     fprintf( stderr, " (%f min)", (double)nMillis / 60000);
106     debug_cal_millis_to_time( nMillis, h, m, s, f);
107     fprintf( stderr, " (%ld:%02ld:%02ld.%ld)", h, m, s, f);
108     fprintf( stderr, "\n");
109     fflush(stderr);
110 }
111 // must use double parens, i.e.:  DUMP_I18N_CAL_MSG(("four is: %d",4));
112 #define DUMP_I18N_CAL_MSG(x) {debug_cal_loc(__FILE__,__LINE__);debug_cal_msg x;debug_i18n_cal_dump(*body);}
113 #else   // erDUMP_I18N_CALENDAR
114 #define DUMP_I18N_CAL_MSG(x)
115 #endif  // erDUMP_I18N_CALENDAR
116 
117 #else   // erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
118 #define DUMP_ICU_CAL_MSG(x)
119 #define DUMP_I18N_CAL_MSG(x)
120 #endif  // erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
121 
122 
123 using namespace ::com::sun::star::uno;
124 using namespace ::com::sun::star::i18n;
125 using namespace ::com::sun::star::lang;
126 
127 
128 namespace i18npool {
129 
130 #define ERROR RuntimeException()
131 
132 Calendar_gregorian::Calendar_gregorian()
133     : mxNatNum(new NativeNumberSupplierService)
134 {
135     init(nullptr);
136 }
137 Calendar_gregorian::Calendar_gregorian(const Era *_earArray)
138     : mxNatNum(new NativeNumberSupplierService)
139 {
140     init(_earArray);
141 }
142 void
143 Calendar_gregorian::init(const Era *_eraArray)
144 {
145     cCalendar = "com.sun.star.i18n.Calendar_gregorian";
146 
147     fieldSet = 0;
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( "", nullptr, nullptr, "calendar=gregorian");
169 
170     /* XXX: not specifying a timezone when creating a calendar assigns the
171      * system's timezone with all DST quirks, invalid times when switching
172      * to/from DST and so on. The XCalendar* interfaces are defined to support
173      * local time and UTC time so we can not override that here.
174      */
175     UErrorCode status = U_ZERO_ERROR;
176     body.reset( icu::Calendar::createInstance( aIcuLocale, status) );
177     if (!body || !U_SUCCESS(status)) throw ERROR;
178     eraArray=_eraArray;
179 }
180 
181 Calendar_gregorian::~Calendar_gregorian()
182 {
183 }
184 
185 Calendar_hanja::Calendar_hanja()
186 {
187     cCalendar = "com.sun.star.i18n.Calendar_hanja";
188 }
189 
190 OUString SAL_CALL
191 Calendar_hanja::getDisplayName( sal_Int16 displayIndex, sal_Int16 idx, sal_Int16 nameType )
192 {
193     if ( displayIndex == CalendarDisplayIndex::AM_PM ) {
194         // Am/Pm string for Korean Hanja calendar will refer to Japanese locale
195         css::lang::Locale jaLocale("ja", OUString(), OUString());
196         if (idx == 0) return LocaleDataImpl::get()->getLocaleItem(jaLocale).timeAM;
197         else if (idx == 1) return LocaleDataImpl::get()->getLocaleItem(jaLocale).timePM;
198         else throw ERROR;
199     }
200     else
201         return Calendar_gregorian::getDisplayName( displayIndex, idx, nameType );
202 }
203 
204 void SAL_CALL
205 Calendar_hanja::loadCalendar( const OUString& /*uniqueID*/, const css::lang::Locale& rLocale )
206 {
207     // Since this class could be called by service name 'hanja_yoil', we have to
208     // rename uniqueID to get right calendar defined in locale data.
209     Calendar_gregorian::loadCalendar("hanja", rLocale);
210 }
211 
212 static const Era gengou_eraArray[] = {
213     {1868,  1,  1, 0},  // Meiji
214     {1912,  7, 30, 0},  // Taisho
215     {1926, 12, 25, 0},  // Showa
216     {1989,  1,  8, 0},  // Heisei
217     {2019,  5,  1, 0},  // Reiwa
218     {0, 0, 0, 0}
219 };
220 Calendar_gengou::Calendar_gengou() : Calendar_gregorian(gengou_eraArray)
221 {
222     cCalendar = "com.sun.star.i18n.Calendar_gengou";
223 }
224 
225 static const Era ROC_eraArray[] = {
226     {1912, 1, 1, kDisplayEraForcedLongYear},    // #i116701#
227     {0, 0, 0, 0}
228 };
229 Calendar_ROC::Calendar_ROC() : Calendar_gregorian(ROC_eraArray)
230 {
231     cCalendar = "com.sun.star.i18n.Calendar_ROC";
232 }
233 
234 /**
235 * The start year of the Korean traditional calendar (Dan-gi) is the inaugural
236 * year of Dan-gun (BC 2333).
237 */
238 static const Era dangi_eraArray[] = {
239     {-2332, 1, 1, 0},
240     {0, 0, 0, 0}
241 };
242 Calendar_dangi::Calendar_dangi() : Calendar_gregorian(dangi_eraArray)
243 {
244     cCalendar = "com.sun.star.i18n.Calendar_dangi";
245 }
246 
247 static const Era buddhist_eraArray[] = {
248     {-542, 1, 1, 0},
249     {0, 0, 0, 0}
250 };
251 Calendar_buddhist::Calendar_buddhist() : Calendar_gregorian(buddhist_eraArray)
252 {
253     cCalendar = "com.sun.star.i18n.Calendar_buddhist";
254 }
255 
256 void SAL_CALL
257 Calendar_gregorian::loadCalendar( const OUString& uniqueID, const css::lang::Locale& rLocale )
258 {
259     // init. fieldValue[]
260     getValue();
261 
262     aLocale = rLocale;
263     const Sequence< Calendar2 > xC = LocaleDataImpl::get()->getAllCalendars2(rLocale);
264     for (const auto& rCal : xC)
265     {
266         if (uniqueID == rCal.Name)
267         {
268             aCalendar = rCal;
269             // setup minimalDaysInFirstWeek
270             setMinimumNumberOfDaysForFirstWeek(
271                     aCalendar.MinimumNumberOfDaysForFirstWeek);
272             // setup first day of week
273             for (sal_Int16 day = sal::static_int_cast<sal_Int16>(
274                         aCalendar.Days.getLength()-1); day>=0; day--)
275             {
276                 if (aCalendar.StartOfWeek == aCalendar.Days[day].ID)
277                 {
278                     setFirstDayOfWeek( day);
279                     return;
280                 }
281             }
282         }
283     }
284     // Calendar is not for the locale
285     throw ERROR;
286 }
287 
288 
289 css::i18n::Calendar2 SAL_CALL
290 Calendar_gregorian::getLoadedCalendar2()
291 {
292     return aCalendar;
293 }
294 
295 css::i18n::Calendar SAL_CALL
296 Calendar_gregorian::getLoadedCalendar()
297 {
298     return LocaleDataImpl::downcastCalendar( aCalendar);
299 }
300 
301 OUString SAL_CALL
302 Calendar_gregorian::getUniqueID()
303 {
304     return aCalendar.Name;
305 }
306 
307 void SAL_CALL
308 Calendar_gregorian::setDateTime( double fTimeInDays )
309 {
310     // ICU handles dates in milliseconds as double values and uses floor()
311     // to obtain integer values, which may yield a date decremented by one
312     // for odd (historical) timezone values where the computed value due to
313     // rounding errors has a fractional part in milliseconds. Ensure we
314     // pass a value without fraction here. If not, that may lead to
315     // fdo#44286 or fdo#52619 and the like, e.g. when passing
316     // -2136315212000.000244 instead of -2136315212000.000000
317     double fM = fTimeInDays * U_MILLIS_PER_DAY;
318     double fR = rtl::math::round( fM );
319     SAL_INFO_IF( fM != fR, "i18npool",
320             "Calendar_gregorian::setDateTime: " << std::fixed << fM << " rounded to " << fR);
321     UErrorCode status = U_ZERO_ERROR;
322     body->setTime( fR, status);
323     if ( !U_SUCCESS(status) ) throw ERROR;
324     getValue();
325 }
326 
327 double SAL_CALL
328 Calendar_gregorian::getDateTime()
329 {
330     if (fieldSet) {
331         setValue();
332         getValue();
333     }
334     UErrorCode status = U_ZERO_ERROR;
335     double fR = body->getTime(status);
336     if ( !U_SUCCESS(status) ) throw ERROR;
337     return fR / U_MILLIS_PER_DAY;
338 }
339 
340 void SAL_CALL
341 Calendar_gregorian::setLocalDateTime( double fTimeInDays )
342 {
343     // See setDateTime() for why the rounding.
344     double fM = fTimeInDays * U_MILLIS_PER_DAY;
345     double fR = rtl::math::round( fM );
346     SAL_INFO_IF( fM != fR, "i18npool",
347             "Calendar_gregorian::setLocalDateTime: " << std::fixed << fM << " rounded to " << fR);
348     int32_t nZoneOffset, nDSTOffset;
349     UErrorCode status = U_ZERO_ERROR;
350     body->getTimeZone().getOffset( fR, TRUE, nZoneOffset, nDSTOffset, status );
351     if ( !U_SUCCESS(status) ) throw ERROR;
352     status = U_ZERO_ERROR;
353     body->setTime( fR - (nZoneOffset + nDSTOffset), status );
354     if ( !U_SUCCESS(status) ) throw ERROR;
355     getValue();
356 }
357 
358 double SAL_CALL
359 Calendar_gregorian::getLocalDateTime()
360 {
361     if (fieldSet) {
362         setValue();
363         getValue();
364     }
365     UErrorCode status = U_ZERO_ERROR;
366     double fTime = body->getTime( status );
367     if ( !U_SUCCESS(status) ) throw ERROR;
368     status = U_ZERO_ERROR;
369     int32_t nZoneOffset = body->get( UCAL_ZONE_OFFSET, status );
370     if ( !U_SUCCESS(status) ) throw ERROR;
371     status = U_ZERO_ERROR;
372     int32_t nDSTOffset = body->get( UCAL_DST_OFFSET, status );
373     if ( !U_SUCCESS(status) ) throw ERROR;
374     return (fTime + (nZoneOffset + nDSTOffset)) / U_MILLIS_PER_DAY;
375 }
376 
377 bool Calendar_gregorian::setTimeZone( const OUString& rTimeZone )
378 {
379     if (fieldSet)
380     {
381         setValue();
382         getValue();
383     }
384     const icu::UnicodeString aID( reinterpret_cast<const UChar*>(rTimeZone.getStr()), rTimeZone.getLength());
385     const std::unique_ptr<const icu::TimeZone> pTZ( icu::TimeZone::createTimeZone(aID));
386     if (!pTZ)
387         return false;
388 
389     body->setTimeZone(*pTZ);
390     return true;
391 }
392 
393 // map field value from gregorian calendar to other calendar, it can be overwritten by derived class.
394 // By using eraArray, it can take care Japanese and Taiwan ROC calendar.
395 void Calendar_gregorian::mapFromGregorian()
396 {
397     if (!eraArray)
398         return;
399 
400     sal_Int16 e, y, m, d;
401 
402     e = fieldValue[CalendarFieldIndex::ERA];
403     y = fieldValue[CalendarFieldIndex::YEAR];
404     m = fieldValue[CalendarFieldIndex::MONTH] + 1;
405     d = fieldValue[CalendarFieldIndex::DAY_OF_MONTH];
406 
407     // since the year is reversed for first era, it is reversed again here for Era compare.
408     if (e == 0)
409         y = 1 - y;
410 
411     for (e = 0; eraArray[e].year; e++)
412         if ((y != eraArray[e].year) ? y < eraArray[e].year :
413                 (m != eraArray[e].month) ? m < eraArray[e].month : d < eraArray[e].day)
414             break;
415 
416     fieldValue[CalendarFieldIndex::ERA] = e;
417     fieldValue[CalendarFieldIndex::YEAR] =
418         sal::static_int_cast<sal_Int16>( (e == 0) ? (eraArray[0].year - y) : (y - eraArray[e-1].year + 1) );
419 }
420 
421 #define FIELDS  ((1 << CalendarFieldIndex::ERA) | (1 << CalendarFieldIndex::YEAR))
422 // map field value from other calendar to gregorian calendar, it can be overwritten by derived class.
423 // By using eraArray, it can take care Japanese and Taiwan ROC calendar.
424 void Calendar_gregorian::mapToGregorian()
425 {
426     if (eraArray && (fieldSet & FIELDS)) {
427         sal_Int16 y, e = fieldValue[CalendarFieldIndex::ERA];
428         if (e == 0)
429             y = sal::static_int_cast<sal_Int16>( eraArray[0].year - fieldValue[CalendarFieldIndex::YEAR] );
430         else
431             y = sal::static_int_cast<sal_Int16>( eraArray[e-1].year + fieldValue[CalendarFieldIndex::YEAR] - 1 );
432 
433         fieldSetValue[CalendarFieldIndex::ERA] = y <= 0 ? 0 : 1;
434         fieldSetValue[CalendarFieldIndex::YEAR] = (y <= 0 ? 1 - y : y);
435         fieldSet |= FIELDS;
436     }
437 }
438 
439 /// @throws RuntimeException
440 static UCalendarDateFields fieldNameConverter(sal_Int16 fieldIndex)
441 {
442     UCalendarDateFields f;
443 
444     switch (fieldIndex) {
445         case CalendarFieldIndex::AM_PM:             f = UCAL_AM_PM; break;
446         case CalendarFieldIndex::DAY_OF_MONTH:      f = UCAL_DATE; break;
447         case CalendarFieldIndex::DAY_OF_WEEK:       f = UCAL_DAY_OF_WEEK; break;
448         case CalendarFieldIndex::DAY_OF_YEAR:       f = UCAL_DAY_OF_YEAR; break;
449         case CalendarFieldIndex::DST_OFFSET:        f = UCAL_DST_OFFSET; break;
450         case CalendarFieldIndex::ZONE_OFFSET:       f = UCAL_ZONE_OFFSET; break;
451         case CalendarFieldIndex::HOUR:              f = UCAL_HOUR_OF_DAY; break;
452         case CalendarFieldIndex::MINUTE:            f = UCAL_MINUTE; break;
453         case CalendarFieldIndex::SECOND:            f = UCAL_SECOND; break;
454         case CalendarFieldIndex::MILLISECOND:       f = UCAL_MILLISECOND; break;
455         case CalendarFieldIndex::WEEK_OF_MONTH:     f = UCAL_WEEK_OF_MONTH; break;
456         case CalendarFieldIndex::WEEK_OF_YEAR:      f = UCAL_WEEK_OF_YEAR; break;
457         case CalendarFieldIndex::YEAR:              f = UCAL_YEAR; break;
458         case CalendarFieldIndex::MONTH:             f = UCAL_MONTH; break;
459         case CalendarFieldIndex::ERA:               f = UCAL_ERA; break;
460         default: throw ERROR;
461     }
462     return f;
463 }
464 
465 void SAL_CALL
466 Calendar_gregorian::setValue( sal_Int16 fieldIndex, sal_Int16 value )
467 {
468     if (fieldIndex < 0 || FIELD_INDEX_COUNT <= fieldIndex)
469         throw ERROR;
470     fieldSet |= (1 << fieldIndex);
471     fieldValue[fieldIndex] = value;
472 }
473 
474 bool Calendar_gregorian::getCombinedOffset( sal_Int32 & o_nOffset,
475         sal_Int16 nParentFieldIndex, sal_Int16 nChildFieldIndex ) const
476 {
477     o_nOffset = 0;
478     bool bFieldsSet = false;
479     if (fieldSet & (1 << nParentFieldIndex))
480     {
481         bFieldsSet = true;
482         o_nOffset = static_cast<sal_Int32>( fieldValue[nParentFieldIndex]) * 60000;
483     }
484     if (fieldSet & (1 << nChildFieldIndex))
485     {
486         bFieldsSet = true;
487         if (o_nOffset < 0)
488             o_nOffset -= static_cast<sal_uInt16>( fieldValue[nChildFieldIndex]);
489         else
490             o_nOffset += static_cast<sal_uInt16>( fieldValue[nChildFieldIndex]);
491     }
492     return bFieldsSet;
493 }
494 
495 bool Calendar_gregorian::getZoneOffset( sal_Int32 & o_nOffset ) const
496 {
497     return getCombinedOffset( o_nOffset, CalendarFieldIndex::ZONE_OFFSET,
498             CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS);
499 }
500 
501 bool Calendar_gregorian::getDSTOffset( sal_Int32 & o_nOffset ) const
502 {
503     return getCombinedOffset( o_nOffset, CalendarFieldIndex::DST_OFFSET,
504             CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS);
505 }
506 
507 void Calendar_gregorian::submitFields()
508 {
509     for (sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++)
510     {
511         if (fieldSet & (1 << fieldIndex))
512         {
513             switch (fieldIndex)
514             {
515                 default:
516                     body->set(fieldNameConverter(fieldIndex), fieldSetValue[fieldIndex]);
517                     break;
518                 case CalendarFieldIndex::ZONE_OFFSET:
519                 case CalendarFieldIndex::DST_OFFSET:
520                 case CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS:
521                 case CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS:
522                     break;  // nothing, extra handling
523             }
524         }
525     }
526     sal_Int32 nZoneOffset, nDSTOffset;
527     if (getZoneOffset( nZoneOffset))
528         body->set( fieldNameConverter( CalendarFieldIndex::ZONE_OFFSET), nZoneOffset);
529     if (getDSTOffset( nDSTOffset))
530         body->set( fieldNameConverter( CalendarFieldIndex::DST_OFFSET), nDSTOffset);
531 }
532 
533 void Calendar_gregorian::setValue()
534 {
535     // Copy fields before calling submitFields() directly or indirectly below.
536     memcpy(fieldSetValue, fieldValue, sizeof(fieldSetValue));
537     // Possibly setup ERA and YEAR in fieldSetValue.
538     mapToGregorian();
539 
540     DUMP_ICU_CAL_MSG(("%s\n","setValue() before submission"));
541     DUMP_I18N_CAL_MSG(("%s\n","setValue() before submission"));
542 
543     submitFields();
544 
545     DUMP_ICU_CAL_MSG(("%s\n","setValue() after submission"));
546     DUMP_I18N_CAL_MSG(("%s\n","setValue() after submission"));
547 
548 #if erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR
549     {
550         // force icu::Calendar to recalculate
551         UErrorCode status;
552         sal_Int32 nTmp = body->get( UCAL_DATE, status = U_ZERO_ERROR);
553         DUMP_ICU_CAL_MSG(("%s: %d\n","setValue() result day",nTmp));
554         DUMP_I18N_CAL_MSG(("%s: %d\n","setValue() result day",nTmp));
555     }
556 #endif
557 }
558 
559 void Calendar_gregorian::getValue()
560 {
561     DUMP_ICU_CAL_MSG(("%s\n","getValue()"));
562     DUMP_I18N_CAL_MSG(("%s\n","getValue()"));
563     for (sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++)
564     {
565         if (fieldIndex == CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS ||
566                 fieldIndex == CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS)
567             continue;   // not ICU fields
568 
569         UErrorCode status = U_ZERO_ERROR;
570         sal_Int32 value = body->get( fieldNameConverter(
571                     fieldIndex), status);
572         if ( !U_SUCCESS(status) ) throw ERROR;
573 
574         // Convert millisecond to minute for ZONE and DST and set remainder in
575         // second field.
576         if (fieldIndex == CalendarFieldIndex::ZONE_OFFSET)
577         {
578             sal_Int32 nMinutes = value / 60000;
579             sal_Int16 nMillis = static_cast<sal_Int16>( static_cast<sal_uInt16>(
580                         abs( value - nMinutes * 60000)));
581             fieldValue[CalendarFieldIndex::ZONE_OFFSET] = static_cast<sal_Int16>( nMinutes);
582             fieldValue[CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS] = nMillis;
583         }
584         else if (fieldIndex == CalendarFieldIndex::DST_OFFSET)
585         {
586             sal_Int32 nMinutes = value / 60000;
587             sal_Int16 nMillis = static_cast<sal_Int16>( static_cast<sal_uInt16>(
588                         abs( value - nMinutes * 60000)));
589             fieldValue[CalendarFieldIndex::DST_OFFSET] = static_cast<sal_Int16>( nMinutes);
590             fieldValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] = nMillis;
591         }
592         else
593             fieldValue[fieldIndex] = static_cast<sal_Int16>(value);
594 
595         // offset 1 since the value for week start day SunDay is different between Calendar and Weekdays.
596         if ( fieldIndex == CalendarFieldIndex::DAY_OF_WEEK )
597             fieldValue[fieldIndex]--;   // UCAL_SUNDAY:/* == 1 */ ==> Weekdays::SUNDAY /* ==0 */
598     }
599     mapFromGregorian();
600     fieldSet = 0;
601 }
602 
603 sal_Int16 SAL_CALL
604 Calendar_gregorian::getValue( sal_Int16 fieldIndex )
605 {
606     if (fieldIndex < 0 || FIELD_INDEX_COUNT <= fieldIndex)
607         throw ERROR;
608 
609     if (fieldSet)  {
610         setValue();
611         getValue();
612     }
613 
614     return fieldValue[fieldIndex];
615 }
616 
617 void SAL_CALL
618 Calendar_gregorian::addValue( sal_Int16 fieldIndex, sal_Int32 value )
619 {
620     // since ZONE and DST could not be add, we don't need to convert value here
621     UErrorCode status = U_ZERO_ERROR;
622     body->add(fieldNameConverter(fieldIndex), value, status);
623     if ( !U_SUCCESS(status) ) throw ERROR;
624     getValue();
625 }
626 
627 sal_Bool SAL_CALL
628 Calendar_gregorian::isValid()
629 {
630     if (fieldSet) {
631         sal_Int32 tmp = fieldSet;
632         setValue();
633         memcpy(fieldSetValue, fieldValue, sizeof(fieldSetValue));
634         getValue();
635         for ( sal_Int16 fieldIndex = 0; fieldIndex < FIELD_INDEX_COUNT; fieldIndex++ ) {
636             // compare only with fields that are set and reset fieldSet[]
637             if (tmp & (1 << fieldIndex)) {
638                 if (fieldSetValue[fieldIndex] != fieldValue[fieldIndex])
639                     return false;
640             }
641         }
642     }
643     return true;
644 }
645 
646 // NativeNumberMode has different meaning between Number and Calendar for Asian locales.
647 // Here is the mapping table
648 // calendar(q/y/m/d)    zh_CN           zh_TW           ja              ko
649 // NatNum1              NatNum1/1/7/7   NatNum1/1/7/7   NatNum1/1/4/4   NatNum1/1/7/7
650 // NatNum2              NatNum2/2/8/8   NatNum2/2/8/8   NatNum2/2/5/5   NatNum2/2/8/8
651 // NatNum3              NatNum3/3/3/3   NatNum3/3/3/3   NatNum3/3/3/3   NatNum3/3/3/3
652 // NatNum4                                                              NatNum9/9/11/11
653 
654 static sal_Int16 NatNumForCalendar(const css::lang::Locale& aLocale,
655         sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, sal_Int16 value )
656 {
657     bool isShort = ((nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR ||
658         nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR) && value >= 100) ||
659         nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER ||
660         nCalendarDisplayCode == CalendarDisplayCode::LONG_QUARTER;
661     bool isChinese = aLocale.Language == "zh";
662     bool isJapanese = aLocale.Language == "ja";
663     bool isKorean = aLocale.Language == "ko";
664 
665     if (isChinese || isJapanese || isKorean) {
666         switch (nNativeNumberMode) {
667             case NativeNumberMode::NATNUM1:
668                 if (!isShort)
669                     nNativeNumberMode = isJapanese ? NativeNumberMode::NATNUM4 : NativeNumberMode::NATNUM7;
670                 break;
671             case NativeNumberMode::NATNUM2:
672                 if (!isShort)
673                     nNativeNumberMode = isJapanese ? NativeNumberMode::NATNUM5 : NativeNumberMode::NATNUM8;
674                 break;
675             case NativeNumberMode::NATNUM3:
676                 break;
677             case NativeNumberMode::NATNUM4:
678                 if (isKorean)
679                     return isShort ? NativeNumberMode::NATNUM9 : NativeNumberMode::NATNUM11;
680                 [[fallthrough]];
681             default: return 0;
682         }
683     }
684     return nNativeNumberMode;
685 }
686 
687 static sal_Int32 DisplayCode2FieldIndex(sal_Int32 nCalendarDisplayCode)
688 {
689     switch( nCalendarDisplayCode ) {
690         case CalendarDisplayCode::SHORT_DAY:
691         case CalendarDisplayCode::LONG_DAY:
692             return CalendarFieldIndex::DAY_OF_MONTH;
693         case CalendarDisplayCode::SHORT_DAY_NAME:
694         case CalendarDisplayCode::LONG_DAY_NAME:
695         case CalendarDisplayCode::NARROW_DAY_NAME:
696             return CalendarFieldIndex::DAY_OF_WEEK;
697         case CalendarDisplayCode::SHORT_QUARTER:
698         case CalendarDisplayCode::LONG_QUARTER:
699         case CalendarDisplayCode::SHORT_MONTH:
700         case CalendarDisplayCode::LONG_MONTH:
701         case CalendarDisplayCode::SHORT_MONTH_NAME:
702         case CalendarDisplayCode::LONG_MONTH_NAME:
703         case CalendarDisplayCode::NARROW_MONTH_NAME:
704         case CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME:
705         case CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME:
706         case CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME:
707         case CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME:
708         case CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME:
709         case CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME:
710             return CalendarFieldIndex::MONTH;
711         case CalendarDisplayCode::SHORT_YEAR:
712         case CalendarDisplayCode::LONG_YEAR:
713             return CalendarFieldIndex::YEAR;
714         case CalendarDisplayCode::SHORT_ERA:
715         case CalendarDisplayCode::LONG_ERA:
716             return CalendarFieldIndex::ERA;
717         case CalendarDisplayCode::SHORT_YEAR_AND_ERA:
718         case CalendarDisplayCode::LONG_YEAR_AND_ERA:
719             return CalendarFieldIndex::YEAR;
720         default:
721             return 0;
722     }
723 }
724 
725 sal_Int16 SAL_CALL
726 Calendar_gregorian::getFirstDayOfWeek()
727 {
728     // UCAL_SUNDAY == 1, Weekdays::SUNDAY == 0 => offset -1
729     // Check for underflow just in case we're called "out of sync".
730     return ::std::max( sal::static_int_cast<sal_Int16>(0),
731             sal::static_int_cast<sal_Int16>( static_cast<sal_Int16>(
732                     body->getFirstDayOfWeek()) - 1));
733 }
734 
735 void SAL_CALL
736 Calendar_gregorian::setFirstDayOfWeek( sal_Int16 day )
737 {
738     // Weekdays::SUNDAY == 0, UCAL_SUNDAY == 1 => offset +1
739     body->setFirstDayOfWeek( static_cast<UCalendarDaysOfWeek>( day + 1));
740 }
741 
742 void SAL_CALL
743 Calendar_gregorian::setMinimumNumberOfDaysForFirstWeek( sal_Int16 days )
744 {
745     aCalendar.MinimumNumberOfDaysForFirstWeek = days;
746     body->setMinimalDaysInFirstWeek( static_cast<uint8_t>( days));
747 }
748 
749 sal_Int16 SAL_CALL
750 Calendar_gregorian::getMinimumNumberOfDaysForFirstWeek()
751 {
752     return aCalendar.MinimumNumberOfDaysForFirstWeek;
753 }
754 
755 sal_Int16 SAL_CALL
756 Calendar_gregorian::getNumberOfMonthsInYear()
757 {
758     return static_cast<sal_Int16>(aCalendar.Months.getLength());
759 }
760 
761 
762 sal_Int16 SAL_CALL
763 Calendar_gregorian::getNumberOfDaysInWeek()
764 {
765     return static_cast<sal_Int16>(aCalendar.Days.getLength());
766 }
767 
768 
769 Sequence< CalendarItem > SAL_CALL
770 Calendar_gregorian::getDays()
771 {
772     return LocaleDataImpl::downcastCalendarItems( aCalendar.Days);
773 }
774 
775 
776 Sequence< CalendarItem > SAL_CALL
777 Calendar_gregorian::getMonths()
778 {
779     return LocaleDataImpl::downcastCalendarItems( aCalendar.Months);
780 }
781 
782 
783 Sequence< CalendarItem2 > SAL_CALL
784 Calendar_gregorian::getDays2()
785 {
786     return aCalendar.Days;
787 }
788 
789 
790 Sequence< CalendarItem2 > SAL_CALL
791 Calendar_gregorian::getMonths2()
792 {
793     return aCalendar.Months;
794 }
795 
796 
797 Sequence< CalendarItem2 > SAL_CALL
798 Calendar_gregorian::getGenitiveMonths2()
799 {
800     return aCalendar.GenitiveMonths;
801 }
802 
803 
804 Sequence< CalendarItem2 > SAL_CALL
805 Calendar_gregorian::getPartitiveMonths2()
806 {
807     return aCalendar.PartitiveMonths;
808 }
809 
810 
811 OUString SAL_CALL
812 Calendar_gregorian::getDisplayName( sal_Int16 displayIndex, sal_Int16 idx, sal_Int16 nameType )
813 {
814     OUString aStr;
815 
816     switch( displayIndex ) {
817         case CalendarDisplayIndex::AM_PM:/* ==0 */
818             if (idx == 0) aStr = LocaleDataImpl::get()->getLocaleItem(aLocale).timeAM;
819             else if (idx == 1) aStr = LocaleDataImpl::get()->getLocaleItem(aLocale).timePM;
820             else throw ERROR;
821             break;
822         case CalendarDisplayIndex::DAY:
823             if( idx >= aCalendar.Days.getLength() ) throw ERROR;
824             if (nameType == 0) aStr = aCalendar.Days[idx].AbbrevName;
825             else if (nameType == 1) aStr = aCalendar.Days[idx].FullName;
826             else if (nameType == 2) aStr = aCalendar.Days[idx].NarrowName;
827             else throw ERROR;
828             break;
829         case CalendarDisplayIndex::MONTH:
830             if( idx >= aCalendar.Months.getLength() ) throw ERROR;
831             if (nameType == 0) aStr = aCalendar.Months[idx].AbbrevName;
832             else if (nameType == 1) aStr = aCalendar.Months[idx].FullName;
833             else if (nameType == 2) aStr = aCalendar.Months[idx].NarrowName;
834             else throw ERROR;
835             break;
836         case CalendarDisplayIndex::GENITIVE_MONTH:
837             if( idx >= aCalendar.GenitiveMonths.getLength() ) throw ERROR;
838             if (nameType == 0) aStr = aCalendar.GenitiveMonths[idx].AbbrevName;
839             else if (nameType == 1) aStr = aCalendar.GenitiveMonths[idx].FullName;
840             else if (nameType == 2) aStr = aCalendar.GenitiveMonths[idx].NarrowName;
841             else throw ERROR;
842             break;
843         case CalendarDisplayIndex::PARTITIVE_MONTH:
844             if( idx >= aCalendar.PartitiveMonths.getLength() ) throw ERROR;
845             if (nameType == 0) aStr = aCalendar.PartitiveMonths[idx].AbbrevName;
846             else if (nameType == 1) aStr = aCalendar.PartitiveMonths[idx].FullName;
847             else if (nameType == 2) aStr = aCalendar.PartitiveMonths[idx].NarrowName;
848             else throw ERROR;
849             break;
850         case CalendarDisplayIndex::ERA:
851             if( idx >= aCalendar.Eras.getLength() ) throw ERROR;
852             if (nameType == 0) aStr = aCalendar.Eras[idx].AbbrevName;
853             else if (nameType == 1) aStr = aCalendar.Eras[idx].FullName;
854             else throw ERROR;
855             break;
856         case CalendarDisplayIndex::YEAR:
857             break;
858         default:
859             throw ERROR;
860     }
861     return aStr;
862 }
863 
864 // Methods in XExtendedCalendar
865 OUString SAL_CALL
866 Calendar_gregorian::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode )
867 {
868     return getDisplayStringImpl( nCalendarDisplayCode, nNativeNumberMode, false);
869 }
870 
871 OUString
872 Calendar_gregorian::getDisplayStringImpl( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, bool bEraMode )
873 {
874     sal_Int16 value = getValue(sal::static_int_cast<sal_Int16>( DisplayCode2FieldIndex(nCalendarDisplayCode) ));
875     OUString aOUStr;
876 
877     if (nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER ||
878             nCalendarDisplayCode == CalendarDisplayCode::LONG_QUARTER) {
879         Sequence< OUString> xR = LocaleDataImpl::get()->getReservedWord(aLocale);
880         sal_Int16 quarter = value / 3;
881         // Since this base class method may be called by derived calendar
882         // classes where a year consists of more than 12 months we need a check
883         // to not run out of bounds of reserved quarter words. Perhaps a more
884         // clean way (instead of dividing by 3) would be to first get the
885         // number of months, divide by 4 and then use that result to divide the
886         // actual month value.
887         if ( quarter > 3 )
888             quarter = 3;
889         quarter = sal::static_int_cast<sal_Int16>( quarter +
890             ((nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER) ?
891             reservedWords::QUARTER1_ABBREVIATION : reservedWords::QUARTER1_WORD) );
892         aOUStr = xR[quarter];
893     } else {
894         // The "#100211# - checked" comments serve for detection of "use of
895         // sprintf is safe here" conditions. An sprintf encountered without
896         // having that comment triggers alarm ;-)
897         char aStr[10];
898         switch( nCalendarDisplayCode ) {
899             case CalendarDisplayCode::SHORT_MONTH:
900                 value += 1;     // month is zero based
901                 [[fallthrough]];
902             case CalendarDisplayCode::SHORT_DAY:
903                 sprintf(aStr, "%d", value);     // #100211# - checked
904                 break;
905             case CalendarDisplayCode::LONG_YEAR:
906                 if ( aCalendar.Name == "gengou" )
907                     sprintf(aStr, "%02d", value);     // #100211# - checked
908                 else
909                     sprintf(aStr, "%d", value);     // #100211# - checked
910                 break;
911             case CalendarDisplayCode::LONG_MONTH:
912                 value += 1;     // month is zero based
913                 sprintf(aStr, "%02d", value);   // #100211# - checked
914                 break;
915             case CalendarDisplayCode::SHORT_YEAR:
916                 // Take last 2 digits, or only one if value<10, for example,
917                 // in case of the Gengou calendar. For combined era+year always
918                 // the full year is displayed, without leading 0.
919                 // Workaround for non-combined calls in certain calendars is
920                 // the kDisplayEraForcedLongYear flag, but this also could get
921                 // called for YY not only E format codes, no differentiation
922                 // possible here; the good news is that usually the Gregorian
923                 // calendar is the default and hence YY calls for Gregorian and
924                 // E for the other calendar and currently (2013-02-28) ROC is
925                 // the only calendar using this.
926                 // See i#116701 and fdo#60915
927                 if (value < 100 || bEraMode || (eraArray && (eraArray[0].flags & kDisplayEraForcedLongYear)))
928                     sprintf(aStr, "%d", value); // #100211# - checked
929                 else
930                     sprintf(aStr, "%02d", value % 100); // #100211# - checked
931                 break;
932             case CalendarDisplayCode::LONG_DAY:
933                 sprintf(aStr, "%02d", value);   // #100211# - checked
934                 break;
935 
936             case CalendarDisplayCode::SHORT_DAY_NAME:
937                 return getDisplayName(CalendarDisplayIndex::DAY, value, 0);
938             case CalendarDisplayCode::LONG_DAY_NAME:
939                 return getDisplayName(CalendarDisplayIndex::DAY, value, 1);
940             case CalendarDisplayCode::NARROW_DAY_NAME:
941                 return getDisplayName(CalendarDisplayIndex::DAY, value, 2);
942             case CalendarDisplayCode::SHORT_MONTH_NAME:
943                 return getDisplayName(CalendarDisplayIndex::MONTH, value, 0);
944             case CalendarDisplayCode::LONG_MONTH_NAME:
945                 return getDisplayName(CalendarDisplayIndex::MONTH, value, 1);
946             case CalendarDisplayCode::NARROW_MONTH_NAME:
947                 return getDisplayName(CalendarDisplayIndex::MONTH, value, 2);
948             case CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME:
949                 return getDisplayName(CalendarDisplayIndex::GENITIVE_MONTH, value, 0);
950             case CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME:
951                 return getDisplayName(CalendarDisplayIndex::GENITIVE_MONTH, value, 1);
952             case CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME:
953                 return getDisplayName(CalendarDisplayIndex::GENITIVE_MONTH, value, 2);
954             case CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME:
955                 return getDisplayName(CalendarDisplayIndex::PARTITIVE_MONTH, value, 0);
956             case CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME:
957                 return getDisplayName(CalendarDisplayIndex::PARTITIVE_MONTH, value, 1);
958             case CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME:
959                 return getDisplayName(CalendarDisplayIndex::PARTITIVE_MONTH, value, 2);
960             case CalendarDisplayCode::SHORT_ERA:
961                 return getDisplayName(CalendarDisplayIndex::ERA, value, 0);
962             case CalendarDisplayCode::LONG_ERA:
963                 return getDisplayName(CalendarDisplayIndex::ERA, value, 1);
964 
965             case CalendarDisplayCode::SHORT_YEAR_AND_ERA:
966                 return  getDisplayStringImpl( CalendarDisplayCode::SHORT_ERA, nNativeNumberMode, true ) +
967                     getDisplayStringImpl( CalendarDisplayCode::SHORT_YEAR, nNativeNumberMode, true );
968 
969             case CalendarDisplayCode::LONG_YEAR_AND_ERA:
970                 return  getDisplayStringImpl( CalendarDisplayCode::LONG_ERA, nNativeNumberMode, true ) +
971                     getDisplayStringImpl( CalendarDisplayCode::LONG_YEAR, nNativeNumberMode, true );
972 
973             default:
974                 throw ERROR;
975         }
976         aOUStr = OUString::createFromAscii(aStr);
977     }
978     // NatNum12 used only for selected parts
979     if (nNativeNumberMode > 0 && nNativeNumberMode != 12) {
980         // For Japanese calendar, first year calls GAN, see bug 111668 for detail.
981         if (eraArray == gengou_eraArray && value == 1
982             && (nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR ||
983                 nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR)
984             && (nNativeNumberMode == NativeNumberMode::NATNUM1 ||
985                 nNativeNumberMode == NativeNumberMode::NATNUM2)) {
986             static sal_Unicode gan = 0x5143;
987             return OUString(&gan, 1);
988         }
989         sal_Int16 nNatNum = NatNumForCalendar(aLocale, nCalendarDisplayCode, nNativeNumberMode, value);
990         if (nNatNum > 0)
991             return mxNatNum->getNativeNumberString(aOUStr, aLocale, nNatNum);
992     }
993     return aOUStr;
994 }
995 
996 // Methods in XExtendedCalendar
997 OUString SAL_CALL
998 Calendar_buddhist::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode )
999 {
1000     // make year and era in different order for year before and after 0.
1001     if ((nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR_AND_ERA ||
1002                 nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR_AND_ERA) &&
1003             getValue(CalendarFieldIndex::ERA) == 0) {
1004         if (nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR_AND_ERA)
1005             return  getDisplayStringImpl( CalendarDisplayCode::SHORT_YEAR, nNativeNumberMode, true ) +
1006                 getDisplayStringImpl( CalendarDisplayCode::SHORT_ERA, nNativeNumberMode, true );
1007         else
1008             return  getDisplayStringImpl( CalendarDisplayCode::LONG_YEAR, nNativeNumberMode, true ) +
1009                 getDisplayStringImpl( CalendarDisplayCode::LONG_ERA, nNativeNumberMode, true );
1010     }
1011     return Calendar_gregorian::getDisplayString(nCalendarDisplayCode, nNativeNumberMode);
1012 }
1013 
1014 OUString SAL_CALL
1015 Calendar_gregorian::getImplementationName()
1016 {
1017     return OUString::createFromAscii(cCalendar);
1018 }
1019 
1020 sal_Bool SAL_CALL
1021 Calendar_gregorian::supportsService(const OUString& rServiceName)
1022 {
1023     return cppu::supportsService(this, rServiceName);
1024 }
1025 
1026 Sequence< OUString > SAL_CALL
1027 Calendar_gregorian::getSupportedServiceNames()
1028 {
1029     Sequence< OUString > aRet { OUString::createFromAscii(cCalendar) };
1030     return aRet;
1031 }
1032 
1033 }
1034 
1035 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1036