Blob Blame History Raw
From 4ac4c151af9f743203728792f9840c4bafbb9390 Mon Sep 17 00:00:00 2001
From: Michael Stahl <mstahl@redhat.com>
Date: Tue, 10 Dec 2013 15:14:00 +0100
Subject: [PATCH 056/109] sax, xmloff: fix ODF import/export of
 text:time/text:time-value

The value written for an Impress time field is something like
text:time-value="0000-00-00T23:28:07" (in LO 3.5+) or
text:time-value="0-00-00T23:28:07" (in OOo 3.3) which contains an
invalid all-zero date.  Such values are actually rejected by the
ODF import since commit ae3e2f170045a1525f67e9f3e9b7e03d94f2b56b.

Actually there was no real support to read the RelaxNG type
timeOrDateTime before.

So fix that by:
- adding convertTimeOrDateTime/parseTimeOrDateTime functions to
  sax::Converter
- recognizing and ignoring the 2 invalid all-zero values written by
  LO 3.5 and historic OOo respectively
- writing a bare "time" in text:time-value if the DateTime struct
  contains zero Date members
  (Older OOo versions and AOO cannot actually read that, but everything
  they _can_ read is invalid ODF...)

(cherry picked from commit cc407e50e8a1a74f9d1ed29d444dce9bd2e9167a)

The backport contains one change:
XMLTextFieldExport::ProcessTimeOrDateTime() still writes the invalid
value (to not add new backwards compat issues in stable branch),
so this patch only fixes the import side of things.

Conflicts:
	sax/source/tools/converter.cxx
	xmloff/source/text/txtfldi.cxx

Change-Id: I754076caee74a5163ed3f972af0f23796aa14f9f
Reviewed-on: https://gerrit.libreoffice.org/7026
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Eike Rathke <erack@redhat.com>
---
 include/sax/tools/converter.hxx   |   9 ++
 sax/qa/cppunit/test_converter.cxx | 138 +++++++++++++++++++++++-
 sax/source/tools/converter.cxx    | 213 ++++++++++++++++++++++++++++----------
 xmloff/inc/txtflde.hxx            |   6 ++
 xmloff/source/text/txtflde.cxx    |  24 ++++-
 xmloff/source/text/txtfldi.cxx    |   4 +-
 6 files changed, 331 insertions(+), 63 deletions(-)

diff --git a/include/sax/tools/converter.hxx b/include/sax/tools/converter.hxx
index 615308c..683fadb 100644
--- a/include/sax/tools/converter.hxx
+++ b/include/sax/tools/converter.hxx
@@ -160,10 +160,19 @@ public:
                                 const com::sun::star::util::DateTime& rDateTime,
                                    bool bAddTimeIf0AM = false );
 
+    /** convert util::DateTime to ISO "time" or "dateTime" string */
+    static void convertTimeOrDateTime(OUStringBuffer& rBuffer,
+                            const com::sun::star::util::DateTime& rDateTime,
+                            sal_Int16 const* pTimeZoneOffset);
+
     /** convert ISO "date" or "dateTime" string to util::DateTime */
     static bool convertDateTime( com::sun::star::util::DateTime& rDateTime,
                                  const OUString& rString );
 
