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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit d6f35024a6e01bb0e11549e1f6ce2ce93d785197
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Jan 25 00:27:43 2026 +0100

    Simplify `ComparisonFilter` by delegating to `TimeMethods` when the 
operands are temporal.
    It will also enable optimization in future commits.
---
 .../org/apache/sis/filter/ComparisonFilter.java    | 342 +++------------
 .../main/org/apache/sis/filter/TemporalFilter.java |   2 +-
 .../org/apache/sis/filter/TemporalOperation.java   | 129 +++---
 .../org/apache/sis/temporal/DefaultInstant.java    |   8 +-
 .../main/org/apache/sis/temporal/TimeMethods.java  | 468 ++++++++++++++++++---
 .../apache/sis/storage/base/MetadataBuilder.java   |   4 +-
 6 files changed, 542 insertions(+), 411 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
index 3c48ac4b32..50da8c794e 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
@@ -18,26 +18,14 @@ package org.apache.sis.filter;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.DateTimeException;
 import java.util.List;
 import java.util.Collection;
-import java.util.Date;
-import java.util.Calendar;
 import java.util.Objects;
-import java.time.Instant;
-import java.time.LocalTime;
-import java.time.OffsetTime;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
-import java.time.ZoneId;
-import java.time.chrono.ChronoLocalDate;
-import java.time.chrono.ChronoLocalDateTime;
-import java.time.chrono.ChronoZonedDateTime;
-import java.time.temporal.ChronoField;
-import java.time.temporal.Temporal;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.filter.base.Node;
 import org.apache.sis.filter.base.BinaryFunctionWidening;
