This is an automated email from the ASF dual-hosted git repository.

stevedlawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 45fc21ab7 Tolerate sub-millisecond differences in TDML dateTime 
comparison
45fc21ab7 is described below

commit 45fc21ab73c1d22c83e22f66744de88aa12dfa15
Author: Guichard Desrosiers <[email protected]>
AuthorDate: Wed Jun 10 20:02:48 2026 -0400

    Tolerate sub-millisecond differences in TDML dateTime comparison
    
    ICU calendars are millisecond-precision, so fractional microseconds in
    xs:dateTime values are not preserved on parse. Truncate fractional seconds
    to milliseconds from the XMLGregorianCalendars before comparison, so
    sub-millisecond differences are ignored rather than flagged as mismatches.
    
    DAFFODIL-3086
---
 .../org/apache/daffodil/lib/xml/XMLUtils.scala     | 25 ++++++
 .../section23/dfdl_functions/Functions.tdml        | 89 +++++++++++++++++++++-
 .../dfdl_expressions/TestDFDLExpressions.scala     |  3 +
 3 files changed, 116 insertions(+), 1 deletion(-)

diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala 
b/daffodil-core/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
index 702da577e..219a2c509 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
@@ -19,6 +19,7 @@ package org.apache.daffodil.lib.xml
 
 import java.io.File
 import java.io.IOException
+import java.math.RoundingMode
 import java.net.URI
 import java.net.URISyntaxException
 import java.nio.charset.StandardCharsets
@@ -28,6 +29,7 @@ import java.nio.file.StandardOpenOption
 import javax.xml.XMLConstants
 import javax.xml.datatype.DatatypeConstants
 import javax.xml.datatype.DatatypeFactory
+import javax.xml.datatype.XMLGregorianCalendar
 import scala.annotation.tailrec
 import scala.collection.mutable
 import scala.collection.mutable.ArrayBuilder
@@ -1302,6 +1304,27 @@ Differences were (path, expected, actual):
     }
   }
 
