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;

Reply via email to