From ae7401002a0b6dabe4136dd8c5654ee2a54e7db6 Mon Sep 17 00:00:00 2001 From: Eike Rathke Date: Thu, 4 May 2017 17:01:08 +0200 Subject: Number formatter: handle negative signed year date as BCE Accept input -YYYY-MM-DD or -Y/M/D or M/D/-Y or D.M.-Y ... and display likewise if no era word is to be displayed. Change-Id: I199d34354d5b91dbe2a6a6ac3ae4b50d5dbde670 --- svl/source/numbers/zforfind.cxx | 107 +++++++++++++++++++++++++++++++++++++--- svl/source/numbers/zforfind.hxx | 10 +++- svl/source/numbers/zformat.cxx | 44 +++++++++++++++++ 3 files changed, 153 insertions(+), 8 deletions(-) diff --git a/svl/source/numbers/zforfind.cxx b/svl/source/numbers/zforfind.cxx index c45050e31093..08fc26e20435 100644 --- a/svl/source/numbers/zforfind.cxx +++ b/svl/source/numbers/zforfind.cxx @@ -65,6 +65,7 @@ const sal_uInt8 ImpSvNumberInputScan::nMatchedUsedAsReturn = 0x10; static const sal_Unicode cNoBreakSpace = 0xA0; static const sal_Unicode cNarrowNoBreakSpace = 0x202F; +static const sal_Int16 kDefaultEra = 1; // Gregorian CE, positive year ImpSvNumberInputScan::ImpSvNumberInputScan( SvNumberFormatter* pFormatterP ) : @@ -122,6 +123,7 @@ void ImpSvNumberInputScan::Reset() nAmPm = 0; nPosThousandString = 0; nLogical = 0; + mnEra = kDefaultEra; nStringScanNumFor = 0; nStringScanSign = 0; nMatchedAllStrings = nMatchedVirgin; @@ -999,12 +1001,15 @@ sal_uInt16 ImpSvNumberInputScan::ImplGetYear( sal_uInt16 nIndex ) sal_uInt16 nYear = 0; sal_Int32 nLen = sStrArray[nNums[nIndex]].getLength(); - if (nLen <= 4) + // 16-bit integer year width can have 5 digits, allow for one additional + // leading zero as convention. + if (nLen <= 6) { nYear = (sal_uInt16) sStrArray[nNums[nIndex]].toInt32(); + // A year in another, not Gregorian CE era is never expanded. // A year < 100 entered with at least 3 digits with leading 0 is taken // as is without expansion. - if (nYear < 100 && nLen < 3) + if (mnEra == kDefaultEra && nYear < 100 && nLen < 3) { nYear = SvNumberFormatter::ExpandTwoDigitYear( nYear, nYear2000 ); } @@ -1123,6 +1128,26 @@ bool ImpSvNumberInputScan::MayBeMonthDate() } +/** If a string is a separator plus '-' minus sign preceding a 'Y' year in + a date pattern at position nPat. + */ +static bool lcl_IsSignedYearSep( const OUString& rStr, const OUString& rPat, sal_Int32 nPat ) +{ + bool bOk = false; + sal_Int32 nLen = rStr.getLength(); + if (nLen > 1 && rStr[nLen-1] == '-') + { + --nLen; + if (nPat + nLen < rPat.getLength() && rPat[nPat+nLen] == 'Y') + { + // Signed year is possible. + bOk = (rPat.indexOf( rStr.copy( 0, nLen), nPat) == nPat); + } + } + return bOk; +} + + bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt ) { if (nAcceptedDatePattern >= -1) @@ -1210,6 +1235,10 @@ bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt ) { nPat += nLen - 1; } + else if ((bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat))) + { + nPat += nLen - 2; + } else if (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ') { using namespace comphelper::string; @@ -1295,7 +1324,7 @@ bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt ) } -bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos ) +bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear ) { // If not initialized yet start with first number, if any. if (!IsAcceptedDatePattern( (nAnzNums ? nNums[0] : 0))) @@ -1321,6 +1350,12 @@ bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_I { const sal_Int32 nLen = sStrArray[nNext].getLength(); bool bOk = (rPat.indexOf( sStrArray[nNext], nPat) == nPat); + if (!bOk) + { + bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat); + if (bOk) + rSignedYear = true; + } if (!bOk && (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ')) { // The same ugly trailing blanks check as in @@ -1358,6 +1393,30 @@ sal_uInt16 ImpSvNumberInputScan::GetDatePatternNumbers() } +bool ImpSvNumberInputScan::IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType ) +{ + if (GetDatePatternNumbers() <= nNumber) + return false; + + sal_uInt16 nNum = 0; + const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern]; + for (sal_Int32 nPat = 0; nPat < rPat.getLength(); ++nPat) + { + switch (rPat[nPat]) + { + case 'Y': + case 'M': + case 'D': + if (nNum == nNumber) + return rPat[nPat] == cType; + ++nNum; + break; + } + } + return false; +} + + sal_uInt32 ImpSvNumberInputScan::GetDatePatternOrder() { // If not initialized yet start with first number, if any. @@ -1910,6 +1969,9 @@ input for the following reasons: break; } // switch (nAnzNums) + if (mnEra != kDefaultEra) + pCal->setValue( CalendarFieldIndex::ERA, mnEra ); + if ( res && pCal->isValid() ) { double fDiff = DateTime(*pNullDate) - pCal->getEpochStart(); @@ -2231,8 +2293,9 @@ bool ImpSvNumberInputScan::ScanMidString( const OUString& rString, } else if (nDecPos == 2) // . dup: 12.4. { + bool bSignedYear = false; if (bDecSepInDateSeps || // . also date separator - SkipDatePatternSeparator( nStringPos, nPos)) + SkipDatePatternSeparator( nStringPos, nPos, bSignedYear)) { if ( eScannedType != css::util::NumberFormat::UNDEFINED && eScannedType != css::util::NumberFormat::DATE && @@ -2311,7 +2374,8 @@ bool ImpSvNumberInputScan::ScanMidString( const OUString& rString, } const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); - bool bDate = SkipDatePatternSeparator( nStringPos, nPos); // 12/31 31.12. 12/31/1999 31.12.1999 + bool bSignedYear = false; + bool bDate = SkipDatePatternSeparator( nStringPos, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999 if (!bDate) { const OUString& rDate = pFormatter->GetDateSep(); @@ -2348,6 +2412,13 @@ bool ImpSvNumberInputScan::ScanMidString( const OUString& rString, } SkipBlanks(rString, nPos); } + if (bSignedYear) + { + if (mnEra != kDefaultEra) // signed year twice? + return MatchedReturn(); + + mnEra = 0; // BCE + } } const sal_Int32 nMonthStart = nPos; @@ -2568,8 +2639,9 @@ bool ImpSvNumberInputScan::ScanEndString( const OUString& rString, } else if (nDecPos == 2) // . dup: 12.4. { + bool bSignedYear = false; if (bDecSepInDateSeps || // . also date separator - SkipDatePatternSeparator( nAnzStrings-1, nPos)) + SkipDatePatternSeparator( nAnzStrings-1, nPos, bSignedYear)) { if ( eScannedType != css::util::NumberFormat::UNDEFINED && eScannedType != css::util::NumberFormat::DATE && @@ -2685,7 +2757,8 @@ bool ImpSvNumberInputScan::ScanEndString( const OUString& rString, } } - bool bDate = SkipDatePatternSeparator( nAnzStrings-1, nPos); // 12/31 31.12. 12/31/1999 31.12.1999 + bool bSignedYear = false; + bool bDate = SkipDatePatternSeparator( nAnzStrings-1, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999 if (!bDate) { const OUString& rDate = pFormatter->GetDateSep(); @@ -3444,6 +3517,26 @@ bool ImpSvNumberInputScan::IsNumberFormat( const OUString& rString, // s if (res) { + // Accept signed date only for ISO date with at least four digits in + // year to not have an input of -M-D-Y arbitrarily recognized. The + // final order is only determined in GetDateRef(). + // Also accept for Y/M/D date pattern match, i.e. if the first number + // is year. + // Accept only if the year immediately follows the sign character with + // no space in between. + if (nSign && (eScannedType == css::util::NumberFormat::DATE || + eScannedType == css::util::NumberFormat::DATETIME) && mnEra == kDefaultEra && + (IsDatePatternNumberOfType(0,'Y') || (MayBeIso8601() && sStrArray[nNums[0]].getLength() >= 4))) + { + const sal_Unicode c = sStrArray[0][sStrArray[0].getLength()-1]; + if (c == '-' || c == '+') + { + // A '+' sign doesn't change the era. + if (nSign < 0) + mnEra = 0; // BCE + nSign = 0; + } + } if ( nNegCheck || // ')' not found for '(' (nSign && (eScannedType == css::util::NumberFormat::DATE || eScannedType == css::util::NumberFormat::DATETIME))) // signed date/datetime diff --git a/svl/source/numbers/zforfind.hxx b/svl/source/numbers/zforfind.hxx index e7d0be0d9acf..6f95d4c6e6b3 100644 --- a/svl/source/numbers/zforfind.hxx +++ b/svl/source/numbers/zforfind.hxx @@ -115,6 +115,7 @@ private: short nESign; // Sign of exponent short nAmPm; // +1 AM, -1 PM, 0 if none short nLogical; // -1 => False, 1 => True + sal_Int16 mnEra; // Era if date, 0 => BCE, 1 => CE (currently only Gregorian) sal_uInt16 nThousand; // Count of group (AKA thousand) separators sal_uInt16 nPosThousandString; // Position of concatenated 000,000,000 string short eScannedType; // Scanned type @@ -386,14 +387,21 @@ private: /** Sets (not advances!) rPos to sStrArray[nParticle].getLength() if string matches separator in pattern at nParticle. + Also detects a signed year case like M/D/-Y + @returns TRUE if separator matched. */ - bool SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos ); + bool SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear ); /** Returns count of numbers in accepted date pattern. */ sal_uInt16 GetDatePatternNumbers(); + /** Whether numeric string nNumber is of type cType in accepted date + pattern, 'Y', 'M' or 'D'. + */ + bool IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType ); + /** Obtain order of accepted date pattern coded as, for example, ('D'<<16)|('M'<<8)|'Y' */ diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx index fa0b536b5b69..24da6cb89549 100644 --- a/svl/source/numbers/zformat.cxx +++ b/svl/source/numbers/zformat.cxx @@ -3463,6 +3463,30 @@ bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const return bIsIso; } +static bool lcl_hasEra( const ImpSvNumFor& rNumFor ) +{ + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nAnz = rNumFor.GetCount(); + for ( sal_uInt16 i = 0; i < nAnz; i++ ) + { + switch ( rInfo.nTypeArray[i] ) + { + case NF_KEY_RR : + case NF_KEY_G : + case NF_KEY_GG : + case NF_KEY_GGG : + return true; + } + } + return false; +} + +static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor ) +{ + return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 && + rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor ); +} + bool SvNumberformat::ImpGetDateOutput(double fNumber, sal_uInt16 nIx, OUStringBuffer& sBuff) @@ -3583,6 +3607,11 @@ bool SvNumberformat::ImpGetDateOutput(double fNumber, { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); if ( bOtherCalendar ) { @@ -3594,6 +3623,11 @@ bool SvNumberformat::ImpGetDateOutput(double fNumber, { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); if (aYear.getLength() < 4) { @@ -3930,6 +3964,11 @@ bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); if ( bOtherCalendar ) { @@ -3941,6 +3980,11 @@ bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, { SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); if (aYear.getLength() < 4) { -- cgit