From 508f5b7767210318f1a23efc6db928c87a5eca6f Mon Sep 17 00:00:00 2001 From: Eike Rathke Date: Tue, 21 Sep 2021 19:26:51 +0200 Subject: [PATCH] Add ISO 8601 date+time with milliseconds format NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 YYYY-MM-DD"T"HH:MM:SS,000 using either ',' or '.' separator, '.' if Time100SecSep is '.' ',' else A prerequisite for tdf#88359 to not lose data when importing such, especially without "Detect special numbers" on. Change-Id: I02ab682636e6ddbcc4537183a3625ea61662f016 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/122400 Reviewed-by: Eike Rathke Tested-by: Jenkins --- include/svl/zforlist.hxx | 3 ++- svl/qa/unit/svl.cxx | 3 ++- svl/source/numbers/zforlist.cxx | 30 +++++++++++++++++++++++++++--- svl/source/numbers/zforscan.cxx | 32 ++++++++++++++++++++++++++++++-- svx/source/items/numfmtsh.cxx | 3 ++- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/include/svl/zforlist.hxx b/include/svl/zforlist.hxx index a9b9fa37748a..59b748e4381e 100644 --- a/include/svl/zforlist.hxx +++ b/include/svl/zforlist.hxx @@ -247,11 +247,12 @@ enum NfIndexTableOffset NF_DATETIME_ISO_YYYYMMDD_HHMMSS, // 1997-10-08 01:23:45 ISO (with blank instead of T) NF_DATETIME_ISO_YYYYMMDDTHHMMSS, // 1997-10-08T01:23:45 ISO + NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, // 1997-10-08T01:23:45,678 ISO with milliseconds // XXX When adding values here, follow the comment above about // svx/source/items/numfmtsh.cxx - NF_INDEX_TABLE_ENTRIES // == 60, reserved to not be used in i18npool locale data. + NF_INDEX_TABLE_ENTRIES // == 61, reserved to not be used in i18npool locale data. // XXX Adding values above may increment the reserved area that can't be // used by i18npool's locale data FormatCode definitions, see the diff --git a/svl/qa/unit/svl.cxx b/svl/qa/unit/svl.cxx index 8baffff86cab..155fc9cbf1da 100644 --- a/svl/qa/unit/svl.cxx +++ b/svl/qa/unit/svl.cxx @@ -233,6 +233,7 @@ void Test::testNumberFormat() const char* pDateTimeExt2[] = { "YYYY-MM-DD HH:MM:SS", "YYYY-MM-DD\"T\"HH:MM:SS", + "YYYY-MM-DD\"T\"HH:MM:SS.000", nullptr }; @@ -264,7 +265,7 @@ void Test::testNumberFormat() { NF_TEXT, NF_TEXT, 1, pText }, { NF_DATETIME_SYS_DDMMYYYY_HHMM, NF_DATETIME_SYS_DDMMYYYY_HHMM, 1, pDateTimeExt1 }, { NF_FRACTION_3D, NF_FRACTION_100, 7, pFractionExt }, - { NF_DATETIME_ISO_YYYYMMDD_HHMMSS, NF_DATETIME_ISO_YYYYMMDDTHHMMSS, 2, pDateTimeExt2 } + { NF_DATETIME_ISO_YYYYMMDD_HHMMSS, NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, 3, pDateTimeExt2 } }; SvNumberFormatter aFormatter(m_xContext, eLang); diff --git a/svl/source/numbers/zforlist.cxx b/svl/source/numbers/zforlist.cxx index e623a0239743..dca678d81abd 100644 --- a/svl/source/numbers/zforlist.cxx +++ b/svl/source/numbers/zforlist.cxx @@ -169,7 +169,8 @@ sal_uInt32 const indexTable[NF_INDEX_TABLE_ENTRIES] = { ZF_STANDARD_FRACTION + 7, // NF_FRACTION_10 ZF_STANDARD_FRACTION + 8, // NF_FRACTION_100 ZF_STANDARD_DATETIME + 2, // NF_DATETIME_ISO_YYYYMMDD_HHMMSS - ZF_STANDARD_DATETIME + 3 // NF_DATETIME_ISO_YYYYMMDDTHHMMSS + ZF_STANDARD_DATETIME + 3, // NF_DATETIME_ISO_YYYYMMDDTHHMMSS + ZF_STANDARD_DATETIME + 5 // NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 }; /** @@ -1273,11 +1274,18 @@ bool SvNumberFormatter::IsNumberFormat(const OUString& sString, // Preserve ISO 8601 input. if (pStringScanner->HasIso8601Tsep()) { - F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, ActLnge ); + if (pStringScanner->GetDecPos()) + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, ActLnge ); + else + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, ActLnge ); } else if (pStringScanner->CanForceToIso8601( DateOrder::Invalid)) { - F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, ActLnge ); + /* TODO: add a millisecond format with space instead of 'T'? */ + if (pStringScanner->GetDecPos()) + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, ActLnge ); + else + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, ActLnge ); } else { @@ -1639,6 +1647,8 @@ sal_uInt32 SvNumberFormatter::GetEditFormat( double fNumber, sal_uInt32 nFIndex, case SvNumFormatType::DATETIME : if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eLang)) nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eLang ); + else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eLang)) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eLang ); else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eLang) || (pFormat && pFormat->IsIso8601( 0 ))) nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eLang ); else @@ -2893,6 +2903,20 @@ void SvNumberFormatter::ImpGenerateFormats( sal_uInt32 CLOffset, bool bNoAdditio assert(pFormat); pFormat->SetComment("ISO 8601"); // not to be localized + // YYYY-MM-DD"T"HH:MM:SS,000 ISO with milliseconds and ',' or '.' decimal separator + aSingleFormatCode.Code = + rKeyword[NF_KEY_YYYY] + "-" + + rKeyword[NF_KEY_MM] + "-" + + rKeyword[NF_KEY_DD] + "\"T\"" + + rKeyword[NF_KEY_HH] + ":" + + rKeyword[NF_KEY_MMI] + ":" + + rKeyword[NF_KEY_SS] + (GetLocaleData()->getTime100SecSep() == "." ? "." : ",") + + "000"; + pFormat = ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATETIME+5 /* NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 */ ); + assert(pFormat); + pFormat->SetComment("ISO 8601"); // not to be localized + // Scientific number aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::SCIENTIFIC_NUMBER, aLocale ); diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx index f6cdfdb5b339..a5f25637678a 100644 --- a/svl/source/numbers/zforscan.cxx +++ b/svl/source/numbers/zforscan.cxx @@ -1432,9 +1432,37 @@ sal_Int32 ImpSvNumberformatScan::ScanType() eNewType = SvNumFormatType::TEXT; break; default: - if (pLoc->getTime100SecSep() == sStrArray[i]) + // Separator for SS,0 + if ((eScannedType & SvNumFormatType::TIME) + && 0 < i && (nTypeArray[i-1] == NF_KEY_S || nTypeArray[i-1] == NF_KEY_SS)) { - bDecSep = true; // for SS,0 + // For ISO 8601 only YYYY-MM-DD"T"HH:MM:SS,0 accept both + // ',' and '.' regardless of locale's separator, and only + // those. + // XXX NOTE: this catches only known separators of + // NF_SYMBOLTYPE_DEL as all NF_SYMBOLTYPE_STRING are + // skipped during the loop. Meant to error out if the + // Time100SecSep or decimal separator differ and were used. + if ((eScannedType & SvNumFormatType::DATE) + && 11 <= i && i < nStringsCnt-1 + && (nTypeArray[i-6] == NF_SYMBOLTYPE_STRING + && (sStrArray[i-6] == "\"T\"" || sStrArray[i-6] == "\\T" + || sStrArray[i-6] == "T")) + && (nTypeArray[i-11] == NF_KEY_YYYY) + && (nTypeArray[i-9] == NF_KEY_M || nTypeArray[i-9] == NF_KEY_MM) + && (nTypeArray[i-7] == NF_KEY_D || nTypeArray[i-7] == NF_KEY_DD) + && (nTypeArray[i-5] == NF_KEY_H || nTypeArray[i-5] == NF_KEY_HH) + && (nTypeArray[i-3] == NF_KEY_MI || nTypeArray[i-3] == NF_KEY_MMI) + && (nTypeArray[i+1] == NF_SYMBOLTYPE_DEL && sStrArray[i+1][0] == '0')) + + { + if (sStrArray[i].getLength() == 1 && (sStrArray[i][0] == ',' || sStrArray[i][0] == '.')) + bDecSep = true; + else + return nPos; // Error + } + else if (pLoc->getTime100SecSep() == sStrArray[i]) + bDecSep = true; } eNewType = SvNumFormatType::UNDEFINED; break; diff --git a/svx/source/items/numfmtsh.cxx b/svx/source/items/numfmtsh.cxx index 6ea63332191c..30f76ab3afb9 100644 --- a/svx/source/items/numfmtsh.cxx +++ b/svx/source/items/numfmtsh.cxx @@ -660,7 +660,7 @@ short SvxNumberFormatShell::FillEListWithDateTime_Impl(std::vector& rL // Always add the internally generated ISO formats. nSelPos = FillEListWithFormats_Impl(rList, nSelPos, NF_DATETIME_ISO_YYYYMMDD_HHMMSS, - NF_DATETIME_ISO_YYYYMMDDTHHMMSS, false); + NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, false); return nSelPos; } @@ -720,6 +720,7 @@ bool SvxNumberFormatShell::IsEssentialFormat_Impl(SvNumFormatType eType, sal_uIn case NF_DATETIME_SYS_DDMMYYYY_HHMMSS: case NF_DATETIME_ISO_YYYYMMDD_HHMMSS: case NF_DATETIME_ISO_YYYYMMDDTHHMMSS: + case NF_DATETIME_ISO_YYYYMMDDTHHMMSS000: return true; default: break;