diff options
author | Eike Rathke <erack@redhat.com> | 2016-07-26 23:18:30 +0200 |
---|---|---|
committer | Eike Rathke <erack@redhat.com> | 2016-07-26 23:20:07 +0200 |
commit | 8e91b0177cbf6554acf7ccfbcc573f647e7fcf4d (patch) | |
tree | dc1b3a376d713fd56f5293754eb88375dc61f64a /i18npool/source | |
parent | ce3577056dd44d35b38894b7ec573951208209eb (diff) |
Resolves: tdf#100046 the dreaded 1945-04-02 Double DST, also tdf#79663
A coward not having changed this earlier, partially taken from a patch that was
submitted for tdf#79663 but broke API, thanks to Isamu Mogi again.
This removes all the clutter around timezone and DST correction that was
necessary for early ICU versions, which apparently gets things right now and
the clutter can still be confused by corner cases.
Change-Id: I9a90f933e8db8c6e0db145520ebf71cc27621abc
Diffstat (limited to 'i18npool/source')
-rw-r--r-- | i18npool/source/calendar/calendar_gregorian.cxx | 247 |
1 files changed, 4 insertions, 243 deletions
diff --git a/i18npool/source/calendar/calendar_gregorian.cxx b/i18npool/source/calendar/calendar_gregorian.cxx index 275b24dff0ed..f3fa8ea6dac8 100644 --- a/i18npool/source/calendar/calendar_gregorian.cxx +++ b/i18npool/source/calendar/calendar_gregorian.cxx @@ -491,260 +491,21 @@ void Calendar_gregorian::submitFields() throw(css::uno::RuntimeException) body->set( fieldNameConverter( CalendarFieldIndex::DST_OFFSET), nDSTOffset); } -void Calendar_gregorian::submitValues( sal_Int32 nEra, sal_Int32 nYear, - sal_Int32 nMonth, sal_Int32 nDay, sal_Int32 nHour, sal_Int32 nMinute, - sal_Int32 nSecond, sal_Int32 nMilliSecond, sal_Int32 nZone, sal_Int32 nDST ) - throw(css::uno::RuntimeException) -{ - submitFields(); - if (nEra >= 0) - body->set( UCAL_ERA, nEra); - if (nYear >= 0) - body->set( UCAL_YEAR, nYear); - if (nMonth >= 0) - body->set( UCAL_MONTH, nMonth); - if (nDay >= 0) - body->set( UCAL_DATE, nDay); - if (nHour >= 0) - body->set( UCAL_HOUR_OF_DAY, nHour); - if (nMinute >= 0) - body->set( UCAL_MINUTE, nMinute); - if (nSecond >= 0) - body->set( UCAL_SECOND, nSecond); - if (nMilliSecond >= 0) - body->set( UCAL_MILLISECOND, nMilliSecond); - if (nZone != 0) - body->set( UCAL_ZONE_OFFSET, nZone); - if (nDST != 0) - body->set( UCAL_DST_OFFSET, nDST); -} - -static void lcl_setCombinedOffsetFieldValues( sal_Int32 nValue, - sal_Int16 rFieldSetValue[], sal_Int16 rFieldValue[], - sal_Int16 nParentFieldIndex, sal_Int16 nChildFieldIndex ) -{ - sal_Int32 nTrunc = nValue / 60000; - rFieldSetValue[nParentFieldIndex] = rFieldValue[nParentFieldIndex] = - static_cast<sal_Int16>( nTrunc); - sal_uInt16 nMillis = static_cast<sal_uInt16>( abs( nValue - nTrunc * 60000)); - rFieldSetValue[nChildFieldIndex] = rFieldValue[nChildFieldIndex] = - static_cast<sal_Int16>( nMillis); -} - void Calendar_gregorian::setValue() throw(RuntimeException) { - // Correct DST glitch, see also localtime/gmtime conversion pitfalls at - // http://www.erack.de/download/timetest.c - - // #i24082# in order to make the DST correction work in all - // circumstances, the time values have to be always resubmitted, - // regardless whether specified by the caller or not. It is not - // sufficient to rely on the ICU internal values previously set, as the - // following may happen: - // - Let 2004-03-28T02:00 be the onsetRule. - // - On 2004-03-29 (calendar initialized with 2004-03-29T00:00 DST) set - // a date of 2004-03-28 => calendar results in 2004-03-27T23:00 no DST. - // - Correcting this with simply "2004-03-28 no DST" and no time - // specified results in 2004-03-29T00:00, the ICU internal 23:00 time - // being adjusted to 24:00 in this case, switching one day further. - // => submit 2004-03-28T00:00 no DST. - - // This got even weirder since ICU incorporated also historical data, - // even the timezone may differ for different dates! It is necessary to - // let ICU choose the corresponding OlsonTimeZone transitions and adapt - // values. - // #i86094# gives examples where that went wrong: - // TZ=Europe/Moscow date <= 1919-07-01 - // zone +2:30:48 (!) instead of +3h, DST +2h instead of +1h - // TZ=America/St_Johns date <= 1935-03-30 - // zone -3:30:52 (!) instead of -3:30 - // Copy fields before calling submitFields() directly or indirectly below. memcpy(fieldSetValue, fieldValue, sizeof(fieldSetValue)); // Possibly setup ERA and YEAR in fieldSetValue. mapToGregorian(); - DUMP_ICU_CAL_MSG(("%s\n","setValue() before any submission")); - DUMP_I18N_CAL_MSG(("%s\n","setValue() before any submission")); - - bool bNeedZone = !(fieldSet & (1 << CalendarFieldIndex::ZONE_OFFSET)); - bool bNeedDST = !(fieldSet & (1 << CalendarFieldIndex::DST_OFFSET)); - sal_Int32 nZone1, nDST1, nEra, nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone0, nDST0; - nZone1 = nDST1 = nZone0 = nDST0 = 0; - nEra = nYear = nMonth = nDay = nHour = nMinute = nSecond = nMilliSecond = -1; - if ( bNeedZone || bNeedDST ) - { - UErrorCode status; - if ( !(fieldSet & (1 << CalendarFieldIndex::ERA)) ) - { - nEra = body->get( UCAL_ERA, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nEra = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::YEAR)) ) - { - nYear = body->get( UCAL_YEAR, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nYear = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::MONTH)) ) - { - nMonth = body->get( UCAL_MONTH, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nMonth = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::DAY_OF_MONTH)) ) - { - nDay = body->get( UCAL_DATE, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nDay = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::HOUR)) ) - { - nHour = body->get( UCAL_HOUR_OF_DAY, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nHour = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::MINUTE)) ) - { - nMinute = body->get( UCAL_MINUTE, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nMinute = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::SECOND)) ) - { - nSecond = body->get( UCAL_SECOND, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nSecond = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::MILLISECOND)) ) - { - nMilliSecond = body->get( UCAL_MILLISECOND, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nMilliSecond = -1; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::ZONE_OFFSET)) ) - { - nZone0 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nZone0 = 0; - } - if ( !(fieldSet & (1 << CalendarFieldIndex::DST_OFFSET)) ) - { - nDST0 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nDST0 = 0; - } - - // Submit values to obtain a time zone and DST corresponding to the date/time. - submitValues( nEra, nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone0, nDST0); - - DUMP_ICU_CAL_MSG(("%s\n","setValue() in bNeedZone||bNeedDST after submitValues()")); - DUMP_I18N_CAL_MSG(("%s\n","setValue() in bNeedZone||bNeedDST after submitValues()")); - nZone1 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nZone1 = 0; - nDST1 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nDST1 = 0; - } + DUMP_ICU_CAL_MSG(("%s\n","setValue() before submission")); + DUMP_I18N_CAL_MSG(("%s\n","setValue() before submission")); - // The original submission, may lead to a different zone/DST and - // different date. submitFields(); - DUMP_ICU_CAL_MSG(("%s\n","setValue() after original submission")); - DUMP_I18N_CAL_MSG(("%s\n","setValue() after original submission")); - if ( bNeedZone || bNeedDST ) - { - UErrorCode status; - sal_Int32 nZone2 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nZone2 = nZone1; - sal_Int32 nDST2 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nDST2 = nDST1; - if ( nZone0 != nZone1 || nZone2 != nZone1 || nDST0 != nDST1 || nDST2 != nDST1 ) - { - // Due to different DSTs, resulting date values may differ if - // DST is onset at 00:00 and the very onsetRule date was - // submitted with DST off => date-1 23:00, for example, which - // is not what we want. - // Resubmit all values, this time including DST => date 01:00 - // Similar for zone differences. - // If already the first full submission with nZone0 and nDST0 - // lead to date-1 23:00, the original submission was based on - // that date if it wasn't a full date (nDST0 set, nDST1 not - // set, nDST2==nDST1). If it was January 1st without year we're - // even off by one year now. Resubmit all values including new - // DST => date 00:00. - - // Set field values accordingly in case they were used. - if (!bNeedZone) - lcl_setCombinedOffsetFieldValues( nZone2, fieldSetValue, - fieldValue, CalendarFieldIndex::ZONE_OFFSET, - CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS); - if (!bNeedDST) - lcl_setCombinedOffsetFieldValues( nDST2, fieldSetValue, - fieldValue, CalendarFieldIndex::DST_OFFSET, - CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS); - submitValues( nEra, nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone2, nDST2); - DUMP_ICU_CAL_MSG(("%s\n","setValue() after Zone/DST glitch resubmit")); - DUMP_I18N_CAL_MSG(("%s\n","setValue() after Zone/DST glitch resubmit")); - - // Time zone transition => resubmit. - // TZ=America/St_Johns date <= 1935-03-30 - // -3:30:52 (!) instead of -3:30 - // if first submission included time zone -3:30 that would be wrong. - bool bResubmit = false; - sal_Int32 nZone3 = body->get( UCAL_ZONE_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nZone3 = nZone2; - if (nZone3 != nZone2) - { - bResubmit = true; - if (!bNeedZone) - lcl_setCombinedOffsetFieldValues( nZone3, fieldSetValue, - fieldValue, CalendarFieldIndex::ZONE_OFFSET, - CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS); - } + DUMP_ICU_CAL_MSG(("%s\n","setValue() after submission")); + DUMP_I18N_CAL_MSG(("%s\n","setValue() after submission")); - // If the DST onset rule says to switch from 00:00 to 01:00 and - // we tried to set onsetDay 00:00 with DST, the result was - // onsetDay-1 23:00 and no DST, which is not what we want. So - // once again without DST, resulting in onsetDay 01:00 and DST. - // Yes, this seems to be weird, but logically correct. - // It doesn't even have to be on an onsetDay as the DST is - // factored in all days by ICU and there seems to be some - // unknown behavior. - // TZ=Asia/Tehran 1999-03-22 exposes this, for example. - sal_Int32 nDST3 = body->get( UCAL_DST_OFFSET, status = U_ZERO_ERROR); - if ( !U_SUCCESS(status) ) - nDST3 = nDST2; - if (nDST2 != nDST3 && !nDST3) - { - bResubmit = true; - if (!bNeedDST) - { - fieldSetValue[CalendarFieldIndex::DST_OFFSET] = - fieldValue[CalendarFieldIndex::DST_OFFSET] = 0; - fieldSetValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] = - fieldValue[CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS] = 0; - } - } - if (bResubmit) - { - submitValues( nEra, nYear, nMonth, nDay, nHour, nMinute, nSecond, nMilliSecond, nZone3, nDST3); - DUMP_ICU_CAL_MSG(("%s\n","setValue() after Zone/DST glitch 2nd resubmit")); - DUMP_I18N_CAL_MSG(("%s\n","setValue() after Zone/DST glitch 2nd resubmit")); - } - SAL_INFO( "i18npool", "Calendar_gregorian::setValue:" - " nZone0 " << nZone0 << ", nDST0 " << nDST0 << - ", nZone1 " << nZone1 << ", nDST1 " << nDST1 << - ", nZone2 " << nZone2 << ", nDST2 " << nDST2 << - ", nZone3 " << nZone3 << ", nDST3 " << nDST3); - } - } #if erDUMP_ICU_CALENDAR || erDUMP_I18N_CALENDAR { // force icu::Calendar to recalculate |