+import org.apache.sis.temporal.TimeMethods;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.filter.Filter;
@@ -203,7 +191,9 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
                             pass = evaluate(left, element);
                         }
                         switch (matchAction) {
-                            default: return false;              // Unknown 
enumeration.
+                            default: {
+                                return false;                   // Unknown 
enumeration.
+                            }
                             case ALL: {
                                 if (!pass) return false;
                                 match = true;                   // Remember 
that we have at least 1 value.
@@ -230,6 +220,7 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
 
     /**
      * Compares the given objects. If both values are numerical, then this 
method delegates to an {@code applyAs…} method.
+     * If both values are temporal, then this method delegates to {@link 
TimeMethods} with runtime detection of the type.
      * For other kind of objects, this method delegates to a {@code 
compare(…)} method. If the two objects are not of the
      * same type, then the less accurate one is converted to the most accurate 
type if possible.
      *
@@ -248,71 +239,12 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
             final Number r = apply((Number) left, (Number) right);
             if (r != null) return r.intValue() != 0;
         }
-        /*
-         * For legacy java.util.Date, the compareTo(…) method is consistent 
only for dates of the same class.
-         * Otherwise A.compareTo(B) and B.compareTo(A) are inconsistent if one 
object is a java.util.Date and
-         * the other object is a java.sql.Timestamp. In such case, we compare 
the dates as java.time objects.
-         */
-        if (left instanceof Date && right instanceof Date) {
-            if (left.getClass() == right.getClass()) {
-                return fromCompareTo(((Date) left).compareTo((Date) right));
-            }
-            left  = fromLegacy((Date) left);
-            right = fromLegacy((Date) right);
-        }
-        /*
-         * Temporal objects have complex conversion rules. We take Instant as 
the most accurate and unambiguous type.
-         * So if at least one value is an Instant, try to unconditionally 
promote the other value to an Instant too.
-         * This conversion will fail if the other object has some undefined 
fields; for example java.sql.Date has no
-         * time fields (we do not assume that the values of those fields are 
zero).
-         *
-         * OffsetTime and OffsetDateTime are final classes that do not 
implement a java.time.chrono interface.
-         * Note that OffsetDateTime is convertible into OffsetTime by dropping 
the date fields, but we do not
-         * (for now) perform comparisons that would ignore the date fields of 
an operand.
-         */
-        if (left instanceof Temporal || right instanceof Temporal) {        // 
Use || because an operand may be Date.
-            if (left instanceof Instant) {
-                final Instant t = toInstant(right);
-                if (t != null) return fromCompareTo(((Instant) 
left).compareTo(t));
-            } else if (right instanceof Instant) {
-                final Instant t = toInstant(left);
-                if (t != null) return fromCompareTo(t.compareTo((Instant) 
right));
-            } else if (left instanceof OffsetDateTime) {
-                final OffsetDateTime t = toOffsetDateTime(right);
-                if (t != null) return compare((OffsetDateTime) left, t);
-            } else if (right instanceof OffsetDateTime) {
-                final OffsetDateTime t = toOffsetDateTime(left);
-                if (t != null) return compare(t, (OffsetDateTime) right);
-            } else if (left instanceof OffsetTime && right instanceof 
OffsetTime) {
-                return compare((OffsetTime) left, (OffsetTime) right);
-            }
-            /*
-             * Comparisons of temporal objects implementing java.time.chrono 
interfaces. We need to check the most
-             * complete types first. If the type are different, we reduce to 
the type of the less smallest operand.
-             * For example if an operand is a date+time and the other operand 
is only a date, then the time fields
-             * will be ignored and a warning will be reported.
-             */
-            if (left instanceof ChronoLocalDateTime<?>) {
-                final ChronoLocalDateTime<?> t = toLocalDateTime(right);
-                if (t != null) return compare((ChronoLocalDateTime<?>) left, 
t);
-            } else if (right instanceof ChronoLocalDateTime<?>) {
-                final ChronoLocalDateTime<?> t = toLocalDateTime(left);
-                if (t != null) return compare(t, (ChronoLocalDateTime<?>) 
right);
-            }
-            if (left instanceof ChronoLocalDate) {
-                final ChronoLocalDate t = toLocalDate(right);
-                if (t != null) return compare((ChronoLocalDate) left, t);
-            } else if (right instanceof ChronoLocalDate) {
-                final ChronoLocalDate t = toLocalDate(left);
-                if (t != null) return compare(t, (ChronoLocalDate) right);
-            }
-            if (left instanceof LocalTime) {
-                final LocalTime t = toLocalTime(right);
-                if (t != null) return fromCompareTo(((LocalTime) 
left).compareTo(t));
-            } else if (right instanceof LocalTime) {
-                final LocalTime t = toLocalTime(left);
-                if (t != null) return fromCompareTo(t.compareTo((LocalTime) 
right));
-            }
+        try {
+            Boolean t = TimeMethods.compareIfTemporal(temporalTest(), left, 
right);
+            if (t != null) return t;
+        } catch (DateTimeException e) {
+            warning(e);
+            return false;
         }
         /*
          * Test character strings only after all specialized types have been 
tested. The intent is that if an
@@ -345,148 +277,7 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
     }
 
     /**
-     * Converts a legacy {@code Date} object to an object from the {@link 
java.time} package.
-     * We performs this conversion before to compare to {@code Date} instances 
that are not of
-     * the same class, because the {@link Date#compareTo(Date)} method in such 
case is not well
-     * defined.
-     */
-    private static Temporal fromLegacy(final Date value) {
-        if (value instanceof java.sql.Timestamp) {
-            return ((java.sql.Timestamp) value).toLocalDateTime();
-        } else if (value instanceof java.sql.Date) {
-            return ((java.sql.Date) value).toLocalDate();
-        } else if (value instanceof java.sql.Time) {
-            return ((java.sql.Time) value).toLocalTime();
-        } else {
-            // Implementation of above toFoo() methods use system default time 
zone.
-            return LocalDateTime.ofInstant(value.toInstant(), 
ZoneId.systemDefault());
-        }
-    }
-
-    /**
-     * Converts the given object to an {@link Instant}, or returns {@code 
null} if unconvertible.
-     * This method handles a few types from the {@link java.time} package and 
legacy types like
-     * {@link Date} (with a special case for SQL dates) and {@link Calendar}.
-     */
-    private static Instant toInstant(final Object value) {
-        if (value instanceof Instant) {
-            return (Instant) value;
-        } else if (value instanceof OffsetDateTime) {
-            return ((OffsetDateTime) value).toInstant();
-        } else if (value instanceof ChronoZonedDateTime) {
-            return ((ChronoZonedDateTime) value).toInstant();
-        } else if (value instanceof Date) {
-            try {
-                return ((Date) value).toInstant();
-            } catch (UnsupportedOperationException e) {
-                /*
-                 * java.sql.Date and java.sql.Time cannot be converted to 
Instant because a part
-                 * of their coordinates on the timeline is undefined.  For 
example in the case of
-                 * java.sql.Date the hours, minutes and seconds are 
unspecified (which is not the
-                 * same thing as assuming that those values are zero).
-                 */
-            }
-        } else if (value instanceof Calendar) {
-            return ((Calendar) value).toInstant();
-        }
-        return null;
-    }
-
-    /**
-     * Converts the given object to an {@link OffsetDateTime}, or returns 
{@code null} if unconvertible.
-     */
-    private static OffsetDateTime toOffsetDateTime(final Object value) {
-        if (value instanceof OffsetDateTime) {
-            return (OffsetDateTime) value;
-        } else if (value instanceof ZonedDateTime) {
-            return ((ZonedDateTime) value).toOffsetDateTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link ChronoLocalDateTime}, or returns 
{@code null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Timestamp} 
objects.
-     * Conversion may lost timezone information.
-     */
-    private static ChronoLocalDateTime<?> toLocalDateTime(final Object value) {
-        if (value instanceof ChronoLocalDateTime<?>) {
-            return (ChronoLocalDateTime<?>) value;
-        } else if (value instanceof ChronoZonedDateTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((ChronoZonedDateTime) value).toLocalDateTime();
-        } else if (value instanceof OffsetDateTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((OffsetDateTime) value).toLocalDateTime();
-        } else if (value instanceof java.sql.Timestamp) {
-            return ((java.sql.Timestamp) value).toLocalDateTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link ChronoLocalDate}, or returns 
{@code null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Date} 
objects.
-     * Conversion may lost timezone information and time fields.
-     */
-    private static ChronoLocalDate toLocalDate(final Object value) {
-        if (value instanceof ChronoLocalDate) {
-            return (ChronoLocalDate) value;
-        } else if (value instanceof ChronoLocalDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((ChronoLocalDateTime) value).toLocalDate();
-        } else if (value instanceof ChronoZonedDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((ChronoZonedDateTime) value).toLocalDate();
-        } else if (value instanceof OffsetDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((OffsetDateTime) value).toLocalDate();
-        } else if (value instanceof java.sql.Date) {
-            return ((java.sql.Date) value).toLocalDate();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link LocalTime}, or returns {@code 
null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Time} 
objects.
-     * Conversion may lost timezone information.
-     */
-    private static LocalTime toLocalTime(final Object value) {
-        if (value instanceof LocalTime) {
-            return (LocalTime) value;
-        } else if (value instanceof OffsetTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((OffsetTime) value).toLocalTime();
-        } else if (value instanceof java.sql.Time) {
-            return ((java.sql.Time) value).toLocalTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Invoked when a conversion cause a field to be ignored. For example if a 
"date+time" object is compared
-     * with a "date" object, the "time" field is ignored. Expected values are:
-     *
-     * <ul>
-     *   <li>{@link ChronoField#OFFSET_SECONDS}: time zone is ignored.</li>
-     *   <li>{@link ChronoField#SECOND_OF_DAY}:  time of dat and time zone are 
ignored.</li>
-     * </ul>
-     *
-     * @param  field  the field which is ignored.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-460";>SIS-460</a>
-     */
-    private static void ignoringField(final ChronoField field) {
-        // TODO
-    }
-
-    /**
-     * Converts the boolean result as an integer for use as a return value of 
the {@code applyAs…} methods.
+     * Converts the Boolean result as an integer for use as a return value of 
the {@code applyAs…} methods.
      * This is a helper class for subclasses.
      */
     private static Number number(final boolean result) {
@@ -499,37 +290,14 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
     protected abstract boolean fromCompareTo(int result);
 
     /**
-     * Compares two times with time-zone information. Implementations shall 
not use {@code compareTo(…)} because
-     * that method compares more information than desired in order to ensure 
consistency with {@code equals(…)}.
-     */
-    protected abstract boolean compare(OffsetTime left, OffsetTime right);
-
-    /**
-     * Compares two dates with time-zone information. Implementations shall 
not use {@code compareTo(…)} because
-     * that method compares more information than desired in order to ensure 
consistency with {@code equals(…)}.
-     */
-    protected abstract boolean compare(OffsetDateTime left, OffsetDateTime 
right);
-
-    /**
-     * Compares two dates without time-of-day and time-zone information. 
Implementations shall not use
-     * {@code compareTo(…)} because that method also compares chronology, 
which is not desired for the
-     * purpose of "is before" or "is after" comparison functions.
-     */
-    protected abstract boolean compare(ChronoLocalDate left, ChronoLocalDate 
right);
-
-    /**
-     * Compares two dates without time-zone information. Implementations shall 
not use {@code compareTo(…)}
-     * because that method also compares chronology, which is not desired for 
the purpose of "is before" or
-     * "is after" comparison functions.
-     */
-    protected abstract boolean compare(ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right);
-
-    /**
-     * Compares two dates with time-zone information. Implementations shall 
not use {@code compareTo(…)}
-     * because that method also compares chronology, which is not desired for 
the purpose of "is before"
+     * Returns an identification of the test to use if the operands are 
temporal.
+     * We do not use {@code compareTo(…)} for temporal objects because that 
method
+     * also compares chronology, which is not desired for the purpose of "is 
before"
      * or "is after" comparison functions.
+     *
+     * @return identification of the test to apply on temporal objects.
      */
-    protected abstract boolean compare(ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right);
+    protected abstract TimeMethods.Test temporalTest();
 
     /** Delegates to {@link BigDecimal#compareTo(BigDecimal)} and interprets 
the result with {@link #fromCompareTo(int)}. */
     @Override protected final Number applyAsDecimal (BigDecimal left, 
BigDecimal right) {return number(fromCompareTo(left.compareTo(right)));}
@@ -569,13 +337,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result < 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left < right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left < right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return left.isBefore(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return left.isBefore(right);}
+        @Override protected Number  applyAsDouble(double left, double right) 
{return number(left < right);}
+        @Override protected Number  applyAsLong  (long   left, long   right) 
{return number(left < right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.BEFORE;}
     }
 
 
@@ -611,13 +377,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result <= 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left <= right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left <= right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return !left.isAfter(right);}
+        @Override protected Number  applyAsDouble(double left, double right) 
{return number(left <= right);}
+        @Override protected Number  applyAsLong  (long   left, long   right) 
{return number(left <= right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.NOT_AFTER;}
     }
 
 
@@ -653,13 +417,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result > 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left > right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left > right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return left.isAfter(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return left.isAfter(right);}
+        @Override protected Number  applyAsDouble(double left, double right) 
{return number(left > right);}
+        @Override protected Number  applyAsLong  (long   left, long   right) 
{return number(left > right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.AFTER;}
     }
 
 
@@ -695,13 +457,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result >= 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left >= right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left >= right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return !left.isBefore(right);}
+        @Override protected Number  applyAsDouble(double left, double right) 
{return number(left >= right);}
+        @Override protected Number  applyAsLong  (long   left, long   right) 
{return number(left >= right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.NOT_BEFORE;}
     }
 
 
@@ -737,13 +497,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result == 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left == right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left == right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return left.isEqual(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return left.isEqual(right);}
+        @Override protected Number  applyAsDouble(double left, double right) 
{return number(left == right);}
+        @Override protected Number  applyAsLong  (long   left, long   right) 
{return number(left == right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.EQUAL;}
     }
 
 
@@ -779,13 +537,11 @@ abstract class ComparisonFilter<R> extends 
BinaryFunctionWidening<R, Object, Obj
         @Override protected boolean fromCompareTo(final int result) {return 
result != 0;}
 
         /** Performs the comparison and returns the result as 0 (false) or 1 
(true). */
-        @Override protected Number  applyAsDouble(double                 left, 
double                 right) {return number(left != right);}
-        @Override protected Number  applyAsLong  (long                   left, 
long                   right) {return number(left != right);}
-        @Override protected boolean compare      (OffsetTime             left, 
OffsetTime             right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, 
OffsetDateTime         right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, 
ChronoLocalDate        right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, 
ChronoLocalDateTime<?> right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, 
ChronoZonedDateTime<?> right) {return !left.isEqual(right);}
+        @Override protected Number  applyAsDouble(double  left, double right) 
{return number(left != right);}
+        @Override protected Number  applyAsLong  (long    left, long   right) 
{return number(left != right);}
+
+        /** For comparisons of temporal objects. */
+        @Override protected TimeMethods.Test temporalTest() {return 
TimeMethods.Test.NOT_EQUAL;}
     }
 
 
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
index c32399731f..c696cb2815 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
@@ -35,7 +35,7 @@ import org.opengis.filter.InvalidFilterValueException;
 /**
  * Temporal operations between a period and a Java temporal object or between 
two periods.
  * The base class represents the general case when we don't know which 
operands are periods.
- * The subclasses represent specializations when the type of temporal values 
is known in advance.
+ * The subclasses provide specializations when the types of temporal values 
are known in advance.
  *
  * <p>In the context of this class, "instant" can be understood as 
<abbr>ISO</abbr> 19108 instant
  * or as the various {@link java.time} objects, not restricted to {@link 
java.time.Instant}.</p>
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
index 55ba782f6b..143560c721 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
@@ -23,9 +23,6 @@ import java.time.temporal.Temporal;
 import org.apache.sis.util.internal.shared.Strings;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.temporal.TimeMethods;
-import static org.apache.sis.temporal.TimeMethods.BEFORE;
-import static org.apache.sis.temporal.TimeMethods.AFTER;
-import static org.apache.sis.temporal.TimeMethods.EQUAL;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.temporal.Period;
@@ -180,23 +177,26 @@ abstract class TemporalOperation<T> implements 
Serializable {
      * Returns {@code true} if {@code other} is non-null and the specified 
comparison evaluates to {@code true}.
      * This is a helper function for {@code evaluate(…)} methods 
implementations.
      *
-     * @param  test   enumeration value such as {@link TimeMethods#BEFORE} or 
{@link TimeMethods#AFTER}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}.
      * @param  other  the argument to give to the test method call, or {@code 
null} if none.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
-    protected final boolean compare(final int test, final T self, final 
Instant other) {
+    protected final boolean compare(final TimeMethods.Test test, final T self, 
final Instant other) {
         if (other != null) {
             final Temporal position;
             final Optional<IndeterminateValue> p = 
other.getIndeterminatePosition();
             if (p.isPresent()) {
-                if (p.get() == IndeterminateValue.NOW) {
+                final IndeterminateValue v = p.get();
+                if (v == IndeterminateValue.NOW) {
                     position = comparators.now();
                 } else {
                     switch (test) {
-                        case BEFORE: if (p.get() != IndeterminateValue.AFTER)  
return false; else break;
-                        case AFTER:  if (p.get() != IndeterminateValue.BEFORE) 
return false; else break;
+                        case BEFORE:     if (v != IndeterminateValue.AFTER)  
return false; else break;
+                        case AFTER:      if (v != IndeterminateValue.BEFORE) 
return false; else break;
+                        case NOT_BEFORE: if (v == IndeterminateValue.BEFORE) 
return false; else break;
+                        case NOT_AFTER:  if (v == IndeterminateValue.AFTER)  
return false; else break;
                         default: return false;
                     }
                     position = other.getPosition();
@@ -215,41 +215,42 @@ abstract class TemporalOperation<T> implements 
Serializable {
      * Returns {@code true} if both arguments are non-null and the specified 
comparison evaluates to {@code true}.
      * This is a helper function for {@code evaluate(…)} methods 
implementations.
      *
-     * @param  test   enumeration value such as {@link TimeMethods#BEFORE} or 
{@link TimeMethods#AFTER}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}, or {@code null} if none.
      * @param  other  the argument to give to the test method call, or {@code 
null} if none.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
-    @SuppressWarnings("unchecked")
-    protected static boolean compare(final int test, final Instant self, final 
Instant other) {
+    protected static boolean compare(final TimeMethods.Test test, final 
Instant self, final Instant other) {
         if (self == null || other == null) {
             return false;
         }
-        final IndeterminateValue p1 = 
self.getIndeterminatePosition().orElse(null);
+        final IndeterminateValue p1 = self 
.getIndeterminatePosition().orElse(null);
         final IndeterminateValue p2 = 
other.getIndeterminatePosition().orElse(null);
         if (p1 != null || p2 != null) {
-            if (p1 == p2) {
-                return (test == EQUAL) && (p1 == IndeterminateValue.NOW);
-            }
+            final IndeterminateValue a1, a2;    // Values which would create 
ambiguities.
             switch (test) {
-                case BEFORE: if (isAmbiguous(p1, IndeterminateValue.BEFORE) || 
isAmbiguous(p2, IndeterminateValue.AFTER))  return false; else break;
-                case AFTER:  if (isAmbiguous(p1, IndeterminateValue.AFTER)  || 
isAmbiguous(p2, IndeterminateValue.BEFORE)) return false; else break;
-                default: return false;
+                case BEFORE: case NOT_AFTER:  a1 = IndeterminateValue.BEFORE; 
a2 = IndeterminateValue.AFTER;  break;
+                case AFTER:  case NOT_BEFORE: a1 = IndeterminateValue.AFTER;  
a2 = IndeterminateValue.BEFORE; break;
+                case EQUAL:  return (p1 == p2) && (p1 == 
IndeterminateValue.NOW);
+                default:     return false;
+            }
+            if (isAmbiguous(p1, a1) || isAmbiguous(p2, a2)) {
+                return false;
             }
         }
-        return TimeMethods.compareAny(test, self.getPosition(), 
other.getPosition());
+        return TimeMethods.compareLenient(test, self.getPosition(), 
other.getPosition());
     }
 
     /**
      * Returns {@code true} if using the {@code p} value would be ambiguous.
      *
-     * @param  p         the indeterminate value to test.
+     * @param  p1         the indeterminate value to test.
      * @param  required  the required value for a non-ambiguous comparison.
      * @return whether using the given value would be ambiguous.
      */
-    private static boolean isAmbiguous(final IndeterminateValue p, final 
IndeterminateValue required) {
-        return (p != null) && (p != IndeterminateValue.NOW) && (p != required);
+    private static boolean isAmbiguous(final IndeterminateValue p1, final 
IndeterminateValue required) {
+        return (p1 != null) && (p1 != IndeterminateValue.NOW) && (p1 != 
required);
     }
 
 
@@ -306,20 +307,20 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(T self, Period other) {
-            return compare(EQUAL, self, other.getBeginning()) &&
-                   compare(EQUAL, self, other.getEnding());
+            return compare(TimeMethods.Test.EQUAL, self, other.getBeginning()) 
&&
+                   compare(TimeMethods.Test.EQUAL, self, other.getEnding());
         }
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(Period self, T other) {
-            return compare(EQUAL, other, self.getBeginning()) &&
-                   compare(EQUAL, other, self.getEnding());
+            return compare(TimeMethods.Test.EQUAL, other, self.getBeginning()) 
&&
+                   compare(TimeMethods.Test.EQUAL, other, self.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, Period other) {
-            return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
-                   compare(EQUAL, self.getEnding(),    other.getEnding());
+            return compare(TimeMethods.Test.EQUAL, self.getBeginning(), 
other.getBeginning()) &&
+                   compare(TimeMethods.Test.EQUAL, self.getEnding(),    
other.getEnding());
         }
     }
 
@@ -360,17 +361,17 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Relationship not defined by ISO 19108:2006. */
         @Override public boolean evaluate(T self, Period other) {
-            return compare(BEFORE, self, other.getBeginning());
+            return compare(TimeMethods.Test.BEFORE, self, 
other.getBeginning());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, T other) {
-            return compare(AFTER, other, self.getEnding());
+            return compare(TimeMethods.Test.AFTER, other, self.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, Period other) {
-            return compare(BEFORE, self.getEnding(), other.getBeginning());
+            return compare(TimeMethods.Test.BEFORE, self.getEnding(), 
other.getBeginning());
         }
     }
 
@@ -411,17 +412,17 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Relationship not defined by ISO 19108:2006. */
         @Override public boolean evaluate(T self, Period other) {
-            return compare(AFTER, self, other.getEnding());
+            return compare(TimeMethods.Test.AFTER, self, other.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, T other) {
-            return compare(BEFORE, other, self.getBeginning());
+            return compare(TimeMethods.Test.BEFORE, other, 
self.getBeginning());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, Period other) {
-            return compare(AFTER, self.getBeginning(), other.getEnding());
+            return compare(TimeMethods.Test.AFTER, self.getBeginning(), 
other.getEnding());
         }
     }
 
@@ -450,8 +451,8 @@ abstract class TemporalOperation<T> implements Serializable 
{
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL,  self.getBeginning(), other.getBeginning()) 
&&
-                   compare(BEFORE, self.getEnding(),    other.getEnding());
+            return compare(TimeMethods.Test.EQUAL,  self.getBeginning(), 
other.getBeginning()) &&
+                   compare(TimeMethods.Test.BEFORE, self.getEnding(),    
other.getEnding());
         }
     }
 
@@ -480,8 +481,8 @@ abstract class TemporalOperation<T> implements Serializable 
{
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL, self.getEnding(),    other.getEnding()) &&
-                   compare(AFTER, self.getBeginning(), other.getBeginning());
+            return compare(TimeMethods.Test.EQUAL, self.getEnding(),    
other.getEnding()) &&
+                   compare(TimeMethods.Test.AFTER, self.getBeginning(), 
other.getBeginning());
         }
     }
 
@@ -511,13 +512,13 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(Period self, T other) {
-            return compare(EQUAL, other, self.getBeginning());
+            return compare(TimeMethods.Test.EQUAL, other, self.getBeginning());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
-                   compare(AFTER, self.getEnding(),    other.getEnding());
+            return compare(TimeMethods.Test.EQUAL, self.getBeginning(), 
other.getBeginning()) &&
+                   compare(TimeMethods.Test.AFTER, self.getEnding(),    
other.getEnding());
         }
     }
 
@@ -547,13 +548,13 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final T other) {
-            return compare(EQUAL, other, self.getEnding());
+            return compare(TimeMethods.Test.EQUAL, other, self.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL,  self.getEnding(),    other.getEnding()) &&
-                   compare(BEFORE, self.getBeginning(), other.getBeginning());
+            return compare(TimeMethods.Test.EQUAL,  self.getEnding(),    
other.getEnding()) &&
+                   compare(TimeMethods.Test.BEFORE, self.getBeginning(), 
other.getBeginning());
         }
     }
 
@@ -587,17 +588,17 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(final T self, final Period other) {
-            return compare(EQUAL, self, other.getBeginning());
+            return compare(TimeMethods.Test.EQUAL, self, other.getBeginning());
         }
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(final Period self, final T other) {
-            return compare(EQUAL, other, self.getEnding());
+            return compare(TimeMethods.Test.EQUAL, other, self.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL, self.getEnding(), other.getBeginning());
+            return compare(TimeMethods.Test.EQUAL, self.getEnding(), 
other.getBeginning());
         }
     }
 
@@ -631,17 +632,17 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(final T self, final Period other) {
-            return compare(EQUAL, self, other.getEnding());
+            return compare(TimeMethods.Test.EQUAL, self, other.getEnding());
         }
 
         /** Extension to ISO 19108: handle instant as a tiny period. */
         @Override public boolean evaluate(final Period self, final T other) {
-            return compare(EQUAL, other, self.getBeginning());
+            return compare(TimeMethods.Test.EQUAL, other, self.getBeginning());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(EQUAL, self.getBeginning(), other.getEnding());
+            return compare(TimeMethods.Test.EQUAL, self.getBeginning(), 
other.getEnding());
         }
     }
 
