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: */
