You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2024/06/23 22:03:23 UTC

(sis) branch main updated (bb9d125402 -> 86b20ccbda)

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

desruisseaux pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git


    from bb9d125402 Post-merge imports reorganization on the main branch.
     add 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.
     add 534ca09f91 Move `TimeMethods` to `org.apache.sis.temporal` for allowing its use in the implementation of `DefaultInstant.findRelativePosition(TemporalPrimitive)`.
     add 90c0f789d7 Rename `StandardDateFormat` as `LenientDateFormat`.
     add 2aea6e15e3 Add a `GeneralDuration.parse(CharSequence)` method. Add more factory methods.
     add 06a582ccd0 Rename `TemporalUtilities` as `TemporalObjects`.
     add 993397769e Fix shapefile index offset and length in incorrect unit
     add c1c5e12061 Allow the association of GML identifiers to temporal object. It requires making temporal objects lenient comparable.
     add 87cfdd0ab0 Add SQL Dialect supportsReadOnlyUpdate flag, not supported by SQLite
     add e50aa45469 Add support for gpkg geometry header detection and parsing
     add e520c8b28d Formatting and bug fix in the setting of the buffer limit and the use of the Geopackage SRID.
     add 4e0580c957 Actually, `GeometryGetter` does not need to invoke `ByteBuffer.slice()` because WKB readers should start reading from current buffer position.
     add 0edda0c161 Implements `DefaultPeriod.findRelativePosition()`.
     add 0cae00bc0e Delegate `AxisDirections.opposite(…)` to the GeoAPI method.
     add f358804252 Merge branch 'geoapi-4.0' into geoapi-3.1 - Completion of the work on temporal objects - Some work on Shapefile and SQL store
     new 86b20ccbda Merge branch 'geoapi-3.1'

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/sis/coverage/grid/PixelInCell.java  |   3 +
 .../main/org/apache/sis/filter/TemporalFilter.java |  22 +-
 .../org/apache/sis/filter/TemporalOperation.java   | 107 ++++--
 .../main/org/apache/sis/filter/privy/Visitor.java  |   2 +-
 .../apache/sis/geometry/wrapper/Geometries.java    |   1 +
 .../apache/sis/geometry/wrapper/esri/Factory.java  |   5 +-
 .../test/org/apache/sis/filter/PeriodLiteral.java  |  34 +-
 .../org/apache/sis/filter/TemporalFilterTest.java  |   2 +-
 .../org.apache.sis.metadata/main/module-info.java  |  12 +
 .../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 |  12 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |  21 +-
 .../metadata/iso/identification/DefaultUsage.java  |  21 +-
 .../sis/metadata/iso/legacy/TemporalToDate.java    |   2 +-
 .../metadata/iso/lineage/DefaultProcessStep.java   |  25 +-
 .../org/apache/sis/metadata/sql/privy/Dialect.java |  22 +-
 .../geoapi/temporal/IndeterminateValue.java}       |  21 +-
 .../sis/pending/geoapi/temporal/Instant.java}      |  31 +-
 .../apache/sis/pending/geoapi/temporal/Period.java |  17 +-
 .../geoapi/temporal}/TemporalOperatorName.java     |  28 +-
 .../sis/pending/geoapi/temporal/package-info.java  |   0
 .../apache/sis/pending/temporal/DefaultPeriod.java |  71 ----
 .../sis/pending/temporal/TemporalUtilities.java    | 137 --------
 .../apache/sis/pending/temporal/package-info.java  |  26 --
 .../org/apache/sis/temporal/DefaultInstant.java    | 335 +++++++++++++++++++
 .../org/apache/sis/temporal/DefaultPeriod.java     | 206 ++++++++++++
 .../temporal/DefaultPeriodDuration.java            |   9 +-
 .../org/apache/sis/temporal/GeneralDuration.java   | 369 +++++++++++++++++++++
 .../apache/sis/temporal/LenientDateFormat.java}    |  17 +-
 .../org/apache/sis/temporal}/TemporalDate.java     |  38 +--
 .../org/apache/sis/temporal/TemporalObject.java    |  89 +++++
 .../org/apache/sis/temporal/TemporalObjects.java   | 154 +++++++++
 .../main/org/apache/sis/temporal}/TimeMethods.java | 120 ++++---
 .../org/apache/sis/temporal}/package-info.java     |   7 +-
 .../apache/sis/xml/bind/IdentifierMapAdapter.java  |   1 +
 .../org/apache/sis/xml/bind/gml/TM_Primitive.java  |  14 +-
 .../apache/sis/xml/bind/gml/TimePeriodBound.java   |  19 +-
 .../apache/sis/xml/bind/gts/TM_PeriodDuration.java |   2 +-
 .../org/apache/sis/xml/privy/XmlUtilities.java     |   2 +-
 .../iso/citation/DefaultCitationDateTest.java      |  10 +-
 .../sis/metadata/iso/extent/DefaultExtentTest.java |   3 +-
 .../apache/sis/temporal/DefaultInstantTest.java    | 215 ++++++++++++
 .../org/apache/sis/temporal/DefaultPeriodTest.java | 131 ++++++++
 .../apache/sis/temporal/GeneralDurationTest.java}  |  24 +-
 .../sis/temporal/LenientDateFormatTest.java}       |  46 +--
 .../apache/sis/xml/bind/gml/TimePeriodTest.java    |  17 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |   4 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   6 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   2 +-
 .../main/org/apache/sis/io/wkt/WKTFormat.java      |   6 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   2 +-
 .../sis/referencing/datum/AbstractDatum.java       |   2 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   6 +-
 .../operation/CoordinateOperationFinder.java       |   2 +-
 .../sis/referencing/privy/AxisDirections.java      |  11 +
 .../sis/referencing/privy/ExtentSelector.java      |   2 +-
 .../referencing/privy/GeodeticObjectBuilder.java   |   2 +-
 .../test/org/apache/sis/io/wkt/ElementTest.java    |   2 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   2 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |   2 +-
 .../datum/DefaultTemporalDatumTest.java            |   2 +-
 .../sis/referencing/privy/AxisDirectionsTest.java  |   3 +-
 .../sis/test/integration/MetadataVerticalTest.java |   2 +-
 .../sis/storage/geotiff/reader/XMLMetadata.java    |   6 +-
 .../storage/geotiff/reader/XMLMetadataTest.java    |   4 +-
 .../apache/sis/storage/netcdf/base/CRSBuilder.java |   2 +-
 .../sis/storage/netcdf/classic/ChannelDecoder.java |   8 +-
 .../sis/storage/netcdf/classic/VariableInfo.java   |   4 +-
 .../apache/sis/storage/netcdf/base/TestCase.java   |   2 +-
 .../sis/storage/sql/feature/FeatureStream.java     |   4 +-
 .../sis/storage/sql/feature/GeometryGetter.java    |  48 ++-
 .../main/org/apache/sis/storage/gpx/Copyright.java |   2 +-
 .../sis/storage/xml/stream/StaxStreamReader.java   |   6 +-
 .../main/org/apache/sis/storage/csv/Store.java     |   6 +-
 .../org/apache/sis/storage/csv/TimeEncoding.java   |   6 +-
 .../src/org.apache.sis.util/main/module-info.java  |   4 -
 .../main/org/apache/sis/measure/RangeFormat.java   |  44 ++-
 .../main/org/apache/sis/pending/jdk/JDK18.java     |  11 +
 .../LazyCandidate.java}                            |  20 +-
 .../main/org/apache/sis/util/resources/Errors.java |   7 +-
 .../apache/sis/util/resources/Errors.properties    |   3 +-
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 .../sis/storage/shapefile/ShapefileStore.java      |   8 +-
 .../sis/storage/shapefile/shp/ShapeWriter.java     |   4 +-
 .../sis/storage/shapefile/shx/IndexWriter.java     |   6 +-
 93 files changed, 2158 insertions(+), 576 deletions(-)
 copy endorsed/src/{org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/package-info.java => org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java} (76%)
 copy endorsed/src/{org.apache.sis.util/main/org/apache/sis/pending/geoapi/temporal/Period.java => org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java} (55%)
 rename endorsed/src/{org.apache.sis.util => org.apache.sis.metadata}/main/org/apache/sis/pending/geoapi/temporal/Period.java (76%)
 rename endorsed/src/{org.apache.sis.feature/main/org/apache/sis/pending/geoapi/filter => org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal}/TemporalOperatorName.java (62%)
 rename endorsed/src/{org.apache.sis.util => org.apache.sis.metadata}/main/org/apache/sis/pending/geoapi/temporal/package-info.java (100%)
 delete mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
 delete mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
 delete mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/package-info.java
 create mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
 create mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
 rename endorsed/src/org.apache.sis.metadata/main/org/apache/sis/{pending => }/temporal/DefaultPeriodDuration.java (89%)
 create mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
 rename endorsed/src/{org.apache.sis.util/main/org/apache/sis/util/privy/StandardDateFormat.java => org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java} (97%)
 rename endorsed/src/{org.apache.sis.util/main/org/apache/sis/util/privy => org.apache.sis.metadata/main/org/apache/sis/temporal}/TemporalDate.java (87%)
 create mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObject.java
 create mode 100644 endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java
 rename endorsed/src/{org.apache.sis.feature/main/org/apache/sis/filter => org.apache.sis.metadata/main/org/apache/sis/temporal}/TimeMethods.java (79%)
 copy endorsed/src/{org.apache.sis.feature/main/org/apache/sis/filter/internal => org.apache.sis.metadata/main/org/apache/sis/temporal}/package-info.java (80%)
 create mode 100644 endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java
 create mode 100644 endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java
 copy endorsed/src/{org.apache.sis.feature/test/org/apache/sis/coverage/privy/ImageLayoutTest.java => org.apache.sis.metadata/test/org/apache/sis/temporal/GeneralDurationTest.java} (59%)
 rename endorsed/src/{org.apache.sis.util/test/org/apache/sis/util/privy/StandardDateFormatTest.java => org.apache.sis.metadata/test/org/apache/sis/temporal/LenientDateFormatTest.java} (77%)
 copy endorsed/src/org.apache.sis.util/main/org/apache/sis/util/{ConditionallySafe.java => privy/LazyCandidate.java} (67%)