@@ -675,8 +676,8 @@ abstract class TemporalOperation<T> implements Serializable 
{
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(AFTER,  self.getBeginning(), other.getBeginning()) 
&&
-                   compare(BEFORE, self.getEnding(),    other.getEnding());
+            return compare(TimeMethods.Test.AFTER,  self.getBeginning(), 
other.getBeginning()) &&
+                   compare(TimeMethods.Test.BEFORE, self.getEnding(),    
other.getEnding());
         }
     }
 
@@ -711,14 +712,14 @@ abstract class TemporalOperation<T> implements 
Serializable {
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final T other) {
-            return compare(AFTER,  other, self.getBeginning()) &&
-                   compare(BEFORE, other, self.getEnding());
+            return compare(TimeMethods.Test.AFTER,  other, 
self.getBeginning()) &&
+                   compare(TimeMethods.Test.BEFORE, other, self.getEnding());
         }
 
         /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
-            return compare(BEFORE, self.getBeginning(), other.getBeginning()) 
&&
-                   compare(AFTER,  self.getEnding(),    other.getEnding());
+            return compare(TimeMethods.Test.BEFORE, self.getBeginning(), 
other.getBeginning()) &&
+                   compare(TimeMethods.Test.AFTER,  self.getEnding(),    
other.getEnding());
         }
     }
 
