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 c22dfe85c Replace ICU-based date/time comparison in TDML Runner with
XMLGregorianCalendar
c22dfe85c is described below
commit c22dfe85cee2d40bda655f426981b66682d946d6
Author: Guichard Desrosiers <[email protected]>
AuthorDate: Mon Jun 1 23:34:26 2026 -0400
Replace ICU-based date/time comparison in TDML Runner with
XMLGregorianCalendar
The TDML date/time comparison previously used the
DFDLDate/Time/DateTimeConversion
classes, which create ICU objects. This broke cross-testing against the IBM
DFDL
cross tester, which depends on an older ICU version (e.g. it lacks
Calendar.clone()).
Compare xs:date/time/dateTime values by parsing into XMLGregorianCalendar
and using
its XSD order relation (compare()), keeping ICU off the comparison path
entirely.
XMLGregorianCalendar implements XSD 1.0, which does not permit year zero,
so it
rejects values like "0000-01-01". Two tests whose data uses year zero
(yearfromdate_03 and yearfromdatetime_03) are temporarily ignored as a
result.
These expose a pre-existing Daffodil bug: Daffodil produces year-zero
values that
are invalid under XSD 1.0, which needs to be addressed separately. The
tests should
be re-enabled (or converted to negative tests) once that is resolved.
Broaden the catch in verifyParserTestData from XMLDifferenceException back
to
Exception, so exceptions thrown transitively by compareAndReport's callees
are
wrapped as TDMLExceptions. Restores the generic catch that was in place
before
commit b9fb1e53ba95, which narrowed it to XMLDifferenceException.
DAFFODIL-3077
---
.../org/apache/daffodil/lib/xml/XMLUtils.scala | 48 ++++++++++++++--------
.../org/apache/daffodil/tdml/TDMLRunner.scala | 5 ++-
.../dfdl_expressions/TestDFDLExpressions.scala | 6 ++-
3 files changed, 38 insertions(+), 21 deletions(-)
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 d2a817912..702da577e 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
@@ -26,6 +26,8 @@ import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import javax.xml.XMLConstants
+import javax.xml.datatype.DatatypeConstants
+import javax.xml.datatype.DatatypeFactory
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ArrayBuilder
@@ -33,9 +35,6 @@ import scala.math.abs
import scala.util.matching.Regex
import scala.xml.*
-import org.apache.daffodil.lib.calendar.DFDLDateConversion
-import org.apache.daffodil.lib.calendar.DFDLDateTimeConversion
-import org.apache.daffodil.lib.calendar.DFDLTimeConversion
import org.apache.daffodil.lib.exceptions.*
import org.apache.daffodil.lib.iapi.DaffodilSchemaSource
import org.apache.daffodil.lib.iapi.URISchemaSource
@@ -54,6 +53,9 @@ import org.xml.sax.XMLReader
object XMLUtils {
+ // DatatypeFactory creation is relatively expensive, so create it once and
reuse.
+ private lazy val datatypeFactory = DatatypeFactory.newInstance()
+
lazy val schemaForDFDLSchemas =
Misc.getRequiredResource("org/apache/daffodil/xsd/XMLSchema_for_DFDL.xsd")
@@ -1300,6 +1302,30 @@ Differences were (path, expected, actual):
}
}
+ /**
+ * Compares two XSD date/time lexical strings (`xs:date`, `xs:time`, or
+ * `xs:dateTime`) for value equality by parsing both into
`XMLGregorianCalendar`
+ * and comparing via the XSD `·order·` relation.
+ *
+ * Note that we intentionally do not use Daffodil's
DFDL*Conversion.fromXMLString
+ * classes which keeps ICU off the comparison path entirely and allows the
+ * IBM DFDL cross tester (pinned to an older ICU version) to share this code
without
+ * hitting newer-ICU-only methods (DAFFODIL-3077).
+ *
+ * @param dataA the first value's lexical string
+ * @param dataB the second value's lexical string
+ * @return true if the two values are equal under the XSD order relation
+ * @throws IllegalArgumentException if either string is not a valid lexical
+ * representation of an XSD 1.0 date/time.
+ *
+ * @throws NullPointerException if either string is null
+ */
+ private def dateTimeIsSame(dataA: String, dataB: String): Boolean = {
+ val a = datatypeFactory.newXMLGregorianCalendar(dataA)
+ val b = datatypeFactory.newXMLGregorianCalendar(dataB)
+ a.compare(b) == DatatypeConstants.EQUAL
+ }
+
/**
* Compares two strings of xml text, optionally using type information to
tolerate insignificant differences, and
* optionally using a tolerance amount for floating point comparison.
@@ -1326,20 +1352,8 @@ Differences were (path, expected, actual):
maybeType match {
case Some("xs:hexBinary") => dataA.equalsIgnoreCase(dataB)
- case Some("xs:date") => {
- val a = DFDLDateConversion.fromXMLString(dataA)
- val b = DFDLDateConversion.fromXMLString(dataB)
- a == b
- }
- case Some("xs:time") => {
- val a = DFDLTimeConversion.fromXMLString(dataA)
- val b = DFDLTimeConversion.fromXMLString(dataB)
- a == b
- }
- case Some("xs:dateTime") => {
- val a = DFDLDateTimeConversion.fromXMLString(dataA)
- val b = DFDLDateTimeConversion.fromXMLString(dataB)
- a == b
+ case Some("xs:date") | Some("xs:time") | Some("xs:dateTime") => {
+ dateTimeIsSame(dataA, dataB)
}
case Some("xs:double") => {
val a = strToDouble(dataA)
diff --git
a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
index adf842ce7..74443cd32 100644
--- a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
+++ b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
@@ -75,7 +75,6 @@ import org.apache.daffodil.lib.util.SchemaUtils
import org.apache.daffodil.lib.util.ThreadSafePool
import org.apache.daffodil.lib.xml.DaffodilXMLLoader
import org.apache.daffodil.lib.xml.XMLUtils
-import org.apache.daffodil.lib.xml.XMLUtils.XMLDifferenceException
import org.apache.daffodil.tdml.DiagnosticType.DiagnosticType
import org.apache.daffodil.tdml.processor.AbstractTDMLDFDLProcessorFactory
import org.apache.daffodil.tdml.processor.TDML
@@ -1789,7 +1788,9 @@ object VerifyTestCase {
try {
XMLUtils.compareAndReport(expected, actual)
} catch {
- case e: XMLDifferenceException =>
+ // compareAndReport can throw various exceptions transitively;
+ // catch broadly so all are wrapped as a TDMLException.
+ case e: Exception =>
throw TDMLException(e.getMessage, implString)
}
}
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 1f24cd24e..e0b118288 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
@@ -766,7 +766,8 @@ class TestDFDLFunctions extends TdmlTests {
@Test def yearfromdatetime_01 = test
@Test def yearfromdatetime_02 = test
- @Test def yearfromdatetime_03 = test
+ // DAFFODIL-3084
+ @Ignore @Test def yearfromdatetime_03 = test
@Test def monthfromdatetime_01 = test
@Test def monthfromdatetime_02 = test
@Test def dayfromdatetime_01 = test
@@ -788,7 +789,8 @@ class TestDFDLFunctions extends TdmlTests {
@Test def yearfromdate_01 = test
@Test def yearfromdate_02 = test
- @Test def yearfromdate_03 = test
+ // DAFFODIL-3084
+ @Ignore @Test def yearfromdate_03 = test
@Test def monthfromdate_01 = test
@Test def monthfromdate_02 = test
@Test def dayfromdate_01 = test