summaryrefslogtreecommitdiff
path: root/svl
diff options
context:
space:
mode:
authorEike Rathke <erack@redhat.com>2017-05-04 17:01:08 +0200
committerEike Rathke <erack@redhat.com>2017-05-04 17:01:34 +0200
commitae7401002a0b6dabe4136dd8c5654ee2a54e7db6 (patch)
treede13d5835e4977b17df0280b8aa813377695c935 /svl
parent83f1381039898329a3e67330c1e59c039311363f (diff)
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
Diffstat (limited to 'svl')
-rw-r--r--svl/source/numbers/zforfind.cxx107
-rw-r--r--svl/source/numbers/zforfind.hxx10
-rw-r--r--svl/source/numbers/zformat.cxx44
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)
{