@@ -749,9 +750,9 @@ abstract class TemporalOperation<T> implements Serializable 
{
         @Override public boolean evaluate(final Period self, final Period 
other) {
             final Instant selfBegin, selfEnd, otherBegin, otherEnd;
             return ((otherBegin = other.getBeginning()) != null) &&
-                   ((selfBegin  = self.getBeginning())  != null) && 
compare(BEFORE, selfBegin, otherBegin) &&
-                   ((selfEnd    = self.   getEnding())  != null) && 
compare(AFTER,  selfEnd,   otherBegin) &&
-                   ((otherEnd   = other.  getEnding())  != null) && 
compare(BEFORE, selfEnd,   otherEnd);
+                   ((selfBegin  = self.getBeginning())  != null) && 
compare(TimeMethods.Test.BEFORE, selfBegin, otherBegin) &&
+                   ((selfEnd    = self.   getEnding())  != null) && 
compare(TimeMethods.Test.AFTER,  selfEnd,   otherBegin) &&
+                   ((otherEnd   = other.  getEnding())  != null) && 
compare(TimeMethods.Test.BEFORE, selfEnd,   otherEnd);
         }
     }
 
@@ -782,9 +783,9 @@ abstract class TemporalOperation<T> implements Serializable 
{
         @Override public boolean evaluate(final Period self, final Period 
other) {
             final Instant selfBegin, selfEnd, otherBegin, otherEnd;
             return ((selfBegin  = self.getBeginning())  != null) &&
-                   ((otherBegin = other.getBeginning()) != null) && 
compare(AFTER,  selfBegin,  otherBegin) &&
-                   ((otherEnd   = other.  getEnding())  != null) && 
compare(BEFORE, selfBegin,  otherEnd)   &&
-                   ((selfEnd    = self.   getEnding())  != null) && 
compare(AFTER,  selfEnd,    otherEnd);
+                   ((otherBegin = other.getBeginning()) != null) && 
compare(TimeMethods.Test.AFTER,  selfBegin,  otherBegin) &&
+                   ((otherEnd   = other.  getEnding())  != null) && 
compare(TimeMethods.Test.BEFORE, selfBegin,  otherEnd)   &&
+                   ((selfEnd    = self.   getEnding())  != null) && 
compare(TimeMethods.Test.AFTER,  selfEnd,    otherEnd);
         }
     }
 