+    /** convert ISO "time" or "dateTime" string to util::DateTime */
+    static bool parseTimeOrDateTime(com::sun::star::util::DateTime& rDateTime,
+                                 const OUString& rString);
+
     /** convert ISO "date" or "dateTime" string to util::DateTime or
         util::Date */
     static bool convertDateOrDateTime(
diff --git a/sax/qa/cppunit/test_converter.cxx b/sax/qa/cppunit/test_converter.cxx
index cfda248..f4b0c12 100644
--- a/sax/qa/cppunit/test_converter.cxx
+++ b/sax/qa/cppunit/test_converter.cxx
@@ -53,6 +53,7 @@ public:
 
     void testDuration();
     void testDateTime();
+    void testTime();
     void testDouble();
     void testMeasure();
     void testBool();
@@ -64,6 +65,7 @@ public:
     CPPUNIT_TEST_SUITE(ConverterTest);
     CPPUNIT_TEST(testDuration);
     CPPUNIT_TEST(testDateTime);
+    CPPUNIT_TEST(testTime);
     CPPUNIT_TEST(testDouble);
     CPPUNIT_TEST(testMeasure);
     CPPUNIT_TEST(testBool);
@@ -240,7 +242,7 @@ void ConverterTest::testDateTime()
     doTestDateTimeF( "0001-13-01T00:00:00" ); // invalid: M > 12
     doTestDateTimeF( "0001-01-32T00:00:00" ); // invalid: D > 31
     doTestDateTimeF( "0001-01-01T25:00:00" ); // invalid: H > 24
-    doTestDateTimeF( "0001-01-01T00:60:00" ); // invalid: H > 59
+    doTestDateTimeF( "0001-01-01T00:60:00" ); // invalid: M > 59
     doTestDateTimeF( "0001-01-01T00:00:60" ); // invalid: S > 59
     doTestDateTimeF( "0001-01-01T24:01:00" ); // invalid: H=24, but M != 0
     doTestDateTimeF( "0001-01-01T24:00:01" ); // invalid: H=24, but S != 0
@@ -251,9 +253,143 @@ void ConverterTest::testDateTime()
     doTestDateTimeF( "0001-01-02T00:00:00-14:01" ); // invalid: TZ < -14:00
     doTestDateTimeF( "2100-02-29T00:00:00-00:00" ); // invalid: no leap year
     doTestDateTimeF( "1900-02-29T00:00:00-00:00" ); // invalid: no leap year
+    doTestDateTimeF( "00:00:00" ); // invalid: no date
+    doTestDateTimeF( "T00:00:00" ); // invalid: no date
     SAL_INFO("sax.cppunit","\nSAX CONVERTER TEST END");
 }
 
