From 1b13548f33720d80f53d493f6d70cbcce6b1a0fb Mon Sep 17 00:00:00 2001 From: Eike Rathke Date: Tue, 2 May 2017 15:53:04 +0200 Subject: BASIC: handle the full tools::Date range from -32768-01-01 to 32767-12-31 * Input of two-digit years only possible through CDateFromIso() though to maintain compatibility with previous behavior and also VBA mode. * VBA mode restricted to years 1..9999 Change-Id: Ia9574c3bf136619b4831b349d263c96b162d1ed4 --- basic/source/runtime/methods.cxx | 105 ++++++++++++++++++++++++-------------- basic/source/runtime/methods1.cxx | 15 ++---- 2 files changed, 72 insertions(+), 48 deletions(-) (limited to 'basic/source/runtime') diff --git a/basic/source/runtime/methods.cxx b/basic/source/runtime/methods.cxx index 82bd2ce03134..0b1698b9eabc 100644 --- a/basic/source/runtime/methods.cxx +++ b/basic/source/runtime/methods.cxx @@ -1865,7 +1865,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, dDate ) ) + if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, dDate ) ) { pVal->PutDate( dDate ); } @@ -1980,7 +1980,7 @@ void SbxDateFromUNODateTime( SbxValue *pVal, const css::util::DateTime& aUnoDT) double dDate(0.0); if( implDateTimeSerial( aUnoDT.Year, aUnoDT.Month, aUnoDT.Day, aUnoDT.Hours, aUnoDT.Minutes, aUnoDT.Seconds, - dDate ) ) + false, dDate ) ) { pVal->PutDate( dDate ); } @@ -2021,7 +2021,7 @@ RTLFUNC(CDateFromUnoDateTime) SbxBase::SetError( ERRCODE_SBX_CONVERSION ); } -// Function to convert date to ISO 8601 date format +// Function to convert date to ISO 8601 date format YYYYMMDD RTLFUNC(CDateToIso) { (void)pBasic; @@ -2031,11 +2031,13 @@ RTLFUNC(CDateToIso) { double aDate = rPar.Get(1)->GetDate(); + // Date may actually even be -YYYYYMMDD char Buffer[11]; - snprintf( Buffer, sizeof( Buffer ), "%04d%02d%02d", - implGetDateYear( aDate ), - implGetDateMonth( aDate ), - implGetDateDay( aDate ) ); + sal_Int16 nYear = implGetDateYear( aDate ); + snprintf( Buffer, sizeof( Buffer ), (nYear < 0 ? "%05d%02d%02d" : "%04d%02d%02d"), + static_cast(nYear), + static_cast(implGetDateMonth( aDate )), + static_cast(implGetDateDay( aDate )) ); OUString aRetStr = OUString::createFromAscii( Buffer ); rPar.Get(0)->PutString( aRetStr ); } @@ -2056,34 +2058,51 @@ RTLFUNC(CDateFromIso) do { OUString aStr = rPar.Get(1)->GetOUString(); + if (aStr.isEmpty()) + break; + + // Valid formats are + // YYYYMMDD -YYYMMDD YYYYYMMDD -YYYYYMMDD + // YYYY-MM-DD -YYYY-MM-DD YYYYY-MM-DD -YYYYY-MM-DD + + sal_Int32 nSign = 1; + if (aStr[0] == '-') + { + nSign = -1; + aStr = aStr.copy(1); + } const sal_Int32 nLen = aStr.getLength(); - if (nLen != 8 && nLen != 10) + + // Now valid + // YYYYMMDD YYYYYMMDD + // YYYY-MM-DD YYYYY-MM-DD + if (nLen < 8 || 11 < nLen) break; OUString aYearStr, aMonthStr, aDayStr; - if (nLen == 8) + if (nLen == 8 || nLen == 9) { - // YYYYMMDD + // (Y)YYYYMMDD if (!comphelper::string::isdigitAsciiString(aStr)) break; - aYearStr = aStr.copy( 0, 4 ); - aMonthStr = aStr.copy( 4, 2 ); - aDayStr = aStr.copy( 6, 2 ); + const sal_Int32 nMonthPos = (nLen == 9 ? 5 : 4); + aYearStr = aStr.copy( 0, nMonthPos ); + aMonthStr = aStr.copy( nMonthPos, 2 ); + aDayStr = aStr.copy( nMonthPos + 2, 2 ); } else { - // YYYY-MM-DD - const sal_Int32 nSep1 = aStr.indexOf('-'); - if (nSep1 != 4) + // (Y)YYYY-MM-DD + const sal_Int32 nMonthSep = (nLen == 11 ? 5 : 4); + if (aStr.indexOf('-') != nMonthSep) break; - const sal_Int32 nSep2 = aStr.indexOf('-', nSep1+1); - if (nSep2 != 7) + if (aStr.indexOf('-', nMonthSep + 1) != nMonthSep + 3) break; - aYearStr = aStr.copy( 0, 4 ); - aMonthStr = aStr.copy( 5, 2 ); - aDayStr = aStr.copy( 8, 2 ); + aYearStr = aStr.copy( 0, nMonthSep ); + aMonthStr = aStr.copy( nMonthSep + 1, 2 ); + aDayStr = aStr.copy( nMonthSep + 4, 2 ); if ( !comphelper::string::isdigitAsciiString(aYearStr) || !comphelper::string::isdigitAsciiString(aMonthStr) || !comphelper::string::isdigitAsciiString(aDayStr)) @@ -2091,8 +2110,8 @@ RTLFUNC(CDateFromIso) } double dDate; - if (!implDateSerial( (sal_Int16)aYearStr.toInt32(), - (sal_Int16)aMonthStr.toInt32(), (sal_Int16)aDayStr.toInt32(), dDate )) + if (!implDateSerial( (sal_Int16)(nSign * aYearStr.toInt32()), + (sal_Int16)aMonthStr.toInt32(), (sal_Int16)aDayStr.toInt32(), false, dDate )) break; rPar.Get(0)->PutDate( dDate ); @@ -2124,7 +2143,7 @@ RTLFUNC(DateSerial) sal_Int16 nDay = rPar.Get(3)->GetInteger(); double dDate; - if( implDateSerial( nYear, nMonth, nDay, dDate ) ) + if( implDateSerial( nYear, nMonth, nDay, true, dDate ) ) { rPar.Get(0)->PutDate( dDate ); } @@ -4893,36 +4912,46 @@ sal_Int16 implGetDateYear( double aDate ) return nRet; } -bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, double& rdRet ) +bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, bool bUseTwoDigitYear, 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 + // 1930..1999, VBA mode overrides bUseTwoDigitYear (as if that was always + // true). For VBA years > 9999 are invalid. + // For StarBASIC, if bUseTwoDigitYear==true then years in the range 0..99 + // can not be input as they are 2-digit for 1900..1999, years<0 are + // accepted. If bUseTwoDigitYear==false then all years are accepted, but + // year 0 is invalid (last day BCE -0001-12-31, first day CE 0001-01-01). #if HAVE_FEATURE_SCRIPTING - if ( nYear < 30 && SbiRuntime::isVBAEnabled() ) + if ( (nYear < 0 || 9999 < nYear) && SbiRuntime::isVBAEnabled() ) + { + StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT ); + return false; + } + else if ( nYear < 30 && SbiRuntime::isVBAEnabled() ) { nYear += 2000; } else #endif { - if ( nYear < 100 ) + if ( 0 <= nYear && nYear < 100 && (bUseTwoDigitYear || SbiRuntime::isVBAEnabled()) ) { nYear += 1900; } } + Date aCurDate( nDay, nMonth, nYear ); - if ((nYear < 100 || nYear > 9999) ) - { -#if HAVE_FEATURE_SCRIPTING - StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT ); -#endif - return false; - } + /* TODO: also StarBASIC should provide the rollover mechanism, probably we + * can use tools::Date::AddMonths() and operator+=() for both, VBA and + * StarBASIC. If called from CDateFromIso or CDateFromUnoDate it should not + * rollover. */ #if HAVE_FEATURE_SCRIPTING if ( !SbiRuntime::isVBAEnabled() ) #endif { - if ( (nMonth < 1 || nMonth > 12 )|| - (nDay < 1 || nDay > 31 ) ) + if ( nMonth < 1 || nDay < 1 || !aCurDate.IsValidDate() ) { #if HAVE_FEATURE_SCRIPTING StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT ); @@ -4982,10 +5011,10 @@ double implTimeSerial( sal_Int16 nHours, sal_Int16 nMinutes, sal_Int16 nSeconds bool implDateTimeSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, sal_Int16 nHour, sal_Int16 nMinute, sal_Int16 nSecond, - double& rdRet ) + bool bUseTwoDigitYear, double& rdRet ) { double dDate; - if(!implDateSerial(nYear, nMonth, nDay, dDate)) + if(!implDateSerial(nYear, nMonth, nDay, bUseTwoDigitYear, 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 f38d47a882bf..650c0d92fdc9 100644 --- a/basic/source/runtime/methods1.cxx +++ b/basic/source/runtime/methods1.cxx @@ -2075,7 +2075,7 @@ RTLFUNC(DateAdd) sal_Int32 nTargetYear = lNumber + nYear; nTargetYear16 = limitToINT16( nTargetYear ); nTargetMonth = nMonth; - bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, dNewDate ); + bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, dNewDate ); break; } case INTERVAL_Q: @@ -2119,7 +2119,7 @@ RTLFUNC(DateAdd) nTargetYear = (sal_Int32)nYear + nYearsAdd; } nTargetYear16 = limitToINT16( nTargetYear ); - bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, dNewDate ); + bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false, dNewDate ); break; } default: break; @@ -2130,16 +2130,11 @@ RTLFUNC(DateAdd) // Overflow? sal_Int16 nNewYear, nNewMonth, nNewDay; implGetDayMonthYear( nNewYear, nNewMonth, nNewDay, dNewDate ); - if( nNewYear > 9999 || nNewYear < 100 ) - { - StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT ); - return; - } sal_Int16 nCorrectionDay = nDay; while( nNewMonth > nTargetMonth ) { nCorrectionDay--; - implDateSerial( nTargetYear16, nTargetMonth, nCorrectionDay, dNewDate ); + implDateSerial( nTargetYear16, nTargetMonth, nCorrectionDay, false, dNewDate ); implGetDayMonthYear( nNewYear, nNewMonth, nNewDay, dNewDate ); } dNewDate += dHoursMinutesSeconds; @@ -2334,7 +2329,7 @@ double implGetDateOfFirstDayInFirstWeek nFirstWeekMinDays = 7; // vbFirstFourDays double dBaseDate; - implDateSerial( nYear, 1, 1, dBaseDate ); + implDateSerial( nYear, 1, 1, false, dBaseDate ); sal_Int16 nWeekDay0101 = implGetWeekDay( dBaseDate ); sal_Int16 nDayDiff = nWeekDay0101 - nFirstDay; @@ -2397,7 +2392,7 @@ RTLFUNC(DatePart) { sal_Int16 nYear = implGetDateYear( dDate ); double dBaseDate; - implDateSerial( nYear, 1, 1, dBaseDate ); + implDateSerial( nYear, 1, 1, false, dBaseDate ); nRet = 1 + sal_Int32( dDate - dBaseDate ); break; } -- cgit