From 40c9a129e5a53e6eadfe8ca80c98ccf7eda957f9 Mon Sep 17 00:00:00 2001 From: Eike Rathke Date: Tue, 15 May 2018 19:36:52 +0200 Subject: Resolves: tdf#117612 truncate DateAdd("m",...) to last day of month ... instead of resulting in error because of roll-over not being set. Fallout from commit 6d424f07701bf26d8fb173563b567d5f097c33e2 CommitDate: Tue May 2 23:12:34 2017 +0200 Replace mouth-painted "inaccurate around leap year" rollover algorithm that does stricter checking but DateAdd() needs a lax checking with truncate to last day of month. Change-Id: I9d6f95ad3ac38257d492019bd621070491e98e76 --- basic/source/inc/date.hxx | 9 ++++++++- basic/source/runtime/methods.cxx | 37 +++++++++++++++++++++++++++---------- basic/source/runtime/methods1.cxx | 20 ++++---------------- basic/source/sbx/sbxscan.cxx | 2 +- 4 files changed, 40 insertions(+), 28 deletions(-) (limited to 'basic') diff --git a/basic/source/inc/date.hxx b/basic/source/inc/date.hxx index 4b28f1d66b02..c0cfd66a5cf7 100644 --- a/basic/source/inc/date.hxx +++ b/basic/source/inc/date.hxx @@ -24,7 +24,14 @@ #include #include -bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, bool bUseTwoDigitYear, bool bRollOver, double& rdRet ); +enum class SbDateCorrection +{ + None, + RollOver, + TruncateToMonth +}; + +bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet ); double implTimeSerial( sal_Int16 nHour, sal_Int16 nMinute, sal_Int16 nSecond); bool implDateTimeSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, sal_Int16 nHour, sal_Int16 nMinute, sal_Int16 nSecond, diff --git a/basic/source/runtime/methods.cxx b/basic/source/runtime/methods.cxx index 74c44259fd91..589105ff3935 100644 --- a/basic/source/runtime/methods.cxx +++ b/basic/source/runtime/methods.cxx @@ -1725,7 +1725,7 @@ css::util::Date SbxDateToUNODate( const SbxValue* const pVal ) void SbxDateFromUNODate( SbxValue *pVal, const css::util::Date& aUnoDate) { double dDate; - if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, false, dDate ) ) + if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, SbDateCorrection::None, dDate ) ) { pVal->PutDate( dDate ); } @@ -1955,7 +1955,8 @@ void SbRtl_CDateFromIso(StarBASIC *, SbxArray & rPar, bool) double dDate; if (!implDateSerial( static_cast(nSign * aYearStr.toInt32()), - static_cast(aMonthStr.toInt32()), static_cast(aDayStr.toInt32()), bUseTwoDigitYear, false, dDate )) + static_cast(aMonthStr.toInt32()), static_cast(aDayStr.toInt32()), + bUseTwoDigitYear, SbDateCorrection::None, dDate )) break; rPar.Get(0)->PutDate( dDate ); @@ -1984,7 +1985,7 @@ void SbRtl_DateSerial(StarBASIC *, SbxArray & rPar, bool) sal_Int16 nDay = rPar.Get(3)->GetInteger(); double dDate; - if( implDateSerial( nYear, nMonth, nDay, true, true, dDate ) ) + if( implDateSerial( nYear, nMonth, nDay, true, SbDateCorrection::RollOver, dDate ) ) { rPar.Get(0)->PutDate( dDate ); } @@ -4547,7 +4548,7 @@ sal_Int16 implGetDateYear( double aDate ) } bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, - bool bUseTwoDigitYear, bool bRollOver, double& rdRet ) + bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet ) { // XXX NOTE: For VBA years<0 are invalid and years in the range 0..29 and // 30..99 can not be input as they are 2-digit for 2000..2029 and @@ -4610,14 +4611,14 @@ bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, * documentation would need to be adapted. As is, the DateSerial() runtime * function works as dumb as documented.. (except that the resulting date * is checked for validity now and not just day<=31 and month<=12). - * If change wanted then simply remove overriding bRollOver here and adapt + * If change wanted then simply remove overriding RollOver here and adapt * documentation.*/ #if HAVE_FEATURE_SCRIPTING - if (!SbiRuntime::isVBAEnabled()) - bRollOver = false; + if (eCorr == SbDateCorrection::RollOver && !SbiRuntime::isVBAEnabled()) + eCorr = SbDateCorrection::None; #endif - if (nYear == 0 || (!bRollOver && (nAddMonths || nAddDays || !aCurDate.IsValidDate()))) + if (nYear == 0 || (eCorr == SbDateCorrection::None && (nAddMonths || nAddDays || !aCurDate.IsValidDate()))) { #if HAVE_FEATURE_SCRIPTING StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT ); @@ -4625,13 +4626,29 @@ bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, return false; } - if (bRollOver) + if (eCorr != SbDateCorrection::None) { aCurDate.Normalize(); if (nAddMonths) aCurDate.AddMonths( nAddMonths); if (nAddDays) aCurDate.AddDays( nAddDays); + if (eCorr == SbDateCorrection::TruncateToMonth && aCurDate.GetMonth() != nMonth) + { + if (aCurDate.GetYear() == SAL_MAX_INT16 && nMonth == 12) + { + // Roll over and back not possible, hard max. + aCurDate.SetMonth(12); + aCurDate.SetDay(31); + } + else + { + aCurDate.SetMonth(nMonth); + aCurDate.SetDay(1); + aCurDate.AddMonths(1); + aCurDate.AddDays(-1); + } + } } long nDiffDays = GetDayDiff( aCurDate ); @@ -4654,7 +4671,7 @@ bool implDateTimeSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, double& rdRet ) { double dDate; - if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, false/*bRollOver*/, dDate)) + if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, SbDateCorrection::None, dDate)) return false; rdRet += dDate + implTimeSerial(nHour, nMinute, nSecond); return true; diff --git a/basic/source/runtime/methods1.cxx b/basic/source/runtime/methods1.cxx index 68dfb503f572..3a1964783ff5 100644 --- a/basic/source/runtime/methods1.cxx +++ b/basic/source/runtime/methods1.cxx @@ -1898,7 +1898,7 @@ void SbRtl_DateAdd(StarBASIC *, SbxArray & rPar, bool) nTargetYear16 = limitDate( nTargetYear, nMonth, nDay ); /* TODO: should the result be error if the date was limited? It never was. */ nTargetMonth = nMonth; - bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, true, dNewDate ); + bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, SbDateCorrection::TruncateToMonth, dNewDate ); break; } case INTERVAL_Q: @@ -1943,26 +1943,14 @@ void SbRtl_DateAdd(StarBASIC *, SbxArray & rPar, bool) } nTargetYear16 = limitDate( nTargetYear, nTargetMonth, nDay ); /* TODO: should the result be error if the date was limited? It never was. */ - bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, true, dNewDate ); + bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, SbDateCorrection::TruncateToMonth, dNewDate ); break; } default: break; } if( bOk ) - { - // Overflow? - sal_Int16 nNewYear, nNewMonth, nNewDay; - implGetDayMonthYear( nNewYear, nNewMonth, nNewDay, dNewDate ); - sal_Int16 nCorrectionDay = nDay; - while( nNewMonth > nTargetMonth ) - { - nCorrectionDay--; - implDateSerial( nTargetYear16, nTargetMonth, nCorrectionDay, false, true, dNewDate ); - implGetDayMonthYear( nNewYear, nNewMonth, nNewDay, dNewDate ); - } dNewDate += dHoursMinutesSeconds; - } } rPar.Get(0)->PutDate( dNewDate ); @@ -2147,7 +2135,7 @@ double implGetDateOfFirstDayInFirstWeek nFirstWeekMinDays = 7; // vbFirstFourDays double dBaseDate; - implDateSerial( nYear, 1, 1, false, false, dBaseDate ); + implDateSerial( nYear, 1, 1, false, SbDateCorrection::None, dBaseDate ); sal_Int16 nWeekDay0101 = implGetWeekDay( dBaseDate ); sal_Int16 nDayDiff = nWeekDay0101 - nFirstDay; @@ -2207,7 +2195,7 @@ void SbRtl_DatePart(StarBASIC *, SbxArray & rPar, bool) { sal_Int16 nYear = implGetDateYear( dDate ); double dBaseDate; - implDateSerial( nYear, 1, 1, false, false, dBaseDate ); + implDateSerial( nYear, 1, 1, false, SbDateCorrection::None, dBaseDate ); nRet = 1 + sal_Int32( dDate - dBaseDate ); break; } diff --git a/basic/source/sbx/sbxscan.cxx b/basic/source/sbx/sbxscan.cxx index c725aff2397d..3003906678d1 100644 --- a/basic/source/sbx/sbxscan.cxx +++ b/basic/source/sbx/sbxscan.cxx @@ -774,7 +774,7 @@ void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const { sal_Int16 nYear = implGetDateYear( nNumber ); double dBaseDate; - implDateSerial( nYear, 1, 1, true, false, dBaseDate ); + implDateSerial( nYear, 1, 1, true, SbDateCorrection::None, dBaseDate ); sal_Int32 nYear32 = 1 + sal_Int32( nNumber - dBaseDate ); rRes = OUString::number(nYear32); } -- cgit v1.2.3