+static void doTestTime(util::DateTime const & rdt, char const*const pis,
+        char const*const i_pos = 0)
+{
+    char const*const pos((i_pos) ? i_pos : pis);
+    OUString is(OUString::createFromAscii(pis));
+    util::DateTime odt;
+    SAL_INFO("sax.cppunit","about to convert '" << is << "'");
+    bool bSuccess( Converter::parseTimeOrDateTime(odt, is) );
+    SAL_INFO("sax.cppunit","Y:" << odt.Year << " M:" << odt.Month << " D:" << odt.Day << "  H:" << odt.Hours << " M:" << odt.Minutes << " S:" << odt.Seconds << " nS:" << odt.NanoSeconds << " UTC: " << (bool)odt.IsUTC);
+    CPPUNIT_ASSERT(bSuccess);
+    CPPUNIT_ASSERT(eqDateTime(rdt, odt));
+    OUStringBuffer buf;
+    Converter::convertTimeOrDateTime(buf, odt, 0);
+    SAL_INFO("sax.cppunit","" << buf.toString());
+    CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(pos),
+                         buf.makeStringAndClear());
+}
+
+static void doTestTimeF(char const*const pis)
+{
+    util::DateTime odt;
+    bool bSuccess = Converter::parseTimeOrDateTime(odt,
+            OUString::createFromAscii(pis));
+    SAL_INFO("sax.cppunit","Y:" << odt.Year << " M:" << odt.Month << " D:" << odt.Day << "  H:" << odt.Hours << "H M:" << odt.Minutes << " S:" << odt.Seconds << " nS:" << odt.NanoSeconds);
+    CPPUNIT_ASSERT(!bSuccess);
+}
+
+void ConverterTest::testTime() // time or dateTime + horrible backcompat mess
+{
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, false),
+            "0001-01-01T00:00:00" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, false),
+            "0001-01-01T00:00:00" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, true),
+            "0001-01-01T00:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, -1, false),
+            "-0001-01-01T00:00:00");
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, -1, true),
+            "-0001-01-01T01:00:00+01:00", "-0001-01-01T00:00:00Z");
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, -324, false),
+            "-0324-01-01T00:00:00" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, true),
+            "0001-01-01T00:00:00-00:00", "0001-01-01T00:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, true),
+            "0001-01-01T00:00:00+00:00", "0001-01-01T00:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 12, 2, 1, 1, true),
+            "0001-01-02T00:00:00-12:00", "0001-01-02T12:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 12, 1, 1, 1, true),
+            "0001-01-02T00:00:00+12:00", "0001-01-01T12:00:00Z"  );
+    doTestTime( util::DateTime(990000000, 59, 59, 23, 31, 12, 9999, false),
+            "9999-12-31T23:59:59.99",  "9999-12-31T23:59:59.990000000" );
+    doTestTime( util::DateTime(990000000, 59, 59, 23, 31, 12, 9999, true),
+            "9999-12-31T23:59:59.99Z", "9999-12-31T23:59:59.990000000Z" );
+    doTestTime( util::DateTime(999999999, 59, 59, 23, 31, 12, 9999, false),
+            "9999-12-31T23:59:59.9999999999999999999999999999999999999",
+            "9999-12-31T23:59:59.999999999" );
+    doTestTime( util::DateTime(999999999, 59, 59, 23, 31, 12, 9999, true),
+            "9999-12-31T23:59:59.9999999999999999999999999999999999999Z",
+            "9999-12-31T23:59:59.999999999Z" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 29, 2, 2000, true), // leap year
+            "2000-02-29T00:00:00-00:00", "2000-02-29T00:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 0, 29, 2, 1600, true), // leap year
+            "1600-02-29T00:00:00-00:00", "1600-02-29T00:00:00Z" );
+    doTestTime( util::DateTime(0, 0, 0, 24, 1, 1, 333, false)
+                /*(0, 0, 0, 0, 2, 1, 333)*/,
+            "0333-01-01T24:00:00"/*, "0333-01-02T00:00:00"*/ );
+    // While W3C XMLSchema specifies a minimum of 4 year digits we are lenient
+    // in what we accept.
+    doTestTime( util::DateTime(0, 0, 0, 0, 1, 1, 1, false),
+            "1-01-01T00:00:00", "0001-01-01T00:00:00" );
+
+    doTestTime( util::DateTime(0, 0, 0, 0, 0, 0, 0, false), "00:00:00" );
+    doTestTime( util::DateTime(0, 0, 0, 24, 0, 0, 0, false), "24:00:00" );
+    doTestTime( util::DateTime(0, 0, 59, 0, 0, 0, 0, false), "00:59:00" );
+    doTestTime( util::DateTime(0, 1, 2, 4, 0, 0, 0, true), "04:02:01Z" );
+    doTestTime( util::DateTime(0, 1, 2, 4, 0, 0, 0, true),
+            "05:02:01+01:00", "04:02:01Z" );
+    doTestTime( util::DateTime(0, 11, 12, 9, 0, 0, 0, true),
+            "05:12:11-04:00", "09:12:11Z" );
+    doTestTime( util::DateTime(990000000, 59, 59, 23, 0, 0, 0, false),
+            "23:59:59.99",  "23:59:59.990000000" );
+    doTestTime( util::DateTime(990000000, 59, 59, 23, 0, 0, 0, true),
+            "23:59:59.99Z",  "23:59:59.990000000Z" );
+    // backwards compatible: recognize invalid 0000-00-00 date (LO 3.5)
+    doTestTime( util::DateTime(0, 1, 0, 0, 0, 0, 0, false),
+            "0000-00-00T00:00:01", "00:00:01" );
+    // backwards compatible: recognize invalid 0-00-00 date (OOo)
+    doTestTime( util::DateTime(0, 0, 1, 0, 0, 0, 0, false),
+            "0-00-00T00:01:00", "00:01:00" );
+
+    doTestTimeF( "+0001-01-01T00:00:00" ); // invalid: ^+
+    doTestTimeF( "0001-1-01T00:00:00" ); // invalid: < 2 M
+    doTestTimeF( "0001-01-1T00:00:00" ); // invalid: < 2 D
+    doTestTimeF( "0001-01-01T0:00:00" ); // invalid: < 2 H
+    doTestTimeF( "0001-01-01T00:0:00" ); // invalid: < 2 M
+    doTestTimeF( "0001-01-01T00:00:0" ); // invalid: < 2 S
+    doTestTimeF( "0001-01-01T00:00:00." ); // invalid: .$
+    doTestTimeF( "0001-01-01T00:00:00+1:00" ); // invalid: < 2 TZ H
+    doTestTimeF( "0001-01-01T00:00:00+00:1" ); // invalid: < 2 TZ M
+    doTestTimeF( "0001-13-01T00:00:00" ); // invalid: M > 12
+    doTestTimeF( "0001-01-32T00:00:00" ); // invalid: D > 31
+    doTestTimeF( "0001-01-01T25:00:00" ); // invalid: H > 24
+    doTestTimeF( "0001-01-01T00:60:00" ); // invalid: M > 59
+    doTestTimeF( "0001-01-01T00:00:60" ); // invalid: S > 59
+    doTestTimeF( "0001-01-01T24:01:00" ); // invalid: H=24, but M != 0
+    doTestTimeF( "0001-01-01T24:00:01" ); // invalid: H=24, but S != 0
+    doTestTimeF( "0001-01-01T24:00:00.1" ); // invalid: H=24, but H != 0
+    doTestTimeF( "0001-01-02T00:00:00+15:00" ); // invalid: TZ > +14:00
+    doTestTimeF( "0001-01-02T00:00:00+14:01" ); // invalid: TZ > +14:00
+    doTestTimeF( "0001-01-02T00:00:00-15:00" ); // invalid: TZ < -14:00
+    doTestTimeF( "0001-01-02T00:00:00-14:01" ); // invalid: TZ < -14:00
+    doTestTimeF( "2100-02-29T00:00:00-00:00" ); // invalid: no leap year
+    doTestTimeF( "1900-02-29T00:00:00-00:00" ); // invalid: no leap year
+    doTestTimeF( "T00:00:00" ); // invalid: T
+    doTestTimeF( "0:00:00" ); // invalid: < 2 H
+    doTestTimeF( "00:0:00" ); // invalid: < 2 M
+    doTestTimeF( "00:00:0" ); // invalid: < 2 S
+    doTestTimeF( "00:00:00." ); // invalid: .$
+    doTestTimeF( "00:00:00+1:00" ); // invalid: < 2 TZ H
+    doTestTimeF( "00:00:00+00:1" ); // invalid: < 2 TZ M
+    doTestTimeF( "25:00:00" ); // invalid: H > 24
+    doTestTimeF( "00:60:00" ); // invalid: M > 59
+    doTestTimeF( "00:00:60" ); // invalid: S > 59
+    doTestTimeF( "24:01:00" ); // invalid: H=24, but M != 0
+    doTestTimeF( "24:00:01" ); // invalid: H=24, but S != 0
+    doTestTimeF( "24:00:00.1" ); // invalid: H=24, but H != 0
+    doTestTimeF( "00:00:00+15:00" ); // invalid: TZ > +14:00
+    doTestTimeF( "00:00:00+14:01" ); // invalid: TZ > +14:00
+    doTestTimeF( "00:00:00-15:00" ); // invalid: TZ < -14:00
+    doTestTimeF( "00:00:00-14:01" ); // invalid: TZ < -14:00
+}
+
 void doTestDouble(char const*const pis, double const rd,
         sal_Int16 const nSourceUnit, sal_Int16 const nTargetUnit)
 {
diff --git a/sax/source/tools/converter.cxx b/sax/source/tools/converter.cxx
index e99690e..bc8b0c1 100644
--- a/sax/source/tools/converter.cxx
+++ b/sax/source/tools/converter.cxx
@@ -1231,6 +1231,69 @@ void Converter::convertDate(
     convertDateTime(i_rBuffer, dt, false);
 }
 
+static void convertTime(
+        OUStringBuffer& i_rBuffer,
+        const com::sun::star::util::DateTime& i_rDateTime)
+{
+    if (i_rDateTime.Hours   < 10) {
+        i_rBuffer.append(sal_Unicode('0'));
+    }
+    i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Hours)   )
+             .append(sal_Unicode(':'));
+    if (i_rDateTime.Minutes < 10) {
+        i_rBuffer.append(sal_Unicode('0'));
+    }
+    i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Minutes) )
+             .append(sal_Unicode(':'));
+    if (i_rDateTime.Seconds < 10) {
+        i_rBuffer.append(sal_Unicode('0'));
+    }
+    i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Seconds) );
+    if (i_rDateTime.NanoSeconds > 0) {
+        OSL_ENSURE(i_rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
+        i_rBuffer.append(sal_Unicode('.'));
+        std::ostringstream ostr;
+        ostr.fill('0');
+        ostr.width(9);
+        ostr << i_rDateTime.NanoSeconds;
+        i_rBuffer.append(OUString::createFromAscii(ostr.str().c_str()));
+    }
+}
+
+static void convertTimeZone(
+        OUStringBuffer& i_rBuffer,
+        const com::sun::star::util::DateTime& i_rDateTime,
+        sal_Int16 const* pTimeZoneOffset)
+{
+    if (pTimeZoneOffset)
+    {
+        lcl_AppendTimezone(i_rBuffer, *pTimeZoneOffset);
+    }
+    else if (i_rDateTime.IsUTC)
+    {
+        lcl_AppendTimezone(i_rBuffer, 0);
+    }
+}
+
+/** convert util::DateTime to ISO "time" or "dateTime" string */
+void Converter::convertTimeOrDateTime(
+        OUStringBuffer& i_rBuffer,
+        const com::sun::star::util::DateTime& i_rDateTime,
+        sal_Int16 const* pTimeZoneOffset)
+{
+    if (i_rDateTime.Year == 0 ||
+        i_rDateTime.Month < 1 || i_rDateTime.Month > 12 ||
+        i_rDateTime.Day < 1 || i_rDateTime.Day > 31)
+    {
+        convertTime(i_rBuffer, i_rDateTime);
+        convertTimeZone(i_rBuffer, i_rDateTime, pTimeZoneOffset);
+    }
+    else
+    {
+        convertDateTime(i_rBuffer, i_rDateTime, true);
+    }
+}
+
 /** convert util::DateTime to ISO "date" or "dateTime" string */
 void Converter::convertDateTime(
         OUStringBuffer& i_rBuffer,
@@ -1238,10 +1301,7 @@ void Converter::convertDateTime(
         bool i_bAddTimeIf0AM )
 {
     const sal_Unicode dash('-');
-    const sal_Unicode col (':');
-    const sal_Unicode dot ('.');
     const sal_Unicode zero('0');
-    const sal_Unicode tee ('T');
 
     sal_Int32 const nYear(abs(i_rDateTime.Year));
     if (i_rDateTime.Year < 0) {
@@ -1271,42 +1331,11 @@ void Converter::convertDateTime(
         i_rDateTime.Hours   != 0 ||
         i_bAddTimeIf0AM )
     {
-        i_rBuffer.append(tee);
-        if( i_rDateTime.Hours   < 10 ) {
-            i_rBuffer.append(zero);
-        }
-        i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Hours)   )
-                 .append(col);
-        if( i_rDateTime.Minutes < 10 ) {
-            i_rBuffer.append(zero);
-        }
-        i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Minutes) )
-                 .append(col);
-        if( i_rDateTime.Seconds < 10 ) {
-            i_rBuffer.append(zero);
-        }
-        i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Seconds) );
-        if( i_rDateTime.NanoSeconds > 0 ) {
-            OSL_ENSURE(i_rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
-            i_rBuffer.append(dot);
-            std::ostringstream ostr;
-            ostr.fill('0');
-            ostr.width(9);
-            ostr << i_rDateTime.NanoSeconds;
-            i_rBuffer.append(OUString::createFromAscii(ostr.str().c_str()));
-        }
+        i_rBuffer.append(sal_Unicode('T'));
+        convertTime(i_rBuffer, i_rDateTime);
     }
 
