include/sax/tools/converter.hxx   |    6 +++
 oox/source/core/xmlfilterbase.cxx |   29 ++++++++++++++++++
 sax/source/tools/converter.cxx    |   60 ++++++++++++++++++++++++++------------
 3 files changed, 77 insertions(+), 18 deletions(-)

New commits:
commit 5a9e966b323a1605976a53ca6bac750741036eba
Author:     Noel Grandin <[email protected]>
AuthorDate: Tue Nov 4 13:56:56 2025 +0200
Commit:     Noel Grandin <[email protected]>
CommitDate: Sat Nov 8 13:11:38 2025 +0100

    mso-test: invalid datetime in props
    
    This is using the test document from tdf119234-3.odt.
    When importing and exporting to DOCX, we generate an invalid entry in
      docprops/custom.xml
    because we dont know how to handle a DateTimeWithTimezone value..
    
    Change-Id: I79981f8a09db432730a9b0c1cf186305b0055923
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193400
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Michael Stahl <[email protected]>
    (cherry picked from commit 97cf170c20c7ff1e1b3820a769cd3167a57d0c46)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193600
    Reviewed-by: Noel Grandin <[email protected]>
    Tested-by: Jenkins

diff --git a/include/sax/tools/converter.hxx b/include/sax/tools/converter.hxx
index 6b18cac11bf4..f39e293bdf98 100644
--- a/include/sax/tools/converter.hxx
+++ b/include/sax/tools/converter.hxx
@@ -259,6 +259,12 @@ public:
                                  sal_Int16 const* pTimeZoneOffset,
                                    bool bAddTimeIf0AM = false );
 
+    /** convert util::DateTime to XMLSchema-2 "date" or "dateTime" string */
+    static void convertDateTime( OStringBuffer& rBuffer,
+                                const css::util::DateTime& rDateTime,
+                                 sal_Int16 const* pTimeZoneOffset,
+                                   bool bAddTimeIf0AM = false );
+
     /** convert util::DateTime to XMLSchema-2 "time" or "dateTime" string */
     static void convertTimeOrDateTime(OUStringBuffer& rBuffer,
                             const css::util::DateTime& rDateTime);
diff --git a/oox/source/core/xmlfilterbase.cxx 
b/oox/source/core/xmlfilterbase.cxx
index 6fe4ec4c226d..173b60d8ac7b 100644
--- a/oox/source/core/xmlfilterbase.cxx
+++ b/oox/source/core/xmlfilterbase.cxx
@@ -28,6 +28,7 @@
 #include <com/sun/star/embed/XRelationshipAccess.hpp>
 #include <com/sun/star/frame/XModel.hpp>
 #include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/util/DateTimeWithTimezone.hpp>
 #include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
 #include <com/sun/star/xml/sax/Writer.hpp>
@@ -624,6 +625,31 @@ writeElement( const FSHelperPtr& pDoc, sal_Int32 
nXmlElement, const util::DateTi
     pDoc->endElement( nXmlElement );
 }
 