@@ -813,9 +814,9 @@ abstract class TemporalOperation<T> implements Serializable 
{
         @Override public boolean evaluate(final Period self, final Period 
other) {
             final Instant selfBegin, selfEnd, otherBegin, otherEnd;
             return ((selfBegin  = self.getBeginning())  != null) &&
-                   ((otherEnd   = other.  getEnding())  != null) && 
compare(BEFORE, selfBegin, otherEnd) &&
+                   ((otherEnd   = other.  getEnding())  != null) && 
compare(TimeMethods.Test.BEFORE, selfBegin, otherEnd) &&
                    ((selfEnd    = self.   getEnding())  != null) &&
-                   ((otherBegin = other.getBeginning()) != null) && 
compare(AFTER, selfEnd, otherBegin);
+                   ((otherBegin = other.getBeginning()) != null) && 
compare(TimeMethods.Test.AFTER, selfEnd, otherBegin);
         }
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
index 80613993e9..33415a75cd 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
@@ -283,8 +283,12 @@ cmp:    if (canTestBefore | canTestAfter | canTestEqual) {
                     return true;
                 }
                 final Temporal other = that.getPosition();
-                return Objects.equals(position, other) ||       // Needed in 
all cases for testing null values.
-                        (mode.isIgnoringMetadata() && 
TimeMethods.compareAny(TimeMethods.EQUAL, position, other));
+                if (Objects.equals(position, other)) {       // Needed anyway 
for testing null values.
+                    return true;
+                }
+                if (mode.isIgnoringMetadata()) {
+                    return TimeMethods.compareLenient(TimeMethods.Test.EQUAL, 
position, other);
+                }
             }
         }
         return false;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
index 45af8e82b2..935762c40f 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
@@ -18,6 +18,7 @@ package org.apache.sis.temporal;
 
 import java.util.Map;
 import java.util.Date;
+import java.util.Calendar;
 import java.util.Optional;
 import java.util.function.Supplier;
 import java.util.function.BiFunction;
@@ -79,11 +80,81 @@ public class TimeMethods<T> implements Serializable {
     public final Class<T> type;
 
     /**
-     * Enumeration values for a test to apply.
+     * The test to apply: equal, before or after.
      *
-     * @see #compare(int, T, TemporalAccessor)
+     * @see #compare(Test, T, TemporalAccessor)
      */
-    public static final int BEFORE=1, AFTER=2, EQUAL=0;
+    public enum Test {
+        /** Identifies the <var>A</var> = <var>B</var> test. */
+        EQUAL() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m) {return 
m.isEqual;}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)  {return 
m.isEqual.test(a, b);}
+            @Override     boolean fromCompareTo(int result)            {return 
result == 0;}
+        },
+
+        /** Identifies the <var>A</var> {@literal <} <var>B</var> test. */
+        BEFORE() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m) {return 
m.isBefore;}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)  {return 
m.isBefore.test(a, b);}
+            @Override     boolean fromCompareTo(int result)            {return 
result < 0;}
+        },
+
+        /** Identifies the <var>A</var> {@literal >} <var>B</var> test. */
+        AFTER() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m) {return 
m.isAfter;}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)  {return 
m.isAfter.test(a, b);}
+            @Override     boolean fromCompareTo(int result)            {return 
result > 0;}
+        },
+
+        /** Identifies the <var>A</var> ≠ <var>B</var> test. */
+        NOT_EQUAL() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m) {return 
 m.isEqual.negate();}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)  {return 
!m.isEqual.test(a, b);}
+            @Override     boolean fromCompareTo(int result)            {return 
result != 0;}
+        },
+
+        /** Identifies the <var>A</var> ≥ <var>B</var> test. */
+        NOT_BEFORE() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m) {return 
 m.isBefore.negate();}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)  {return 
!m.isBefore.test(a, b);}
+            @Override     boolean fromCompareTo(int result)            {return 
result >= 0;}
+        },
+
+        /** Identifies the <var>A</var> ≤ <var>B</var> test. */
+        NOT_AFTER() {
+            @Override <T> BiPredicate<T,T> predicate(TimeMethods<T> m)  
{return  m.isAfter.negate();}
+            @Override <T> boolean compare(TimeMethods<T> m, T a, T b)   
{return !m.isAfter.test(a, b);}
+            @Override     boolean fromCompareTo(int result)             
{return result <= 0;}
+        };
+
+        /**
+         * Returns the predicate to use for this test.
+         *
+         * @param  <T>  the type of temporal objects expected by the predicate.
+         * @param  m    the collection of predicate for the type of temporal 
objects.
+         * @return the predicate for this test.
+         */
+        abstract <T> BiPredicate<T,T> predicate(TimeMethods<T> m);
+
+        /**
+         * Executes the test between the given temporal objects.
+         *
+         * @param  <T>    the type of temporal objects expected by the 
predicate.
+         * @param  m      the collection of predicate for the type of temporal 
objects.
+         * @param  self   the object on which to invoke the method identified 
by this test.
+         * @param  other  the argument to give to the test method call.
+         * @return the result of performing the comparison identified by this 
test.
+         */
+        abstract <T> boolean compare(TimeMethods<T> m, T self, T other);
+
+        /**
+         * Returns whether the test pass according the result of a {@code 
compareTo(…)} method.
+         *
+         * @param  result  the {@code compareTo(…)} result.
+         * @return whether the test pass.
+         */
+        abstract boolean fromCompareTo(int result);
+    }
 
     /**
      * Predicate to execute for testing the ordering between temporal objects.
@@ -147,59 +218,233 @@ public class TimeMethods<T> implements Serializable {
         return false;
     }
 
+    /**
+     * Returns the predicate to use for this test.
+     *
+     * @param  test   the test to apply (before, after and/or equal).
+     * @return the predicate for the requested test.
+     */
+    public final BiPredicate<T,T> predicate(final Test test) {
+        return test.predicate(this);
+    }
+
     /**
      * Delegates the comparison to the method identified by the {@code test} 
argument.
      * This method is overridden in subclasses where the delegation can be 
more direct.
      *
-     * @param  test   {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code 
test}.
      */