(sis) 01/01: Merge branch 'geoapi-3.1'

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 86b20ccbdab615b3b94bc9a32bd08d323f4a0c06
Merge: bb9d125402 f358804252
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jun 24 00:01:23 2024 +0200

    Merge branch 'geoapi-3.1'

 .../org/apache/sis/coverage/grid/PixelInCell.java  |   3 +
 .../main/org/apache/sis/filter/TemporalFilter.java |  22 +-
 .../org/apache/sis/filter/TemporalOperation.java   | 107 ++++--
 .../main/org/apache/sis/filter/privy/Visitor.java  |   2 +-
 .../apache/sis/geometry/wrapper/Geometries.java    |   1 +
 .../apache/sis/geometry/wrapper/esri/Factory.java  |   5 +-
 .../test/org/apache/sis/filter/PeriodLiteral.java  |  34 +-
 .../org/apache/sis/filter/TemporalFilterTest.java  |   2 +-
 .../org.apache.sis.metadata/main/module-info.java  |  12 +
 .../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 |  12 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |  21 +-
 .../metadata/iso/identification/DefaultUsage.java  |  21 +-
 .../sis/metadata/iso/legacy/TemporalToDate.java    |   2 +-
 .../metadata/iso/lineage/DefaultProcessStep.java   |  25 +-
 .../org/apache/sis/metadata/sql/privy/Dialect.java |  22 +-
 .../geoapi/temporal/IndeterminateValue.java}       |  17 +-
 .../sis/pending/geoapi/temporal/Instant.java}      |  31 +-
 .../apache/sis/pending/geoapi/temporal/Period.java |  17 +-
 .../geoapi/temporal}/TemporalOperatorName.java     |  28 +-
 .../sis/pending/geoapi/temporal/package-info.java  |   0
 .../apache/sis/pending/temporal/DefaultPeriod.java |  71 ----
 .../sis/pending/temporal/TemporalUtilities.java    | 137 --------
 .../org/apache/sis/temporal/DefaultInstant.java    | 335 +++++++++++++++++++
 .../org/apache/sis/temporal/DefaultPeriod.java     | 206 ++++++++++++
 .../temporal/DefaultPeriodDuration.java            |   9 +-
 .../org/apache/sis/temporal/GeneralDuration.java   | 369 +++++++++++++++++++++
 .../apache/sis/temporal/LenientDateFormat.java}    |  17 +-
 .../org/apache/sis/temporal}/TemporalDate.java     |  38 +--
 .../org/apache/sis/temporal/TemporalObject.java    |  89 +++++
 .../org/apache/sis/temporal/TemporalObjects.java   | 154 +++++++++
 .../main/org/apache/sis/temporal}/TimeMethods.java | 120 ++++---
 .../sis/{pending => }/temporal/package-info.java   |   9 +-
 .../apache/sis/xml/bind/IdentifierMapAdapter.java  |   1 +
 .../org/apache/sis/xml/bind/gml/TM_Primitive.java  |  14 +-
 .../apache/sis/xml/bind/gml/TimePeriodBound.java   |  19 +-
 .../apache/sis/xml/bind/gts/TM_PeriodDuration.java |   2 +-
 .../org/apache/sis/xml/privy/XmlUtilities.java     |   2 +-
 .../iso/citation/DefaultCitationDateTest.java      |  10 +-
 .../sis/metadata/iso/extent/DefaultExtentTest.java |   3 +-
 .../apache/sis/temporal/DefaultInstantTest.java    | 215 ++++++++++++
 .../org/apache/sis/temporal/DefaultPeriodTest.java | 131 ++++++++
 .../GeneralDurationTest.java}                      |  35 +-
 .../sis/temporal/LenientDateFormatTest.java}       |  46 +--
 .../apache/sis/xml/bind/gml/TimePeriodTest.java    |  17 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   2 +-
 .../main/org/apache/sis/io/wkt/AbstractParser.java |   4 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   6 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   2 +-
 .../main/org/apache/sis/io/wkt/WKTFormat.java      |   6 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   2 +-
 .../sis/referencing/datum/AbstractDatum.java       |   2 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   6 +-
 .../operation/CoordinateOperationFinder.java       |   2 +-
 .../sis/referencing/privy/AxisDirections.java      |  11 +
 .../sis/referencing/privy/ExtentSelector.java      |   2 +-
 .../referencing/privy/GeodeticObjectBuilder.java   |   2 +-
 .../test/org/apache/sis/io/wkt/ElementTest.java    |   2 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   2 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |   2 +-
 .../datum/DefaultTemporalDatumTest.java            |   2 +-
 .../sis/referencing/privy/AxisDirectionsTest.java  |   3 +-
 .../sis/test/integration/MetadataVerticalTest.java |   2 +-
 .../sis/storage/geotiff/reader/XMLMetadata.java    |   6 +-
 .../storage/geotiff/reader/XMLMetadataTest.java    |   4 +-
 .../apache/sis/storage/netcdf/base/CRSBuilder.java |   2 +-
 .../sis/storage/netcdf/classic/ChannelDecoder.java |   8 +-
 .../sis/storage/netcdf/classic/VariableInfo.java   |   4 +-
 .../apache/sis/storage/netcdf/base/TestCase.java   |   2 +-
 .../sis/storage/sql/feature/FeatureStream.java     |   4 +-
 .../sis/storage/sql/feature/GeometryGetter.java    |  48 ++-
 .../main/org/apache/sis/storage/gpx/Copyright.java |   2 +-
 .../sis/storage/xml/stream/StaxStreamReader.java   |   6 +-
 .../main/org/apache/sis/storage/csv/Store.java     |   6 +-
 .../org/apache/sis/storage/csv/TimeEncoding.java   |   6 +-
 .../src/org.apache.sis.util/main/module-info.java  |   4 -
 .../main/org/apache/sis/measure/RangeFormat.java   |  44 ++-
 .../main/org/apache/sis/pending/jdk/JDK18.java     |  11 +
 .../privy/LazyCandidate.java}                      |  26 +-
 .../main/org/apache/sis/util/resources/Errors.java |   7 +-
 .../apache/sis/util/resources/Errors.properties    |   3 +-
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 .../sis/storage/shapefile/ShapefileStore.java      |   8 +-
 .../sis/storage/shapefile/shp/ShapeWriter.java     |   4 +-
 .../sis/storage/shapefile/shx/IndexWriter.java     |   6 +-
 92 files changed, 2162 insertions(+), 561 deletions(-)

diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
index cd33d042c1,5b2b7a1f8d..a50a4ad129
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java
@@@ -109,7 -113,22 +109,10 @@@ public enum PixelInCell 
       *
       * @return all names of this constant. This array is never null and never empty.
       */
 -    @Override
      public String[] names() {
+         if (this == CELL_CENTER) {
+             return new String[] {name(), identifier, "cell centre"};
+         }
          return new String[] {name(), identifier};
      }
 -
 -    /**
 -     * Returns the enumeration of the same kind as this item.
 -     * This is equivalent to {@link #values()}.
 -     *
 -     * @return the enumeration of the same kind as this item.
 -     */
 -    @Override
 -    public ControlledVocabulary[] family() {
 -        return values();
 -    }
  }
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
index 21629a0fda,38d1984a85..4d3b5d4bea
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
@@@ -16,12 -16,19 +16,15 @@@
   */
  package org.apache.sis.filter;
  
+ import java.time.DateTimeException;
  import org.apache.sis.util.Classes;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.temporal.TimeMethods;
  import org.apache.sis.feature.privy.FeatureExpression;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.temporal.Period;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.TemporalOperator;
 -import org.opengis.filter.TemporalOperatorName;
 -import org.opengis.filter.InvalidFilterValueException;
 +// Specific to the main branch:
 +import org.apache.sis.pending.geoapi.temporal.Period;
- import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
  
  
  /**
@@@ -172,6 -179,8 +175,8 @@@ class TemporalFilter<R,T> extends Binar
      /**
       * Determines if the test(s) represented by this filter passes with the given operands.
       * Values of {@link #expression1} and {@link #expression2} shall be two single values.
+      *
 -     * @throws InvalidFilterValueException if two temporal objects cannot be compared.
++     * @throws IllegalArgumentException if two temporal objects cannot be compared.
       */
      @Override
      public boolean test(final R candidate) {
@@@ -190,6 -199,9 +195,9 @@@
                  } else {
                      return operation.evaluate(left, right);
                  }
