sax/source/tools/converter.cxx         |   29 +++++++----------------------
 sc/qa/unit/subsequent_export_test4.cxx |   32 ++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 22 deletions(-)

New commits:
commit 0dc4cf8c5a8427b249eff0afb8851b9113b023c2
Author:     Mike Kaganski <[email protected]>
AuthorDate: Sun Nov 2 19:15:48 2025 +0500
Commit:     Christian Lohmaier <[email protected]>
CommitDate: Tue Nov 4 13:19:55 2025 +0100

    tdf#165733: fix leap year detection BCE
    
    lcl_isLeapYear used a simple "is divisible" method to check if a given
    year is leap. But that didn't take BCE into account: we use historical
    numbering system, where before year 0001 was year -0001; and that year
    was leap.
    
    Use comphelper::date::getDaysInMonth for that, which is correct, and
    also deduplicates the code. Make sure that years beyond sal_Int16 are
    considered an error: they are not representable in css::util::Date,
    and comphelper::date::getDaysInMonth takes years as sal_Int16.
    
    Change-Id: I34f1ad4d5d03e05a7cf787f9997b04b1d705b583
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193302
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>
    (cherry picked from commit 97bc8766e297c8d5c22b2f6d4bd7b6d7258782fb)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193314
    Reviewed-by: Adolfo Jayme Barrientos <[email protected]>
    (cherry picked from commit c96933d95f1eee1018f893dfc08d9b5d52891201)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193327
    Reviewed-by: Xisco Fauli <[email protected]>
    Tested-by: Ilmari Lauhakangas <[email protected]>
    Tested-by: Christian Lohmaier <[email protected]>
    Reviewed-by: Christian Lohmaier <[email protected]>

diff --git a/sax/source/tools/converter.cxx b/sax/source/tools/converter.cxx
index 8eb334113401..360b4cd32918 100644
--- a/sax/source/tools/converter.cxx
+++ b/sax/source/tools/converter.cxx
@@ -26,6 +26,7 @@
 #include <com/sun/star/util/Time.hpp>
 #include <optional>
 
+#include <comphelper/date.hxx>
 #include <rtl/ustrbuf.hxx>
 #include <rtl/math.hxx>
 #include <rtl/character.hxx>
@@ -1743,25 +1744,6 @@ bool Converter::parseDateTime(   util::DateTime& 
rDateTime,
             rString);
 }
 
-static bool lcl_isLeapYear(const sal_uInt32 nYear)
-{
-    return ((nYear % 4) == 0)
-        && (((nYear % 100) != 0) || ((nYear % 400) == 0));
-}
-
-static sal_uInt16
-lcl_MaxDaysPerMonth(const sal_Int32 nMonth, const sal_Int32 nYear)
-{
-    static const sal_uInt16 s_MaxDaysPerMonth[12] =
-        { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-    assert(0 < nMonth && nMonth <= 12);
-    if ((2 == nMonth) && lcl_isLeapYear(nYear))
-    {
-        return 29;
-    }
-    return s_MaxDaysPerMonth[nMonth - 1];
-}
-
 static void lcl_ConvertToUTC(
         sal_Int16 & o_rYear, sal_uInt16 & o_rMonth, sal_uInt16 & o_rDay,
         sal_uInt16 & o_rHours, sal_uInt16 & o_rMinutes,
@@ -1794,7 +1776,7 @@ static void lcl_ConvertToUTC(
             return; // handle time without date - don't adjust what isn't there
         }
         o_rDay += nDayAdd;
-        sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(o_rMonth, o_rYear));
+        sal_Int16 const 
nDaysInMonth(comphelper::date::getDaysInMonth(o_rMonth, o_rYear));
         if (o_rDay <= nDaysInMonth)
         {
             return;
@@ -1834,7 +1816,7 @@ static void lcl_ConvertToUTC(
             return;
         }
         sal_Int16 const nPrevMonth((o_rMonth == 1) ? 12 : o_rMonth - 1);
-        sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(nPrevMonth, o_rYear));
+        sal_Int16 const 
nDaysInMonth(comphelper::date::getDaysInMonth(nPrevMonth, o_rYear));
         o_rDay += nDaysInMonth;
         --o_rMonth;
         if (0 == o_rMonth)
@@ -1898,6 +1880,7 @@ static bool lcl_parseDate(
         {
             bSuccess &= (0 < nYear);
         }
+        bSuccess &= (nYear <= (isNegative ? 32768 : 32767)); // Must fit into 
css::util::Date
         bSuccess &= (nPos < string.size()); // not last token
     }
     if (bSuccess && ('-' != string[nPos])) // separator
@@ -1931,7 +1914,9 @@ static bool lcl_parseDate(
         }
         if (nMonth > 0) // not possible to check if month was missing
         {
-            bSuccess &= (nDay <= lcl_MaxDaysPerMonth(nMonth, nYear));
+            sal_Int32 nMaxDay
+                = comphelper::date::getDaysInMonth(nMonth, isNegative ? -nYear 
: nYear);
+            bSuccess &= (nDay <= nMaxDay);
         }
         else assert(bIgnoreInvalidOrMissingDate);
     }
diff --git a/sc/qa/unit/subsequent_export_test4.cxx 
b/sc/qa/unit/subsequent_export_test4.cxx
index 0a27ab309907..25efb2268b59 100644
--- a/sc/qa/unit/subsequent_export_test4.cxx
+++ b/sc/qa/unit/subsequent_export_test4.cxx
@@ -2443,6 +2443,38 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf150229)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf165733_leap_day_BCE)
+{
+    createScDoc();
+
+    {
+        ScDocument* pDoc = getScDoc();
+        // Set -0001-02-29 (proleptic Gregorian leap day), which is 
-0001-03-02 in proleptic Julian.
+        // If we ever change UI to use proleptic Gregorian, this will change:
+        pDoc->SetString(0, 0, 0, u"-0001-03-02"_ustr);
+        CPPUNIT_ASSERT_EQUAL(-693900.0, pDoc->GetValue(0, 0, 0));
+        CPPUNIT_ASSERT_EQUAL(SvNumFormatType::DATE,
+                             
pDoc->GetFormatTable()->GetType(pDoc->GetNumberFormat(0, 0, 0)));
+    }
+
+    saveAndReload(u"calc8"_ustr);
+
+    {
+        xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
+        CPPUNIT_ASSERT(pXmlDoc);
+        assertXPath(pXmlDoc, "//table:table-cell", "date-value", 
u"-0001-02-29");
+
+        ScDocument* pDoc = getScDoc();
+        // without the fix, this failed with:
+        // - Expected: -693900
+        // - Actual  : 0
+        // because import didn't recognize 1 BCE as a leap year, and 
considered 29th invalid in Feb:
+        CPPUNIT_ASSERT_EQUAL(-693900.0, pDoc->GetValue(0, 0, 0));
+        CPPUNIT_ASSERT_EQUAL(SvNumFormatType::DATE,
+                             
pDoc->GetFormatTable()->GetType(pDoc->GetNumberFormat(0, 0, 0)));
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to