-    boolean delegate(final int test, final T self, final T other) {
-        final BiPredicate<T,T> p;
-        switch (test) {
-            case BEFORE: p = isBefore; break;
-            case AFTER:  p = isAfter;  break;
-            case EQUAL:  p = isEqual;  break;
-            default: throw new AssertionError(test);
-        }
-        return p.test(self, other);
+    boolean delegate(final Test test, final T self, final T other) {
+        return test.compare(this, self, other);
     }
 
     /**
      * Compares an object of class {@code <T>} with a temporal object of 
unknown class.
      * The other object is typically the beginning or ending of a period.
      *
-     * @param  test   {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
     @SuppressWarnings("unchecked")
-    public final boolean compare(final int test, final T self, final 
TemporalAccessor other) {
+    public final boolean compare(final Test test, final T self, final 
TemporalAccessor other) {
         if (type.isInstance(other)) {
             return delegate(test, self, (T) other);         // Safe because of 
above `isInstance(…)` check.
         }
         return compareAsInstants(test, accessor(self), other);
     }
 
+    /**
+     * Returns {@code TRUE} if both arguments are non-null and the specified 
comparison evaluates to {@code true}.
+     * If the two objects are not of compatible type, they are converted. If 
at least one object is not temporal,
+     * then this method returns {@code null} rather than throwing {@link 
DateTimeException}.
+     *
+     * <p>This method should be used in last resort because it may be 
expensive.</p>
+     *
+     * @param  test   the test to apply (before, after and/or equal).
+     * @param  self   the object on which to invoke the method identified by 
{@code test}, or {@code null} if none.
+     * @param  other  the argument to give to the test method call, or {@code 
null} if none.
+     * @return the comparison result, or {@code null} if the given objects 
were not recognized as temporal.
+     * @throws DateTimeException if the two objects are temporal objects but 
cannot be compared.
+     */
+    public static Boolean compareIfTemporal(final Test test, Object self, 
Object other) {
+        if (self == null || other == null) {
+            return Boolean.FALSE;
+        }
+        boolean isTemporal = false;
+        if (self  instanceof TemporalDate) {self  = ((TemporalDate)  
self).temporal; isTemporal = true;}
+        if (other instanceof TemporalDate) {other = ((TemporalDate) 
other).temporal; isTemporal = true;}
+        /*
+         * For legacy java.util.Date, the compareTo(…) method is consistent 
only for dates of the same class.
+         * Otherwise A.compareTo(B) and B.compareTo(A) are inconsistent if one 
object is a java.util.Date and
+         * the other object is a java.sql.Timestamp. In such case, we compare 
the dates as java.time objects.
+         */
+        if (self instanceof Date && other instanceof Date) {
+            if (self.getClass() == other.getClass()) {
+                return test.fromCompareTo(((Date) self).compareTo((Date) 
other));
+            }
+            self  = fromLegacy((Date) self);
+            other = fromLegacy((Date) other);
+            isTemporal = true;          // For skipping unecessary `if (x 
instanceof Temporal)` checks.
+        }
+        // Use `||` because an operand by still be a `java.utl.Date`.
+        if (isTemporal || self instanceof Temporal || other instanceof 
Temporal) {
+            return compareAny(test, self, other);
+        }
+        return null;
+    }
+
     /**
      * Returns {@code true} if both arguments are non-null and the specified 
comparison evaluates to {@code true}.
      * The type of the objects being compared is determined dynamically, which 
has a performance cost.
      * The {@code compare(…)} methods should be preferred when the type is 
known in advance.
      *
-     * @param  test   {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}, or {@code null} if none.
      * @param  other  the argument to give to the test method call, or {@code 
null} if none.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
-    @SuppressWarnings("unchecked")
-    public static boolean compareAny(final int test, final Temporal self, 
final Temporal other) {
-        return (self != null) && (other != null)
-                && compare(test, (Class) 
Classes.findCommonClass(self.getClass(), other.getClass()), self, other);
+    public static boolean compareLenient(final Test test, final Temporal self, 
final Temporal other) {
+        if (self != null && other != null) {
+            Boolean c = compareAny(test, self, other);
+            if (c != null) return c;
+        }
+        return false;
+    }
+
+    /**
+     * Implementation of lenient comparisons.
+     * Temporal objects have complex conversion rules. We take Instant as the 
most accurate and unambiguous type.
+     * So if at least one value is an Instant, try to unconditionally promote 
the other value to an Instant too.
+     * This conversion will fail if the other object has some undefined 
fields. For example {@link java.sql.Date}
+     * has no time fields (we do not assume that the values of those fields 
are zero).
+     *
+     * @param  test   the test to apply (before, after and/or equal).
+     * @param  self   the object on which to invoke the method identified by 
{@code test}.
+     * @param  other  the argument to give to the test method call.
+     * @return the comparison result, or {@code null} if the given objects 
were not recognized as temporal.
+     * @throws DateTimeException if the two objects cannot be compared.
+     */
+    private static Boolean compareAny(final Test test, Object self, Object 
other) {
+        Class<?> type = self.getClass();
+adapt:  if (self != other.getClass()) {
+            Temporal converted;
+            /*
+             * OffsetTime and OffsetDateTime are final classes that do not 
implement a java.time.chrono interface.
+             * Note that OffsetDateTime is convertible into OffsetTime by 
dropping the date fields, but we do not
+             * (for now) perform comparisons that would ignore the date fields 
of an operand.
+             */
+            if (self instanceof Instant) {
+                converted = toInstant(other);
+                if (converted != null) {
+                    other = converted;
+                    type  = Instant.class;
+                    break adapt;
+                }
+            } else if (other instanceof Instant) {
+                converted = toInstant(self);
+                if (converted != null) {
+                    self = converted;
+                    type = Instant.class;
+                    break adapt;
+                }
+            } else if (self instanceof OffsetDateTime) {
+                converted = toOffsetDateTime(other);
+                if (converted != null) {
+                    other = converted;
+                    type  = OffsetDateTime.class;
+                    break adapt;
+                }
+            } else if (other instanceof OffsetDateTime) {
+                converted = toOffsetDateTime(self);
+                if (converted != null) {
+                    self = converted;
+                    type = OffsetDateTime.class;
+                    break adapt;
+                }
+            }
+            /*
+             * Comparisons of temporal objects implementing java.time.chrono 
interfaces. We need to check the most
+             * complete types first. If the type are different, we reduce to 
the type of the less smallest operand.
+             * For example if an operand is a date+time and the other operand 
is only a date, then the time fields
+             * will be ignored and a warning will be reported.
+             */
+            if (self instanceof ChronoLocalDateTime<?>) {
+                converted = toLocalDateTime(other);
+                if (converted != null) {
+                    other = converted;
+                    type  = ChronoLocalDateTime.class;
+                    break adapt;
+                }
+            } else if (other instanceof ChronoLocalDateTime<?>) {
+                converted = toLocalDateTime(self);
+                if (converted != null) {
+                    self = converted;
+                    type = ChronoLocalDateTime.class;
+                    break adapt;
+                }
+            }
+            // No else, we want this as a fallback.
+            if (self instanceof ChronoLocalDate) {
+                converted = toLocalDate(other);
+                if (converted != null) {
+                    other = converted;
+                    type  = ChronoLocalDate.class;
+                    break adapt;
+                }
+            } else if (other instanceof ChronoLocalDate) {
+                converted = toLocalDate(self);
+                if (converted != null) {
+                    self = converted;
+                    type = ChronoLocalDate.class;
+                    break adapt;
+                }
+            }
+            // No else, we want this as a fallback.
+            if (self instanceof LocalTime) {
+                converted = toLocalTime(other);
+                if (converted != null) {
+                    other = converted;
+                    type  = LocalTime.class;
+                    break adapt;
+                }
+            } else if (other instanceof LocalTime) {
+                converted = toLocalTime(self);
+                if (converted != null) {
+                    self = converted;
+                    type = LocalTime.class;
+                    break adapt;
+                }
+            }
+            // No else, we want this as a fallback.
+            if (self instanceof Temporal && other instanceof Temporal) {
+                type  = Classes.findCommonClass(self.getClass(), 
other.getClass());
+            } else {
+                return null;
+            }
+        }
+        return castAndCompare(test, type, self, other);
+    }
+
+    /**
+     * Delegates to {@link #compare(int, Class, Object, Object)} after 
verification of the type.
+     *
+     * @param  test   the test to apply (before, after and/or equal).
+     * @param  type   base class of the {@code self} and {@code other} 
arguments.
+     * @param  self   the object on which to invoke the method identified by 
{@code test}.
+     * @param  other  the argument to give to the test method call.
+     * @return the result of performing the comparison identified by {@code 
test}.
+     * @throws ClassCastException if {@code self} or {@code other} is not an 
instance of {@code type}.
+     * @throws DateTimeException if the two objects cannot be compared.
+     */
+    private static <T> boolean castAndCompare(Test test, Class<T> type, Object 
self, Object other) {
+        return compare(test, type, type.cast(self), type.cast(other));
     }
 
     /**
@@ -208,14 +453,14 @@ public class TimeMethods<T> implements Serializable {
      * are not always the same as {@code compareTo(…)}.
      *
      * @param  <T>    base class of the objects to compare.
-     * @param  test   {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  type   base class of the {@code self} and {@code other} 
arguments.
      * @param  self   the object on which to invoke the method identified by 
{@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
-    public static <T> boolean compare(final int test, final Class<T> type, 
final T self, final T other) {
+    public static <T> boolean compare(final Test test, final Class<T> type, 
final T self, final T other) {
         /*
          * The following cast is not strictly true, it should be `<? extends 
T>`.
          * However, because of the `isInstance(…)` check and because <T> is 
used
@@ -230,19 +475,10 @@ public class TimeMethods<T> implements Serializable {
             /*
              * Found one of the special cases listed in `INTERFACES` or 
`FINAL_TYPE`.
              * If the other type is compatible, the comparison is executed 
directly.
-             * Note: the `switch` statement is equivalent to `tc.compare(test, 
…)`,
-             * but is inlined because that method is never overridden in this 
context.
              */
             if (tc.type.isInstance(other)) {
                 assert tc.type.isAssignableFrom(type) : tc;     // Those types 
are not necessarily equal.
-                final BiPredicate<? super T, ? super T> p;
-                switch (test) {
-                    case BEFORE: p = tc.isBefore; break;
-                    case AFTER:  p = tc.isAfter;  break;
-                    case EQUAL:  p = tc.isEqual;  break;
-                    default: throw new AssertionError(test);
-                }
-                return p.test(self, other);
+                return test.compare(tc, self, other);
             }
         } else if (self instanceof Comparable<?> && 
self.getClass().isInstance(other)) {
             /*
@@ -253,12 +489,7 @@ public class TimeMethods<T> implements Serializable {
              */
             @SuppressWarnings("unchecked")          // Safe because 
verification done by `isInstance(…)`.
             final int c = ((Comparable) self).compareTo(other);
-            switch (test) {
-                case BEFORE: return c <  0;
-                case AFTER:  return c >  0;
-                case EQUAL:  return c == 0;
-                default: throw new AssertionError(test);
-            }
+            return test.fromCompareTo(c);
         }
         /*
          * If we reach this point, the two operands are of different classes 
and we cannot compare them directly.
@@ -285,23 +516,20 @@ public class TimeMethods<T> implements Serializable {
      * Compares two temporal objects as instants.
      * This is a last-resort fallback, when objects cannot be compared by 
their own methods.
      *
-     * @param  test   {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+     * @param  test   the test to apply (before, after and/or equal).
      * @param  self   the object on which to invoke the method identified by 
{@code test}.
      * @param  other  the argument to give to the test method call.
      * @return the result of performing the comparison identified by {@code 
test}.
      * @throws DateTimeException if the two objects cannot be compared.
      */
-    private static boolean compareAsInstants(final int test, final 
TemporalAccessor self, final TemporalAccessor other) {
+    private static boolean compareAsInstants(final Test test, final 
TemporalAccessor self, final TemporalAccessor other) {
         long t1 =  self.getLong(ChronoField.INSTANT_SECONDS);
         long t2 = other.getLong(ChronoField.INSTANT_SECONDS);
         if (t1 == t2) {
             t1 =  self.getLong(ChronoField.NANO_OF_SECOND);     // Should be 
present according Javadoc.
             t2 = other.getLong(ChronoField.NANO_OF_SECOND);
-            if (t1 == t2) {
-                return test == EQUAL;
-            }
         }
-        return test == ((t1 < t2) ? BEFORE : AFTER);
+        return test.fromCompareTo(Long.compare(t1, t2));
     }
 
     /**
@@ -367,15 +595,15 @@ public class TimeMethods<T> implements Serializable {
      */
     private static <T> TimeMethods<? super T> fallback(final Class<T> type) {
         return new TimeMethods<>(type,
-                (self, other) -> compare(BEFORE, type, self, other),
-                (self, other) -> compare(AFTER,  type, self, other),
-                (self, other) -> compare(EQUAL,  type, self, other),
+                (self, other) -> compare(Test.BEFORE, type, self, other),
+                (self, other) -> compare(Test.AFTER,  type, self, other),
+                (self, other) -> compare(Test.EQUAL,  type, self, other),
                 null, null, false)
         {
             @Override public boolean isDynamic() {
                 return true;
             }
-            @Override boolean delegate(final int test, final T self, final T 
other) {
+            @Override boolean delegate(final Test test, final T self, final T 
other) {
                 return compare(test, type, self, other);
             }
         };
@@ -429,9 +657,10 @@ public class TimeMethods<T> implements Serializable {
      *   </li>
      * </ul>
      *
+     * @param  <T>       type of the {@code time} argument.
      * @param  time      the temporal object to return with the specified 
timezone, or {@code null} if none.
      * @param  timezone  the desired timezone. Cannot be {@code null}.
-     * @param  allowAdd
+     * @param  allowAdd  whether to allow the addition of a time zone in an 
object that initially had none.
      * @return a temporal object with the specified timezone, if it was 
possible to apply a timezone.
      */
     public static <T> Optional<Temporal> withZone(final T time, final ZoneId 
timezone, final boolean allowAdd) {
@@ -541,6 +770,147 @@ public class TimeMethods<T> implements Serializable {
         }
     }
 
+    /**
+     * Converts a legacy {@code Date} object to an object from the {@link 
java.time} package.
+     * We performs this conversion before to compare to {@code Date} instances 
that are not of
+     * the same class, because the {@link Date#compareTo(Date)} method in such 
case is not well
+     * defined.
+     */
+    private static Temporal fromLegacy(final Date value) {
+        if (value instanceof java.sql.Timestamp) {
+            return ((java.sql.Timestamp) value).toLocalDateTime();
+        } else if (value instanceof java.sql.Date) {
+            return ((java.sql.Date) value).toLocalDate();
+        } else if (value instanceof java.sql.Time) {
+            return ((java.sql.Time) value).toLocalTime();
+        } else {
+            // Implementation of above toFoo() methods use system default time 
zone.
+            return LocalDateTime.ofInstant(value.toInstant(), 
ZoneId.systemDefault());
+        }
+    }
+
+    /**
+     * Converts the given object to an {@link Instant}, or returns {@code 
null} if unconvertible.
+     * This method handles a few types from the {@link java.time} package and 
legacy types like
+     * {@link Date} (with a special case for SQL dates) and {@link Calendar}.
+     */
+    private static Instant toInstant(final Object value) {
+        if (value instanceof Instant) {
+            return (Instant) value;
+        } else if (value instanceof OffsetDateTime) {
+            return ((OffsetDateTime) value).toInstant();
+        } else if (value instanceof ChronoZonedDateTime) {
+            return ((ChronoZonedDateTime) value).toInstant();
+        } else if (value instanceof Date) {
+            try {
+                return ((Date) value).toInstant();
+            } catch (UnsupportedOperationException e) {
+                /*
+                 * java.sql.Date and java.sql.Time cannot be converted to 
Instant because a part
+                 * of their coordinates on the timeline is undefined.  For 
example in the case of
+                 * java.sql.Date the hours, minutes and seconds are 
unspecified (which is not the
+                 * same thing as assuming that those values are zero).
+                 */
+            }
+        } else if (value instanceof Calendar) {
+            return ((Calendar) value).toInstant();
+        }
+        return null;
+    }
+
+    /**
+     * Converts the given object to an {@link OffsetDateTime}, or returns 
{@code null} if unconvertible.
+     */
+    private static OffsetDateTime toOffsetDateTime(final Object value) {
+        if (value instanceof OffsetDateTime) {
+            return (OffsetDateTime) value;
+        } else if (value instanceof ZonedDateTime) {
+            return ((ZonedDateTime) value).toOffsetDateTime();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Converts the given object to a {@link ChronoLocalDateTime}, or returns 
{@code null} if unconvertible.
+     * This method handles the case of legacy SQL {@link java.sql.Timestamp} 
objects.
+     * Conversion may lost timezone information.
+     */
+    private static ChronoLocalDateTime<?> toLocalDateTime(final Object value) {
+        if (value instanceof ChronoLocalDateTime<?>) {
+            return (ChronoLocalDateTime<?>) value;
+        } else if (value instanceof ChronoZonedDateTime) {
+            ignoringField(ChronoField.OFFSET_SECONDS);
+            return ((ChronoZonedDateTime) value).toLocalDateTime();
+        } else if (value instanceof OffsetDateTime) {
+            ignoringField(ChronoField.OFFSET_SECONDS);
+            return ((OffsetDateTime) value).toLocalDateTime();
+        } else if (value instanceof java.sql.Timestamp) {
+            return ((java.sql.Timestamp) value).toLocalDateTime();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Converts the given object to a {@link ChronoLocalDate}, or returns 
{@code null} if unconvertible.
+     * This method handles the case of legacy SQL {@link java.sql.Date} 
objects.
+     * Conversion may lost timezone information and time fields.
+     */
+    private static ChronoLocalDate toLocalDate(final Object value) {
+        if (value instanceof ChronoLocalDate) {
+            return (ChronoLocalDate) value;
+        } else if (value instanceof ChronoLocalDateTime) {
+            ignoringField(ChronoField.SECOND_OF_DAY);
+            return ((ChronoLocalDateTime) value).toLocalDate();
+        } else if (value instanceof ChronoZonedDateTime) {
+            ignoringField(ChronoField.SECOND_OF_DAY);
+            return ((ChronoZonedDateTime) value).toLocalDate();
+        } else if (value instanceof OffsetDateTime) {
+            ignoringField(ChronoField.SECOND_OF_DAY);
+            return ((OffsetDateTime) value).toLocalDate();
+        } else if (value instanceof java.sql.Date) {
+            return ((java.sql.Date) value).toLocalDate();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Converts the given object to a {@link LocalTime}, or returns {@code 
null} if unconvertible.
+     * This method handles the case of legacy SQL {@link java.sql.Time} 
objects.
+     * Conversion may lost timezone information.
+     */
+    private static LocalTime toLocalTime(final Object value) {
+        if (value instanceof LocalTime) {
+            return (LocalTime) value;
+        } else if (value instanceof OffsetTime) {
+            ignoringField(ChronoField.OFFSET_SECONDS);
+            return ((OffsetTime) value).toLocalTime();
+        } else if (value instanceof java.sql.Time) {
+            return ((java.sql.Time) value).toLocalTime();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Invoked when a conversion cause a field to be ignored. For example if a 
"date+time" object is compared
+     * with a "date" object, the "time" field is ignored. Expected values are:
+     *
+     * <ul>
+     *   <li>{@link ChronoField#OFFSET_SECONDS}: time zone is ignored.</li>
+     *   <li>{@link ChronoField#SECOND_OF_DAY}:  time of dat and time zone are 
ignored.</li>
+     * </ul>
+     *
+     * @param  field  the field which is ignored.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-460";>SIS-460</a>
+     */
+    private static void ignoringField(final ChronoField field) {
+        // TODO
+    }
+
     /**
      * Returns a string representation of this set of operations for debugging 
purposes.
      *
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index 705f54d29c..177724488b 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -1145,8 +1145,8 @@ public class MetadataBuilder {
         for (final Iterator<CitationDate> it = dates.iterator(); 
it.hasNext();) {
             final CitationDate existing = it.next();
             if (type.equals(existing.getDateType())) {
-                final int method = type.name().startsWith("LATE_") ? 
TimeMethods.BEFORE : TimeMethods.AFTER;
-                if (TimeMethods.compareAny(method, 
existing.getReferenceDate(), date.getReferenceDate())) {
+                TimeMethods.Test method = type.name().startsWith("LATE_") ? 
TimeMethods.Test.BEFORE : TimeMethods.Test.AFTER;
+                if (TimeMethods.compareLenient(method, 
existing.getReferenceDate(), date.getReferenceDate())) {
                     it.remove();
                     break;
                 }

Reply via email to