+             } catch (DateTimeException e) {
 -                throw new InvalidFilterValueException(Errors.format(
++                throw new IllegalArgumentException(Errors.format(
+                         Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass()));
              }
          }
          return false;
@@@ -224,8 -236,11 +232,11 @@@
              final T left = expression1.apply(candidate);
              if (left != null) {
                  final T right = expression2.apply(candidate);
-                 if (right != null) {
+                 if (right != null) try {
                      return operation.evaluate(left, right);
+                 } catch (DateTimeException e) {
 -                    throw new InvalidFilterValueException(Errors.format(
++                    throw new IllegalArgumentException(Errors.format(
+                             Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass()));
                  }
              }
              return false;
@@@ -261,8 -276,11 +272,11 @@@
              final Period left = expression1.apply(candidate);
              if (left != null) {
                  final Period right = expression2.apply(candidate);
-                 if (right != null) {
+                 if (right != null) try {
                      return operation.evaluate(left, right);
+                 } catch (DateTimeException e) {
 -                    throw new InvalidFilterValueException(Errors.format(
++                    throw new IllegalArgumentException(Errors.format(
+                             Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass()));
                  }
              }
              return false;
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
index db72aafd22,a59b174dc6..c95e471866
--- 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
@@@ -16,18 -16,22 +16,22 @@@
   */
  package org.apache.sis.filter;
  
+ import java.util.Optional;
  import java.io.Serializable;
+ import java.time.DateTimeException;
  import java.time.temporal.Temporal;
- import org.apache.sis.util.Classes;
  import org.apache.sis.util.privy.Strings;
  import org.apache.sis.util.collection.WeakHashSet;
- import static org.apache.sis.filter.TimeMethods.BEFORE;
- import static org.apache.sis.filter.TimeMethods.AFTER;
- import static org.apache.sis.filter.TimeMethods.EQUAL;
+ import org.apache.sis.temporal.TimeMethods;
+ import static org.apache.sis.temporal.TimeMethods.BEFORE;
+ import static org.apache.sis.temporal.TimeMethods.AFTER;
+ import static org.apache.sis.temporal.TimeMethods.EQUAL;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.temporal.Period;
 -import org.opengis.temporal.Instant;
 -import org.opengis.filter.TemporalOperatorName;
 -import org.opengis.temporal.IndeterminateValue;
 +// Specific to the main branch:
 +import org.apache.sis.pending.geoapi.temporal.Period;
- import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
++import org.apache.sis.pending.geoapi.temporal.Instant;
++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
index f6b47d6f17,b314efe3de..517433d4bb
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java
@@@ -23,14 -23,15 +23,14 @@@ import java.util.Collections
  import java.util.function.BiConsumer;
  import org.apache.sis.feature.internal.Resources;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.util.CodeList;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.LogicalOperatorName;
 -import org.opengis.filter.SpatialOperatorName;
 -import org.opengis.filter.DistanceOperatorName;
 -import org.opengis.filter.TemporalOperatorName;
 -import org.opengis.filter.ComparisonOperatorName;
 +// Specific to the main branch:
 +import org.apache.sis.filter.Filter;
 +import org.apache.sis.filter.Expression;
 +import org.apache.sis.pending.geoapi.filter.LogicalOperatorName;
 +import org.apache.sis.pending.geoapi.filter.SpatialOperatorName;
 +import org.apache.sis.pending.geoapi.filter.DistanceOperatorName;
- import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
 +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java
index faa3eb8cf3,f547361c23..e724f0968b
--- 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,11 -18,13 +18,12 @@@ package org.apache.sis.filter
  
  import java.time.Instant;
  import java.io.Serializable;
+ import org.apache.sis.temporal.TemporalObjects;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.feature.Feature;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.Literal;
 -import org.opengis.temporal.Period;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.pending.geoapi.filter.Literal;
 +import org.apache.sis.pending.geoapi.temporal.Period;
  
  
  /**
@@@ -47,17 -49,35 +48,40 @@@ final class PeriodLiteral implements Pe
      /**
       * Returns the constant value held by this object.
       */
-     @Override public Period getValue() {return this;}
+     @Override
+     public Period getValue() {
+         return this;
+     }
+ 
 +    @Override public Period apply(AbstractFeature input) {return this;}
 +
-     /** Returns a bound of this period. */
-     @Override public Instant getBeginning() {return Instant.ofEpochMilli(begin);}
-     @Override public Instant getEnding()    {return Instant.ofEpochMilli(end);}
+     /**
+      * Returns the beginning of this period.
+      */
+     @Override
 -    public org.opengis.temporal.Instant getBeginning() {
++    public org.apache.sis.pending.geoapi.temporal.Instant getBeginning() {
+         return TemporalObjects.createInstant(Instant.ofEpochMilli(begin));
+     }
+ 
+     /**
+      * Returns the ending of this period.
+      */
+     @Override
 -    public org.opengis.temporal.Instant getEnding() {
++    public org.apache.sis.pending.geoapi.temporal.Instant getEnding() {
+         return TemporalObjects.createInstant(Instant.ofEpochMilli(end));
+     }
+ 
+     /**
+      * Not needed for the tests.
+      */
+     @Override
 -    public <N> Expression<Feature,N> toValueType(Class<N> target) {
++    public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) {
+         throw new UnsupportedOperationException();
+     }
  
 +    /** Not needed for the tests. */
-     @Override public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) {throw new UnsupportedOperationException();}
 +    @Override public Class<AbstractFeature> getResourceClass() {return AbstractFeature.class;}
 +
      /**
       * Hash code value. Used by the tests for checking the results of deserialization.
       */
diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
index a07fd7b129,b349e62672..7c043c6476
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
@@@ -26,10 -26,12 +26,10 @@@ import static org.junit.jupiter.api.Ass
  import org.apache.sis.test.TestCase;
  import static org.apache.sis.test.Assertions.assertSerializedEquals;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.temporal.Period;
 -import org.opengis.feature.Feature;
 -import org.opengis.filter.FilterFactory;
 -import org.opengis.filter.TemporalOperator;
 -import org.opengis.filter.TemporalOperatorName;
 +// Specific to the main branch:
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.pending.geoapi.temporal.Period;
- import org.apache.sis.pending.geoapi.filter.TemporalOperatorName;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.metadata/main/module-info.java
index d4ac51c38b,2f35b34abf..eae5879e88
--- 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 +76,18 @@@ module org.apache.sis.metadata 
      /*
       * Internal API open only to other Apache SIS modules.
       */
++    exports org.apache.sis.pending.geoapi.temporal to
++            org.apache.sis.feature;
++
+     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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java
index 1b72a5ab4d,732bc85b94..49942940fe
--- 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,13 -31,8 +31,13 @@@ import org.opengis.metadata.acquisition
  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;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115_2;
 +
  
  /**
   * Identification of a significant collection point within an operation.
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java
index 107f978fea,8f75d6a434..3a9d3ddd7b
--- 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,13 -25,8 +25,13 @@@ import org.opengis.metadata.citation.Ci
  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;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Reference date and event used to describe it.
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index d2f256f7de,207f5ce244..074078276d
--- 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,13 -30,8 +30,13 @@@ import org.opengis.metadata.distributio
  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;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Common ways in which the resource may be obtained or received, and related instructions
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index 06b4b7afc0,a47f0e1dea..6c60fc875c
--- 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
@@@ -41,12 -41,6 +41,11 @@@ import org.apache.sis.util.iso.Types
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.metadata.citation.ResponsibleParty;
  
 +// Specific to the main branch:
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import org.apache.sis.pending.temporal.DefaultPeriod;
 +
  
  /**
   * Brief description of ways in which the resource(s) is/are currently or has been used.
@@@ -173,20 -167,12 +172,20 @@@ public class DefaultUsage extends ISOMe
          super(object);
          if (object != null) {
              specificUsage             = object.getSpecificUsage();
 -            usageDates                = copyCollection(object.getUsageDates(), TemporalPrimitive.class);
              userDeterminedLimitations = object.getUserDeterminedLimitations();
              userContactInfo           = copyCollection(object.getUserContactInfo(), ResponsibleParty.class);
 -            responses                 = copyCollection(object.getResponses(), InternationalString.class);
 -            additionalDocumentation   = copyCollection(object.getAdditionalDocumentation(), Citation.class);
 -            identifiedIssues          = copyCollection(object.getIdentifiedIssues(), Citation.class);
 +            if (object instanceof DefaultUsage) {
 +                final var c = (DefaultUsage) object;
 +                usageDates                = copyCollection(c.getUsageDates(), TemporalPrimitive.class);
 +                responses                 = copyCollection(c.getResponses(), InternationalString.class);
 +                additionalDocumentation   = copyCollection(c.getAdditionalDocumentation(), Citation.class);
 +                identifiedIssues          = copyCollection(c.getIdentifiedIssues(), Citation.class);
 +            } else {
-                 TemporalPrimitive t = TemporalUtilities.createInstant(TemporalDate.toTemporal(object.getUsageDate()));
++                Date t = object.getUsageDate();
 +                if (t != null) {
-                     usageDates = List.of(t);
++                    usageDates = List.of(TemporalObjects.createInstant(TemporalDate.toTemporal(t)));
 +                }
 +            }
          }
      }
  
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
index 9fa6dcf771,feb25054d3..e45767dde6
--- 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
@@@ -41,12 -41,10 +41,13 @@@ import org.apache.sis.temporal.Temporal
  
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.metadata.citation.ResponsibleParty;
+ import org.apache.sis.temporal.TemporalDate;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.maintenance.Scope;
 +// Specific to the main branch:
 +import org.opengis.metadata.quality.Scope;
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
  
  
  /**
@@@ -189,16 -187,14 +190,17 @@@ public class DefaultProcessStep extend
          if (object != null) {
              description           = object.getDescription();
              rationale             = object.getRationale();
-             stepDateTime          = TemporalUtilities.createInstant(object.getDate());
+             stepDateTime          = toInstant(object.getDate());
              processors            = copyCollection(object.getProcessors(), ResponsibleParty.class);
 -            references            = copyCollection(object.getReferences(), Citation.class);
              sources               = copyCollection(object.getSources(), Source.class);
 -            scope                 = object.getScope();
              outputs               = copyCollection(object.getOutputs(), Source.class);
              processingInformation = object.getProcessingInformation();
              reports               = copyCollection(object.getReports(), ProcessStepReport.class);
 +            if (object instanceof DefaultProcessStep) {
-                 references = copyCollection(((DefaultProcessStep) object).getReferences(), Citation.class);
-                 scope      = ((DefaultProcessStep) object).getScope();
++                final var c = (DefaultProcessStep) object;
++                references = copyCollection(c.getReferences(), Citation.class);
++                scope      = c.getScope();
 +            }
          }
      }
  
@@@ -305,7 -302,14 +307,11 @@@
      @Deprecated(since="1.0")
      @XmlElement(name = "dateTime", namespace = LegacyNamespaces.GMD)
      public Date getDate() {
-         return FilterByVersion.LEGACY_METADATA.accept() ? TemporalUtilities.getAnyDate(getStepDateTime()) : null;
+         if (FilterByVersion.LEGACY_METADATA.accept()) {
+             Date date = TemporalObjects.getAnyDate(getStepDateTime());
 -            if (date == null) {
 -                date = ProcessStep.super.getDate();
 -            }
+             return date;
+         }
+         return null;
      }
  
      /**
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java
index a30135526f,0bddfc2b60..7377346529
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java
@@@ -14,19 -14,17 +14,20 @@@
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
- package org.apache.sis.pending.geoapi.filter;
 -package org.apache.sis.map.service.se1;
++package org.apache.sis.pending.geoapi.temporal;
++
++import java.util.Locale;
  
  
  /**
-  * Placeholder for GeoAPI 3.1 interfaces (not yet released).
-  * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
 - * Subclassed by symbolizer who have resource caches.
 - * This could contain images or models.
 - * The cache instance may be shared by multiple {@link SymbolizerToScene2D} instances
 - * using method {@link SymbolizerToScene2D#sharedCache(org.apache.sis.internal.renderer.SymbolizerCache) }.
++ * Indications that a temporal position is not precisely known.
+  *
 - * @author Johann Sorel (Geomatys)
++ * @author  Martin Desruisseaux (Geomatys)
   */
- @SuppressWarnings("doclint:missing")
- public enum TemporalOperatorName {
-     AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS,
-     OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS;
 -public abstract class SymbolizerCache {
++public enum IndeterminateValue {
++    UNKNOWN, NOW, BEFORE, AFTER;
  
 +    public String identifier() {
-         return name().toLowerCase();
++        return name().toLowerCase(Locale.US);
 +    }
  }
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java
index f8ccb863ff,0000000000..02b3685f77
mode 100644,000000..100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java
@@@ -1,44 -1,0 +1,57 @@@
 +/*
 + * 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.geoapi.temporal;
 +
++import java.util.Optional;
 +import java.time.temporal.Temporal;
++import java.time.temporal.TemporalAmount;
 +import org.opengis.temporal.TemporalPrimitive;
 +
 +
 +/**
 + * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
 + *
 + * @author  Martin Desruisseaux (Geomatys)
-  * @since   0.3
++ * @since   1.5
 + * @version 1.5
 + */
- public interface Period extends TemporalPrimitive {
++public interface Instant extends TemporalPrimitive {
 +    /**
-      * Links this period to the instant at which it ends.
-      *
-      * @return The beginning instant.
++     * Returns the date, time or position on the time-scale represented by this primitive.
 +     */
-     Temporal getBeginning();
++    Temporal getPosition();
 +
 +    /**
-      * Links this period to the instant at which it ends.
++     * Returns the reason why the temporal position is missing or inaccurate.
 +     *
-      * @return The end instant.
++     * @return the reason why the position is indeterminate.
 +     */
-     Temporal getEnding();
++    default Optional<IndeterminateValue> getIndeterminatePosition() {
++        return Optional.empty();
++    }
++
++    /**
++     * Returns the distance from this instant to another instant or a period (optional operation).
++     */
++    default TemporalAmount distance(TemporalPrimitive other) {
++        throw new UnsupportedOperationException();
++    }
++
++    default TemporalOperatorName findRelativePosition(TemporalPrimitive other) {
++        throw new UnsupportedOperationException();
++    }
 +}
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java
index f8ccb863ff,a6eeffe0b8..1212795911
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java
@@@ -14,31 -14,32 +14,42 @@@
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
 -package org.apache.sis.xml.bind.referencing;
 +package org.apache.sis.pending.geoapi.temporal;
  
- import java.time.temporal.Temporal;
 -import org.opengis.referencing.datum.VerticalDatumType;
 -import org.apache.sis.xml.bind.gml.CodeListAdapter;
++import java.time.temporal.TemporalAmount;
 +import org.opengis.temporal.TemporalPrimitive;
  
  
  /**
 - * JAXB adapter for (un)marshalling of GeoAPI code list.
 + * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
   *
   * @author  Martin Desruisseaux (Geomatys)
 + * @since   0.3
 + * @version 1.5
   */
 -@SuppressWarnings("deprecation")
 -public final class CD_VerticalDatumType extends CodeListAdapter<VerticalDatumType> {
 +public interface Period extends TemporalPrimitive {
      /**
 -     * Empty constructor for JAXB only.
 +     * Links this period to the instant at which it ends.
 +     *
 +     * @return The beginning instant.
       */
-     Temporal getBeginning();
 -    public CD_VerticalDatumType() {
 -    }
++    Instant getBeginning();
  
      /**
 -     * {@inheritDoc}
 +     * Links this period to the instant at which it ends.
       *
 -     * @return {@code VerticalDatumType.class}
 +     * @return The end instant.
       */
-     Temporal getEnding();
 -    @Override
 -    protected Class<VerticalDatumType> getCodeListClass() {
 -        return VerticalDatumType.class;
++    Instant getEnding();
++
++    /**
++     * Returns the duration of this period (optional operation).
++     */
++    default TemporalAmount length() {
++        return getBeginning().distance(getEnding());
++    }
++
++    default TemporalOperatorName findRelativePosition(TemporalPrimitive other) {
++        throw new UnsupportedOperationException();
+     }
  }
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java
index a30135526f,0000000000..50a05aa191
mode 100644,000000..100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java
@@@ -1,32 -1,0 +1,54 @@@
 +/*
 + * 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.geoapi.filter;
++package org.apache.sis.pending.geoapi.temporal;
++
++import java.util.Optional;
 +
 +
 +/**
 + * Placeholder for GeoAPI 3.1 interfaces (not yet released).
 + * Shall not be visible in public API, as it will be deleted after next GeoAPI release.
 + */
 +@SuppressWarnings("doclint:missing")
 +public enum TemporalOperatorName {
-     AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS,
-     OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS;
++    AFTER, BEFORE(AFTER), BEGINS, BEGUN_BY(BEGINS), CONTAINS, DURING(CONTAINS),
++    EQUALS, OVERLAPS, MEETS, ENDS,
++    OVERLAPPED_BY(OVERLAPS), MET_BY(MEETS), ENDED_BY(ENDS), ANY_INTERACTS;
++
++    static {
++        EQUALS.reversed = EQUALS;
++        ANY_INTERACTS.reversed = ANY_INTERACTS;
++    }
++
++    private TemporalOperatorName reversed;
++
++    private TemporalOperatorName() {
++    }
++
++    private TemporalOperatorName(TemporalOperatorName r) {
++        reversed = r;
++        r.reversed = this;
++    }
 +
 +    public String identifier() {
 +        return name().toLowerCase();
 +    }
++
++    public Optional<TemporalOperatorName> reversed() {
++        return Optional.of(reversed);
++    }
 +}
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
index 0000000000,0eb5682cbe..7e4a105e37
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
@@@ -1,0 -1,336 +1,335 @@@
+ /*
+  * 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.time.Duration;
+ import java.time.DateTimeException;
+ import java.time.ZonedDateTime;
+ import java.time.temporal.Temporal;
+ import java.time.temporal.TemporalAmount;
++import org.opengis.temporal.TemporalPrimitive;
+ import org.apache.sis.util.Classes;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.ComparisonMode;
+ 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;
 -import org.opengis.temporal.IndeterminatePositionException;
++// Specific to the main branch:
++import org.apache.sis.pending.geoapi.temporal.Period;
++import org.apache.sis.pending.geoapi.temporal.Instant;
++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue;
++import org.apache.sis.pending.geoapi.temporal.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.
+  *
+  * <h2>Thread-safety</h2>
+  * Instances of this class are mostly immutable, except for the list of identifiers.
+  * All instances are thread-safe.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ final class DefaultInstant extends TemporalObject implements Instant {
+     /**
+      * For cross-version compatibility.
+      */
+     private static final long serialVersionUID = 3898772638524283287L;
+ 
+     /**
+      * The constant for the "unknown" instant.
+      */
+     static final DefaultInstant UNKNOWN = new DefaultInstant(null, IndeterminateValue.UNKNOWN);
+ 
+     /**
+      * 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 given instant as an instance of this implementation class.
+      *
+      * @param  other  the other instance to cast or copy, or {@code null}.
+      * @return the given instant as an {@code DefaultInstant}, or {@code null} if the given value was null.
+      */
+     public static DefaultInstant castOrCopy(final Instant other) {
+         if (other == null || other instanceof DefaultInstant) {
+             return (DefaultInstant) other;
+         } else {
+             final var indeterminate = other.getIndeterminatePosition().orElse(null);
+             if (indeterminate == IndeterminateValue.UNKNOWN) {
+                 return UNKNOWN;
+             }
+             final Temporal position = other.getPosition();
+             if (indeterminate == null || indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) {
+                 Objects.requireNonNull(position);
+             }
+             return new DefaultInstant(position, 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 final Temporal getPosition() {
+         if (indeterminate != IndeterminateValue.NOW) {
+             return position;
+         }
+         return ZonedDateTime.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 final 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);
+         }
+         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;
+         }
+         throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass()));
+     }
+ 
+     /**
+      * Determines the position of this instant 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 instant and the other primitive.
+      * @throws DateTimeException if the temporal objects cannot be compared.
+      */
+     @Override
+     public TemporalOperatorName findRelativePosition(final TemporalPrimitive other) {
+         ArgumentChecks.ensureNonNull("other", other);
+         if (other instanceof Instant) {
+             return relativeToInstant((Instant) other);
+         }
+         if (other instanceof Period) {
+             return relativeToPeriod((Period) other);
+         }
+         throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass()));
+     }
+ 
+     /**
+      * Determines the position of this instant relative to a period.
+      *
+      * @param  other the period for which to determine the relative position.
+      * @return a temporal operator which is true when evaluated between this primitive and the period.
+      * @throws DateTimeException if the temporal objects cannot be compared.
+      */
+     final TemporalOperatorName relativeToPeriod(final Period other) {
+         TemporalOperatorName relation = relativeToInstant(other.getBeginning());
+         String erroneous;
+         if (relation == TemporalOperatorName.BEFORE) return relation;
+         if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.BEGINS;
+         if (relation == TemporalOperatorName.AFTER) {
+             relation = relativeToInstant(other.getEnding());
+             if (relation == TemporalOperatorName.AFTER)  return relation;
+             if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.ENDS;
+             if (relation == TemporalOperatorName.BEFORE) return TemporalOperatorName.DURING;
+             erroneous = "ending";
+         } else {
+             erroneous = "beginning";
+         }
+         // Should never happen.
+         throw new DateTimeException(Errors.format(Errors.Keys.IllegalMapping_2, erroneous, relation));
+     }
+ 
+     /**
+      * Determines the position of this instant relative to another instant.
+      *
+      * @param  other the other instant 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.
+      */
+     @SuppressWarnings({"rawtypes", "unchecked"})    // See end of method.
+     private TemporalOperatorName relativeToInstant(final Instant other) {
+         boolean canTestBefore = true;
+         boolean canTestAfter  = true;
+         boolean canTestEqual  = true;
+         if (indeterminate != null && indeterminate != IndeterminateValue.NOW) {
+             canTestBefore = (indeterminate == IndeterminateValue.BEFORE);
+             canTestAfter  = (indeterminate == IndeterminateValue.AFTER);
+             canTestEqual  = false;
+         }
+         final IndeterminateValue oip = other.getIndeterminatePosition().orElse(null);
+         if (oip != null) {
+             if (oip != IndeterminateValue.NOW) {
+                 canTestBefore &= (oip == IndeterminateValue.AFTER);
+                 canTestAfter  &= (oip == IndeterminateValue.BEFORE);
+                 canTestEqual   = false;
+             } else if (indeterminate == IndeterminateValue.NOW) {
+                 return TemporalOperatorName.EQUALS;
+             }
+         }
+ cmp:    if (canTestBefore | canTestAfter | canTestEqual) {
+             final Temporal t1;                  // Same as `this.position` except if "now".
+             final Temporal t2;                  // Position of the other instant.
+             final TimeMethods<?> comparators;   // The "is before", "is after" and "is equal" methods to invoke.
+             /*
+              * First, resolve the case when the indeterminate value is "now". Do not invoke `getPosition()`
+              * because the results could differ by a few nanoseconds when two "now" instants are compared,
+              * and also for getting a temporal object of the same type than the other instant.
+              */
+             if (oip == IndeterminateValue.NOW) {
+                 t1 = position;
+                 if (t1 == null) break cmp;
+                 comparators = TimeMethods.find(t1.getClass());
+                 t2 = comparators.now();
+             } else {
+                 t2 = other.getPosition();
+                 if (t2 == null) break cmp;
+                 if (indeterminate == IndeterminateValue.NOW) {
+                     comparators = TimeMethods.find(t2.getClass());
+                     t1 = comparators.now();
+                 } else {
+                     t1 = position;
+                     if (t1 == null) break cmp;
+                     comparators = TimeMethods.find(Classes.findCommonClass(t1.getClass(), t2.getClass()));
+                 }
+             }
+             // This is where the @SuppressWarnings(…) apply.
+             if (canTestBefore && ((TimeMethods) comparators).isBefore.test(t1, t2)) return TemporalOperatorName.BEFORE;
+             if (canTestAfter  && ((TimeMethods) comparators).isAfter .test(t1, t2)) return TemporalOperatorName.AFTER;
+             if (canTestEqual  && ((TimeMethods) comparators).isEqual .test(t1, t2)) return TemporalOperatorName.EQUALS;
+         }
 -        throw new IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition));
++        throw new DateTimeException(Errors.format(Errors.Keys.IndeterminatePosition));
+     }
+ 
+     /**
+      * Compares this instant with the given object, optionally ignoring timezone.
+      * If the comparison mode ignores metadata, this method compares only the position on the timeline.
+      *
+      * @param  other  the object to compare to {@code this}.
+      * @param  mode   the strictness level of the comparison.
+      * @return {@code true} if both objects are equal according the given comparison mode.
+      */
+     @Override
+     public boolean equals(final Object object, final ComparisonMode mode) {
+         if (mode.equals(ComparisonMode.STRICT)) {   // Use `mode.equals(…)` for opportunistic null check.
+             return equals(object);
+         }
+         if (object instanceof Instant) {
+             final var that = (Instant) object;
+             if (indeterminate == that.getIndeterminatePosition().orElse(null)) {
+                 if (indeterminate == IndeterminateValue.NOW || indeterminate == IndeterminateValue.UNKNOWN) {
+                     return true;
+                 }
+                 final Temporal other = that.getPosition();
+                 return Objects.equals(position, other) ||       // Needed in all cases for testing null values.
+                         (mode.isIgnoringMetadata() && TimeMethods.compareAny(TimeMethods.EQUAL, position, other));
+             }
+         }
+         return false;
+     }
+ 
+     /**
+      * 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 indeterminate == that.indeterminate
+                     && Objects.equals(position, that.position)
+                     && equalIdentifiers(that);
+         }
+         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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
index 0000000000,a5142cc6c0..0db49a9a5b
mode 000000,100644..100644
--- 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
@@@ -1,0 -1,206 +1,206 @@@
+ /*
+  * 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.Map;
+ import java.time.DateTimeException;
+ import java.time.temporal.TemporalAmount;
++import org.opengis.temporal.TemporalPrimitive;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.util.privy.LazyCandidate;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.temporal.Instant;
 -import org.opengis.temporal.Period;
 -import org.opengis.temporal.TemporalPrimitive;
 -import org.opengis.filter.TemporalOperatorName;
++// Specific to the main branch:
++import org.apache.sis.pending.geoapi.temporal.Instant;
++import org.apache.sis.pending.geoapi.temporal.Period;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
+ 
+ 
+ /**
+  * Default implementation of GeoAPI period.
+  *
+  * <h2>Thread-safety</h2>
+  * Instances of this class are mostly immutable, except for the list of identifiers.
+  * All instances are thread-safe.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ final class DefaultPeriod extends TemporalObject implements Period {
+     /**
+      * 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);
+     }
+ 
+     /**
+      * Determines the position of this period 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 period and the other primitive.
+      * @throws DateTimeException if the temporal objects cannot be compared.
+      */
+     @Override
+     public TemporalOperatorName findRelativePosition(final TemporalPrimitive other) {
+         ArgumentChecks.ensureNonNull("other", other);
+         if (other instanceof Instant) {
+             return DefaultInstant.castOrCopy((Instant) other).relativeToPeriod(this).reversed().orElseThrow();
+         }
+         if (other instanceof Period) {
+             final var period = (Period) other;
+             String erroneous;
+             TemporalOperatorName relation = DefaultInstant.castOrCopy(beginning).relativeToPeriod(period);
+             final var map = POSITIONS.get(relation);
+             if (map != null) {
+                 relation = DefaultInstant.castOrCopy(ending).relativeToPeriod(period);
+                 final var result = map.get(relation);
+                 if (result != null) {
+                     return result;
+                 }
+                 erroneous = "ending";
+             } else {
+                 erroneous = "beginning";
+             }
+             // Should never happen.
+             throw new DateTimeException(Errors.format(Errors.Keys.IllegalMapping_2, erroneous, relation));
+         }
+         throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass()));
+     }
+ 
+     /**
+      * Relative positions for given pairs (beginning, ending) relative positions.
+      * Keys of this static map are the relative positions of the beginning of this period relative to the other period.
+      * Keys of the enclosed maps are the relative positions of the ending of this period relative to the other period.
+      */
+     @LazyCandidate
+     private static final Map<TemporalOperatorName, Map<TemporalOperatorName, TemporalOperatorName>> POSITIONS = Map.of(
+             TemporalOperatorName.BEFORE, Map.of(
+                     TemporalOperatorName.BEFORE, TemporalOperatorName.BEFORE,
+                     TemporalOperatorName.BEGINS, TemporalOperatorName.MEETS,
+                     TemporalOperatorName.DURING, TemporalOperatorName.OVERLAPS,
+                     TemporalOperatorName.ENDS,   TemporalOperatorName.ENDED_BY,
+                     TemporalOperatorName.AFTER,  TemporalOperatorName.CONTAINS),
+             TemporalOperatorName.BEGINS, Map.of(
+                     TemporalOperatorName.BEGINS, TemporalOperatorName.MEETS,
+                     TemporalOperatorName.DURING, TemporalOperatorName.BEGINS,
+                     TemporalOperatorName.ENDS,   TemporalOperatorName.EQUALS,
+                     TemporalOperatorName.AFTER,  TemporalOperatorName.BEGUN_BY),
+             TemporalOperatorName.DURING, Map.of(
+                     TemporalOperatorName.DURING, TemporalOperatorName.DURING,
+                     TemporalOperatorName.ENDS,   TemporalOperatorName.ENDS,
+                     TemporalOperatorName.AFTER,  TemporalOperatorName.OVERLAPPED_BY),
+             TemporalOperatorName.ENDS, Map.of(
+                     TemporalOperatorName.ENDS,   TemporalOperatorName.MET_BY,
+                     TemporalOperatorName.AFTER,  TemporalOperatorName.MET_BY),
+             TemporalOperatorName.AFTER, Map.of(
+                     TemporalOperatorName.AFTER,  TemporalOperatorName.AFTER));
+ 
+     /**
+      * 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)
+                    && equalIdentifiers(other);
+         }
+         return false;
+     }
+ 
+     /**
+      * Compares this period with the given object, optionally ignoring timezone.
+      * If the comparison mode ignores metadata, this method compares only the position on the timeline.
+      *
+      * @param  other  the object to compare to {@code this}.
+      * @param  mode   the strictness level of the comparison.
+      * @return {@code true} if both objects are equal according the given comparison mode.
+      */
+     @Override
+     public boolean equals(final Object object, final ComparisonMode mode) {
+         if (mode.equals(ComparisonMode.STRICT)) {   // Use `mode.equals(…)` for opportunistic null check.
+             return equals(object);
+         }
+         if (object instanceof Period) {
+             final var other = (Period) object;
+             return Utilities.deepEquals(beginning, other.getBeginning(), mode) &&
+                    Utilities.deepEquals(ending,    other.getEnding(),    mode);
+         }
+         return false;
+     }
+ }
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java
index fb98bc56fe,c0372e7397..0ef273bdd6
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java
@@@ -20,13 -20,21 +20,16 @@@ import java.io.Serializable
  import java.time.temporal.TemporalAmount;
  import org.opengis.temporal.PeriodDuration;
  
 -// Specific to the geoapi-3.1 branch:
 -import java.time.temporal.Temporal;
 -import java.time.temporal.TemporalUnit;
 -import java.util.List;
 -
  
  /**
-  * Default implementation of GeoAPI period duration. This is a temporary class;
-  * GeoAPI temporal interfaces are expected to change a lot in a future revision.
+  * Default implementation of GeoAPI period duration.
   *
   * @author  Martin Desruisseaux (Geomatys)
+  *
+  * @deprecated This is a temporary class for compatibility with GeoAPI 3.x only.
+  * It should disappear with GeoAPI 4.0.
   */
+ @Deprecated
  @SuppressWarnings("serial")
  public final class DefaultPeriodDuration implements PeriodDuration, Serializable {
      /**
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
index 0000000000,6cd1724710..894d4c6ac5
mode 000000,100644..100644
--- 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
@@@ -1,0 -1,370 +1,369 @@@
+ /*
+  * 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.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;
++// Specific to the main branch:
++import org.apache.sis.pending.geoapi.temporal.Instant;
++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue;
+ 
+ 
+ /**
+  * A data type to be used for describing length or distance in the temporal dimension.
+  * This implementation combines {@link java.time.Period} with {@link java.time.Duration}
+  * for situations where both of them are needed together (which is not recommended).
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  */
+ public 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.
+      */
+     public 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.
+      */
+     public 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 duration for the given components.
+      * If any component is zero, the other component is returned.
+      *
+      * @param  period  the period.
+      * @param  time    the component.
+      * @return the temporal amount from the given components.
+      */
+     public static TemporalAmount of(final Period period, final Duration time) {
+         if (period.isZero()) return time;
+         if (time.isZero()) return period;
+         return new GeneralDuration(period, time);
+     }
+ 
+     /**
+      * Parses a temporal amount which may contain a period and a duration part.
+      * This method returns a {@link Period} or {@link Duration} if those objects
+      * are sufficient, or an instance of {@code GeneralDuration} is last resort.
+      *
+      * @param  text  the text to parse.
+      * @return the parsed period and/or duration.
+      * @throws DateTimeParseException if the given text cannot be parsed.
+      *
+      * @see Period#parse(CharSequence)
+      * @see Duration#parse(CharSequence)
+      */
+     public static TemporalAmount parse(final CharSequence text) {
+         char previousLetter = 0;
+         final int length = text.length();
+         for (int i=0; i<length; i++) {
+             char c = text.charAt(i);
+             if (c >= 'a' && c <= 'z') c -= 'a' - 'A';       // Quick upper case, ASCII characters only.
+             if (c >= 'A' && c <= 'Z') {
+                 if (c == 'T') {
+                     if (previousLetter == 'P') {
+                         return Duration.parse(text);
+                     }
+                     return of(Period.parse(text.subSequence(0, i)),
+                             Duration.parse(new StringBuilder(length - i + 1).append('P').append(text, i, length)));
+                 }
+                 previousLetter = c;
+             }
+         }
+         return Period.parse(text);
+     }
+ 
+     /**
+      * 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));
++        throw new DateTimeException(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);
+         /*
+          * 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.
+          */
+         final 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.
+             }
+         }
+         /*
+          * 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) {
+                 final boolean isPositive = d1.isBefore(d2);
+                 if (isPositive || d1.isAfter(d2)) {                 // Require the period to be non-zero.
+                     if (isPositive ? time.isNegative() : JDK18.isPositive(time)) {
+                         long n = time.toDays();                     // Truncated toward 0.
+                         if (isPositive) {
+                             d2 = d2.plus (--n, ChronoUnit.DAYS);    // `n` is negative. Reduces period by decreasing the ending.
+                         } else {
+                             d1 = d1.minus(++n, ChronoUnit.DAYS);    // `n` is positive. Reduces period by increasing the beginning.
+                         }
+                         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 (time.isZero()) {
+                     return period;
+                 }
+             } else {
+                 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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java
index 0000000000,027c086aa2..72877deddd
mode 000000,100644..100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java
@@@ -1,0 -1,154 +1,154 @@@
+ /*
+  * 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;
+ 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.IndeterminateValue;
 -import org.opengis.temporal.Instant;
 -import org.opengis.temporal.Period;
++// Specific to the main branch:
++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue;
++import org.apache.sis.pending.geoapi.temporal.Instant;
++import org.apache.sis.pending.geoapi.temporal.Period;
+ 
+ 
+ /**
+  * Utilities related to ISO 19108 objects.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @author  Guilhem Legal (Geomatys)
+  */
+ public final class TemporalObjects {
+     /**
+      * Do not allow instantiation of this class.
+      */
+     private TemporalObjects() {
+     }
+ 
+     /**
+      * Creates an instant for the given Java temporal instant.
+      *
+      * @param  position  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 position) {
+         return (position == null) ? DefaultInstant.UNKNOWN : new DefaultInstant(position, null);
+     }
+ 
+     /**
+      * Creates an instant for the given Java temporal instant associated to the indeterminate value.
+      * This is used for creating "before" or "after" instant.
+      *
+      * @param  position       the date and/or time for which to create instant.
+      * @param  indeterminate  the indeterminate value, or {@code null} if the value is not indeterminate.
+      * @return the instant.
+      */
+     public static Instant createInstant(final Temporal position, final IndeterminateValue indeterminate) {
+         if (indeterminate == IndeterminateValue.UNKNOWN) {
+             return DefaultInstant.UNKNOWN;
+         }
+         if (indeterminate == null || indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) {
+             ArgumentChecks.ensureNonNull("position", position);
+         }
+         return new DefaultInstant(position, indeterminate);
+     }
+ 
+     /**
+      * Creates an instant for the given indeterminate value.
+      * The given value cannot be "before" or "after".
+      *
+      * @param  indeterminate  the indeterminate value.
+      * @return the instant for the given indeterminate value.
+      * @throws IllegalArgumentException if the given value is "before" or "after".
+      */
+     public static Instant createInstant(final IndeterminateValue indeterminate) {
+         ArgumentChecks.ensureNonNull("indeterminate", indeterminate);
+         if (indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) {
+             throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "indeterminate", indeterminate));
+         }
+         return (indeterminate == IndeterminateValue.UNKNOWN) ? DefaultInstant.UNKNOWN : new DefaultInstant(null, indeterminate);
+     }
+ 
+     /**
+      * 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));
+     }
+ 
+     /**
+      * 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(Instant beginning, Instant ending) {
+         if (beginning == null && ending == null) {
+             return null;
+         }
+         if (beginning == null) beginning = DefaultInstant.UNKNOWN;
+         if    (ending == null)    ending = DefaultInstant.UNKNOWN;
+         return new DefaultPeriod(beginning, 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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java
index d291a2adcf,4cd2880a96..cae2ac62a2
--- 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,11 -23,11 +23,11 @@@ import org.opengis.temporal.TemporalPri
  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.TemporalObjects;
  import org.apache.sis.util.resources.Errors;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.temporal.Period;
 +// Specific to the main branch:
 +import org.apache.sis.pending.geoapi.temporal.Period;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
index ab2ee6fb11,5b51345ec5..921666dc80
--- 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
@@@ -23,6 -22,9 +22,9 @@@ import jakarta.xml.bind.annotation.XmlE
  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;
++// Specific to the main branch:
++import org.apache.sis.pending.geoapi.temporal.Instant;
+ 
  
  /**
   * The {@linkplain TimePeriod#begin begin} or {@linkplain TimePeriod#end end} position in
diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
index c841f55160,55e52af59c..7c97bc963d
--- 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,13 -47,14 +47,13 @@@ public final class DefaultCitationDateT
       */
      @Test
      public void testCopyConstructor() {
-         final CitationDate original = new CitationDate() {
-             @Override public Date     getDate()     {return new Date(1305716658508L);}
-             @Override public DateType getDateType() {return DateType.CREATION;}
+         final var original = new CitationDate() {
 -            @Override public Temporal getReferenceDate() {return Instant.ofEpochMilli(1305716658508L);}
++            @Override public Date     getDate()          {return new Date(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());
-         assertFalse(copy.equals(original, ComparisonMode.STRICT)); // Opportunist test.
 -        assertTrue (copy.equals(original, ComparisonMode.DEBUG));
+         assertFalse(copy.equals(original, ComparisonMode.STRICT));          // Opportunist test.
      }
  }
diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java
index 0000000000,403defe9f3..4607c106c1
mode 000000,100644..100644
--- 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
@@@ -1,0 -1,215 +1,215 @@@
+ /*
+  * 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.Period;
+ import java.time.Duration;
+ import java.time.LocalDate;
+ import java.time.LocalDateTime;
+ import java.time.ZonedDateTime;
+ import java.time.ZoneOffset;
+ import org.opengis.temporal.TemporalPrimitive;
+ 
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.filter.TemporalOperatorName;
 -import org.opengis.temporal.IndeterminateValue;
 -import org.opengis.temporal.IndeterminatePositionException;
++// Specific to the main branch:
++import java.time.DateTimeException;
++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue;
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
+ 
+ // Test dependencies
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.function.Executable;
+ 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 TemporalObjects#createInstant(Temporal)} too.
+      */
+     @Test
+     public void testGetPosition() {
+         var date    = LocalDate.of(2010, 5, 1);
+         var instant = TemporalObjects.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());
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} between instants.
+      */
+     @Test
+     public void testRelativePositionBetweenInstants() {
+         final var t1981 = new DefaultInstant(LocalDate.of(1981, 6, 5), null);
+         final var t2000 = new DefaultInstant(LocalDate.of(2000, 1, 1), null);
+         assertEquals(TemporalOperatorName.BEFORE, t1981.findRelativePosition(t2000));
+         assertEquals(TemporalOperatorName.AFTER,  t2000.findRelativePosition(t1981));
+         assertEquals(TemporalOperatorName.EQUALS, t2000.findRelativePosition(t2000));
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} between an instant and a period.
+      */
+     @Test
+     public void testRelativePositionBetweenInstantAndPeriod() {
+         final var before = new DefaultInstant(LocalDate.of(1981, 1, 1), null);
+         final var begins = new DefaultInstant(LocalDate.of(1981, 6, 5), null);
+         final var during = new DefaultInstant(LocalDate.of(1990, 1, 1), null);
+         final var ends   = new DefaultInstant(LocalDate.of(2000, 1, 1), null);
+         final var after  = new DefaultInstant(LocalDate.of(2000, 1, 2), null);
+         final var period = new DefaultPeriod(begins, ends);
+         assertEquals(TemporalOperatorName.BEFORE, before.findRelativePosition(period));
+         assertEquals(TemporalOperatorName.BEGINS, begins.findRelativePosition(period));
+         assertEquals(TemporalOperatorName.DURING, during.findRelativePosition(period));
+         assertEquals(TemporalOperatorName.ENDS,   ends  .findRelativePosition(period));
+         assertEquals(TemporalOperatorName.AFTER,  after .findRelativePosition(period));
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} with indeterminate instants.
+      * The position tested are "before" and "after".
+      */
+     @Test
+     public void testIndeterminatePosition() {
+         final var before2000 = new DefaultInstant(LocalDate.of(2000, 1, 1), IndeterminateValue.BEFORE);
+         final var  after2010 = new DefaultInstant(LocalDate.of(2010, 1, 1), IndeterminateValue.AFTER);
+         final var before2020 = new DefaultInstant(LocalDate.of(2020, 1, 1), IndeterminateValue.BEFORE);
+ 
+         assertEquals(TemporalOperatorName.BEFORE, before2000.findRelativePosition( after2010));
+         assertEquals(TemporalOperatorName.AFTER,   after2010.findRelativePosition(before2000));
+         assertIndeterminate(() ->  after2010.findRelativePosition(before2020));
+         assertIndeterminate(() -> before2000.findRelativePosition(before2020));
+         assertIndeterminate(() -> before2020.findRelativePosition(before2000));
+     }
+ 
+     /**
+      * Asserts that the result of the given comparison is indeterminate.
+      *
+      * @param  c  the comparison to perform.
+      */
+     private static void assertIndeterminate(final Executable c) {
 -        assertNotNull(assertThrows(IndeterminatePositionException.class, c).getMessage());
++        assertNotNull(assertThrows(DateTimeException.class, c).getMessage());
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two locale dates.
+      */
+     @Test
+     public void testDistanceBetweenLocalDates() {
+         final var t1981 = new DefaultInstant(LocalDate.of(1981, 6, 5), null);
+         final var t2000 = new DefaultInstant(LocalDate.of(2000, 8, 8), null);
+         final Period expected = Period.of(19, 2, 3);
+         assertEquals(expected,    t1981.distance(t2000));
+         assertEquals(expected,    t2000.distance(t1981));
+         assertEquals(Period.ZERO, t2000.distance(t2000));
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two dates with timezone.
+      */
+     @Test
+     public void testDistanceBetweenZonedDates() {
+         final var t2000 = new DefaultInstant(ZonedDateTime.of(2000, 6, 5, 12, 4, 0, 0, ZoneOffset.UTC), null);
+         final var t2001 = new DefaultInstant(ZonedDateTime.of(2001, 6, 5, 14, 4, 0, 0, ZoneOffset.UTC), null);
+         final Duration expected = Duration.ofDays(365).plusHours(2);
+         assertEquals(expected,      t2000.distance(t2001));
+         assertEquals(expected,      t2001.distance(t2000));
+         assertEquals(Duration.ZERO, t2000.distance(t2000));
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two dates with times.
+      * The period cannot be expressed with standard {@link java.time} objects.
+      */
+     @Test
+     public void testDistanceBetweenLocalDateTimes() {
+         final var t1 = new DefaultInstant(LocalDateTime.of(2000, 6, 5, 12, 4, 0, 0), null);
+         final var t3 = new DefaultInstant(LocalDateTime.of(2001, 6, 9, 14, 4, 0, 0), null);
+         final var t2 = new DefaultInstant(LocalDateTime.of(2001, 6, 9, 10, 4, 0, 0), null);
+ 
+         Object expected = "P1Y4DT2H";
+         assertEquals(expected,    t1.distance(t3).toString());
+         assertEquals(expected,    t3.distance(t1).toString());
+         assertEquals(Period.ZERO, t1.distance(t1));
+ 
+         expected = "P1Y3DT22H";
+         assertEquals(expected,    t1.distance(t2).toString());
+         assertEquals(expected,    t2.distance(t1).toString());
+         assertEquals(Period.ZERO, t2.distance(t2));
+ 
+         expected = Duration.ofHours(4);
+         assertEquals(expected,    t2.distance(t3));
+         assertEquals(expected,    t3.distance(t2));
+         assertEquals(Period.ZERO, t3.distance(t3));
+     }
+ 
+     /**
+      * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between an instant and a period.
+      */
+     @Test
+     public void testDistanceWithPeriod() {
+         final var before = new DefaultInstant(LocalDate.of(1981, 1, 1), null);
+         final var begins = new DefaultInstant(LocalDate.of(1981, 6, 5), null);
+         final var during = new DefaultInstant(LocalDate.of(1990, 1, 1), null);
+         final var ends   = new DefaultInstant(LocalDate.of(2000, 1, 1), null);
+         final var after  = new DefaultInstant(LocalDate.of(2000, 1, 2), null);
+         final var period = new DefaultPeriod(begins, ends);
+ 
+         assertEquals(Period.of(0, 5, 4), before.distance(period));
+         assertEquals(Period.ZERO,        begins.distance(period));
+         assertEquals(Duration.ZERO,      during.distance(period));      // `Duration` considered an implementation details.
+         assertEquals(Period.ZERO,        ends  .distance(period));
+         assertEquals(Period.of(0, 0, 1), after .distance(period));
+     }
+ }
diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java
index 0000000000,9fbeb6c8f9..73829fe91b
mode 000000,100644..100644
--- 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
@@@ -1,0 -1,128 +1,131 @@@
+ /*
+  * 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;
+ import java.time.Period;
+ import java.time.Year;
+ import org.opengis.temporal.TemporalPrimitive;
+ 
+ // Test dependencies
+ import org.junit.jupiter.api.Test;
+ import static org.junit.jupiter.api.Assertions.*;
+ import org.apache.sis.test.TestCase;
 -import org.opengis.filter.TemporalOperatorName;
++
++// Specific to the main branch
++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName;
+ 
+ 
+ /**
+  * 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 TemporalObjects#createPeriod(Temporal, Temporal)} too.
+      */
+     @Test
+     public void testBounds() {
+         var beginning = LocalDate.of(2010, 5, 1);
+         var ending    = LocalDate.of(2015, 8, 6);
+         var period    = TemporalObjects.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 = TemporalObjects.createPeriod(LocalDate.of(2000, 1, 1), LocalDate.of(2010, 1, 1));
+         var p2 = TemporalObjects.createPeriod(LocalDate.of(1988, 1, 1), LocalDate.of(2010, 1, 1));
+         var p3 = TemporalObjects.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 = TemporalObjects.createPeriod(LocalDate.of(2000, 1, 1), LocalDate.of(2010, 1, 1));
+         assertEquals("2000-01-01/2010-01-01", p1.toString());
+     }
+ 
+     /**
+      * Tests {@link DefaultPeriod#length()}.
+      */
+     @Test
+     public void testLength() {
+         var beginning = LocalDate.of(2010, 5, 1);
+         var ending    = LocalDate.of(2015, 8, 6);
+         var period    = TemporalObjects.createPeriod(beginning, ending);
+         assertEquals(Period.of(5, 3, 5), period.length());
+     }
+ 
+     /**
+      * Tests {@link DefaultPeriod#findRelativePosition(TemporalPrimitive)}.
+      */
+     @Test
+     public void testFindRelativePosition() {
+         var p04 = TemporalObjects.createPeriod(Year.of(2000), Year.of(2004));
+         var p56 = TemporalObjects.createPeriod(Year.of(2005), Year.of(2006));
+         var p13 = TemporalObjects.createPeriod(Year.of(2001), Year.of(2003));
+         var p14 = TemporalObjects.createPeriod(Year.of(2001), Year.of(2004));
+         assertRelativePositionEquals(TemporalOperatorName.EQUALS,    p04, p04);
+         assertRelativePositionEquals(TemporalOperatorName.BEFORE,    p04, p56);
+         assertRelativePositionEquals(TemporalOperatorName.CONTAINS,  p04, p13);
+         assertRelativePositionEquals(TemporalOperatorName.ENDED_BY,  p04, p14);
+         assertRelativePositionEquals(TemporalOperatorName.EQUALS,    p56, p56);
+         assertRelativePositionEquals(TemporalOperatorName.AFTER,     p56, p13);
+         assertRelativePositionEquals(TemporalOperatorName.AFTER,     p56, p14);
+         assertRelativePositionEquals(TemporalOperatorName.EQUALS,    p13, p13);
+         assertRelativePositionEquals(TemporalOperatorName.BEGINS,    p13, p14);
+         assertRelativePositionEquals(TemporalOperatorName.EQUALS,    p14, p14);
+     }
+ 
+     /**
+      * Finds the relative position of {@code p1} relative to {@code p2} and compare against the expected value.
+      * Then reverses argument order and test again.
+      *
+      * @param expected  the expected result.
+      * @param self      period for which to find the relative position.
+      * @param other     the period against which {@code self} is compared.
+      */
+     private static void assertRelativePositionEquals(TemporalOperatorName expected,
 -            org.opengis.temporal.Period self, org.opengis.temporal.Period other)
++            org.apache.sis.pending.geoapi.temporal.Period self,
++            org.apache.sis.pending.geoapi.temporal.Period other)
+     {
+         assertEquals(expected, self.findRelativePosition(other));
+         assertEquals(expected.reversed().orElseThrow(), other.findRelativePosition(self));
+     }
+ }
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index c88ab0f08e,433f389f3f..abaa640846
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@@ -87,11 -87,8 +87,11 @@@ import org.apache.sis.util.iso.Types
  // Specific to the main and geoapi-3.1 branches:
  import org.opengis.referencing.ReferenceIdentifier;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceSystem;
 +import org.apache.sis.referencing.internal.ServicesForMetadata;
- import org.apache.sis.util.privy.TemporalDate;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
++import org.apache.sis.temporal.TemporalDate;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index 87d21d75f6,455ce924a3..2ad8984f4c
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@@ -35,10 -35,10 +35,10 @@@ import org.apache.sis.io.wkt.Formatter
  import org.apache.sis.io.wkt.FormattableObject;
  
  // Specific to the main and geoapi-3.1 branches:
- import org.apache.sis.util.privy.TemporalDate;
+ import org.apache.sis.temporal.TemporalDate;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceIdentifier;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 4cd1a9ec72,7bf491410a..a0cfacca43
--- 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
@@@ -121,12 -121,9 +121,12 @@@ import static org.apache.sis.util.privy
  import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
  import static org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.Identifier;
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
++import org.apache.sis.temporal.TemporalDate;
 +import org.apache.sis.referencing.internal.ServicesForMetadata;
- import org.apache.sis.util.privy.TemporalDate;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
index 3826d39454,29b606fbce..e0023115c4
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java
@@@ -71,90 -65,7 +71,98 @@@ public final class AxisDirections exten
       *
       * @see #isUserDefined(AxisDirection)
       */
 -    private static final int LAST_ORDINAL = UNSPECIFIED.ordinal();
 +    private static final int LAST_ORDINAL = DISPLAY_DOWN.ordinal();
 +
 +    /**
 +     * Forward direction.
 +     * For an observer at the centre of the object this is will be towards its front, bow or nose.
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="forward", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection FORWARD = AxisDirection.valueOf("FORWARD");
 +
 +    /**
 +     * Starboard direction.
 +     * For an observer at the centre of the object this will be towards its right.
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="starboard", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection STARBOARD = AxisDirection.valueOf("STARBOARD");
 +
 +    /**
 +     * Port direction.
 +     * For an observer at the centre of the object this will be towards its left.
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="port", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection PORT = AxisDirection.valueOf("PORT");
 +
 +    /**
 +     * Direction of geographic angles (bearing).
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection CLOCKWISE = AxisDirection.valueOf("CLOCKWISE");
 +
 +    /**
 +     * Direction of arithmetic angles. Used in polar coordinate systems.
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE");
 +
 +    /**
 +     * Distance from the origin in a polar coordinate system.
 +     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
 +     */
 +    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM");
 +
++    /**
++     * Distance toward the origin.
++     * Added in ISO 19111:2019 (was not in ISO 19111:2007).
++     */
++    @UML(identifier="towards", obligation=CONDITIONAL, specification=ISO_19111)
++    public static final AxisDirection TOWARDS = AxisDirection.valueOf("TOWARDS");
++
 +    /**
 +     * Axis positive direction is unspecified.
 +     */
 +    @UML(identifier="unspecified", obligation=CONDITIONAL, specification=ISO_19111)
 +    public static final AxisDirection UNSPECIFIED = AxisDirection.valueOf("UNSPECIFIED");
 +
 +    /**
 +     * For each direction, the opposite direction.
 +     * This map shall be immutable after construction.
 +     */
 +    private static final Map<AxisDirection,AxisDirection> OPPOSITES = new HashMap<>(24);
 +    static {
 +        put(UNSPECIFIED,       UNSPECIFIED);
 +        put(NORTH,             SOUTH);
 +        put(NORTH_NORTH_EAST,  SOUTH_SOUTH_WEST);
 +        put(NORTH_EAST,        SOUTH_WEST);
 +        put(EAST_NORTH_EAST,   WEST_SOUTH_WEST);
 +        put(EAST,              WEST);
 +        put(EAST_SOUTH_EAST,   WEST_NORTH_WEST);
 +        put(SOUTH_EAST,        NORTH_WEST);
 +        put(SOUTH_SOUTH_EAST,  NORTH_NORTH_WEST);
 +        put(UP,                DOWN);
 +        put(FUTURE,            PAST);
 +        put(COLUMN_POSITIVE,   COLUMN_NEGATIVE);
 +        put(ROW_POSITIVE,      ROW_NEGATIVE);
 +        put(DISPLAY_RIGHT,     DISPLAY_LEFT);
 +        put(DISPLAY_UP,        DISPLAY_DOWN);
 +        put(COUNTER_CLOCKWISE, CLOCKWISE);
++        put(AWAY_FROM,         TOWARDS);
 +    }
 +
 +    /**
 +     * Stores the given directions in the {@link #OPPOSITES} array.
 +     */
 +    private static void put(final AxisDirection dir, final AxisDirection opposite) {
 +        OPPOSITES.put(dir, opposite);
 +        OPPOSITES.put(opposite, dir);
 +    }
  
      /**
       * Proposed abbreviations for some axis directions.
diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
index 42c5ae51e6,778b40195b..fefbca7818
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java
@@@ -61,8 -61,8 +61,8 @@@ import org.apache.sis.referencing.cs.Ax
  import org.apache.sis.referencing.internal.Resources;
  import org.apache.sis.parameter.Parameters;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
 +// Specific to the main branch:
- import org.apache.sis.util.privy.TemporalDate;
++import org.apache.sis.temporal.TemporalDate;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
index e312138ac2,296cb93297..1373e98bd9
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java
@@@ -52,11 -52,11 +52,11 @@@ import static org.apache.sis.test.Asser
  import static org.apache.sis.test.TestUtilities.*;
  
  // Specific to the main and geoapi-3.1 branches:
- import org.apache.sis.util.privy.TemporalDate;
+ import org.apache.sis.temporal.TemporalDate;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.datum.RealizationMethod;
 -import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
 +// Specific to the main branch:
 +import org.opengis.referencing.datum.VerticalDatumType;
 +import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
index adb23afaff,dc6845ddef..96a022b0fe
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java
@@@ -35,11 -35,12 +35,11 @@@ import static org.apache.sis.test.TestU
  import static org.apache.sis.test.TestUtilities.getScope;
  
  // Specific to the main and geoapi-3.1 branches:
- import org.apache.sis.util.privy.TemporalDate;
+ import org.apache.sis.temporal.TemporalDate;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import static org.opengis.referencing.ObjectDomain.*;
 -import static org.opengis.referencing.IdentifiedObject.*;
 -import static org.opengis.test.Assertions.assertIdentifierEquals;
 +// Specific to the main branch:
 +import static org.opengis.referencing.ReferenceSystem.*;
 +import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
index f50eb7f5f9,92f84c5ac2..29672463ff
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java
@@@ -32,12 -32,6 +32,13 @@@ import org.apache.sis.referencing.cs.Ha
  import org.apache.sis.referencing.cs.HardCodedCS;
  import org.apache.sis.test.TestCase;
  
 +// Specific to the main branch:
++import static org.apache.sis.referencing.privy.AxisDirections.TOWARDS;
 +import static org.apache.sis.referencing.privy.AxisDirections.AWAY_FROM;
 +import static org.apache.sis.referencing.privy.AxisDirections.CLOCKWISE;
 +import static org.apache.sis.referencing.privy.AxisDirections.COUNTER_CLOCKWISE;
 +import static org.apache.sis.referencing.privy.AxisDirections.UNSPECIFIED;
 +
  
  /**
   * Tests the {@link AxisDirections} class.
diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
index c39a324c08,e8cdfc64e5..9f3be45a8a
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java
@@@ -59,9 -59,8 +59,9 @@@ import org.apache.sis.util.resources.Er
  import org.apache.sis.measure.NumberRange;
  import org.apache.sis.measure.Units;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.operation.CoordinateOperationFactory;
 +// Specific to the main branch:
++import org.apache.sis.temporal.TemporalDate;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
- import org.apache.sis.util.privy.TemporalDate;
  
  
  /**
diff --cc endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
index 7223b7a6f6,88bc82ca4e..0697471ebc
--- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
+++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java
@@@ -39,16 -39,11 +39,16 @@@ import org.opengis.metadata.constraint.
  import org.opengis.util.InternationalString;
  import org.apache.sis.util.iso.Types;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.metadata.citation.Party;
 -import org.opengis.metadata.citation.Responsibility;
 -import org.opengis.metadata.identification.BrowseGraphic;
 -import org.apache.sis.util.SimpleInternationalString;
 +// Specific to the main branch:
 +import java.util.Date;
 +import org.opengis.metadata.citation.Contact;
 +import org.opengis.metadata.citation.Series;
 +import org.opengis.metadata.citation.ResponsibleParty;
- import org.apache.sis.util.privy.TemporalDate;
 +import org.apache.sis.metadata.iso.citation.AbstractParty;
 +import org.apache.sis.metadata.iso.citation.DefaultCitation;
 +import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 +import org.apache.sis.metadata.iso.constraint.DefaultConstraints;
++import org.apache.sis.temporal.TemporalDate;
  
  
  /**