+static void
+writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const 
util::DateTimeWithTimezone& rTime )
+{
+    if (rTime.Timezone == 0)
+    {
+        writeElement(pDoc, nXmlElement, rTime.DateTimeInTZ);
+        return;
+    }
+
+    if( rTime.DateTimeInTZ.Year == 0 )
+        return;
+
+    if ( ( nXmlElement >> 16 ) != XML_dcterms )
+        pDoc->startElement(nXmlElement);
+    else
+        pDoc->startElement(nXmlElement, FSNS(XML_xsi, XML_type), 
"dcterms:W3CDTF");
+
+    OStringBuffer aStr;
+    sax::Converter::convertDateTime(aStr, rTime.DateTimeInTZ, &rTime.Timezone);
+
+    pDoc->write( aStr );
+
+    pDoc->endElement( nXmlElement );
+}
+
 static void
 writeElement( const FSHelperPtr& pDoc, sal_Int32 nXmlElement, const Sequence< 
OUString >& aItems )
 {
@@ -976,6 +1002,7 @@ writeCustomProperties( XmlFilterBase& rSelf, const 
Reference< XDocumentPropertie
                     util::Date aDate;
                     util::Duration aDuration;
                     util::DateTime aDateTime;
+                    util::DateTimeWithTimezone aDateTimeTZ;
                     if ( rProp.Value >>= num )
                     {
                         // i4 - 4-byte signed integer
@@ -995,6 +1022,8 @@ writeCustomProperties( XmlFilterBase& rSelf, const 
Reference< XDocumentPropertie
                     }
                     else if ( rProp.Value >>= aDateTime )
                             writeElement( pAppProps, FSNS( XML_vt, 
XML_filetime ), aDateTime );
+                    else if ( rProp.Value >>= aDateTimeTZ )
+                            writeElement( pAppProps, FSNS( XML_vt, 
XML_filetime ), aDateTimeTZ );
                     else
                         //no other options
                         OSL_FAIL( "XMLFilterBase::writeCustomProperties 
unsupported value type!" );
diff --git a/sax/source/tools/converter.cxx b/sax/source/tools/converter.cxx
index fd1c125ad663..e6863ada9d31 100644
--- a/sax/source/tools/converter.cxx
+++ b/sax/source/tools/converter.cxx
@@ -1572,8 +1572,9 @@ bool Converter::convertDuration(util::Duration& rDuration,
     return convertDurationHelper(rDuration, o3tl::trim(rString));
 }
 
+template<typename TStringBuffer>
 static void
-lcl_AppendTimezone(OUStringBuffer & i_rBuffer, int const nOffset)
+lcl_AppendTimezone(TStringBuffer & i_rBuffer, int const nOffset)
 {
     if (0 == nOffset)
     {
@@ -1618,18 +1619,19 @@ void Converter::convertDate(
     convertDateTime(i_rBuffer, dt, pTimeZoneOffset);
 }
 
+template<typename TStringBuffer, typename TString>
 static void convertTime(
-        OUStringBuffer& i_rBuffer,
+        TStringBuffer& i_rBuffer,
         const css::util::DateTime& i_rDateTime)
 {
     if (i_rDateTime.Hours   < 10) {
         i_rBuffer.append('0');
     }
-    i_rBuffer.append( 
OUString::number(static_cast<sal_Int32>(i_rDateTime.Hours)) + ":");
+    i_rBuffer.append( 
TString::number(static_cast<sal_Int32>(i_rDateTime.Hours)) + ":");
     if (i_rDateTime.Minutes < 10) {
         i_rBuffer.append('0');
     }
-    i_rBuffer.append( 
OUString::number(static_cast<sal_Int32>(i_rDateTime.Minutes) ) + ":");
+    i_rBuffer.append( 
TString::number(static_cast<sal_Int32>(i_rDateTime.Minutes) ) + ":");
     if (i_rDateTime.Seconds < 10) {
         i_rBuffer.append('0');
     }
@@ -1637,16 +1639,17 @@ static void convertTime(
     if (i_rDateTime.NanoSeconds > 0) {
         OSL_ENSURE(i_rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be 
more than 999 999 999");
         i_rBuffer.append('.');
-        std::ostringstream ostr;
-        ostr.fill('0');
-        ostr.width(9);
-        ostr << i_rDateTime.NanoSeconds;
-        i_rBuffer.appendAscii(ostr.str().c_str());
+        // pad to 9 characters length
+        auto sNanoStr = TString::number(i_rDateTime.NanoSeconds);
+        for (int i=0; i < 9 - sNanoStr.length; i++)
+            i_rBuffer.append('0');
+        i_rBuffer.append(sNanoStr);
     }
 }
 
+template<typename TStringBuffer>
 static void convertTimeZone(
-        OUStringBuffer& i_rBuffer,
+        TStringBuffer& i_rBuffer,
         const css::util::DateTime& i_rDateTime,
         sal_Int16 const* pTimeZoneOffset)
 {
@@ -1669,7 +1672,7 @@ void Converter::convertTimeOrDateTime(
         i_rDateTime.Month < 1 || i_rDateTime.Month > 12 ||
         i_rDateTime.Day < 1 || i_rDateTime.Day > 31)
     {
-        convertTime(i_rBuffer, i_rDateTime);
+        convertTime<OUStringBuffer, OUString>(i_rBuffer, i_rDateTime);
         convertTimeZone(i_rBuffer, i_rDateTime, nullptr);
     }
     else
@@ -1679,14 +1682,15 @@ void Converter::convertTimeOrDateTime(
 }
 
 /** convert util::DateTime to ISO "date" or "dateTime" string */
-void Converter::convertDateTime(
-        OUStringBuffer& i_rBuffer,
+template<typename TStringBuffer, typename TString, typename TStringChar>
+static void lcl_convertDateTime(
+        TStringBuffer& i_rBuffer,
         const css::util::DateTime& i_rDateTime,
         sal_Int16 const*const pTimeZoneOffset,
         bool i_bAddTimeIf0AM )
 {
-    const sal_Unicode dash('-');
-    const sal_Unicode zero('0');
+    const char dash('-');
+    const char zero('0');
 
     sal_Int32 const nYear(abs(i_rDateTime.Year));
     if (i_rDateTime.Year < 0) {
@@ -1701,11 +1705,11 @@ void Converter::convertDateTime(
     if (nYear < 10) {
         i_rBuffer.append(zero);
     }
-    i_rBuffer.append( OUString::number(nYear) + OUStringChar(dash) );
+    i_rBuffer.append( TString::number(nYear) + TStringChar(dash) );
     if( i_rDateTime.Month < 10 ) {
         i_rBuffer.append(zero);
     }
-    i_rBuffer.append( OUString::number(i_rDateTime.Month) + OUStringChar(dash) 
);
+    i_rBuffer.append( TString::number(i_rDateTime.Month) + TStringChar(dash) );
     if( i_rDateTime.Day   < 10 ) {
         i_rBuffer.append(zero);
     }
@@ -1717,12 +1721,32 @@ void Converter::convertDateTime(
         i_bAddTimeIf0AM )
     {
         i_rBuffer.append('T');
-        convertTime(i_rBuffer, i_rDateTime);
+        convertTime<TStringBuffer, TString>(i_rBuffer, i_rDateTime);
     }
 
     convertTimeZone(i_rBuffer, i_rDateTime, pTimeZoneOffset);
 }
 
+/** convert util::DateTime to ISO "date" or "dateTime" string */
+void Converter::convertDateTime(
+        OUStringBuffer& i_rBuffer,
+        const css::util::DateTime& i_rDateTime,
+        sal_Int16 const*const pTimeZoneOffset,
+        bool i_bAddTimeIf0AM )
+{
+    lcl_convertDateTime<OUStringBuffer, OUString, OUStringChar>(i_rBuffer, 
i_rDateTime, pTimeZoneOffset, i_bAddTimeIf0AM);
+}
+
+/** convert util::DateTime to ISO "date" or "dateTime" string */
+void Converter::convertDateTime(
+        OStringBuffer& i_rBuffer,
+        const css::util::DateTime& i_rDateTime,
+        sal_Int16 const*const pTimeZoneOffset,
+        bool i_bAddTimeIf0AM )
+{
+    lcl_convertDateTime<OStringBuffer, OString, OStringChar>(i_rBuffer, 
i_rDateTime, pTimeZoneOffset, i_bAddTimeIf0AM);
+}
+
 /** convert ISO "date" or "dateTime" string to util::DateTime */
 bool Converter::parseDateTime(   util::DateTime& rDateTime,
                                  std::u16string_view rString )

Reply via email to