+  /**
+   * Normalizes an XMLGregorianCalendar in place prior to comparison, so that
+   * values which are logically equal but differ in representation compare as
+   * equal. Mutates the calendar in place.
+   *
+   * Currently supported normalizations:
+   *   - Fractional seconds: truncated to millisecond precision (3 digits),
+   *     dropping sub-millisecond (microsecond) digits, since ICU calendars
+   *     are millisecond-precision.
+   *
+   * Additional normalizations may be added here as needed.
+   *
+   * @param cal the calendar to normalize in place
+   */
+  private def normalizeXmlCalendar(cal: XMLGregorianCalendar): Unit = {
+    val frac = cal.getFractionalSecond
+    if (frac != null) {
+      cal.setFractionalSecond(frac.setScale(3, RoundingMode.DOWN))
+    }
+  }
+
   /**
    * Compares two XSD date/time lexical strings (`xs:date`, `xs:time`, or
    * `xs:dateTime`) for value equality by parsing both into 
`XMLGregorianCalendar`
@@ -1323,6 +1346,8 @@ Differences were (path, expected, actual):
   private def dateTimeIsSame(dataA: String, dataB: String): Boolean = {
     val a = datatypeFactory.newXMLGregorianCalendar(dataA)
     val b = datatypeFactory.newXMLGregorianCalendar(dataB)
+    normalizeXmlCalendar(a)
+    normalizeXmlCalendar(b)
     a.compare(b) == DatatypeConstants.EQUAL
   }
 
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
index 659135850..77db13fb1 100644
--- 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
@@ -14735,7 +14735,94 @@
       </tdml:dfdlInfoset>
     </tdml:infoset>
   </tdml:parserTestCase>
-  
+
+
+  <tdml:defineSchema name="MicrosecondsDateTime">
+    <xs:include 
schemaLocation="/org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+    <dfdl:format ref="ex:GeneralFormat" representation="text" 
lengthKind="delimited" />
+
+    <xs:element name="timestampNano" type="xs:dateTime"
+                dfdl:calendarPatternKind="explicit"
+                dfdl:calendarPattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX" />
+  </tdml:defineSchema>
+
+  <!--
+     Test Name: datetimemicroseconds_01
+        Schema: MicrosecondsDateTime
+          Root: timestampNano
+       Purpose: Verifies the TDML runner ignores microsecond differences when
+                comparing xs:dateTime values. ICU calendars are
+                millisecond-precision, so microseconds are not preserved on
+                parse; the expected value here carries microsecond precision
+                that the parse drops. verifyParserTestData compares expected 
vs.
+                actual via dateTimeIsSame, which uses XMLGregorianCalendar.
+                XMLGregorianCalendar does not tolerate microsecond differences
+                by default, so dateTimeIsSame normalizes each calendar to
+                millisecond precision (truncating sub-millisecond digits) after
+                construction and before comparison. This satisfies 
DAFFODIL-3086.
+                Note: this value does not round-trip, since the dropped
+                microseconds cannot be reproduced on unparse.
+ -->
+  <tdml:parserTestCase name="datetimemicroseconds_01" root="timestampNano"
+                       model="MicrosecondsDateTime"
+                       description="TDML runner ignores fractional 
microseconds in dateTime comparison"
+                       roundTrip="none">
+    <tdml:document>2003-08-24T05:14:15.000-07:00</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:timestampNano>2003-08-24T05:14:15.000003-07:00</ex:timestampNano>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+    Test Name: datetimemicroseconds_02
+       Schema: MicrosecondsDateTime
+         Root: timestampNano
+      Purpose: Microsecond-precision xs:dateTime input parses and round-trips
+               under twoPass. ICU calendars are millisecond-precision, so the
+               sub-millisecond digits are not preserved: unparse emits the
+               millisecond value (differing from the original input, as twoPass
+               expects), and reparse yields the same infoset. Again, the TDML
+               Runner ignores the fractional-microsecond difference when 
comparing
+               via dateTimeIsSame which uses the XMLGregorianCalendar.
+-->
+  <tdml:parserTestCase name="datetimemicroseconds_02" root="timestampNano"
+                       model="MicrosecondsDateTime"
+                       description="Microsecond dateTime round-trips under 
twoPass;
+                       runner ignores fractional microseconds in comparison"
+                       roundTrip="twoPass">
+    <tdml:document>2003-08-24T05:14:15.000003-07:00</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:timestampNano>2003-08-24T05:14:15.000003-07:00</ex:timestampNano>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+    Test Name: datetimezeroMillis
+       Schema: MicrosecondsDateTime
+         Root: timestampNano
+      Purpose: A dateTime with all-zero milliseconds (.000) compares equal to
+               the same value with no fractional part. ICU renders zero
+               milliseconds as no fraction, so the TDML runner must treat
+               "...15.000-07:00" and "...15-07:00" as the same instant.
+               Verifies XMLGregorianCalendar treats .000 milliseconds and no
+               fractional part as the same instant; native behavior, 
independent
+               of fractional-second truncation.
+-->
+  <tdml:parserTestCase name="datetimezeroMillis" root="timestampNano"
+                       model="MicrosecondsDateTime"
+                       description="dateTime with .000 milliseconds compares 
equal to no fractional seconds">
+    <tdml:document>2003-08-24T05:14:15.000-07:00</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:timestampNano>2003-08-24T05:14:15-07:00</ex:timestampNano>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
   <!--
     Test Name: timezonefromdate_01
        Schema: Functions.dfdl.xsd
diff --git 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
index e0b118288..dead3d8de 100644
--- 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
+++ 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
@@ -781,6 +781,9 @@ class TestDFDLFunctions extends TdmlTests {
   @Test def secondsfromdatetime_03 = test
   @Test def timezonefromdatetime_01 = test
   @Test def timezonefromdatetime_02 = test
+  @Test def datetimemicroseconds_01 = test
+  @Test def datetimemicroseconds_02 = test
+  @Test def datetimezeroMillis = test
 
   @Test def xfromdatetime_01 = test
   @Test def xfromdatetime_02 = test

Reply via email to