diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java
index 7f4a478e5..fa429c4b5 100644
--- a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java
+++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java
@@ -101,15 +101,20 @@ public static void validateValue(GDateSpecification v, SchemaType sType, Validat
context.invalid(XmlErrorCodes.DATE, new Object[]{"Date (" + v + ") does not have the set of fields required for " + QNameHelper.readable(sType)});
}
+ // compareToGDate returns 2 for an incomparable pair, which satisfies no bound
+ int c;
+
if ((x = sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE)) != null) {
- if (v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()) <= 0) {
+ c = v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue());
+ if (c <= 0 || c == 2) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_EXCLUSIVE_VALID,
new Object[]{"date", v, g, QNameHelper.readable(sType)});
}
}
if ((x = sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE)) != null) {
- if (v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()) < 0) {
+ c = v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue());
+ if (c < 0 || c == 2) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_INCLUSIVE_VALID,
new Object[]{"date", v, g, QNameHelper.readable(sType)});
}
diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java
index 9612dd2ab..875fc3dc7 100644
--- a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java
+++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java
@@ -102,15 +102,20 @@ public static void validateValue(GDurationSpecification v, SchemaType sType, Val
XmlObject x;
GDuration g;
+ // compareToGDuration returns 2 for an incomparable pair, which satisfies no bound
+ int c;
+
if ((x = sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE)) != null) {
- if (v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()) <= 0) {
+ c = v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue());
+ if (c <= 0 || c == 2) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_EXCLUSIVE_VALID,
new Object[]{"duration", v, g, QNameHelper.readable(sType)});
}
}
if ((x = sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE)) != null) {
- if (v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()) < 0) {
+ c = v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue());
+ if (c < 0 || c == 2) {
context.invalid(XmlErrorCodes.DATATYPE_MIN_INCLUSIVE_VALID,
new Object[]{"duration", v, g, QNameHelper.readable(sType)});
}
diff --git a/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java b/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java
new file mode 100644
index 000000000..75deb5713
--- /dev/null
+++ b/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java
@@ -0,0 +1,115 @@
+/* Copyright 2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package misc.checkin;
+
+import org.apache.xmlbeans.SchemaTypeLoader;
+import org.apache.xmlbeans.XmlBeans;
+import org.apache.xmlbeans.XmlError;
+import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlOptions;
+import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class IncomparableMinFacetValidateTest {
+
+ // dateTime and duration are only partially ordered. A dateTime without a
+ // timezone is incomparable with one that has a timezone when the two are
+ // within 14 hours of each other, and P1M is incomparable with P30D because a
+ // month is 28 to 31 days long.
+ private static final String DT_BOUND = "2000-01-01T12:00:00Z";
+ private static final String DT_INCOMPARABLE = "2000-01-01T12:00:00";
+ private static final String DUR_BOUND = "P30D";
+ private static final String DUR_INCOMPARABLE = "P1M";
+
+ private static final String[] FACETS = {
+ "minInclusive", "minExclusive", "maxInclusive", "maxExclusive"
+ };
+
+ private static String element(String name, String base, String facet, String bound) {
+ return
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " ";
+ }
+
+ private static String xsd() {
+ StringBuilder sb = new StringBuilder(
+ "");
+ for (String facet : FACETS) {
+ sb.append(element("dt-" + facet, "dateTime", facet, DT_BOUND));
+ sb.append(element("dur-" + facet, "duration", facet, DUR_BOUND));
+ }
+ return sb.append("").toString();
+ }
+
+ private static boolean validate(String element, String value) throws Exception {
+ SchemaTypeLoader loader = XmlBeans.loadXsd(new XmlObject[]{SchemaDocument.Factory.parse(xsd())});
+ XmlObject doc = loader.parse(
+ "" + value + "", null, null);
+ List errors = new ArrayList<>();
+ return doc.validate(new XmlOptions().setErrorListener(errors));
+ }
+
+ @Test
+ void incomparableDateTimeFailsEveryBound() throws Exception {
+ // not greater than, less than, or equal to the bound, so no facet is satisfied
+ for (String facet : FACETS) {
+ assertFalse(validate("dt-" + facet, DT_INCOMPARABLE), facet);
+ }
+ }
+
+ @Test
+ void incomparableDurationFailsEveryBound() throws Exception {
+ for (String facet : FACETS) {
+ assertFalse(validate("dur-" + facet, DUR_INCOMPARABLE), facet);
+ }
+ }
+
+ @Test
+ void comparableDateTimeStillChecked() throws Exception {
+ // equal to the bound
+ assertTrue(validate("dt-minInclusive", "2000-01-01T12:00:00Z"));
+ assertFalse(validate("dt-minExclusive", "2000-01-01T12:00:00Z"));
+ // more than 14 hours past the bound, so the missing timezone does not matter
+ assertTrue(validate("dt-minInclusive", "2000-01-03T12:00:00"));
+ assertTrue(validate("dt-minExclusive", "2000-01-03T12:00:00"));
+ // more than 14 hours short of it
+ assertFalse(validate("dt-minInclusive", "1999-12-30T12:00:00"));
+ assertFalse(validate("dt-minExclusive", "1999-12-30T12:00:00"));
+ }
+
+ @Test
+ void comparableDurationStillChecked() throws Exception {
+ assertTrue(validate("dur-minInclusive", "P30D"));
+ assertFalse(validate("dur-minExclusive", "P30D"));
+ // two months are at least 59 days
+ assertTrue(validate("dur-minInclusive", "P2M"));
+ assertTrue(validate("dur-minExclusive", "P2M"));
+ assertFalse(validate("dur-minInclusive", "P27D"));
+ assertFalse(validate("dur-minExclusive", "P27D"));
+ }
+}