-    sal_uInt16 * pTimezone(0); // FIXME pass this as parameter
-    if (pTimezone)
-    {
-        lcl_AppendTimezone(i_rBuffer, *pTimezone);
-    }
-    else if (i_rDateTime.IsUTC)
-    {
-        // append local time
-        lcl_AppendTimezone(i_rBuffer, 0);
-    }
+    convertTimeZone(i_rBuffer, i_rDateTime, 0);
 }
 
 /** convert ISO "date" or "dateTime" string to util::DateTime */
@@ -1379,11 +1408,17 @@ static void lcl_ConvertToUTC(
         {
             return;
         }
+        sal_Int16 nDayAdd(0);
         while (24 <= o_rHours)
         {
             o_rHours -= 24;
-            ++o_rDay;
+            ++nDayAdd;
         }
+        if (o_rDay == 0)
+        {
+            return; // handle time without date - don't adjust what isn't there
+        }
+        o_rDay += nDayAdd;
         sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(o_rMonth, o_rYear));
         if (o_rDay <= nDaysInMonth)
         {
@@ -1414,6 +1449,10 @@ static void lcl_ConvertToUTC(
             ++nDaySubtract;
         }
         o_rHours -= nOffsetHours;
+        if (o_rDay == 0)
+        {
+            return; // handle time without date - don't adjust what isn't there
+        }
         if (nDaySubtract < o_rDay)
         {
             o_rDay -= nDaySubtract;
@@ -1453,18 +1492,17 @@ readDateTimeComponent(const OUString & rString,
     return true;
 }
 
-
-
 /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
-bool Converter::convertDateOrDateTime(
-                util::Date & rDate, util::DateTime & rDateTime,
-                bool & rbDateTime, const OUString & rString )
+static bool lcl_parseDate(
+                bool & isNegative,
+                sal_Int32 & nYear, sal_Int32 & nMonth, sal_Int32 & nDay,
+                bool & bHaveTime,
+                sal_Int32 & nPos,
+                const OUString & string,
+                bool const bIgnoreInvalidOrMissingDate)
 {
     bool bSuccess = true;
-    bool isNegative(false);
 
-    const OUString string = rString.trim().toAsciiUpperCase();
-    sal_Int32 nPos(0);
     if (string.getLength() > nPos)
     {
         if (sal_Unicode('-') == string[nPos])
@@ -1474,13 +1512,15 @@ bool Converter::convertDateOrDateTime(
         }
     }
 
-    sal_Int32 nYear(0);
     {
         // While W3C XMLSchema specifies years with a minimum of 4 digits, be
         // leninent in what we accept for years < 1000. One digit is acceptable
         // if the remainders match.
         bSuccess = readDateTimeComponent(string, nPos, nYear, 1, false);
-        bSuccess &= (0 < nYear);
+        if (!bIgnoreInvalidOrMissingDate)
+        {
+            bSuccess &= (0 < nYear);
+        }
         bSuccess &= (nPos < string.getLength()); // not last token
     }
     if (bSuccess && (sal_Unicode('-') != string[nPos])) // separator
@@ -1492,11 +1532,14 @@ bool Converter::convertDateOrDateTime(
         ++nPos;
     }
 
-    sal_Int32 nMonth(0);
     if (bSuccess)
     {
         bSuccess = readDateTimeComponent(string, nPos, nMonth, 2, true);
-        bSuccess &= (0 < nMonth) && (nMonth <= 12);
+        if (!bIgnoreInvalidOrMissingDate)
+        {
+            bSuccess &= (0 < nMonth);
+        }
+        bSuccess &= (nMonth <= 12);
         bSuccess &= (nPos < string.getLength()); // not last token
     }
     if (bSuccess && (sal_Unicode('-') != string[nPos])) // separator
@@ -1508,14 +1551,16 @@ bool Converter::convertDateOrDateTime(
         ++nPos;
     }
 
-    sal_Int32 nDay(0);
     if (bSuccess)
     {
         bSuccess = readDateTimeComponent(string, nPos, nDay, 2, true);
-        bSuccess &= (0 < nDay) && (nDay <= lcl_MaxDaysPerMonth(nMonth, nYear));
+        if (!bIgnoreInvalidOrMissingDate)
+        {
+            bSuccess &= (0 < nDay);
+        }
+        bSuccess &= (nDay <= lcl_MaxDaysPerMonth(nMonth, nYear));
     }
 
-    bool bHaveTime(false);
     if (bSuccess && (nPos < string.getLength()))
     {
         if (sal_Unicode('T') == string[nPos]) // time separator
@@ -1525,6 +1570,40 @@ bool Converter::convertDateOrDateTime(
         }
     }
 
+    return bSuccess;
+}
+
+/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
+static bool lcl_parseDateTime(
+                util::Date *const pDate, util::DateTime & rDateTime,
+                bool & rbDateTime,
+                const OUString & rString,
+                bool const bIgnoreInvalidOrMissingDate)
+{
+    bool bSuccess = true;
+
+    const OUString string = rString.trim().toAsciiUpperCase();
+
+    bool isNegative(false);
+    sal_Int32 nYear(0);
+    sal_Int32 nMonth(0);
+    sal_Int32 nDay(0);
+    sal_Int32 nPos(0);
+    bool bHaveTime(false);
+
+    if (    !bIgnoreInvalidOrMissingDate
+        ||  string.indexOf(':') == -1  // no time?
+        ||  (string.indexOf('-') != -1
+             && string.indexOf('-') < string.indexOf(':')))
+    {
+        bSuccess &= lcl_parseDate(isNegative, nYear, nMonth, nDay,
+                bHaveTime, nPos, string, bIgnoreInvalidOrMissingDate);
+    }
+    else
+    {
+        bHaveTime = true;
+    }
+
     sal_Int32 nHours(0);
     sal_Int32 nMinutes(0);
     sal_Int32 nSeconds(0);
@@ -1658,7 +1737,7 @@ bool Converter::convertDateOrDateTime(
         sal_uInt16 * pTimezone(0); // FIXME pass this as parameter
         sal_Int16 const nTimezoneOffset = ((bHaveTimezoneMinus) ? (-1) : (+1))
                         * ((nTimezoneHours * 60) + nTimezoneMinutes);
-        if (bHaveTime) // time is optional
+        if (!pDate || bHaveTime) // time is optional
         {
             rDateTime.Year =
                 ((isNegative) ? (-1) : (+1)) * static_cast<sal_Int16>(nYear);
@@ -1691,10 +1770,10 @@ bool Converter::convertDateOrDateTime(
         }
         else
         {
-            rDate.Year =
+            pDate->Year =
                 ((isNegative) ? (-1) : (+1)) * static_cast<sal_Int16>(nYear);
-            rDate.Month = static_cast<sal_uInt16>(nMonth);
-            rDate.Day = static_cast<sal_uInt16>(nDay);
+            pDate->Month = static_cast<sal_uInt16>(nMonth);
+            pDate->Day = static_cast<sal_uInt16>(nDay);
             if (bHaveTimezone)
             {
                 if (pTimezone)
@@ -1713,6 +1792,26 @@ bool Converter::convertDateOrDateTime(
     return bSuccess;
 }
 
+/** convert ISO "time" or "dateTime" string to util::DateTime */
+bool Converter::parseTimeOrDateTime(
+                util::DateTime & rDateTime,
+                const OUString & rString)
+{
+    bool dummy;
+    return lcl_parseDateTime(
+                0, rDateTime, dummy, rString, true);
+}
+
+/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
+bool Converter::convertDateOrDateTime(
+                util::Date & rDate, util::DateTime & rDateTime,
+                bool & rbDateTime,
+                const OUString & rString )
+{
+    return lcl_parseDateTime(
+                &rDate, rDateTime, rbDateTime, rString, false);
+}
+
 
 /** gets the position of the first comma after npos in the string
     rStr. Commas inside '"' pairs are not matched */
diff --git a/xmloff/inc/txtflde.hxx b/xmloff/inc/txtflde.hxx
index 30e607b..53b84cf 100644
--- a/xmloff/inc/txtflde.hxx
+++ b/xmloff/inc/txtflde.hxx
@@ -366,6 +366,12 @@ protected:
         sal_Bool bIsDate,           /// export as date (rather than date/time)?
         sal_uInt16 nPrefix = XML_NAMESPACE_TEXT);   /// attribute name prefix
 
+    /// export time or dateTime
+    void ProcessTimeOrDateTime(
+        enum ::xmloff::token::XMLTokenEnum eXMLName,    /// attribute token
+        const ::com::sun::star::util::DateTime& rTime,  /// date/time value
+        sal_uInt16 nPrefix = XML_NAMESPACE_TEXT);   /// attribute name prefix
+
     /// export all attributes for bibliography data fields
     void ProcessBibliographyData(
         const ::com::sun::star::uno::Reference <
diff --git a/xmloff/source/text/txtflde.cxx b/xmloff/source/text/txtflde.cxx
index 7e013db..0ad8fcb 100644
--- a/xmloff/source/text/txtflde.cxx
+++ b/xmloff/source/text/txtflde.cxx
@@ -1261,17 +1261,17 @@ void XMLTextFieldExport::ExportFieldHelper(
         if (xPropSetInfo->hasPropertyByName(sPropertyDateTimeValue))
         {
             // no value -> current time
-            ProcessDateTime(XML_TIME_VALUE,
+            ProcessTimeOrDateTime(XML_TIME_VALUE,
                             GetDateTimeProperty(sPropertyDateTimeValue,
                                                 rPropSet),
-                            sal_False );
+                            XML_NAMESPACE_TEXT);
         }
         if (xPropSetInfo->hasPropertyByName(sPropertyDateTime))
         {
             // no value -> current time
-            ProcessDateTime(XML_TIME_VALUE,
+            ProcessTimeOrDateTime(XML_TIME_VALUE,
                             GetDateTimeProperty(sPropertyDateTime,rPropSet),
-                            sal_False );
+                            XML_NAMESPACE_TEXT);
         }
         if (xPropSetInfo->hasPropertyByName(sPropertyIsFixed))
         {
@@ -2674,6 +2674,22 @@ void XMLTextFieldExport::ProcessDateTime(enum XMLTokenEnum eName,
     }
 }
 
+/// export a time or dateTime
+void XMLTextFieldExport::ProcessTimeOrDateTime(enum XMLTokenEnum eName,
+                                         const util::DateTime& rTime,
+                                         sal_uInt16 nPrefix)
+{
+    OUStringBuffer aBuffer;
+
+    // date/time value
+//    ::sax::Converter::convertTimeOrDateTime(aBuffer, rTime, 0);
+// NOTE: for 4.1 continue writing the invalid value that old versions can read
+    ::sax::Converter::convertDateTime(aBuffer, rTime);
+
+    // output attribute
+    ProcessString(eName, aBuffer.makeStringAndClear(), sal_True, nPrefix);
+}
+
 
 SvXMLEnumMapEntry const aBibliographyDataTypeMap[] =
 {
diff --git a/xmloff/source/text/txtfldi.cxx b/xmloff/source/text/txtfldi.cxx
index cf6bc10..875677c 100644
--- a/xmloff/source/text/txtfldi.cxx
+++ b/xmloff/source/text/txtfldi.cxx
@@ -1106,6 +1106,7 @@ void XMLTimeFieldImportContext::ProcessAttribute(
     {
         case XML_TOK_TEXTFIELD_TIME_VALUE:
         {
+            // FIXME double appears unused?
             double fTmp;
             if (GetImport().GetMM100UnitConverter().
                 convertDateTime(fTmp, sAttrValue))
@@ -1114,7 +1115,8 @@ void XMLTimeFieldImportContext::ProcessAttribute(
                 bTimeOK = sal_True;
             }
 
-            if (::sax::Converter::convertDateTime(aDateTimeValue, sAttrValue))
+            if (::sax::Converter::parseTimeOrDateTime(aDateTimeValue,
+                        sAttrValue))
             {
                 bTimeOK = sal_True;
             }
-- 
1.8.4.2