From 942de6a01ba990e5f3bc55ce4ab3737a03f67f39 Mon Sep 17 00:00:00 2001 From: Eike Rathke Date: Wed, 19 Jun 2019 23:03:49 +0200 Subject: Resolves: tdf#92503 introduce TimeZone to calendar loading and default to UTC Without that, the system's time zone was used which on DST transition dates leads to non-existent times when switching to/from DST. As the calendar use and number parser/formatter nor conversions or calculations are time zone aware, using not DST afflicted UTC is the better choice. Change-Id: I3303c6620d8c4b9d081555c8293954fb1bd67895 Reviewed-on: https://gerrit.libreoffice.org/74386 Reviewed-by: Eike Rathke Tested-by: Jenkins --- i18npool/inc/calendarImpl.hxx | 8 +++--- i18npool/inc/calendar_gregorian.hxx | 1 + i18npool/source/calendar/calendarImpl.cxx | 36 +++++++++++++++++++++---- i18npool/source/calendar/calendar_gregorian.cxx | 21 +++++++++++++++ 4 files changed, 58 insertions(+), 8 deletions(-) (limited to 'i18npool') diff --git a/i18npool/inc/calendarImpl.hxx b/i18npool/inc/calendarImpl.hxx index b35decb0f750..299bfe79bae2 100644 --- a/i18npool/inc/calendarImpl.hxx +++ b/i18npool/inc/calendarImpl.hxx @@ -84,6 +84,8 @@ public: // XCalendar4 virtual void SAL_CALL setLocalDateTime(double TimeInDays) override; virtual double SAL_CALL getLocalDateTime() override; + virtual void SAL_CALL loadDefaultCalendarTZ(const css::lang::Locale& rLocale, const OUString& rTimeZone) override; + virtual void SAL_CALL loadCalendarTZ(const OUString& uniqueID, const css::lang::Locale& rLocale, const OUString& rTimeZone) override; //XServiceInfo virtual OUString SAL_CALL getImplementationName() override; @@ -92,9 +94,9 @@ public: private: struct lookupTableItem { - lookupTableItem(const OUString& _uniqueID, css::uno::Reference < css::i18n::XCalendar4 > const & _xCalendar) - : uniqueID(_uniqueID), xCalendar(_xCalendar) {} - OUString uniqueID; + lookupTableItem(const OUString& rCacheID, css::uno::Reference < css::i18n::XCalendar4 > const & _xCalendar) + : m_aCacheID(rCacheID), xCalendar(_xCalendar) {} + OUString m_aCacheID; css::uno::Reference < css::i18n::XCalendar4 > xCalendar; }; std::vector lookupTable; diff --git a/i18npool/inc/calendar_gregorian.hxx b/i18npool/inc/calendar_gregorian.hxx index 894f2246e30b..aa9a02965d79 100644 --- a/i18npool/inc/calendar_gregorian.hxx +++ b/i18npool/inc/calendar_gregorian.hxx @@ -54,6 +54,7 @@ public: Calendar_gregorian(); Calendar_gregorian(const Era *_eraArray); void init(const Era *_eraArray); + bool setTimeZone( const OUString& rTimeZone ); /** * Destructor diff --git a/i18npool/source/calendar/calendarImpl.cxx b/i18npool/source/calendar/calendarImpl.cxx index 93ff9b16fa0b..f0962104a4b0 100644 --- a/i18npool/source/calendar/calendarImpl.cxx +++ b/i18npool/source/calendar/calendarImpl.cxx @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -40,12 +41,12 @@ CalendarImpl::~CalendarImpl() } void SAL_CALL -CalendarImpl::loadDefaultCalendar( const Locale& rLocale ) +CalendarImpl::loadDefaultCalendarTZ( const Locale& rLocale, const OUString& rTimeZone ) { Sequence< Calendar2 > xC = LocaleDataImpl::get()->getAllCalendars2(rLocale); for (sal_Int32 i = 0; i < xC.getLength(); i++) { if (xC[i].Default) { - loadCalendar(xC[i].Name, rLocale); + loadCalendarTZ(xC[i].Name, rLocale, rTimeZone); return; } } @@ -53,14 +54,16 @@ CalendarImpl::loadDefaultCalendar( const Locale& rLocale ) } void SAL_CALL -CalendarImpl::loadCalendar(const OUString& uniqueID, const Locale& rLocale ) +CalendarImpl::loadCalendarTZ( const OUString& uniqueID, const Locale& rLocale, const OUString& rTimeZone ) { Reference < XCalendar4 > xOldCalendar( xCalendar ); // backup + const OUString aCacheID( uniqueID + "_" + rTimeZone); + bool bTimeZone = true; sal_Int32 i; for (i = 0; i < sal::static_int_cast(lookupTable.size()); i++) { lookupTableItem &listItem = lookupTable[i]; - if (uniqueID == listItem.uniqueID) { + if (aCacheID == listItem.m_aCacheID) { xCalendar = listItem.xCalendar; break; } @@ -85,7 +88,16 @@ CalendarImpl::loadCalendar(const OUString& uniqueID, const Locale& rLocale ) throw ERROR; xCalendar.set(xI, UNO_QUERY); - lookupTable.emplace_back( uniqueID, xCalendar ); + if (!rTimeZone.isEmpty()) + { + /* XXX NOTE: currently (2019-06-19) calendar implementations derive + * from Calendar_gregorian, even Hijri and Jewish. If that should + * change in future this should be adapted. */ + Calendar_gregorian* pCal = dynamic_cast(xCalendar.get()); + bTimeZone = (pCal && pCal->setTimeZone(rTimeZone)); + } + + lookupTable.emplace_back( aCacheID, xCalendar ); } if ( !xCalendar.is() ) @@ -103,6 +115,10 @@ CalendarImpl::loadCalendar(const OUString& uniqueID, const Locale& rLocale ) xCalendar = xOldCalendar; throw; } + + if (!bTimeZone) + // The calendar is usable but is not in the expected time zone. + throw ERROR; } Calendar2 SAL_CALL @@ -164,6 +180,16 @@ CalendarImpl::getLocalDateTime() return xCalendar->getLocalDateTime(); } +void SAL_CALL CalendarImpl::loadDefaultCalendar( const css::lang::Locale& rLocale ) +{ + loadDefaultCalendarTZ( rLocale, OUString()); +} + +void SAL_CALL CalendarImpl::loadCalendar( const OUString& uniqueID, const css::lang::Locale& rLocale ) +{ + loadCalendarTZ( uniqueID, rLocale, OUString()); +} + OUString SAL_CALL CalendarImpl::getUniqueID() { diff --git a/i18npool/source/calendar/calendar_gregorian.cxx b/i18npool/source/calendar/calendar_gregorian.cxx index 18676361027a..f3b228efc04e 100644 --- a/i18npool/source/calendar/calendar_gregorian.cxx +++ b/i18npool/source/calendar/calendar_gregorian.cxx @@ -167,6 +167,11 @@ Calendar_gregorian::init(const Era *_eraArray) * */ icu::Locale aIcuLocale( "", nullptr, nullptr, "calendar=gregorian"); + /* XXX: not specifying a timezone when creating a calendar assigns the + * system's timezone with all DST quirks, invalid times when switching + * to/from DST and so on. The XCalendar* interfaces are defined to support + * local time and UTC time so we can not override that here. + */ UErrorCode status = U_ZERO_ERROR; body.reset( icu::Calendar::createInstance( aIcuLocale, status) ); if (!body || !U_SUCCESS(status)) throw ERROR; @@ -369,6 +374,22 @@ Calendar_gregorian::getLocalDateTime() return (fTime + (nZoneOffset + nDSTOffset)) / U_MILLIS_PER_DAY; } +bool Calendar_gregorian::setTimeZone( const OUString& rTimeZone ) +{ + if (fieldSet) + { + setValue(); + getValue(); + } + const icu::UnicodeString aID( reinterpret_cast(rTimeZone.getStr()), rTimeZone.getLength()); + const std::unique_ptr pTZ( icu::TimeZone::createTimeZone(aID)); + if (!pTZ) + return false; + + body->setTimeZone(*pTZ); + return true; +} + // map field value from gregorian calendar to other calendar, it can be overwritten by derived class. // By using eraArray, it can take care Japanese and Taiwan ROC calendar. void Calendar_gregorian::mapFromGregorian() -- cgit v1.2.3