i18npool/source/calendar/calendar_gregorian.cxx | 53 ++++++++++++++++-------- svl/source/numbers/zforlist.cxx | 20 ++++++++- svl/source/numbers/zformat.cxx | 32 ++++++++++++-- 3 files changed, 83 insertions(+), 22 deletions(-)
New commits: commit ebc454ad4eb1f4cd4d84a7db367bb71a457c4e5c Author: Eike Rathke <er...@redhat.com> AuthorDate: Wed Sep 29 23:14:18 2021 +0200 Commit: Eike Rathke <er...@redhat.com> CommitDate: Thu Sep 30 02:04:01 2021 +0200 Resolves: tdf#144697 Format out-of-bounds date(+time) as #FMT error i.e. < -32768-01-01 or > 32767-12-31 They couldn't be input or stored as proleptic Gregorian in file formats anyway. Additionally in i18npool handle the absolute year values casting conversion int32 <-> int16 where era 0 BCE year 32768 is fielded as -32768 but still is a valid year for our proleptic Gregorian, so it isn't displayed as --32768. Change-Id: Ifdd482f07e04c2a4296fd0556bbef7f1d3e15676 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/122835 Reviewed-by: Eike Rathke <er...@redhat.com> Tested-by: Jenkins diff --git a/i18npool/source/calendar/calendar_gregorian.cxx b/i18npool/source/calendar/calendar_gregorian.cxx index 6a1dad685309..df998b3c6cf3 100644 --- a/i18npool/source/calendar/calendar_gregorian.cxx +++ b/i18npool/source/calendar/calendar_gregorian.cxx @@ -127,6 +127,22 @@ using namespace ::com::sun::star::lang; namespace i18npool { +// Cast positive int32 values to signed int16, needed for +// fieldValue[CalendarFieldIndex::YEAR] where absolute value of an era year may +// be 32768 that with static_cast<sal_Int16> becomes -32768, which we also +// do here but indicating places in a controlled way to map back. +static inline sal_Int16 cast32To16( sal_Int32 a ) +{ + // More than uint16 can't be handled, should not occur. + assert(a >= 0 && a < static_cast<sal_Int32>(SAL_MAX_INT16) - static_cast<sal_Int32>(SAL_MIN_INT16)); + return static_cast<sal_Int16>(a); +} +// And back. +static inline sal_Int32 cast16To32( sal_Int16 i ) +{ + return static_cast<sal_Int32>(static_cast<sal_uInt16>(i)); +} + Calendar_gregorian::Calendar_gregorian() : mxNatNum(new NativeNumberSupplierService) { @@ -409,10 +425,11 @@ void Calendar_gregorian::mapFromGregorian() if (!eraArray) return; - sal_Int16 e, y, m, d; + sal_Int16 e, m, d; + sal_Int32 y; e = fieldValue[CalendarFieldIndex::ERA]; - y = fieldValue[CalendarFieldIndex::YEAR]; + y = cast16To32(fieldValue[CalendarFieldIndex::YEAR]); m = fieldValue[CalendarFieldIndex::MONTH] + 1; d = fieldValue[CalendarFieldIndex::DAY_OF_MONTH]; @@ -427,7 +444,7 @@ void Calendar_gregorian::mapFromGregorian() fieldValue[CalendarFieldIndex::ERA] = e; fieldValue[CalendarFieldIndex::YEAR] = - sal::static_int_cast<sal_Int16>( (e == 0) ? (eraArray[0].year - y) : (y - eraArray[e-1].year + 1) ); + cast32To16( (e == 0) ? (eraArray[0].year - y) : (y - eraArray[e-1].year + 1) ); } #define FIELDS ((1 << CalendarFieldIndex::ERA) | (1 << CalendarFieldIndex::YEAR)) @@ -436,14 +453,15 @@ void Calendar_gregorian::mapFromGregorian() void Calendar_gregorian::mapToGregorian() { if (eraArray && (fieldSet & FIELDS)) { - sal_Int16 y, e = fieldValue[CalendarFieldIndex::ERA]; + sal_Int16 e = fieldValue[CalendarFieldIndex::ERA]; + sal_Int32 y; if (e == 0) - y = sal::static_int_cast<sal_Int16>( eraArray[0].year - fieldValue[CalendarFieldIndex::YEAR] ); + y = eraArray[0].year - cast16To32(fieldValue[CalendarFieldIndex::YEAR]); else - y = sal::static_int_cast<sal_Int16>( eraArray[e-1].year + fieldValue[CalendarFieldIndex::YEAR] - 1 ); + y = eraArray[e-1].year + cast16To32(fieldValue[CalendarFieldIndex::YEAR] - 1); fieldSetValue[CalendarFieldIndex::ERA] = y <= 0 ? 0 : 1; - fieldSetValue[CalendarFieldIndex::YEAR] = (y <= 0 ? 1 - y : y); + fieldSetValue[CalendarFieldIndex::YEAR] = cast32To16(y <= 0 ? 1 - y : y); fieldSet |= FIELDS; } } @@ -664,7 +682,7 @@ Calendar_gregorian::isValid() // NatNum4 NatNum9/9/11/11 static sal_Int16 NatNumForCalendar(const css::lang::Locale& aLocale, - sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, sal_Int16 value ) + sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, sal_Int32 value ) { bool isShort = ((nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR || nCalendarDisplayCode == CalendarDisplayCode::LONG_YEAR) && value >= 100) || @@ -883,7 +901,8 @@ Calendar_gregorian::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 OUString Calendar_gregorian::getDisplayStringImpl( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode, bool bEraMode ) { - sal_Int16 value = getValue(sal::static_int_cast<sal_Int16>( DisplayCode2FieldIndex(nCalendarDisplayCode) )); + sal_Int32 value = cast16To32( getValue( sal::static_int_cast<sal_Int16>( + DisplayCode2FieldIndex(nCalendarDisplayCode)))); OUString aOUStr; if (nCalendarDisplayCode == CalendarDisplayCode::SHORT_QUARTER || @@ -906,23 +925,23 @@ Calendar_gregorian::getDisplayStringImpl( sal_Int32 nCalendarDisplayCode, sal_In // The "#100211# - checked" comments serve for detection of "use of // sprintf is safe here" conditions. An sprintf encountered without // having that comment triggers alarm ;-) - char aStr[10]; + char aStr[12]; // "-2147483648" and \0 switch( nCalendarDisplayCode ) { case CalendarDisplayCode::SHORT_MONTH: value += 1; // month is zero based [[fallthrough]]; case CalendarDisplayCode::SHORT_DAY: - sprintf(aStr, "%d", value); // #100211# - checked + sprintf(aStr, "%" SAL_PRIdINT32, value); // #100211# - checked break; case CalendarDisplayCode::LONG_YEAR: if ( aCalendar.Name == "gengou" ) - sprintf(aStr, "%02d", value); // #100211# - checked + sprintf(aStr, "%02" SAL_PRIdINT32, value); // #100211# - checked else - sprintf(aStr, "%d", value); // #100211# - checked + sprintf(aStr, "%" SAL_PRIdINT32, value); // #100211# - checked break; case CalendarDisplayCode::LONG_MONTH: value += 1; // month is zero based - sprintf(aStr, "%02d", value); // #100211# - checked + sprintf(aStr, "%02" SAL_PRIdINT32, value); // #100211# - checked break; case CalendarDisplayCode::SHORT_YEAR: // Take last 2 digits, or only one if value<10, for example, @@ -937,12 +956,12 @@ Calendar_gregorian::getDisplayStringImpl( sal_Int32 nCalendarDisplayCode, sal_In // the only calendar using this. // See i#116701 and fdo#60915 if (value < 100 || bEraMode || (eraArray && (eraArray[0].flags & kDisplayEraForcedLongYear))) - sprintf(aStr, "%d", value); // #100211# - checked + sprintf(aStr, "%" SAL_PRIdINT32, value); // #100211# - checked else - sprintf(aStr, "%02d", value % 100); // #100211# - checked + sprintf(aStr, "%02" SAL_PRIdINT32, value % 100); // #100211# - checked break; case CalendarDisplayCode::LONG_DAY: - sprintf(aStr, "%02d", value); // #100211# - checked + sprintf(aStr, "%02" SAL_PRIdINT32, value); // #100211# - checked break; case CalendarDisplayCode::SHORT_DAY_NAME: diff --git a/svl/source/numbers/zforlist.cxx b/svl/source/numbers/zforlist.cxx index 755dda187b9b..185dc97f12fa 100644 --- a/svl/source/numbers/zforlist.cxx +++ b/svl/source/numbers/zforlist.cxx @@ -1713,6 +1713,7 @@ void SvNumberFormatter::GetInputLineString(const double& fOutNumber, { pFormat = GetFormatEntry( nKey ); } + assert(pFormat); if (pFormat) { if ( eType == SvNumFormatType::TIME && pFormat->GetFormatPrecision() ) @@ -1720,7 +1721,24 @@ void SvNumberFormatter::GetInputLineString(const double& fOutNumber, ChangeStandardPrec(INPUTSTRING_PRECISION); bPrecChanged = true; } - pFormat->GetOutputString(fOutNumber, sOutString, &pColor); + const bool bOk = pFormat->GetOutputString(fOutNumber, sOutString, &pColor); + + // The #FMT error string must not be used for input as it would lead to + // data loss. This can happen for at least date(+time). Fall back to a + // last resort of plain number in the locale the formatter was + // contructed with. + if (!bOk && eType != SvNumFormatType::NUMBER && sOutString == ImpSvNumberformatScan::sErrStr) + { + pFormat = GetFormatEntry(ZF_STANDARD); + assert(pFormat); + if (pFormat) + { + ChangeStandardPrec(INPUTSTRING_PRECISION); + bPrecChanged = true; + pFormat->GetOutputString(fOutNumber, sOutString, &pColor); + } + } + assert(sOutString != ImpSvNumberformatScan::sErrStr); } if (bPrecChanged) { diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx index 0f618e75ca02..53eb8181c2d6 100644 --- a/svl/source/numbers/zformat.cxx +++ b/svl/source/numbers/zformat.cxx @@ -3647,6 +3647,24 @@ static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rN rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor ); } +/* XXX: if needed this could be stripped from rEpochStart and diff adding and + * moved to tools' DateTime to be reused elsewhere. */ +static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber ) +{ + static const DateTime aCE( Date(1,1,1)); + static const DateTime aMin( Date(1,1, SAL_MIN_INT16)); + static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1)); + static const double fMin = aMin - aCE; + static const double fMax = aMax - aCE; + // Value must be representable in our tools::Date proleptic Gregorian + // calendar as well. + const double fOff = (rNullDate - aCE) + fNumber; + // Add diff between epochs to serial date number. + const double fDiff = rNullDate - rEpochStart; + fNumber += fDiff; + return fMin <= fOff && fOff <= fMax; +} + bool SvNumberformat::ImpGetDateOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) @@ -3655,8 +3673,11 @@ bool SvNumberformat::ImpGetDateOutput(double fNumber, bool bRes = false; CalendarWrapper& rCal = GetCal(); - double fDiff = DateTime(rScan.GetNullDate()) - rCal.getEpochStart(); - fNumber += fDiff; + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } rCal.setLocalDateTime( fNumber ); int nUseMonthCase = 0; // Not decided yet OUString aOrgCalendar; // empty => not changed yet @@ -3922,8 +3943,11 @@ bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, bool bRes = false; CalendarWrapper& rCal = GetCal(); - double fDiff = DateTime(rScan.GetNullDate()) - rCal.getEpochStart(); - fNumber += fDiff; + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); bool bInputLine;