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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new f2faacb692 Reintroduce `org.opengis.temporal.Instant` from ISO 19108, 
because this is not the same thing as `java.time.Instant` despite the name. 
Regroup all temporal objects in an `org.apache.sis.temporal` package, not 
exported for now.
f2faacb692 is described below

commit f2faacb6926784fed19ef087d983ec9790ab23ea
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jun 12 11:20:46 2024 +0200

    Reintroduce `org.opengis.temporal.Instant` from ISO 19108, because this is 
not the same thing as `java.time.Instant` despite the name.
    Regroup all temporal objects in an `org.apache.sis.temporal` package, not 
exported for now.
---
 .../org/apache/sis/filter/TemporalOperation.java   | 118 +++++---
 .../test/org/apache/sis/filter/PeriodLiteral.java  |  44 +--
 .../org.apache.sis.metadata/main/module-info.java  |   9 +
 .../apache/sis/metadata/iso/DefaultMetadata.java   |   2 +-
 .../sis/metadata/iso/acquisition/DefaultEvent.java |   2 +-
 .../iso/acquisition/DefaultRequestedDate.java      |   2 +-
 .../iso/acquisition/DefaultRequirement.java        |   2 +-
 .../sis/metadata/iso/citation/DefaultCitation.java |   2 +-
 .../metadata/iso/citation/DefaultCitationDate.java |   4 +-
 .../distribution/DefaultStandardOrderProcess.java  |   2 +-
 .../metadata/iso/extent/DefaultTemporalExtent.java |   8 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |  21 +-
 .../metadata/iso/identification/DefaultUsage.java  |   6 +-
 .../sis/metadata/iso/legacy/TemporalToDate.java    |   2 +-
 .../metadata/iso/lineage/DefaultProcessStep.java   |   2 +-
 .../apache/sis/pending/temporal/DefaultPeriod.java |  78 -----
 .../org/apache/sis/pending/temporal/Primitive.java |  69 -----
 .../sis/pending/temporal/TemporalUtilities.java    | 127 --------
 .../org/apache/sis/temporal/DefaultInstant.java    | 180 ++++++++++++
 .../org/apache/sis/temporal/DefaultPeriod.java     | 107 +++++++
 .../org/apache/sis/temporal/GeneralDuration.java   | 325 +++++++++++++++++++++
 .../apache/sis/temporal}/StandardDateFormat.java   |   3 +-
 .../org/apache/sis/temporal}/TemporalDate.java     |  38 +--
 .../org/apache/sis/temporal/TemporalUtilities.java | 102 +++++++
 .../sis/{pending => }/temporal/package-info.java   |   9 +-
 .../org/apache/sis/xml/bind/gml/TM_Primitive.java  |   8 +-
 .../apache/sis/xml/bind/gml/TimePeriodBound.java   |  19 +-
 .../org/apache/sis/xml/privy/XmlUtilities.java     |   2 +-
 .../iso/citation/DefaultCitationDateTest.java      |   8 +-
 .../apache/sis/temporal/DefaultInstantTest.java    |  79 +++++
 .../org/apache/sis/temporal/DefaultPeriodTest.java |  79 +++++
 .../sis/temporal}/StandardDateFormatTest.java      |   2 +-
 .../apache/sis/xml/bind/gml/TimePeriodTest.java    |  17 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |   2 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   4 +-
 .../main/org/apache/sis/io/wkt/WKTFormat.java      |   4 +-
 .../sis/referencing/datum/AbstractDatum.java       |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   2 +-
 .../sis/referencing/privy/ExtentSelector.java      |   2 +-
 .../sis/test/integration/MetadataVerticalTest.java |   2 +-
 .../sis/storage/geotiff/reader/XMLMetadata.java    |   2 +-
 .../storage/geotiff/reader/XMLMetadataTest.java    |   4 +-
 .../sis/storage/netcdf/classic/ChannelDecoder.java |   4 +-
 .../sis/storage/netcdf/classic/VariableInfo.java   |   2 +-
 .../apache/sis/storage/netcdf/base/TestCase.java   |   2 +-
 .../sis/storage/xml/stream/StaxStreamReader.java   |   2 +-
 .../main/org/apache/sis/storage/csv/Store.java     |   2 +-
 .../org/apache/sis/storage/csv/TimeEncoding.java   |   4 +-
 .../main/org/apache/sis/measure/RangeFormat.java   |  44 ++-
 .../main/org/apache/sis/pending/jdk/JDK18.java     |  11 +
 .../main/org/apache/sis/util/resources/Errors.java |   5 +
 .../apache/sis/util/resources/Errors.properties    |   1 +
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 geoapi/snapshot                                    |   2 +-
 .../main/org/apache/sis/cql/CQL.java               |   2 +-
 .../org/apache/sis/cql/FilterToCQLVisitor.java     |   2 +-
 57 files changed, 1131 insertions(+), 457 deletions(-)

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 d826f84485..7918cc3f82 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
@@ -163,6 +163,32 @@ abstract class TemporalOperation<T> implements 
Serializable {
      */
     protected abstract boolean evaluate(Period self, Period other);
 
+    /**
+     * Returns the beginning of the given period, or {@code null} if 
indeterminate.
+     *
+     * @param  p the period from which to get the beginning.
+     * @return beginning of the given period, or {@code null} if indeterminate.
+     *
+     * @todo Handle "before" and "after" indeterminate values.
+     */
+    static Temporal getBeginning(final Period p) {
+        final var t = p.getBeginning();
+        return (t != null) ? t.getPosition() : null;
+    }
+
+    /**
+     * Returns the ending of the given period, or {@code null} if 
indeterminate.
+     *
+     * @param  p the period from which to get the ending.
+     * @return ending of the given period, or {@code null} if indeterminate.
+     *
+     * @todo Handle "before" and "after" indeterminate values.
+     */
+    static Temporal getEnding(final Period p) {
+        final var t = p.getEnding();
+        return (t != null) ? t.getPosition() : null;
+    }
+
     /**
      * 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.
@@ -247,20 +273,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(EQUAL, self, getBeginning(other)) &&
+                   compare(EQUAL, self, getEnding(other));
         }
 
         /** 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(EQUAL, other, getBeginning(self)) &&
+                   compare(EQUAL, other, getEnding(self));
         }
 
         /** 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(EQUAL, getBeginning(self), getBeginning(other)) &&
+                   compare(EQUAL, getEnding(self),    getEnding(other));
         }
     }
 
@@ -301,17 +327,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(BEFORE, self, getBeginning(other));
         }
 
         /** 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(AFTER, other, getEnding(self));
         }
 
         /** 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(BEFORE, getEnding(self), getBeginning(other));
         }
     }
 
@@ -352,17 +378,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(AFTER, self, getEnding(other));
         }
 
         /** 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(BEFORE, other, getBeginning(self));
         }
 
         /** 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(AFTER, getBeginning(self), getEnding(other));
         }
     }
 
@@ -391,8 +417,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(EQUAL,  getBeginning(self), getBeginning(other)) &&
+                   compare(BEFORE, getEnding(self),    getEnding(other));
         }
     }
 
@@ -421,8 +447,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(EQUAL, getEnding(self),    getEnding(other)) &&
+                   compare(AFTER, getBeginning(self), getBeginning(other));
         }
     }
 
@@ -452,13 +478,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(EQUAL, other, getBeginning(self));
         }
 
         /** 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(EQUAL, getBeginning(self), getBeginning(other)) &&
+                   compare(AFTER, getEnding(self),    getEnding(other));
         }
     }
 
@@ -488,13 +514,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(EQUAL, other, getEnding(self));
         }
 
         /** 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(EQUAL,  getEnding(self),    getEnding(other)) &&
+                   compare(BEFORE, getBeginning(self), getBeginning(other));
         }
     }
 
@@ -528,17 +554,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(EQUAL, self, getBeginning(other));
         }
 
         /** 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(EQUAL, other, getEnding(self));
         }
 
         /** 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(EQUAL, getEnding(self), getBeginning(other));
         }
     }
 
@@ -572,17 +598,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(EQUAL, self, getEnding(other));
         }
 
         /** 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(EQUAL, other, getBeginning(self));
         }
 
         /** 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(EQUAL, getBeginning(self), getEnding(other));
         }
     }
 
@@ -616,8 +642,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(AFTER,  getBeginning(self), getBeginning(other)) &&
+                   compare(BEFORE, getEnding(self),    getEnding(other));
         }
     }
 
@@ -652,14 +678,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(AFTER,  other, getBeginning(self)) &&
+                   compare(BEFORE, other, getEnding(self));
         }
 
         /** 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(BEFORE, getBeginning(self), getBeginning(other)) &&
+                   compare(AFTER,  getEnding(self),    getEnding(other));
         }
     }
 
@@ -689,10 +715,10 @@ 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) {
             final Temporal 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);
+            return ((otherBegin = getBeginning(other)) != null) &&
+                   ((selfBegin  = getBeginning(self))  != null) && 
compare(BEFORE, selfBegin, otherBegin) &&
+                   ((selfEnd    = getEnding   (self))  != null) && 
compare(AFTER,  selfEnd,   otherBegin) &&
+                   ((otherEnd   = getEnding   (other)) != null) && 
compare(BEFORE, selfEnd,   otherEnd);
         }
     }
 
@@ -722,10 +748,10 @@ 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) {
             final Temporal 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);
+            return ((selfBegin  = getBeginning(self))  != null) &&
+                   ((otherBegin = getBeginning(other)) != null) && 
compare(AFTER,  selfBegin,  otherBegin) &&
+                   ((otherEnd   = getEnding   (other)) != null) && 
compare(BEFORE, selfBegin,  otherEnd)   &&
+                   ((selfEnd    = getEnding   (self))  != null) && 
compare(AFTER,  selfEnd,    otherEnd);
         }
     }
 
@@ -753,10 +779,10 @@ abstract class TemporalOperation<T> implements 
Serializable {
         /** Condition defined by OGC filter specification. */
         @Override public boolean evaluate(final Period self, final Period 
other) {
             final Temporal selfBegin, selfEnd, otherBegin, otherEnd;
-            return ((selfBegin  = self .getBeginning()) != null) &&
-                   ((otherEnd   = other.getEnding())    != null) && 
compare(BEFORE, selfBegin, otherEnd) &&
-                   ((selfEnd    = self .getEnding())    != null) &&
-                   ((otherBegin = other.getBeginning()) != null) && 
compare(AFTER, selfEnd, otherBegin);
+            return ((selfBegin  = getBeginning(self))  != null) &&
+                   ((otherEnd   = getEnding   (other)) != null) && 
compare(BEFORE, selfBegin, otherEnd) &&
+                   ((selfEnd    = getEnding   (self))  != null) &&
+                   ((otherBegin = getBeginning(other)) != null) && 
compare(AFTER, selfEnd, otherBegin);
         }
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
index 4318272100..beaa0746a2 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
@@ -18,19 +18,13 @@ package org.apache.sis.filter;
 
 import java.time.Instant;
 import java.io.Serializable;
+import org.apache.sis.temporal.TemporalUtilities;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.time.temporal.TemporalAmount;
 import org.opengis.feature.Feature;
 import org.opengis.filter.Expression;
 import org.opengis.filter.Literal;
 import org.opengis.temporal.Period;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
 
 
 /**
@@ -55,18 +49,34 @@ final class PeriodLiteral implements Period, 
Literal<Feature,Period>, Serializab
     /**
      * Returns the constant value held by this object.
      */
-    @Override public Period getValue() {return this;}
+    @Override
+    public Period getValue() {
+        return this;
+    }
+
+    /**
+     * Returns the beginning of this period.
+     */
+    @Override
+    public org.opengis.temporal.Instant getBeginning() {
+        return TemporalUtilities.createInstant(Instant.ofEpochMilli(begin));
+    }
 
-    /** Returns a bound of this period. */
-    @Override public Instant getBeginning() {return 
Instant.ofEpochMilli(begin);}
-    @Override public Instant getEnding()    {return Instant.ofEpochMilli(end);}
+    /**
+     * Returns the ending of this period.
+     */
+    @Override
+    public org.opengis.temporal.Instant getEnding() {
+        return TemporalUtilities.createInstant(Instant.ofEpochMilli(end));
+    }
 
-    /** Not needed for the tests. */
-    @Override public Identifier       getName()                              
{throw new UnsupportedOperationException();}
-    @Override public RelativePosition relativePosition(TemporalPrimitive o)  
{throw new UnsupportedOperationException();}
-    @Override public TemporalAmount   distance(TemporalGeometricPrimitive o) 
{throw new UnsupportedOperationException();}
-    @Override public TemporalAmount   length()                               
{throw new UnsupportedOperationException();}
-    @Override public <N> Expression<Feature,N> toValueType(Class<N> target)  
{throw new UnsupportedOperationException();}
+    /**
+     * Not needed for the tests.
+     */
+    @Override
+    public <N> Expression<Feature,N> toValueType(Class<N> target) {
+        throw new UnsupportedOperationException();
+    }
 
     /**
      * Hash code value. Used by the tests for checking the results of 
deserialization.
diff --git a/endorsed/src/org.apache.sis.metadata/main/module-info.java 
b/endorsed/src/org.apache.sis.metadata/main/module-info.java
index 4aacf66825..2f35b34abf 100644
--- a/endorsed/src/org.apache.sis.metadata/main/module-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java
@@ -76,6 +76,15 @@ module org.apache.sis.metadata {
     /*
      * Internal API open only to other Apache SIS modules.
      */
+    exports org.apache.sis.temporal to
+            org.apache.sis.referencing,
+            org.apache.sis.feature,
+            org.apache.sis.storage,
+            org.apache.sis.storage.xml,
+            org.apache.sis.storage.netcdf,
+            org.apache.sis.storage.geotiff,
+            org.apache.sis.cql;                 // In the "incubator" 
sub-project.
+
     exports org.apache.sis.metadata.privy to
             org.apache.sis.referencing,
             org.apache.sis.feature,
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
index d9265feb93..5ba20d67fb 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -62,7 +62,7 @@ import org.apache.sis.util.Emptiable;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.privy.CollectionsExt;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.metadata.MetadataCopier;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java
index 3f61f355ef..732bc85b94 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java
@@ -31,7 +31,7 @@ import org.opengis.metadata.acquisition.PlatformPass;
 import org.opengis.metadata.acquisition.Sequence;
 import org.opengis.metadata.acquisition.Trigger;
 import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequestedDate.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequestedDate.java
index 15f26db867..f1a9226e80 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequestedDate.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequestedDate.java
@@ -23,7 +23,7 @@ import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.acquisition.RequestedDate;
 import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
index 357b1ebb35..355c09c9b7 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
@@ -29,7 +29,7 @@ import org.opengis.metadata.acquisition.RequestedDate;
 import org.opengis.metadata.acquisition.Requirement;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.metadata.citation.Responsibility;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
index d911b0cba3..dfbfcfd24c 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitation.java
@@ -36,7 +36,7 @@ import org.apache.sis.xml.IdentifierMap;
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.NonMarshalledAuthority;
 import org.apache.sis.xml.privy.LegacyNamespaces;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java
index 2db4683608..8f75d6a434 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java
@@ -25,7 +25,7 @@ import org.opengis.metadata.citation.CitationDate;
 import org.opengis.metadata.citation.DateType;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
@@ -50,7 +50,7 @@ import org.apache.sis.util.privy.TemporalDate;
  * @version 1.5
  * @since   0.3
  */
-@TitleProperty(name = "date")
+@TitleProperty(name = "referenceDate")
 @XmlType(name = "CI_Date_Type", propOrder = {
     "referenceDate",
     "dateType"
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index 985cfdb18e..207f5ce244 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
@@ -30,7 +30,7 @@ import org.opengis.metadata.distribution.StandardOrderProcess;
 import org.apache.sis.xml.bind.gco.GO_RecordType;
 import org.apache.sis.xml.bind.gco.GO_Record;
 import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
index dea4892eba..4539bf5493 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
@@ -31,8 +31,8 @@ import org.opengis.metadata.extent.SpatialTemporalExtent;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.privy.ReferencingServices;
-import org.apache.sis.pending.temporal.TemporalUtilities;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalUtilities;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
 
@@ -175,7 +175,7 @@ public class DefaultTemporalExtent extends ISOMetadata 
implements TemporalExtent
     }
 
     /**
-     * Infers a value from the extent as a {@code Instant} object.
+     * Infers a value from the extent as an {@code Instant} object.
      *
      * @param  begin  {@code true} for the start time, or {@code false} for 
the end time.
      * @return the requested time as an instant, or {@code null} if none.
@@ -183,7 +183,7 @@ public class DefaultTemporalExtent extends ISOMetadata 
implements TemporalExtent
     static Temporal getBound(final TemporalPrimitive extent, final boolean 
begin) {
         if (extent instanceof Period) {
             final var p = (Period) extent;
-            return begin ? p.getBeginning() : p.getEnding();
+            return (begin ? p.getBeginning() : p.getEnding()).getPosition();
         }
         return null;
     }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
index 4fa65934dd..704c490bcc 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java
@@ -62,7 +62,6 @@ import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.Range;
 import org.apache.sis.pending.jdk.JDK23;
-import org.apache.sis.pending.temporal.DefaultPeriod;
 import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ComparisonMode;
@@ -71,7 +70,7 @@ import org.apache.sis.util.Static;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 import static org.apache.sis.util.privy.CollectionsExt.nonNull;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 import static 
org.apache.sis.metadata.privy.ReferencingServices.AUTHALIC_RADIUS;
@@ -492,9 +491,9 @@ public final class Extents extends Static {
      */
     @OptionalCandidate
     public static Range<Date> getTimeRange(final Extent extent) {
-        final DefaultPeriod period = getPeriod(extent);
-        final Date min = TemporalDate.toDate(period.getBeginning());
-        final Date max = TemporalDate.toDate(period.getEnding());
+        final Temporal[] period = getPeriod(extent);
+        final Date min = TemporalDate.toDate(period[0]);
+        final Date max = TemporalDate.toDate(period[1]);
         if (min == null && max == null) {
             return null;
         }
@@ -561,9 +560,9 @@ public final class Extents extends Static {
      */
     public static Optional<Instant> getInstant(final Extent extent, final 
ZoneId zone, final double location) {
         ArgumentChecks.ensureFinite("location", location);
-        final DefaultPeriod period = getPeriod(extent);
-        Instant min = TemporalDate.toInstant(period.getBeginning(), zone);
-        Instant max = TemporalDate.toInstant(period.getEnding(), zone);
+        final Temporal[] period = getPeriod(extent);
+        Instant min = TemporalDate.toInstant(period[0], zone);
+        Instant max = TemporalDate.toInstant(period[1], zone);
         if (min == null || location == 1) {
             return Optional.ofNullable(max);
         }
@@ -576,11 +575,11 @@ public final class Extents extends Static {
     /**
      * Returns the minimum and maximum temporal values in an array of length 2.
      *
-     * @param  extent    the extent on which to apply a function, or {@code 
null}.
+     * @param  extent  the extent on which to apply a function, or {@code 
null}.
      * @return the minimum and maximum values. Never null, but may contain 
null elements.
      * @throws DateTimeException if there is more than one temporal extent, 
and some temporal values are not comparable.
      */
-    private static DefaultPeriod getPeriod(final Extent extent) {
+    private static Temporal[] getPeriod(final Extent extent) {
         Temporal min = null;
         Temporal max = null;
         if (extent != null) {
@@ -600,7 +599,7 @@ public final class Extents extends Static {
                 if (  endTime != null && (max == null || TemporalDate.compare( 
 endTime, max) > 0)) max = endTime;
             }
         }
-        return new DefaultPeriod(min, max);
+        return new Temporal[] {min, max};
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index cf6f3f105a..e3a0d20692 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
@@ -34,9 +34,9 @@ import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.internal.Dependencies;
-import org.apache.sis.pending.temporal.TemporalUtilities;
+import org.apache.sis.temporal.TemporalUtilities;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.privy.TemporalDate;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.metadata.citation.Responsibility;
@@ -239,7 +239,7 @@ public class DefaultUsage extends ISOMetadata implements 
Usage {
             final Collection<TemporalPrimitive> usageDates = getUsageDates();
             if (usageDates != null) {
                 for (TemporalPrimitive t : usageDates) {
-                    Date p = TemporalDate.toDate(t.position().orElse(null));
+                    Date p = 
TemporalDate.toDate(TemporalUtilities.getInstant(t));
                     if (p != null) {
                         return p;
                     }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/TemporalToDate.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/TemporalToDate.java
index 2249cde9ff..ab7183702a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/TemporalToDate.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/TemporalToDate.java
@@ -21,7 +21,7 @@ import java.util.AbstractCollection;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Iterator;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
index 05df948518..030509cdfe 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
@@ -37,7 +37,7 @@ import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.bind.gml.TM_Primitive;
 import org.apache.sis.xml.bind.metadata.MD_Scope;
 import org.apache.sis.xml.privy.LegacyNamespaces;
-import org.apache.sis.pending.temporal.TemporalUtilities;
+import org.apache.sis.temporal.TemporalUtilities;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.maintenance.Scope;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
deleted file mode 100644
index 6f4cff5259..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.sis.pending.temporal;
-
-import java.util.Objects;
-import java.time.temporal.Temporal;
-
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.time.Duration;
-import java.time.temporal.TemporalAmount;
-import org.opengis.temporal.Period;
-
-
-/**
- * Default implementation of GeoAPI period. This is a temporary class;
- * GeoAPI temporal interfaces are expected to change a lot in a future 
revision.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class DefaultPeriod extends Primitive implements Period {
-    /** Bounds making this period. */
-    private final Temporal beginning, ending;
-
-    /** Creates a new period with the given bounds. */
-    public DefaultPeriod(final Temporal beginning, final Temporal ending) {
-        this.beginning = beginning;
-        this.ending    = ending;
-    }
-
-    /** The beginning instant at which this period starts. */
-    @Override public Temporal getBeginning() {
-        return beginning;
-    }
-
-    /** The ending instant at which this period ends. */
-    @Override public Temporal getEnding() {
-        return ending;
-    }
-
-    /** Duration of this temporal geometric primitive. */
-    @Override public TemporalAmount length() {
-        return (beginning != null && ending != null) ? 
Duration.between(beginning, ending) : null;
-    }
-
-    /** String representation. */
-    @Override public String toString() {
-        return "[" + beginning + " … " + ending + ']';
-    }
-
-    /** Hash code value of the time position. */
-    @Override public int hashCode() {
-        return Objects.hash(beginning, ending);
-    }
-
-    /** Compares with given object for equality. */
-    @Override public boolean equals(final Object obj) {
-        if (obj instanceof DefaultPeriod) {
-            DefaultPeriod other = (DefaultPeriod) obj;
-            return Objects.equals(other.beginning, beginning) &&
-                   Objects.equals(other.ending, ending);
-        }
-        return false;
-    }
-}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java
deleted file mode 100644
index 0a8220d48c..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/Primitive.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.sis.pending.temporal;
-
-import java.time.temporal.TemporalAmount;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-import org.opengis.temporal.TemporalPrimitive;
-
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
-
-
-/**
- * Base implementation of GeoAPI temporal primitives. This is a temporary 
class;
- * GeoAPI temporal interfaces are expected to change a lot in a future 
revision.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-abstract class Primitive implements TemporalGeometricPrimitive, Identifier {
-    /**
-     * For sub-class constructors.
-     */
-    Primitive() {
-    }
-
-    /**
-     * The primary name by which this object is identified.
-     * This field is inherited from ISO 19111 {@code IdentifiedObject} and is 
in principle mandatory.
-     */
-    @Override
-    public final Identifier getName() {
-        return this;
-    }
-
-    /**
-     * Returns the string representation as the code for this object. This is 
not a correct identifier code,
-     * but we use that as a trick for forcing {@link 
org.apache.sis.util.collection.TreeTableFormat} to show
-     * the temporal value, because the formatter handles {@link 
org.opengis.referencing.IdentifiedObject} in
-     * a special way.
-     */
-    @Override public final String getCode() {
-        return toString();
-    }
-
-    /** Position of this primitive relative to another primitive. */
-    @Override public final RelativePosition relativePosition(TemporalPrimitive 
other) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** Absolute value of the difference between temporal positions. */
-    @Override public final TemporalAmount distance(TemporalGeometricPrimitive 
other) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
deleted file mode 100644
index a8800060f8..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.sis.pending.temporal;
-
-import java.util.Date;
-import java.time.temporal.Temporal;
-import org.opengis.temporal.TemporalPrimitive;
-import org.apache.sis.util.privy.TemporalDate;
-
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.temporal.Period;
-
-
-/**
- * Utilities related to ISO 19108 objects.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @author  Guilhem Legal (Geomatys)
- */
-public final class TemporalUtilities {
-    /**
-     * Do not allow instantiation of this class.
-     */
-    private TemporalUtilities() {
-    }
-
-    /**
-     * Creates an instant for the given Java temporal instant.
-     *
-     * @param  time  the date for which to create instant, or {@code null}.
-     * @return the instant, or {@code null} if the given time was null.
-     */
-    public static TemporalPrimitive createInstant(final Temporal time) {
-        return (time == null) ? null : new DefaultPeriod(time, time);
-    }
-
-    /**
-     * Creates a period for the given begin and end instant.
-     *
-     * @param  beginning  the begin instant (inclusive), or {@code null}.
-     * @param  ending     the end instant (inclusive), or {@code null}.
-     * @return the period, or {@code null} if both arguments are null.
-     *
-     * @todo Needs to avoid assuming UTC timezone.
-     */
-    public static TemporalPrimitive createPeriod(final Temporal beginning, 
final Temporal ending) {
-        return (beginning == null && ending == null) ? null : new 
DefaultPeriod(beginning, ending);
-    }
-
-    /**
-     * Returns the given value as a period if it is not a single point in 
time, or {@code null} otherwise.
-     * This method is mutually exclusive with {@link 
#getInstant(TemporalPrimitive)}: if one method returns
-     * a non-null value, then the other method shall return a null value.
-     *
-     * @param  time  the instant or period for which to get a time range, or 
{@code null}.
-     * @return the period, or {@code null} if none.
-     */
-    public static Period getPeriod(final TemporalPrimitive time) {
-        if (time instanceof Period) {
-            var p = (Period) time;
-            final Temporal begin = p.getBeginning();
-            if (begin != null) {
-                final Temporal end = p.getEnding();
-                if (end != null && !begin.equals(end)) {
-                    return p;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the given value as an ISO 19108 instant (a temporal in Java API)
-     * if the period is a single point in time, or {@code null} otherwise.
-     * This method is mutually exclusive with {@link 
#getPeriod(TemporalPrimitive)}:
-     * if one method returns a non-null value, then the other method shall 
return a null value.
-     *
-     * @param  time  the instant or period for which to get a date, or {@code 
null}.
-     * @return the ISO 19108 instant, or {@code null} if none.
-     */
-    public static Temporal getInstant(final TemporalPrimitive time) {
-        if (time instanceof Period) {
-            var p = (Period) time;
-            final Temporal begin = p.getBeginning();
-            final Temporal end   = p.getEnding();
-            if (end == null) {
-                return begin;
-            }
-            if (begin == null || begin.equals(end)) {
-                return end;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Infers a value from the extent as a {@link Date} object.
-     * This method is used for compatibility with legacy API and may disappear 
in future SIS version.
-     *
-     * @param  time  the instant or period for which to get a date, or {@code 
null}.
-     * @return the requested time as a Java date, or {@code null} if none.
-     */
-    public static Date getAnyDate(final TemporalPrimitive time) {
-        if (time instanceof Period) {
-            var p = (Period) time;
-            Temporal instant;
-            if ((instant = p.getEnding()) != null || (instant = 
p.getBeginning()) != null) {
-                return TemporalDate.toDate(instant);
-            }
-        }
-        return null;
-    }
-}
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
new file mode 100644
index 0000000000..f8c0a5af14
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.DateTimeException;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Period;
+import org.opengis.temporal.Instant;
+import org.opengis.temporal.TemporalPrimitive;
+import org.opengis.temporal.IndeterminateValue;
+import org.opengis.filter.TemporalOperatorName;
+
+
+/**
+ * Default implementation of an instant as defined by ISO 19108.
+ * This is not the same as {@link java.time.Instant}, because the
+ * instant can actually be a date, or may be indeterminate.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class DefaultInstant implements Instant, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 3898772638524283287L;
+
+    /**
+     * The temporal position as a date, time or date/time.
+     * May be {@code null} if {@link #indeterminate} is non-null and not 
"before" or "after".
+     */
+    @SuppressWarnings("serial")         // Standard implementations are 
serializable.
+    private final Temporal position;
+
+    /**
+     * The indeterminate value, or {@code null} if none.
+     */
+    private final IndeterminateValue indeterminate;
+
+    /**
+     * Creates a new instant.
+     *
+     * @param  position       the temporal position, or {@code null} if 
unknown or now.
+     * @param  indeterminate  the indeterminate value, or {@code null} if none.
+     */
+    DefaultInstant(final Temporal position, final IndeterminateValue 
indeterminate) {
+        this.position = position;
+        this.indeterminate = indeterminate;
+    }
+
+    /**
+     * Returns the date, time or position on the time-scale represented by 
this primitive.
+     * Should not be null, unless the value is {@linkplain 
IndeterminateValue#UNKNOWN unknown}.
+     *
+     * @return the date, time or position on the time-scale represented by 
this primitive.
+     */
+    @Override
+    public Temporal getPosition() {
+        if (indeterminate != IndeterminateValue.NOW) {
+            return position;
+        }
+        return java.time.Instant.now();
+    }
+
+    /**
+     * Returns the reason why the temporal position is missing or inaccurate.
+     *
+     * @return the reason why the temporal position is missing or inaccurate.
+     */
+    @Override
+    public Optional<IndeterminateValue> getIndeterminatePosition() {
+        return Optional.ofNullable(indeterminate);
+    }
+
+    /**
+     * Returns the distance from this instant to another instant or period.
+     *
+     * @param  other the other object from which to measure the distance.
+     * @return the distance from this instant to another instant or period.
+     * @throws DateTimeException if the duration cannot be computed.
+     * @throws ArithmeticException if the calculation exceeds the integer 
capacity.
+     */
+    @Override
+    public TemporalAmount distance(final TemporalPrimitive other) {
+        ArgumentChecks.ensureNonNull("other", other);
+        if (other instanceof Instant) {
+            return GeneralDuration.distance(this, (Instant) other, false, 
true);
+        } else if (other instanceof Period) {
+            final var p = (Period) other;
+            TemporalAmount t = GeneralDuration.distance(this, 
p.getBeginning(), false, false);
+            if (t == null) {
+                t = GeneralDuration.distance(this, p.getEnding(), true, false);
+                if (t == null) {
+                    return Duration.ZERO;
+                }
+            }
+            return t;
+        } else {
+            throw new 
DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, 
other.getClass()));
+        }
+    }
+
+    /**
+     * Determines the position of this primitive relative to another temporal 
primitive.
+     * The relative position is identified by an operator which evaluates to 
{@code true}
+     * when the two operands are {@code this} and {@code other}.
+     *
+     * @param  other the other primitive for which to determine the relative 
position.
+     * @return a temporal operator which is true when evaluated between this 
primitive and the other primitive.
+     * @throws DateTimeException if the temporal objects cannot be compared.
+     */
+    @Override
+    public TemporalOperatorName findRelativePosition(final TemporalPrimitive 
other) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Compares this instant with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof DefaultInstant) {
+            final var that = (DefaultInstant) object;
+            return Objects.equals(position, that.position) && indeterminate == 
that.indeterminate;
+        }
+        return false;
+    }
+
+    /**
+     * Computes a hash code value for this instant.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(position, indeterminate);
+    }
+
+    /**
+     * Returns a string representation of this instant.
+     * This is either the date, the indeterminate position (e.g., "now"),
+     * or a combination of both (e.g., "after 2000-01-01").
+     */
+    @Override
+    public String toString() {
+        final var s = new StringBuilder();
+        if (indeterminate != null) {
+            s.append(indeterminate.identifier());
+            if (position != null) {
+                s.append(' ').append(position);
+            }
+        } else {
+            s.append(position);     // Should never be null.
+        }
+        return s.toString();
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
new file mode 100644
index 0000000000..eb5dd82f5c
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.io.Serializable;
+import java.time.temporal.TemporalAmount;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Instant;
+import org.opengis.temporal.Period;
+
+
+/**
+ * Default implementation of GeoAPI period.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class DefaultPeriod implements Period, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 3870895998810224339L;
+
+    /**
+     * Bounds making this period.
+     */
+    @SuppressWarnings("serial")         // Default implementations are 
serializable.
+    private final Instant beginning, ending;
+
+    /**
+     * Creates a new period with the given bounds.
+     */
+    DefaultPeriod(final Instant beginning, final Instant ending) {
+        this.beginning = beginning;
+        this.ending    = ending;
+    }
+
+    /**
+     * Returns the beginning instant at which this period starts.
+     */
+    @Override
+    public Instant getBeginning() {
+        return beginning;
+    }
+
+    /**
+     * Returns the ending instant at which this period ends.
+     */
+    @Override
+    public Instant getEnding() {
+        return ending;
+    }
+
+    /**
+     * Returns the duration of this period.
+     */
+    @Override
+    public TemporalAmount length() {
+        return GeneralDuration.distance(beginning, ending, false, false);
+    }
+
+    /**
+     * Returns a string representation in ISO 8601 format.
+     * The format is {@code <start>/<end>}.
+     */
+    @Override
+    public String toString() {
+        return beginning + "/" + ending;
+    }
+
+    /**
+     * Hash code value of the time position.
+     */
+    @Override
+    public int hashCode() {
+        return beginning.hashCode() + 37 * ending.hashCode();
+    }
+
+    /**
+     * Compares with given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof DefaultPeriod) {
+            final var other = (DefaultPeriod) obj;
+            return beginning.equals(other.beginning) && 
ending.equals(other.ending);
+        }
+        return false;
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
new file mode 100644
index 0000000000..6b9b49ccf7
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.io.Serializable;
+import java.time.Period;
+import java.time.Duration;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.DateTimeException;
+import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoPeriod;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import org.apache.sis.pending.jdk.JDK18;
+import org.apache.sis.util.privy.UnmodifiableArrayList;
+import org.apache.sis.util.resources.Errors;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Instant;
+import org.opengis.temporal.IndeterminateValue;
+import org.opengis.temporal.IndeterminatePositionException;
+
+
+/**
+ * A data type to be used for describing length or distance in the temporal 
dimension.
+ * This implementation combine {@link java.time.Period} with {@link 
java.time.Duration}
+ * for situation where both of them are needed together (which is not 
recommended).
+ *
+ * This class also contains a {@code distance(…)} method for computing the 
distance between two ISO 19108 instants.
+ * This is defined here for reducing class loading in the common case where 
{@code distance(…)} is not invoked.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class GeneralDuration implements TemporalAmount, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -521478824158640275L;
+
+    /**
+     * The period in numbers of years, months and days.
+     * Shall be non-null and non-zero.
+     */
+    private final Period period;
+
+    /**
+     * The time part of the period in numbers of hours, minutes and seconds.
+     * Shall be non-null, non-zero and less than one day.
+     */
+    private final Duration time;
+
+    /**
+     * Creates a new instance with the given parts.
+     * The two parts must be non-null and non-zero.
+     *
+     * @param  period  the period in numbers of years, months and days.
+     * @param  time    the time part of the period in numbers of hours, 
minutes and seconds.
+     */
+    private GeneralDuration(final Period period, final Duration time) {
+        this.period = period;
+        this.time   = time;
+    }
+
+    /**
+     * Returns the temporal position of the given instant if that position is 
determinate or is "now".
+     * Otherwise, throws an exception. If the position is "now", then this 
method returns {@code null}
+     * instead of fetching the current time in order to avoid mismatch when 
comparing two "now" values
+     * that are a few nanoseconds apart.
+     *
+     * @param  t  the instant for which to get the temporal position, or 
{@code null}.
+     * @return temporal position of the given instant, or {@code null} for 
"now".
+     * @throws DateTimeException if the given instant is null or its position 
is indeterminate.
+     */
+    private static Temporal getDeterminatePosition(final Instant t) {
+        if (t != null) {
+            final Optional<IndeterminateValue> p = 
t.getIndeterminatePosition();
+            if (p.isEmpty()) {
+                return t.getPosition();
+            }
+            if (p.get() == IndeterminateValue.NOW) {
+                return null;        // Avoid fetching the current time now.
+            }
+        }
+        throw new 
IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition));
+    }
+
+    /**
+     * Returns the distance between the two given ISO 19108 instants.
+     * If the result is negative, then the return value depends on the {@code 
absolute} argument:
+     * If {@code true}, this method returns the absolute value. Otherwise, it 
returns {@code null}.
+     *
+     * <p>If everything else is equal, methods such as {@link 
ChronoLocalDate#until(ChronoLocalDate)}
+     * will be invoked on the {@code self} instance. It makes a difference in 
the type of the result.
+     * For computing a duration with arguments in the reverse order, the 
{@code negate} parameter can
+     * be set to {@code true}.</p>
+     *
+     * @param  self      the first instant from which to measure the distance.
+     * @param  other     the second instant from which to measure the distance.
+     * @param  negate    whether to negate the result. True for duration from 
{@code other} to {@code self}.
+     * @param  absolute  whether to return absolute value. If false, negative 
result is replaced by null.
+     * @return the distance, or {@code null} if the result is negative and 
{@code absolute} is false.
+     * @throws DateTimeException if the duration cannot be computed.
+     * @throws ArithmeticException if the calculation exceeds the integer 
capacity.
+     */
+    static TemporalAmount distance(final Instant self, final Instant other, 
final boolean negate, final boolean absolute) {
+        /*
+         * Get the temporal value, or null if "now". Other indeterminate 
values cause an exception to be thrown.
+         * We use null for "now" instead of fetching the current time for two 
reasons: avoid mismatch by a few
+         * nanoseconds when comparing `t1` with `t2`, and for choosing a type 
compatible with the other instant.
+         */
+        Temporal t1 = getDeterminatePosition(self);
+        Temporal t2 = getDeterminatePosition(other);
+        if (Objects.equals(t1, t2)) {
+            return Duration.ZERO;
+        }
+        /*
+         * Ensures that the given objects both have a date part, or that none 
of them have a date part.
+         * Note that the "epoch day" field is supported by `LocalDate` as well 
as the dates with zone ID.
+         */
+        boolean hasDate = isSupportedByBoth(ChronoField.EPOCH_DAY, t1, t2);
+        /*
+         * If at least one date has a timezone, then we require that both 
dates have a timezone.
+         * It allows an unambiguous duration in number of days, without 
time-varying months or years.
+         * If one date has a timezone and the other does not, a 
`DateTimeException` will be thrown.
+         *
+         * Note 1: we could be lenient and handle the two dates as if they 
were local, ignoring the timezone.
+         * But we avoid false sense of accuracy for now. We may revisit this 
policy later if there is a need.
+         */
+        if (t1 != null && t1.isSupported(ChronoField.OFFSET_SECONDS)) {
+            if (t2 == null) t2 = ZonedDateTime.now();
+            final Duration p = Duration.between(t1, t2);
+            return (absolute || p.isNegative() == negate) ? p.abs() : null;
+        }
+        if (t2 != null && (!hasDate || 
t2.isSupported(ChronoField.OFFSET_SECONDS))) {
+            if (t1 == null) t1 = ZonedDateTime.now();
+            final Duration p = Duration.between(t2, t1);        // Negative of 
the result.
+            return (absolute || p.isNegative() != negate) ? p.abs() : null;
+        }
+        /*
+         * Ensure that the given temporal objects both have a time part, or 
none of them have a time part.
+         * If only one of them has a time part, we do not interpret the other 
one as an instant at midnight
+         * in order to avoid false sense of accuracy.
+         */
+        final boolean hasTime = isSupportedByBoth(ChronoField.SECOND_OF_DAY, 
t1, t2);
+        if (t1 == null) t1 = LocalDateTime.now();
+        if (t2 == null) t2 = LocalDateTime.now();
+        ChronoLocalDate d1 = null, d2 = null;
+        if (hasDate) {
+            d1 = ChronoLocalDate.from(t1);
+            d2 = ChronoLocalDate.from(t2);
+            if (!absolute && (negate ? d1.isBefore(d2) : d1.isAfter(d2))) {
+                return null;        // Stop early if we can.
+            }
+            hasDate = !d1.isEqual(d2);
+        }
+        /*
+         * Compute the duration in the time part. If negative (after negation 
if `negate` is true),
+         * then we add the necessary number of days to make it positive and 
remove the same number
+         * of days from the date. We adjust the date instead of the period 
computed by `d1.until(d2)`
+         * in order to have the correct adjustment for the variable number of 
days in each month.
+         */
+        Duration time = Duration.ZERO;
+        if (hasTime) {
+            time = Duration.between(LocalTime.from(t1), LocalTime.from(t2));
+            if (hasDate) {
+                if (negate ? JDK18.isPositive(time) : time.isNegative()) {
+                    long n = time.toDays();                     // Truncated 
toward 0.
+                    if (negate) {
+                        d1 = d1.plus(++n, ChronoUnit.DAYS);     // `n` is 
positive. Reduces period by increasing the beginning.
+                    } else {
+                        d2 = d2.plus(--n, ChronoUnit.DAYS);     // `n` is 
negative. Reduces period by decreasing the ending.
+                    }
+                    time = time.minusDays(n);                   // If 
negative, make positive. If positive, make negative.
+                }
+            }
+        }
+        /*
+         * Get the period for the date part, then combine with the time part 
if non-zero.
+         * The result shall be either positive or null.
+         */
+        if (hasDate) {
+            ChronoPeriod period = d1.until(d2);
+            if (!period.isZero()) {
+                if (period.isNegative()) {
+                    if (!(negate | absolute)) {                 // Equivalent 
to (!negate && !absolute).
+                        return null;
+                    }
+                    period = period.negated();
+                } else if (negate & !absolute) {                // Equivalent 
to (negate && !absolute).
+                    return null;
+                }
+                return time.isZero() ? period : new 
GeneralDuration(Period.from(period), time.abs());
+            }
+        }
+        return (absolute || time.isNegative() == negate) ? time.abs() : null;
+    }
+
+    /**
+     * Verifies if the given field is supported by both temporal objects.
+     * Either the field is supported by both objects, or either it is 
supported by none of them.
+     * If one object support the field and the other does not, the two objects 
are considered incompatible.
+     * At least one of the given objects shall be non-null.
+     *
+     * @param  field  the field to test.
+     * @param  t1     the first temporal object, or {@code null} for "now".
+     * @param  t2     the second temporal object, or {@code null} for "now".
+     * @return whether the given object is supported.
+     * @throws DateTimeException if the two objects are incompatible.
+     */
+    private static boolean isSupportedByBoth(final ChronoField field, final 
Temporal t1, final Temporal t2) {
+        final boolean isSupported = (t1 != null ? t1 : t2).isSupported(field);
+        if (t1 != null && t2 != null && isSupported != t2.isSupported(field)) {
+            throw new 
DateTimeException(Errors.format(Errors.Keys.CanNotConvertFromType_2,
+                    (isSupported ? t2 : t1).getClass(),
+                    (isSupported ? t1 : t2).getClass()));
+        }
+        return isSupported;
+    }
+
+    /**
+     * Returns the list of units that are supported by this implementation.
+     * This is the union of the units supported by the date part and by the 
time part.
+     */
+    @Override
+    public List<TemporalUnit> getUnits() {
+        var prefix = period.getUnits();
+        var suffix = time.getUnits();
+        int i      = prefix.size();
+        var units  = prefix.toArray(new TemporalUnit[i + suffix.size()]);
+        for (TemporalUnit unit : suffix) {
+            units[i++] = unit;
+        }
+        return UnmodifiableArrayList.wrap(units);
+    }
+
+    /**
+     * Returns the value of the requested unit.
+     *
+     * @param  unit  the unit to get;
+     * @return value of the specified unit.
+     * @throws UnsupportedTemporalTypeException if the {@code unit} is not 
supported.
+     */
+    @Override
+    public long get(final TemporalUnit unit) {
+        return (unit.isDateBased() ? period : time).get(unit);
+    }
+
+    /**
+     * Adds this duration to the specified temporal object.
+     *
+     * @param temporal  the temporal object to add the amount to.
+     * @return an object with the addition done.
+     */
+    @Override
+    public Temporal addTo(Temporal temporal) {
+        return temporal.plus(period).plus(time);
+    }
+
+    /**
+     * Subtracts this duration from the specified temporal object.
+     *
+     * @param temporal  the temporal object to subtract the amount from.
+     * @return an object with the subtraction done.
+     */
+    @Override
+    public Temporal subtractFrom(Temporal temporal) {
+        return temporal.minus(period).minus(time);
+    }
+
+    /**
+     * Compares this duration with the given object for equality.
+     *
+     * @param  object  the object to compare with this duration.
+     * @return whether the two objects are equal.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object instanceof GeneralDuration) {
+            final var other = (GeneralDuration) object;
+            return period.equals(other.period) && time.equals(other.time);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this duration.
+     *
+     * @return a hash code value for this duration.
+     */
+    @Override
+    public int hashCode() {
+        return period.hashCode() * 31 + time.hashCode();
+    }
+
+    /**
+     * Returns this duration in ISO 8601 format.
+     */
+    @Override
+    public String toString() {
+        return period.toString() + time.toString().substring(1);
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/StandardDateFormat.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/StandardDateFormat.java
similarity index 99%
rename from 
endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/StandardDateFormat.java
rename to 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/StandardDateFormat.java
index 6c4ee5e27f..ce58461976 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/StandardDateFormat.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/StandardDateFormat.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.util.privy;
+package org.apache.sis.temporal;
 
 import java.util.Calendar;
 import java.util.Date;
@@ -42,6 +42,7 @@ import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.DateTimeParseException;
 import java.time.format.SignStyle;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/TemporalDate.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
similarity index 86%
rename from 
endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/TemporalDate.java
rename to 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
index 495237bbb7..86aa1a6259 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/TemporalDate.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.util.privy;
+package org.apache.sis.temporal;
 
 import java.util.Date;
 import java.time.DateTimeException;
@@ -23,10 +23,8 @@ import java.time.LocalTime;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
-import java.time.OffsetTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
 import java.time.temporal.Temporal;
 import java.time.temporal.ChronoField;
 import java.time.temporal.TemporalAccessor;
@@ -34,6 +32,7 @@ import java.time.chrono.ChronoZonedDateTime;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.privy.Constants;
 
 
 /**
@@ -198,39 +197,6 @@ public final class TemporalDate extends Date implements 
LenientComparable {
         return time.plusSeconds(r).plusNanos(Math.round((value - r) * 
Constants.NANOS_PER_SECOND));
     }
 
-    /**
-     * Returns {@code true} if objects of the given class have day, month and 
hour fields.
-     * This method is defined here for having a single class where to 
concentrate such heuristic rules.
-     * Note that {@link Instant} does not have date fields.
-     *
-     * @param  date  class of object to test (may be {@code null}).
-     * @return whether the given class is {@link LocalDate} or one of the 
classes with date + time.
-     *         This list may be expanded in future versions.
-     */
-    public static boolean hasDateFields(final Class<?> date) {
-        return date == LocalDate.class
-            || date == LocalDateTime.class
-            || date == OffsetDateTime.class
-            || date == ZonedDateTime.class;
-    }
-
-    /**
-     * Returns {@code true} if objects of the given class have time fields.
-     * This method is defined here for having a single class where to 
concentrate such heuristic rules.
-     * Note that {@link Instant} does not have hour fields.
-     *
-     * @param  date  class of object to test (may be {@code null}).
-     * @return whether the given class is {@link LocalTime}, {@link 
OffsetTime} or one of the classes with date + time.
-     *         This list may be expanded in future versions.
-     */
-    public static boolean hasTimeFields(final Class<?> date) {
-        return date == LocalTime.class
-            || date == OffsetTime.class
-            || date == LocalDateTime.class
-            || date == OffsetDateTime.class
-            || date == ZonedDateTime.class;
-    }
-
     /**
      * Compares two temporal objects that may not be of the same type.
      * First, this method tries to compare the positions on the timeline.
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java
new file mode 100644
index 0000000000..250fa7ef56
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.util.Date;
+import java.time.temporal.Temporal;
+import org.opengis.temporal.TemporalPrimitive;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.IndeterminateValue;
+import org.opengis.temporal.Instant;
+import org.opengis.temporal.Period;
+
+
+/**
+ * Utilities related to ISO 19108 objects.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Guilhem Legal (Geomatys)
+ */
+public final class TemporalUtilities {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private TemporalUtilities() {
+    }
+
+    /**
+     * Creates an instant for the given Java temporal instant.
+     *
+     * @param  time  the date for which to create instant, or {@code null}.
+     * @return the instant, or an unknown instant if the given time was null.
+     */
+    public static Instant createInstant(final Temporal time) {
+        return new DefaultInstant(time, (time != null) ? null : 
IndeterminateValue.UNKNOWN);
+    }
+
+    /**
+     * Creates a period for the given begin and end instant.
+     *
+     * @param  beginning  the begin instant (inclusive), or {@code null}.
+     * @param  ending     the end instant (exclusive), or {@code null}.
+     * @return the period, or {@code null} if both arguments are null.
+     */
+    public static Period createPeriod(final Temporal beginning, final Temporal 
ending) {
+        if (beginning == null && ending == null) {
+            return null;
+        }
+        return new DefaultPeriod(createInstant(beginning), 
createInstant(ending));
+    }
+
+    /**
+     * Returns the given value as a temporal position, or {@code null} if not 
available.
+     *
+     * @param  time  the instant or period for which to get a date, or {@code 
null}.
+     * @return the temporal position, or {@code null} if indeterminate.
+     */
+    public static Temporal getInstant(final TemporalPrimitive time) {
+        if (time instanceof Instant) {
+            return ((Instant) time).getPosition();
+        }
+        return null;
+    }
+
+    /**
+     * Infers a value from the instant or extent as a {@link Date} object.
+     * This method is used for compatibility with legacy API and may disappear 
in future SIS version.
+     *
+     * @param  time  the instant or period for which to get a date, or {@code 
null}.
+     * @return the requested time as a Java date, or {@code null} if none.
+     */
+    public static Date getAnyDate(final TemporalPrimitive time) {
+        Temporal t = null;
+        if (time instanceof Instant) {
+            t = ((Instant) time).getPosition();
+        } else if (time instanceof Period) {
+            final var p = (Period) time;
+            Instant i = p.getEnding();      // Should never be null, but we 
are paranoiac.
+            if (i == null || (t = i.getPosition()) == null) {
+                i = p.getBeginning();
+                if (i != null) {
+                    t = i.getPosition();
+                }
+            }
+        }
+        return TemporalDate.toDate(t);
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/package-info.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/package-info.java
similarity index 70%
rename from 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/package-info.java
rename to 
endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/package-info.java
index bc684a2a97..d6e0dce1ee 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/package-info.java
@@ -16,11 +16,10 @@
  */
 
 /**
- * Place-holder for a future implementation of {@code org.opengis.temporal} 
interfaces.
- * Those interfaces should be derived from ISO 19108, but they overlap with 
{@code java.time} standard API.
- * Furthermore, the ISO 19108 standard also overlaps with ISO 19111. How to 
resolve those overlaps has not
- * yet been decided.
+ * Implementation of {@code org.opengis.temporal} interfaces.
+ * Contains also utility methods related to {@code java.time}.
+ * This package is for internal use by Apache SIS only.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-package org.apache.sis.pending.temporal;
+package org.apache.sis.temporal;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
index 4596c8becb..8b5eff28bc 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
@@ -23,7 +23,7 @@ import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.xml.privy.XmlUtilities;
 import org.apache.sis.xml.bind.Context;
 import org.apache.sis.xml.bind.gco.PropertyType;
-import org.apache.sis.pending.temporal.TemporalUtilities;
+import org.apache.sis.temporal.TemporalUtilities;
 import org.apache.sis.util.resources.Errors;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -82,8 +82,10 @@ public class TM_Primitive extends PropertyType<TM_Primitive, 
TemporalPrimitive>
      */
     @XmlElement(name = "TimePeriod")
     public final TimePeriod getTimePeriod() {
-        Period period = TemporalUtilities.getPeriod(metadata);
-        return (period != null) ? new TimePeriod(period) : null;
+        if (metadata instanceof Period) {
+            return new TimePeriod((Period) metadata);
+        }
+        return null;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
index ab2ee6fb11..5b51345ec5 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
@@ -16,13 +16,15 @@
  */
 package org.apache.sis.xml.bind.gml;
 
-import java.time.temporal.Temporal;
 import javax.xml.datatype.XMLGregorianCalendar;
 import jakarta.xml.bind.annotation.XmlValue;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlTransient;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Instant;
+
 
 /**
  * The {@linkplain TimePeriod#begin begin} or {@linkplain TimePeriod#end end} 
position in
@@ -98,9 +100,14 @@ public abstract class TimePeriodBound {
          * @param instant        the instant of the new bound, or {@code null}.
          * @param indeterminate  the value to give to {@link 
#indeterminatePosition} if the date is null.
          */
-        GML3(final Temporal instant, final String indeterminate) {
-            value = TimeInstant.toXML(instant);
-            if (value == null) {
+        GML3(final Instant instant, final String indeterminate) {
+            if (instant != null) {
+                value = TimeInstant.toXML(instant.getPosition());
+                if (value == null) {
+                    instant.getIndeterminatePosition().ifPresent((p) -> 
indeterminatePosition = p.identifier());
+                }
+            }
+            if (value == null && indeterminatePosition == null) {
                 indeterminatePosition = indeterminate;
             }
         }
@@ -154,8 +161,8 @@ public abstract class TimePeriodBound {
          *
          * @param instant The instant of the new bound, or {@code null}.
          */
-        GML2(final Temporal instant) {
-            timeInstant = new TimeInstant(instant);
+        GML2(final Instant instant) {
+            timeInstant = new TimeInstant((instant != null) ? 
instant.getPosition() : null);
         }
 
         /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/privy/XmlUtilities.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/privy/XmlUtilities.java
index 2806f5d462..5fb093eaf8 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/privy/XmlUtilities.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/privy/XmlUtilities.java
@@ -48,7 +48,7 @@ import static 
javax.xml.datatype.DatatypeConstants.FIELD_UNDEFINED;
 import org.apache.sis.system.SystemListener;
 import org.apache.sis.system.Modules;
 import org.apache.sis.xml.bind.Context;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
index 92fa66b8ac..55e52af59c 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
@@ -47,14 +47,14 @@ public final class DefaultCitationDateTest extends TestCase 
{
      */
     @Test
     public void testCopyConstructor() {
-        final CitationDate original = new CitationDate() {
+        final var original = new CitationDate() {
             @Override public Temporal getReferenceDate() {return 
Instant.ofEpochMilli(1305716658508L);}
             @Override public DateType getDateType()      {return 
DateType.CREATION;}
         };
-        final DefaultCitationDate copy = new DefaultCitationDate(original);
+        final var copy = new DefaultCitationDate(original);
         assertEquals(Instant.ofEpochMilli(1305716658508L), 
copy.getReferenceDate());
         assertEquals(DateType.CREATION, copy.getDateType());
-        assertTrue (copy.equals(original, ComparisonMode.BY_CONTRACT));
-        assertFalse(copy.equals(original, ComparisonMode.STRICT)); // 
Opportunist test.
+        assertTrue (copy.equals(original, ComparisonMode.DEBUG));
+        assertFalse(copy.equals(original, ComparisonMode.STRICT));          // 
Opportunist test.
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java
new file mode 100644
index 0000000000..d175036219
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.time.LocalDate;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.IndeterminateValue;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.test.TestCase;
+
+
+/**
+ * Tests the {@link DefaultInstant} class.
+ *
+ * @author  Mehdi Sidhoum (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class DefaultInstantTest extends TestCase {
+    /**
+     * Creates a new test case.
+     */
+    public DefaultInstantTest() {
+    }
+
+    /**
+     * Tests {@link DefaultInstant#getPosition()}.
+     * Opportunistically tests {@link 
TemporalUtilities#createInstant(Temporal)} too.
+     */
+    @Test
+    public void testGetPosition() {
+        var date    = LocalDate.of(2010, 5, 1);
+        var instant = TemporalUtilities.createInstant(date);
+        assertEquals(date, instant.getPosition());
+    }
+
+    /**
+     * Test of equals and hash code methods.
+     */
+    @Test
+    public void testEquals() {
+        var instant1 = new DefaultInstant(LocalDate.of(2000, 1, 1), null);
+        var instant2 = new DefaultInstant(LocalDate.of(1988, 1, 1), null);
+        var instant3 = new DefaultInstant(LocalDate.of(1988, 1, 1), null);
+
+        assertNotEquals(instant1, instant2);
+        assertNotEquals(instant1.hashCode(), instant2.hashCode());
+
+        assertEquals(instant3, instant2);
+        assertEquals(instant3.hashCode(), instant2.hashCode());
+    }
+
+    /**
+     * Tests {@link DefaultInstant#toString()}.
+     */
+    @Test
+    public void testToString() {
+        var date = LocalDate.of(2010, 5, 1);
+        assertEquals("2010-05-01", new DefaultInstant(date, null).toString());
+        assertEquals("after 2010-05-01", new DefaultInstant(date, 
IndeterminateValue.AFTER).toString());
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java
new file mode 100644
index 0000000000..6ea04f0a11
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.temporal;
+
+import java.time.LocalDate;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.test.TestCase;
+
+
+/**
+ * Tests the {@link DefaultPeriod} class.
+ *
+ * @author  Mehdi Sidhoum (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class DefaultPeriodTest extends TestCase {
+    /**
+     * Creates a new test case.
+     */
+    public DefaultPeriodTest() {
+    }
+
+    /**
+     * Tests {@link DefaultPeriod#getBeginning()} and {@link 
DefaultPeriod#getEnding()}.
+     * Opportunistically tests {@link TemporalUtilities#createPeriod(Temporal, 
Temporal)} too.
+     */
+    @Test
+    public void testBounds() {
+        var beginning = LocalDate.of(2010, 5, 1);
+        var ending    = LocalDate.of(2015, 8, 6);
+        var period    = TemporalUtilities.createPeriod(beginning, ending);
+        assertEquals(beginning, period.getBeginning().getPosition());
+        assertEquals(ending,    period.getEnding().getPosition());
+    }
+
+    /**
+     * Test of equals and hash code methods.
+     */
+    @Test
+    public void testEquals() {
+        var p1 = TemporalUtilities.createPeriod(LocalDate.of(2000, 1, 1), 
LocalDate.of(2010, 1, 1));
+        var p2 = TemporalUtilities.createPeriod(LocalDate.of(1988, 1, 1), 
LocalDate.of(2010, 1, 1));
+        var p3 = TemporalUtilities.createPeriod(LocalDate.of(1988, 1, 1), 
LocalDate.of(2010, 1, 1));
+
+        assertNotEquals(p1, p2);
+        assertNotEquals(p1.hashCode(), p2.hashCode());
+
+        assertEquals(p3, p2);
+        assertEquals(p3.hashCode(), p2.hashCode());
+    }
+
+    /**
+     * Tests {@link DefaultPeriod#toString()}.
+     */
+    @Test
+    public void testToString() {
+        var p1 = TemporalUtilities.createPeriod(LocalDate.of(2000, 1, 1), 
LocalDate.of(2010, 1, 1));
+        assertEquals("2000-01-01/2010-01-01", p1.toString());
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/privy/StandardDateFormatTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/StandardDateFormatTest.java
similarity index 99%
rename from 
endorsed/src/org.apache.sis.util/test/org/apache/sis/util/privy/StandardDateFormatTest.java
rename to 
endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/StandardDateFormatTest.java
index d20a8c7adf..01e4489973 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/privy/StandardDateFormatTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/StandardDateFormatTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.util.privy;
+package org.apache.sis.temporal;
 
 import java.time.Instant;
 import java.time.LocalDate;
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gml/TimePeriodTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gml/TimePeriodTest.java
index 667ae3cd1a..9934516095 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gml/TimePeriodTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gml/TimePeriodTest.java
@@ -30,6 +30,7 @@ import org.apache.sis.xml.XML;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.MarshallerPool;
 import org.apache.sis.xml.privy.XmlUtilities;
+import org.apache.sis.temporal.TemporalUtilities;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
@@ -108,8 +109,8 @@ public final class TimePeriodTest extends TestCase {
     @Test
     public void testPeriodGML2() throws JAXBException {
         createContext();
-        final var begin = new 
TimePeriodBound.GML2(Instant.parse("1992-01-01T00:00:00Z"));
-        final var end   = new 
TimePeriodBound.GML2(Instant.parse("2007-12-31T00:00:00Z"));
+        final var begin = new 
TimePeriodBound.GML2(TemporalUtilities.createInstant(Instant.parse("1992-01-01T00:00:00Z")));
+        final var end   = new 
TimePeriodBound.GML2(TemporalUtilities.createInstant(Instant.parse("2007-12-31T00:00:00Z")));
         testPeriod(begin, end,
                 "<gml:TimePeriod xmlns:gml=\"" + Namespaces.GML + "\">\n" +
                 "  <gml:begin>\n" +
@@ -160,8 +161,8 @@ public final class TimePeriodTest extends TestCase {
     @Test
     public void testPeriodGML3() throws JAXBException {
         createContext();
-        final var begin = new 
TimePeriodBound.GML3(Instant.parse("1992-01-01T00:00:00Z"), "before");
-        final var end   = new 
TimePeriodBound.GML3(Instant.parse("2007-12-31T00:00:00Z"), "after");
+        final var begin = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(Instant.parse("1992-01-01T00:00:00Z")),
 "before");
+        final var end   = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(Instant.parse("2007-12-31T00:00:00Z")),
 "after");
         testPeriod(begin, end,
                 "<gml:TimePeriod xmlns:gml=\"" + Namespaces.GML + "\">\n" +
                 "  
<gml:beginPosition>1992-01-01T01:00:00+01:00</gml:beginPosition>\n" +
@@ -178,8 +179,8 @@ public final class TimePeriodTest extends TestCase {
     @Test
     public void testSimplifiedPeriodGML3() throws JAXBException {
         createContext();
-        final var begin = new TimePeriodBound.GML3(LocalDate.of(1992,  1,  2), 
"before");
-        final var end   = new TimePeriodBound.GML3(LocalDate.of(2007, 12, 31), 
"after");
+        final var begin = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(LocalDate.of(1992,  1,  
2)), "before");
+        final var end   = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(LocalDate.of(2007, 12, 
31)), "after");
         testPeriod(begin, end,
                 "<gml:TimePeriod xmlns:gml=\"" + Namespaces.GML + "\">\n" +
                 "  <gml:beginPosition>1992-01-02</gml:beginPosition>\n" +
@@ -197,7 +198,7 @@ public final class TimePeriodTest extends TestCase {
     public void testBeforePeriodGML3() throws JAXBException {
         createContext();
         final var begin = new TimePeriodBound.GML3(null, "before");
-        final var end   = new TimePeriodBound.GML3(LocalDate.of(2007, 12, 31), 
"after");
+        final var end   = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(LocalDate.of(2007, 12, 
31)), "after");
         testPeriod(begin, end,
                 "<gml:TimePeriod xmlns:gml=\"" + Namespaces.GML + "\">\n" +
                 "  <gml:beginPosition indeterminatePosition=\"before\"/>\n" +
@@ -214,7 +215,7 @@ public final class TimePeriodTest extends TestCase {
     @Test
     public void testAfterPeriodGML3() throws JAXBException {
         createContext();
-        final var begin = new TimePeriodBound.GML3(LocalDate.of(1992, 1, 2), 
"before");
+        final var begin = new 
TimePeriodBound.GML3(TemporalUtilities.createInstant(LocalDate.of(1992, 1, 2)), 
"before");
         final var end   = new TimePeriodBound.GML3(null, "after");
         testPeriod(begin, end,
                 "<gml:TimePeriod xmlns:gml=\"" + Namespaces.GML + "\">\n" +
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
index efc89b49e2..7ffd22a0b4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java
@@ -59,13 +59,13 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.privy.LocalizedParseException;
-import org.apache.sis.util.privy.TemporalDate;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.Numerics;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.measure.Angle;
 import org.apache.sis.measure.AngleFormat;
 import org.apache.sis.measure.Latitude;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
index 63087a09c4..b4c8de9054 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/AbstractParser.java
@@ -37,7 +37,7 @@ import org.opengis.util.InternationalString;
 import org.apache.sis.system.Loggers;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.measure.Units;
 import org.apache.sis.measure.UnitFormat;
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index 7fa79fc822..4fa4993c0d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@ -72,8 +72,8 @@ import org.apache.sis.util.collection.IntegerList;
 import org.apache.sis.util.privy.X364;
 import org.apache.sis.util.privy.Numerics;
 import org.apache.sis.util.privy.Constants;
-import org.apache.sis.util.privy.TemporalDate;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.TemporalDate;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.system.Configuration;
 import org.apache.sis.metadata.simple.SimpleExtent;
 import org.apache.sis.metadata.iso.extent.Extents;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
index f95fd49678..e8cb817f10 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java
@@ -54,9 +54,9 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.system.Loggers;
 import org.apache.sis.util.privy.Constants;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.system.Loggers;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.privy.ReferencingFactoryContainer;
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index 917dcaaebc..5e730428f7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
@@ -35,7 +35,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.metadata.privy.Identifiers;
 import org.apache.sis.metadata.privy.NameToIdentifier;
 import org.apache.sis.metadata.privy.ImplementationHelper;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 7343d3db34..71296d4885 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -92,9 +92,9 @@ import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.CollectionsExt;
-import org.apache.sis.util.privy.StandardDateFormat;
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.privy.URLs;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtentSelector.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtentSelector.java
index d9c7155512..ea947ae67b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtentSelector.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ExtentSelector.java
@@ -27,8 +27,8 @@ import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.Range;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.privy.TemporalDate;
 import org.apache.sis.util.privy.Constants;
+import org.apache.sis.temporal.TemporalDate;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
index 949b1c0a2d..b933292c8f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/MetadataVerticalTest.java
@@ -39,7 +39,7 @@ import org.opengis.referencing.datum.VerticalDatum;
 import org.apache.sis.system.Loggers;
 import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
index 87fa31eaba..9c34f8352d 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
@@ -40,7 +40,7 @@ import javax.xml.transform.stax.StAXSource;
 import javax.xml.namespace.QName;
 import jakarta.xml.bind.JAXBException;
 import org.apache.sis.io.stream.ChannelDataInput;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.storage.base.MetadataBuilder;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.geotiff.base.Tags;
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/XMLMetadataTest.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/XMLMetadataTest.java
index ea8bf109af..53ec6be1af 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/XMLMetadataTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/reader/XMLMetadataTest.java
@@ -138,7 +138,7 @@ public final class XMLMetadataTest extends TestCase {
                 "      ├─Citation……………………………… My image\n" +
                 "      └─Extent\n" +
                 "          └─Temporal element\n" +
-                "              └─Extent……………… [2018-02-28T03:48:00Z … 
2018-02-28T04:04:00Z]\n",
+                "              └─Extent……………… 
2018-02-28T03:48:00Z/2018-02-28T04:04:00Z\n",
                 metadata.toString());
     }
 
@@ -158,7 +158,7 @@ public final class XMLMetadataTest extends TestCase {
                 "  └─Identification info\n" +
                 "      └─Extent\n" +
                 "          └─Temporal element\n" +
-                "              └─Extent……………… [2018-02-28T03:04Z … 
2018-02-28T04:48Z]\n",
+                "              └─Extent……………… 
2018-02-28T03:04Z/2018-02-28T04:48Z\n",
                 metadata.toString());
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java
index 850a2606d5..44f971a890 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/ChannelDecoder.java
@@ -58,12 +58,12 @@ import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.CollectionsExt;
-import org.apache.sis.util.privy.StandardDateFormat;
-import org.apache.sis.util.privy.TemporalDate;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.temporal.StandardDateFormat;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/VariableInfo.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/VariableInfo.java
index a64cdfe017..3604d9344a 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/VariableInfo.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/classic/VariableInfo.java
@@ -50,11 +50,11 @@ import org.apache.sis.io.stream.Region;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
-import org.apache.sis.util.privy.StandardDateFormat;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
 
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
index cb57b2828f..8bb79e7d3d 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
@@ -31,7 +31,7 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreMock;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.netcdf.ucar.DecoderWrapper;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.setup.GeometryLibrary;
 
 // Test dependencies
diff --git 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
index 9186fcc28c..998733e41d 100644
--- 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
+++ 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/xml/stream/StaxStreamReader.java
@@ -40,8 +40,8 @@ import jakarta.xml.bind.JAXBElement;
 import jakarta.xml.bind.JAXBException;
 import org.apache.sis.xml.bind.Context;
 import org.apache.sis.util.privy.Strings;
-import org.apache.sis.util.privy.StandardDateFormat;
 import org.apache.sis.io.stream.IOUtilities;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.util.collection.BackingStoreException;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 3c2685401a..89eca3c4cf 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@ -52,7 +52,7 @@ import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.util.privy.Numerics;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.StandardDateFormat;
 import org.apache.sis.storage.DataOptionKey;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
index 1571c6b88b..b8ecf2b5f9 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
@@ -22,8 +22,8 @@ import javax.measure.Unit;
 import javax.measure.quantity.Time;
 import org.opengis.referencing.datum.TemporalDatum;
 import org.apache.sis.converter.SurjectiveConverter;
-import org.apache.sis.util.privy.StandardDateFormat;
-import org.apache.sis.util.privy.TemporalDate;
+import org.apache.sis.temporal.StandardDateFormat;
+import org.apache.sis.temporal.TemporalDate;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.measure.Units;
 
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/RangeFormat.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/RangeFormat.java
index 30f8441fd2..24326f7b41 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/RangeFormat.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/RangeFormat.java
@@ -35,6 +35,12 @@ import java.text.ParsePosition;
 import java.time.Instant;
 import java.time.format.FormatStyle;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZonedDateTime;
 import java.time.temporal.Temporal;
 import java.lang.reflect.InaccessibleObjectException;
 import javax.measure.Unit;
@@ -43,7 +49,6 @@ import org.apache.sis.util.Localized;
 import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.privy.LocalizedParseException;
-import org.apache.sis.util.privy.TemporalDate;
 import org.apache.sis.util.privy.Numerics;
 
 
@@ -362,8 +367,8 @@ public class RangeFormat extends Format implements 
Localized {
             elementFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, 
DateFormat.SHORT, locale);
             unitFormat    = null;
         } else if (Temporal.class.isAssignableFrom(elementType)) {
-            final FormatStyle dateStyle = 
TemporalDate.hasDateFields(elementType) ? FormatStyle.SHORT : null;
-            final FormatStyle timeStyle = 
TemporalDate.hasTimeFields(elementType) ? FormatStyle.SHORT : null;
+            final FormatStyle dateStyle = hasDateFields(elementType) ? 
FormatStyle.SHORT : null;
+            final FormatStyle timeStyle = hasTimeFields(elementType) ? 
FormatStyle.SHORT : null;
             elementFormat = new 
DateTimeFormatterBuilder().appendLocalized(dateStyle, 
timeStyle).toFormatter(locale).toFormat();
             unitFormat    = null;
         } else {
@@ -402,6 +407,39 @@ public class RangeFormat extends Format implements 
Localized {
         return (c == closeInclusive) || (c == closeExclusive) || (c == 
closeExclusiveAlt);
     }
 
+    /**
+     * Returns {@code true} if objects of the given class have day, month and 
hour fields.
+     * This method is defined here for having a single class where to 
concentrate such heuristic rules.
+     * Note that {@link Instant} does not have date fields.
+     *
+     * @param  date  class of object to test (may be {@code null}).
+     * @return whether the given class is {@link LocalDate} or one of the 
classes with date + time.
+     *         This list may be expanded in future versions.
+     */
+    public static boolean hasDateFields(final Class<?> date) {
+        return date == LocalDate.class
+            || date == LocalDateTime.class
+            || date == OffsetDateTime.class
+            || date == ZonedDateTime.class;
+    }
+
+    /**
+     * Returns {@code true} if objects of the given class have time fields.
+     * This method is defined here for having a single class where to 
concentrate such heuristic rules.
+     * Note that {@link Instant} does not have hour fields.
+     *
+     * @param  date  class of object to test (may be {@code null}).
+     * @return whether the given class is {@link LocalTime}, {@link 
OffsetTime} or one of the classes with date + time.
+     *         This list may be expanded in future versions.
+     */
+    public static boolean hasTimeFields(final Class<?> date) {
+        return date == LocalTime.class
+            || date == OffsetTime.class
+            || date == LocalDateTime.class
+            || date == OffsetDateTime.class
+            || date == ZonedDateTime.class;
+    }
+
     /**
      * Returns this formatter locale. This is the locale specified at 
construction time if any,
      * or the {@linkplain Locale#getDefault() default locale} at construction 
time otherwise.
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK18.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK18.java
index 3596bf6da7..419bcc225f 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK18.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK18.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.pending.jdk;
 
+import java.time.Duration;
+
 
 /**
  * Place holder for some functionalities defined in a JDK more recent than 
Java 11.
@@ -62,4 +64,13 @@ public final class JDK18 {
         }
         return r;
     }
+
+    /**
+     * Checks if the duration is positive, excluding zero.
+     *
+     * @return true if this duration has a total length greater than zero.
+     */
+    public static boolean isPositive(final Duration d) {
+        return !(d.isNegative() || d.isZero());
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
index 3ebaf7cd64..e8cc5e393d 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
@@ -501,6 +501,11 @@ public class Errors extends IndexedResourceBundle {
          */
         public static final short InconsistentUnitsForCS_1 = 87;
 
+        /**
+         * The position is indeterminate.
+         */
+        public static final short IndeterminatePosition = 206;
+
         /**
          * Index {0} is out of bounds.
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
index 955faafe3e..c348e5bafc 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
@@ -112,6 +112,7 @@ IncompatibleUnitDimension_5       = The \u201c{0}\u201d 
unit of measurement has
 InconsistentAttribute_2           = Value \u201c{1}\u201d of attribute 
\u2018{0}\u2019 is inconsistent with other attributes.
 InconsistentTableColumns          = Inconsistent table columns.
 InconsistentUnitsForCS_1          = Unit of measurement \u201c{0}\u201d is 
inconsistent with coordinate system axes.
+IndeterminatePosition             = The position is indeterminate.
 IndexOutOfBounds_1                = Index {0} is out of bounds.
 IndicesOutOfBounds_2              = Indices ({0}, {1}) are out of bounds.
 InfiniteArgumentValue_1           = Argument \u2018{0}\u2019 cannot take an 
infinite value.
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
index cfc7abb465..ef040968eb 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
@@ -109,6 +109,7 @@ IncompatibleUnitDimension_5       = L\u2019unit\u00e9 de 
mesure \u00ab\u202f{0}\
 InconsistentAttribute_2           = La valeur \u00ab\u202f{1}\u202f\u00bb de 
l\u2019attribut \u2018{0}\u2019 n\u2019est pas coh\u00e9rente avec celles des 
autres attributs.
 InconsistentTableColumns          = Les colonnes des tables ne sont pas 
coh\u00e9rentes.
 InconsistentUnitsForCS_1          = L\u2019unit\u00e9 de mesure 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas coh\u00e9rente avec les axes du 
syst\u00e8me de coordonn\u00e9es.
+IndeterminatePosition             = La position est ind\u00e9termin\u00e9e.
 IndexOutOfBounds_1                = L\u2019index {0} est en dehors des limites 
permises.
 IndicesOutOfBounds_2              = Les index ({0}, {1}) sont en dehors des 
limites permises.
 InfiniteArgumentValue_1           = L\u2019argument \u2018{0}\u2019 ne peut 
pas prendre une valeur infinie.
diff --git a/geoapi/snapshot b/geoapi/snapshot
index df67b0319b..044faffa03 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit df67b0319be5ed161459bfa804ff3e34f366ce3d
+Subproject commit 044faffa03ac7337c5c3d5af3fe73fb9a30cce05
diff --git a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java 
b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java
index 14f8a580de..c747caff37 100644
--- a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java
+++ b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/CQL.java
@@ -61,7 +61,7 @@ import 
org.apache.sis.cql.internal.CQLParser.ExpressionUnaryContext;
 import org.apache.sis.cql.internal.CQLParser.FilterContext;
 import org.apache.sis.cql.internal.CQLParser.FilterGeometryContext;
 import org.apache.sis.cql.internal.CQLParser.FilterTermContext;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.StandardDateFormat;
 import static org.apache.sis.cql.internal.CQLParser.*;
 
 
diff --git 
a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
 
b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
index b7a24b8b69..aebf59b3cb 100644
--- 
a/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
+++ 
b/incubator/src/org.apache.sis.cql/main/org/apache/sis/cql/FilterToCQLVisitor.java
@@ -48,7 +48,7 @@ import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.geometry.wrapper.GeometryWrapper;
 import org.apache.sis.filter.privy.FunctionNames;
 import org.apache.sis.filter.privy.Visitor;
-import org.apache.sis.util.privy.StandardDateFormat;
+import org.apache.sis.temporal.StandardDateFormat;
 
 
 /**

Reply via email to