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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new f071bb2911 If a TIFF `DateTime` tag exists, use it as the temporal 
coordinate of the grid geometry. This behavior is specified by the Defense 
Geospatial Information Working Group (DGIWG).
f071bb2911 is described below

commit f071bb291137857db4b409f5cf42e505901b2848
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Oct 16 15:10:40 2025 +0200

    If a TIFF `DateTime` tag exists, use it as the temporal coordinate of the 
grid geometry.
    This behavior is specified by the Defense Geospatial Information Working 
Group (DGIWG).
---
 .../sis/coverage/grid/GridCoverageProcessor.java   |   2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java |  15 +--
 .../apache/sis/coverage/grid/GridOrientation.java  |  34 ++++--
 .../org/apache/sis/coverage/grid/package-info.java |   2 +-
 .../org.apache.sis.metadata/main/module-info.java  |   2 +-
 .../org/apache/sis/metadata/sql/package-info.java  |   2 +-
 .../org/apache/sis/temporal/LenientDateFormat.java |   5 +-
 .../main/module-info.java                          |   2 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  25 ++++-
 .../referencing/factory/sql/EPSGDataAccess.java    |   2 +-
 .../sis/referencing/factory/sql/package-info.java  |   2 +-
 .../shared/ReferencingFactoryContainer.java        |   5 -
 .../internal/shared/TemporalAccessor.java          |   2 +-
 .../main/module-info.java                          |   2 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  30 ------
 .../sis/storage/geotiff/ImageFileDirectory.java    |  95 +++++++++++------
 .../sis/storage/geotiff/MultiResolutionImage.java  |  27 ++---
 .../org/apache/sis/storage/geotiff/Writer.java     |  20 ++--
 .../org/apache/sis/storage/geotiff/base/Tags.java  |   2 +-
 .../apache/sis/storage/geotiff/package-info.java   |   2 +-
 .../sis/storage/geotiff/reader/CRSBuilder.java     |   6 +-
 .../geotiff/reader/GridGeometryBuilder.java        | 118 +++++++++++++++------
 .../apache/sis/storage/geotiff/reader/Type.java    |   2 +
 .../sis/storage/geotiff/reader/XMLMetadata.java    |   2 +-
 .../sis/storage/geotiff/writer/GeoEncoder.java     |  72 ++++++++++++-
 .../main/module-info.java                          |   2 +-
 .../org/apache/sis/storage/sql/package-info.java   |   2 +-
 .../org.apache.sis.storage/main/module-info.java   |   2 +-
 .../sis/storage/aggregate/CoverageAggregator.java  |   2 +-
 .../apache/sis/storage/base/MetadataFetcher.java   |  28 ++---
 .../main/org/apache/sis/storage/csv/Store.java     |   2 +-
 .../org/apache/sis/storage/csv/TimeEncoding.java   |   7 +-
 .../src/org.apache.sis.util/main/module-info.java  |   2 +-
 .../main/org/apache/sis/math/Statistics.java       |   2 +-
 34 files changed, 333 insertions(+), 194 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index c4648c90a4..b4b2f9a674 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -720,7 +720,7 @@ public class GridCoverageProcessor implements Cloneable {
      * @since 1.5
      */
     public GridCoverage appendDimension(final GridCoverage source, final 
Instant lower, final Duration span) {
-        final DefaultTemporalCRS crs = 
DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+        final DefaultTemporalCRS crs = 
DefaultTemporalCRS.castOrCopy(CommonCRS.defaultTemporal());
         double scale  = crs.toValue(span);
         double offset = crs.toValue(lower);
         long   index  = Numerics.roundAndClamp(offset / scale);             // 
See comment in above method.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 7a73ef7192..ea203379e0 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -134,7 +134,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   1.0
  */
 public class GridGeometry implements LenientComparable, Serializable {
@@ -222,9 +222,10 @@ public class GridGeometry implements LenientComparable, 
Serializable {
     protected final GridExtent extent;
 
     /**
-     * The geodetic envelope, or {@code null} if unknown. If non-null, this 
envelope is usually the grid {@link #extent}
-     * {@linkplain #gridToCRS transformed} to real world coordinates. The 
Coordinate Reference System} (CRS) of this
-     * envelope defines the "real world" CRS of this grid geometry.
+     * The spatiotemporal extent in units of the <abbr>CRS</abbr>, or {@code 
null} if unknown.
+     * If non-null, this envelope is usually the grid {@link #extent} 
{@linkplain #gridToCRS transformed}
+     * to real world coordinates, with the lower coordinates inclusive and the 
upper coordinates exclusive.
+     * The Coordinate Reference System (CRS) of this envelope defines the 
"real world" CRS of this grid geometry.
      *
      * @see #ENVELOPE
      * @see #getEnvelope()
@@ -683,7 +684,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
             this.envelope = null;
         } else {
             this.envelope = target;
-            if (extent != null) {
+            if (extent != null && orientation != GridOrientation.UNKNOWN) {
                 // A non-null `sourceDimensions` implies non-null 
`orientation`.
                 if (sourceDimensions != null && 
orientation.canReorderGridAxis) {
                     if (!ArraysExt.isRange(0, sourceDimensions)) {
@@ -1138,8 +1139,8 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * Returns the start time and end time of coordinates of the grid.
      * If the grid has no temporal dimension, then this method returns an 
empty array.
      * If only the start time or end time is defined, then returns an array of 
length 1.
-     * Otherwise this method returns an array of length 2 with the start time 
in the first element
-     * and the end time in the last element.
+     * Otherwise this method returns an array of length 2 with the start time 
(inclusive)
+     * in the first element and the end time (exclusive) in the last element.
      *
      * @return time range as an array of length 0 (if none), 1 or 2.
      */
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
index 6fc1594eb3..d5c31f9b56 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
@@ -30,10 +30,15 @@ import org.apache.sis.util.resources.Errors;
  * For example, the conversion from grid coordinates to CRS coordinates may 
flip the <var>y</var> axis
  * (grid coordinates increasing toward down on screen), or may swap 
<var>x</var> and <var>y</var> axes, <i>etc.</i>
  * The constants enumerated in this class cover only a few common cases where 
the grid is
- * <a 
href="https://en.wikipedia.org/wiki/Axis-aligned_object";>axis-aligned</a> with 
the CRS.
+ * <a 
href="https://en.wikipedia.org/wiki/Axis-aligned_object";>axis-aligned</a> with 
the <abbr>CRS</abbr>.
+ *
+ * <h4>Custom orientations</h4>
+ * For creating a custom orientations, one of the constants defined in this 
class can be used as a starting point.
+ * Then, the {@link #flipGridAxis(int)}, {@link 
#useVariantOfCRS(AxesConvention)} or {@link #canReorderGridAxis(boolean)}
+ * methods can be invoked.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.6
  *
  * @see GridGeometry#GridGeometry(GridExtent, Envelope, GridOrientation)
  *
@@ -143,6 +148,15 @@ public final class GridOrientation implements Serializable 
{
      */
     public static final GridOrientation DISPLAY = new GridOrientation(2, 
AxesConvention.DISPLAY_ORIENTED, false);
 
+    /**
+     * Unknown image orientation. The {@linkplain 
GridGeometry#getGridToCRS(PixelInCell) grid to CRS}
+     * transforms inferred from this orientation will be null.
+     * All methods in this class invoked on the {@code UNKNOWN} instance will 
return {@code UNKNOWN}.
+     *
+     * @since 1.6
+     */
+    public static final GridOrientation UNKNOWN = new GridOrientation(0, null, 
true);
+
     /**
      * Set of grid axes to reverse, as a bit mask. For any dimension 
<var>i</var>, the bit
      * at {@code 1L << i} is set to 1 if the grid axis at that dimension 
should be flipped.
@@ -163,7 +177,7 @@ public final class GridOrientation implements Serializable {
 
     /**
      * Whether {@link GridExtent} can be rewritten with a different axis order
-     * for matching the CRS axis order specified by {@link #crsVariant}.
+     * for matching the <abbr>CRS</abbr> axis order specified by {@link 
#crsVariant}.
      * If {@code false}, then axis order changes will be handled in the {@code 
gridToCRS} transform instead.
      *
      * @see #canReorderGridAxis(boolean)
@@ -196,6 +210,9 @@ public final class GridOrientation implements Serializable {
         if (dimension >= Long.SIZE) {
             throw new 
ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, 
dimension + 1));
         }
+        if (this == UNKNOWN) {
+            return this;
+        }
         return new GridOrientation(flippedAxes ^ (1L << dimension), 
crsVariant, canReorderGridAxis);
     }
 
@@ -227,7 +244,7 @@ public final class GridOrientation implements Serializable {
      * @see #DISPLAY
      */
     public GridOrientation useVariantOfCRS(final AxesConvention variant) {
-        if (variant == crsVariant) {
+        if (variant == crsVariant || this == UNKNOWN) {
             return this;
         }
         if (variant == AxesConvention.NORMALIZED || variant == 
AxesConvention.ORIGINAL) {
@@ -246,7 +263,7 @@ public final class GridOrientation implements Serializable {
      * @return a grid orientation equals to this one except that it has the 
specified flag.
      */
     public GridOrientation canReorderGridAxis(final boolean enabled) {
-        if (enabled == canReorderGridAxis) {
+        if (enabled == canReorderGridAxis || this == UNKNOWN) {
             return this;
         }
         return new GridOrientation(flippedAxes, crsVariant, enabled);
@@ -261,7 +278,7 @@ public final class GridOrientation implements Serializable {
     @Override
     public boolean equals(final Object other) {
         if (other instanceof GridOrientation) {
-            final GridOrientation that = (GridOrientation) other;
+            final var that = (GridOrientation) other;
             return flippedAxes == that.flippedAxes &&
                    crsVariant  == that.crsVariant  &&
                    canReorderGridAxis == that.canReorderGridAxis;
@@ -283,7 +300,10 @@ public final class GridOrientation implements Serializable 
{
      */
     @Override
     public String toString() {
-        final StringBuilder buffer = new 
StringBuilder(getClass().getSimpleName()).append('[');
+        if (this == UNKNOWN) {
+            return "UNKNOWN";
+        }
+        final var buffer = new 
StringBuilder(getClass().getSimpleName()).append('[');
         String separator = "";
         if (flippedAxes != 0) {
             buffer.append("flip={");
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
index 8fde1c33e5..81b06aaef5 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
@@ -41,7 +41,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   1.0
  */
 package org.apache.sis.coverage.grid;
diff --git a/endorsed/src/org.apache.sis.metadata/main/module-info.java 
b/endorsed/src/org.apache.sis.metadata/main/module-info.java
index e66e26c614..e76f65657f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/module-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java
@@ -23,7 +23,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Cédric Briançon (Geomatys)
  * @author  Cullen Rombach (Image Matters)
- * @version 1.5
+ * @version 1.6
  * @since   0.3
  */
 module org.apache.sis.metadata {
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
index d6396799d0..30d5b7ae9b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
@@ -42,7 +42,7 @@
  *
  * @author  Touraïvane (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.6
  *
  * @see org.apache.sis.referencing.factory.sql
  *
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
index c3672d4178..09176ac3da 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
@@ -124,7 +124,7 @@ public final class LenientDateFormat extends DateFormat {
     }
 
     /**
-     * Parses the given date as an instant, assuming UTC timezone if 
unspecified.
+     * Parses the given date as an instant, assuming <abbr>UTC</abbr> timezone 
if unspecified.
      *
      * @param  text   the text to parse as an instant in UTC timezone by 
default, or {@code null}.
      * @return the instant for the given text, or {@code null} if the given 
text was null.
@@ -135,7 +135,8 @@ public final class LenientDateFormat extends DateFormat {
     }
 
     /**
-     * Parses the given date as an instant, assuming UTC timezone if 
unspecified.
+     * Parses the given date as an instant, assuming <abbr>UTC</abbr> timezone 
if unspecified.
+     * This method is tolerant to date and time separated by a space instead 
of the {@code 'T'} character.
      *
      * @param  text   the text to parse as an instant in UTC timezone by 
default.
      * @param  lower  index of the first character to parse.
diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java 
b/endorsed/src/org.apache.sis.referencing/main/module-info.java
index 27146fb6ca..2457ee6ede 100644
--- a/endorsed/src/org.apache.sis.referencing/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java
@@ -21,7 +21,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
  * @author  Maxime Gavens (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   0.3
  */
 module org.apache.sis.referencing {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index ddd75f1fed..d446fd47cb 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -572,11 +572,33 @@ public enum CommonCRS {
      * their datum can be arbitrary.</p>
      *
      * @return the default two-dimensional geographic CRS with 
(<var>longitude</var>, <var>latitude</var>) axis order.
+     *
+     * @see #defaultTemporal()
      */
     public static GeographicCRS defaultGeographic() {
         return DEFAULT.normalizedGeographic();
     }
 
+    /**
+     * Returns the default temporal <abbr>CRS</abbr> used by the Apache 
<abbr>SIS</abbr> library.
+     * The current implementation uses {@linkplain Temporal#TRUNCATED_JULIAN 
truncated Julian days},
+     * which are the number of days elapsed since May 24, 1968 at 00:00 
<abbr>UTC</abbr>.
+     * However, this default <abbr>CRS</abbr> may change in any future 
<abbr>SIS</abbr> version.
+     * For handling the coordinate values in a way independent of 
<abbr>CRS</abbr> changes,
+     * the can be converted with {@link DefaultTemporalCRS#toInstant(double)}.
+     *
+     * @return the default temporal <abbr>CRS</abbr>.
+     *
+     * @see #defaultGeographic()
+     * @see Temporal#TRUNCATED_JULIAN
+     * @see DefaultTemporalCRS#toInstant(double)
+     *
+     * @since 1.6
+     */
+    public static TemporalCRS defaultTemporal() {
+        return Temporal.TRUNCATED_JULIAN.crs();
+    }
+
     /**
      * Returns a two-dimensional geographic CRS with axes in the non-standard 
but computationally convenient
      * (<var>longitude</var>, <var>latitude</var>) order. The coordinate 
system axes will be oriented toward
@@ -1553,6 +1575,7 @@ public enum CommonCRS {
      * @author  Martin Desruisseaux (Geomatys)
      * @version 1.5
      *
+     * @see #defaultTemporal()
      * @see Engineering#TIME
      *
      * @since 0.4
@@ -1618,7 +1641,7 @@ public enum CommonCRS {
          * (by contrast, the IUGS definition is only about duration).
          *
          * <h4>Application to geodesy</h4>
-         * The tropical year is the unit of measurement used in EPSG geodetic 
database for year duration.
+         * The tropical year is the unit of measurement used in the 
<abbr>EPSG</abbr> geodetic dataset for year duration.
          * It it used for rate of changes such as "centimeters per year". Its 
identifier is EPSG:1029.
          *
          * @see Units#TROPICAL_YEAR
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index b0a8fc0bc9..8ab167b668 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -160,7 +160,7 @@ import org.opengis.referencing.ObjectDomain;
  * @author  Matthias Basler
  * @author  Andrea Aime (TOPP)
  * @author  Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.6
  *
  * @see <a 
href="https://sis.apache.org/tables/CoordinateReferenceSystems.html";>List of 
authority codes</a>
  *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
index bde70bffc3..3df5fafcea 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
@@ -83,7 +83,7 @@
  * @author  Jody Garnett (Refractions)
  * @author  Didier Richard (IGN)
  * @author  John Grange
- * @version 1.5
+ * @version 1.6
  *
  * @see org.apache.sis.metadata.sql
  *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
index 05d07eccc0..e745d32268 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
@@ -51,14 +51,9 @@ import org.opengis.util.Factory;
  * A container of factories frequently used together.
  * Provides also some utility methods working with factories.
  *
- * This class may be temporary until we choose a dependency injection framework
- * See <a href="https://issues.apache.org/jira/browse/SIS-102";>SIS-102</a>.
- *
  * <p>This class is not thread safe. Synchronization, if needed, is caller's 
responsibility.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- *
- * @see <a href="https://issues.apache.org/jira/browse/SIS-102";>SIS-102</a>
  */
 public class ReferencingFactoryContainer implements Localized {
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
index e348b50eba..bab0c03bf3 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
@@ -101,7 +101,7 @@ public final class TemporalAccessor {
             startTime = endTime;
             endTime = null;
         }
-        final Instant[] times = new Instant[(endTime != null) ? 2 : 1];
+        final var times = new Instant[(endTime != null) ? 2 : 1];
         switch (times.length) {
             default: times[1] = endTime;        // Fall through.
             case 1:  times[0] = startTime;      // Fall through.
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
index d65fe43c7f..3e4d1b77c9 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
@@ -22,7 +22,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   0.8
  */
 module org.apache.sis.storage.geotiff {
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 3ffe5083f8..1152138fe2 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -19,11 +19,7 @@ package org.apache.sis.storage.geotiff;
 import java.util.Set;
 import java.util.List;
 import java.util.Locale;
-import java.util.TimeZone;
 import java.util.Optional;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.time.ZoneId;
 import java.net.URI;
 import java.io.IOException;
 import java.nio.charset.Charset;
@@ -121,18 +117,6 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
      */
     final Locale dataLocale;
 
-    /**
-     * The timezone for the date and time parsing, or {@code null} for the 
default.
-     */
-    private final ZoneId timezone;
-
-    /**
-     * The object to use for parsing and formatting dates. Created when first 
needed.
-     *
-     * @see #getDateFormat()
-     */
-    private transient DateFormat dateFormat;
-
     /**
      * The {@link GeoTiffStoreProvider#LOCATION} parameter value, or {@code 
null} if none.
      * This is used for information purpose only, not for actual reading 
operations.
@@ -261,7 +245,6 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
 
         compression = connector.getOption(Compression.OPTION_KEY);
         dataLocale  = connector.getOption(OptionKey.LOCALE);
-        timezone    = connector.getOption(OptionKey.TIMEZONE);
         location    = connector.getStorageAs(URI.class);
         path        = connector.getStorageAs(Path.class);
         try {
@@ -498,19 +481,6 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
         return (path != null) ? Optional.of(new FileSet(path)) : 
Optional.empty();
     }
 
-    /**
-     * Returns the object to use for parsing and formatting dates.
-     */
-    final DateFormat getDateFormat() {
-        if (dateFormat == null) {
-            dateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", 
Locale.US);
-            if (timezone != null) {
-                dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
-            }
-        }
-        return dateFormat;
-    }
-
     /**
      * Returns the reader if it is not closed, or throws an exception 
otherwise.
      *
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index ab44a85017..c8368e99a2 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -17,7 +17,8 @@
 package org.apache.sis.storage.geotiff;
 
 import java.io.IOException;
-import java.text.ParseException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
 import java.util.List;
 import java.util.Arrays;
 import java.util.Optional;
@@ -34,6 +35,7 @@ import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
 import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.citation.DateType;
+import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameSpace;
 import org.opengis.util.FactoryException;
@@ -65,13 +67,14 @@ import 
org.apache.sis.util.internal.shared.UnmodifiableArrayList;
 import org.apache.sis.util.internal.shared.Numerics;
 import org.apache.sis.util.internal.shared.Strings;
 import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.temporal.LenientDateFormat;
 import org.apache.sis.math.Vector;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.pending.jdk.JDK18;
 
 
 /**
- * An Image File Directory (FID) in a TIFF image.
+ * An Image File Directory (FID) in a <abbr>TIFF</abbr> image.
  *
  * <h2>Thread-safety</h2>
  * Public methods should be synchronized because they can be invoked directly 
by users.
@@ -160,8 +163,6 @@ final class ImageFileDirectory extends DataCube {
      *
      * <p><b>Note:</b>
      * the {@link #imageHeight} attribute is named {@code ImageLength} in TIFF 
specification.</p>
-     *
-     * @see #getExtent()
      */
     private long imageWidth = -1, imageHeight = -1;
 
@@ -386,6 +387,15 @@ final class ImageFileDirectory extends DataCube {
      */
     private Predictor predictor;
 
+    /**
+     * The date/time found in the {@code DATE_TIME} tag, or {@code null} if 
none.
+     * According the <abbr>TIFF</abbr> specification, this is the image 
creation date.
+     * But the <abbr>DGIWG</abbr> specification reinterprets that information 
as the time when
+     * the imagery values were collected, which is a point on the 
<abbr>CRS</abbr> temporal axis.
+     * However, we will use this information that way only if {@link 
#referencing} is non-null.
+     */
+    public Instant imageDate;
+
     /**
      * A helper class for building Coordinate Reference System and complete 
related metadata.
      * Contains the following information:
@@ -415,7 +425,7 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * The grid geometry created by {@link GridGeometryBuilder#build(Reader, 
long, long)}.
+     * The grid geometry created by {@link 
GridGeometryBuilder#build(StoreListeners, long, long, Instant)}.
      * It has 2 or 3 dimensions, depending on whether the CRS declares a 
vertical axis or not.
      *
      * @see #getGridGeometry()
@@ -524,7 +534,7 @@ final class ImageFileDirectory extends DataCube {
      * @param  count  the number of values to read.
      * @return {@code null} on success, or the unrecognized value otherwise.
      * @throws IOException if an error occurred while reading the stream.
-     * @throws ParseException if the value need to be parsed as date and the 
parsing failed.
+     * @throws DateTimeParseException if the value need to be parsed as date 
and the parsing failed.
      * @throws NumberFormatException if the value need to be parsed as number 
and the parsing failed.
      * @throws ArithmeticException if the value cannot be represented in the 
expected Java type.
      * @throws IllegalArgumentException if a value which was expected to be a 
singleton is not.
@@ -1009,13 +1019,25 @@ final class ImageFileDirectory extends DataCube {
             }
             /*
              * Date and time of image creation. The format is: "YYYY:MM:DD 
HH:MM:SS" with 24-hour clock.
+             * The <abbr>DGIWG</abbr> specification requires that all 
date/time stamps are expressed in
+             * Coordinated Universal Time (UTC), and that they represent the 
date/time when the imagery
+             * values were collected.
              *
              * Destination: metadata/identificationInfo/citation/date
+             *        Also: gridGeometry/gridToCRS
              */
             case TAG_DATE_TIME: {
-                for (final String value : type.readAsStrings(input(), count, 
encoding())) {
-                    
metadata.addCitationDate(reader.store.getDateFormat().parse(value).toInstant(),
-                            DateType.CREATION, 
ImageMetadataBuilder.Scope.RESOURCE);
+                for (String value : type.readAsStrings(input(), count, 
encoding())) {
+                    // The presence of a letter would be an invalid date 
according TIFF and DGIWG specifications.
+                    // If present anyway, parse as an ISO date. If failure, 
warning will be logged by the caller.
+                    if (!CharSequences.isUpperCase(value)) {
+                        final String[] parts = (String[]) 
CharSequences.split(value, ' ');
+                        if (parts.length == 2) {
+                            value = parts[0].replace(':', '-') + 'T' + 
parts[1] + 'Z';
+                        }
+                    }
+                    imageDate = LenientDateFormat.parseInstantUTC(value);
+                    metadata.addCitationDate(imageDate, DateType.CREATION, 
ImageMetadataBuilder.Scope.RESOURCE);
                 }
                 break;
             }
@@ -1450,22 +1472,45 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * If this IFD has no grid geometry information, derives a grid geometry 
by applying a scale factor
-     * on the grid geometry of another IFD. Information about bands are also 
copied if compatible.
+     * If this <abbr>IFD</abbr> has no grid geometry, derives this information
+     * by scaling the grid geometry of the specified image at full resolution.
+     * Information about bands are also copied if compatible. The scale 
factors are returned,
+     * with the scale of the temporal dimension defined to 1 for telling that 
the time does not change.
+     *
+     * <h4>Conditions</h4>
      * This method should be invoked only when {@link #isReducedResolution()} 
is {@code true}.
      *
      * @param  fullResolution  the full-resolution image.
-     * @param  scales  <var>size of full resolution image</var> / <var>size of 
this image</var> for each grid axis.
-     */
-    final void initReducedResolution(final ImageFileDirectory fullResolution, 
final double[] scales)
-            throws DataStoreException, TransformException
-    {
+     * @return <var>size of full resolution image</var> / <var>size of this 
image</var> for each grid axis.
+     */
+    final double[] initReducedResolution(final ImageFileDirectory 
fullResolution) throws DataStoreException, TransformException {
+        final GridGeometry geometry = fullResolution.getGridGeometry();
+        final GridExtent fullExtent = geometry.getExtent();
+        final int dimension = fullExtent.getDimension();
+        final var axisTypes = new DimensionNameType[dimension];
+        final var scales    = new double[dimension];
+        final var high      = new long[dimension];
+        for (int i=0; i<dimension; i++) {
+            axisTypes[i] = fullExtent.getAxisType(i).orElse(null);
+            final long size;
+            switch (i) {
+                case 0:  size = imageWidth;  break;
+                case 1:  size = imageHeight; break;
+                default: scales[i] = 1; continue;
+            }
+            scales[i] = fullExtent.getSize(i, false) / size;
+            high[i] = size - 1;
+        }
         if (referencing == null) {
-            gridGeometry = new GridGeometry(fullResolution.getGridGeometry(), 
getExtent(), MathTransforms.scale(scales));
+            gridGeometry = new GridGeometry(
+                    geometry,
+                    new GridExtent(axisTypes, null, high, true),
+                    MathTransforms.scale(scales));
         }
         if (samplesPerPixel == fullResolution.samplesPerPixel) {
             sampleDimensions = fullResolution.getSampleDimensions();
         }
+        return scales;
     }
 
     /**
@@ -1483,7 +1528,6 @@ final class ImageFileDirectory extends DataCube {
      * <h4>Thread-safety</h4>
      * This method must be thread-safe because it can be invoked directly by 
the user.
      *
-     * @see #getExtent()
      * @see #getTileSize()
      */
     @Override
@@ -1492,12 +1536,12 @@ final class ImageFileDirectory extends DataCube {
             GridGeometry domain = gridGeometry;
             if (domain == null) {
                 if (referencing != null) try {
-                    domain = referencing.build(reader.store.listeners(), 
imageWidth, imageHeight);
+                    domain = referencing.build(reader.store.listeners(), 
imageWidth, imageHeight, imageDate);
                 } catch (FactoryException e) {
                     throw new 
DataStoreContentException(reader.resources().getString(Resources.Keys.CanNotComputeGridGeometry_1,
 filename()), e);
                 } else {
                     // Fallback if the TIFF file has no GeoKeys.
-                    domain = new GridGeometry(getExtent(), null, null);
+                    domain = new GridGeometry(new GridExtent(imageWidth, 
imageHeight), null, null);
                 }
                 final CoverageModifier.Source source = source();
                 gridGeometry = (source != null) ? 
reader.store.customizer.customize(source, domain) : domain;
@@ -1506,16 +1550,6 @@ final class ImageFileDirectory extends DataCube {
         }
     }
 
-    /**
-     * Returns the image width and height without building the full grid 
geometry.
-     *
-     * @see #getTileSize()
-     * @see #getGridGeometry()
-     */
-    final GridExtent getExtent() {
-        return new GridExtent(imageWidth, imageHeight);
-    }
-
     /**
      * Returns the minimum and maximum non-fill values in the specified band.
      * This is the values explicitly defined in <abbr>TIFF</abbr> tags if 
present,
@@ -1709,7 +1743,6 @@ final class ImageFileDirectory extends DataCube {
      * Returns the size of tiles. This is also the size of the image sample 
model.
      * The number of dimensions is always 2 for {@code ImageFileDirectory}.
      *
-     * @see #getExtent()
      * @see #getSampleModel(int[])
      */
     @Override
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
index 097b4bf978..75792a41a8 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
@@ -40,6 +40,7 @@ import org.apache.sis.storage.base.GridResourceWrapper;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.internal.shared.DirectPositionView;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import static 
org.apache.sis.storage.geotiff.reader.GridGeometryBuilder.BIDIMENSIONAL;
 
 
 /**
@@ -150,7 +151,9 @@ final class MultiResolutionImage extends 
GridResourceWrapper implements StoreRes
     }
 
     /**
-     * Returns the resolution (in units of CRS axes) for the given level.
+     * Returns the resolution (in units of <abbr>CRS</abbr> axes) for the 
given level.
+     * If there is a temporal dimension, its resolution is set to NaN because 
we don't
+     * know the duration.
      *
      * @param  level  the desired resolution level, numbered from finest to 
coarsest resolution.
      * @return resolution at the specified level, not cloned (caller shall not 
modify).
@@ -158,17 +161,12 @@ final class MultiResolutionImage extends 
GridResourceWrapper implements StoreRes
     private double[] resolution(final int level) throws DataStoreException {
         double[] resolution = resolutions[level];
         if (resolution == null) try {
-            final ImageFileDirectory image      = getImageFileDirectory(level);
-            final ImageFileDirectory base       = getImageFileDirectory(0);
-            final GridGeometry       geometry   = base.getGridGeometry();
-            final GridExtent         fullExtent = geometry.getExtent();
-            final GridExtent         subExtent  = image.getExtent();
-            final double[] scales = new double[fullExtent.getDimension()];
-            for (int i=0; i<scales.length; i++) {
-                scales[i] = fullExtent.getSize(i, false) / 
subExtent.getSize(i, false);
-            }
-            image.initReducedResolution(base, scales);
+            final ImageFileDirectory image = getImageFileDirectory(level);
+            final ImageFileDirectory base  = getImageFileDirectory(0);
+            final double[] scales = image.initReducedResolution(base);
+            final GridGeometry geometry = base.getGridGeometry();
             if (geometry.isDefined(GridGeometry.GRID_TO_CRS)) {
+                final GridExtent fullExtent = geometry.getExtent();
                 DirectPosition poi = new 
DirectPositionView.Double(fullExtent.getPointOfInterest(PixelInCell.CELL_CENTER));
                 MatrixSIS gridToCRS = 
MatrixSIS.castOrCopy(geometry.getGridToCRS(PixelInCell.CELL_CENTER).derivative(poi));
                 resolution = gridToCRS.multiply(scales);
@@ -176,7 +174,10 @@ final class MultiResolutionImage extends 
GridResourceWrapper implements StoreRes
                 // Assume an identity transform for the `gridToCRS` of full 
resolution image.
                 resolution = scales;
             }
-            for (int i=0; i<resolution.length; i++) {
+            // Set to NaN only after all matrix multiplications are done.
+            int i = Math.min(BIDIMENSIONAL, resolution.length);
+            Arrays.fill(scales, BIDIMENSIONAL, i, Double.NaN);
+            while (--i >= 0) {
                 resolution[i] = Math.abs(resolution[i]);
             }
             resolutions[level] = resolution;
@@ -257,7 +258,7 @@ final class MultiResolutionImage extends 
GridResourceWrapper implements StoreRes
         synchronized (getSynchronizationLock()) {
 finer:      while (--level > 0) {
                 final double[] resolution = resolution(level);
-                for (int i=0; i<request.length; i++) {
+                for (int i = Math.min(request.length, BIDIMENSIONAL); --i >= 
0;) {
                     if (!(request[i] >= resolution[i])) {            // Use 
`!` for catching NaN.
                         continue finer;
                     }
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
index 3926b00baf..f3aba2e396 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.nio.ByteOrder;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
 import java.util.ArrayDeque;
 import java.util.List;
 import java.util.Deque;
@@ -38,7 +37,6 @@ import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
 import javax.measure.IncommensurableException;
 import org.opengis.util.FactoryException;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.citation.CitationDate;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.DataType;
@@ -55,8 +53,8 @@ import org.apache.sis.storage.geotiff.writer.GeoEncoder;
 import org.apache.sis.storage.geotiff.writer.ReformattedImage;
 import org.apache.sis.io.stream.ChannelDataOutput;
 import org.apache.sis.io.stream.UpdatableWrite;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.internal.shared.Numerics;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.math.Fraction;
@@ -298,6 +296,7 @@ final class Writer extends IOBase implements Flushable {
      * @throws DataStoreException if the given {@code image} has a property
      *         which is not supported by TIFF specification or by this writer.
      */
+    @SuppressWarnings("UseSpecificCatch")
     public final long append(final RenderedImage image, final GridGeometry 
grid, final Metadata metadata)
             throws IOException, DataStoreException
     {
@@ -395,23 +394,18 @@ final class Writer extends IOBase implements Flushable {
         }
         /*
          * Metadata (optional) and GeoTIFF. They are managed by separated 
classes.
+         * Note that `TAG_DATE_TIME` has slightly different interpretation 
depending
+         * on whether we are writing GeoTIFF (`geoKeys != null`) or plain TIFF.
          */
         final double[][] statistics = image.statistics(numBands);
         final  short[][] shortStats = toShorts(statistics, sampleFormat);
-        final MetadataFetcher<String> mf = new 
MetadataFetcher<>(store.dataLocale) {
-            @Override protected boolean accept(final CitationDate info) {
-                return super.accept(info) || creationDate != null;          // 
Limit to a singleton.
-            }
-
-            @Override protected String convertDate(final Date date) {
-                return store.getDateFormat().format(date);
-            }
-        };
+        final var mf = new MetadataFetcher(store.dataLocale);
         mf.accept(metadata);
         GeoEncoder geoKeys = null;
         if (grid != null) try {
             geoKeys = new GeoEncoder(store.listeners());
             geoKeys.write(grid, mf);
+            mf.creationDate = geoKeys.imageDate();  // Unconditional, even if 
the list of empty.
         } catch (IncompleteGridGeometryException | CannotEvaluateException | 
TransformException e) {
             throw new IncompatibleResourceException(e.getMessage(), 
e).addAspect("gridGeometry");
         } catch (FactoryException | IncommensurableException | 
RuntimeException e) {
@@ -460,7 +454,7 @@ final class Writer extends IOBase implements Flushable {
         writeTag((short) TAG_PLANAR_CONFIGURATION,       (short) 
TIFFTag.TIFF_SHORT, planarConfiguration);
         writeTag((short) TAG_RESOLUTION_UNIT,            (short) 
TIFFTag.TIFF_SHORT, RESOLUTION_UNIT_NONE);
         writeTag((short) TAG_SOFTWARE,                   /* TIFF_ASCII */      
      mf.software);
-        writeTag((short) TAG_DATE_TIME,                  /* TIFF_ASCII */      
      mf.creationDate);
+        writeTag((short) TAG_DATE_TIME,                  /* TIFF_ASCII */      
      GeoEncoder.creationDates(mf.creationDate));
         writeTag((short) TAG_ARTIST,                     /* TIFF_ASCII */      
      mf.party);
         writeTag((short) TAG_HOST_COMPUTER,              /* TIFF_ASCII */      
      mf.procedure);
         if (compression.usePredictor()) {
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
index 50eb9c9f38..426a5588eb 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
@@ -52,7 +52,7 @@ public final class Tags {
 
     /**
      * Embedded XML-encoded instance documents prepared using 19139-based 
schema.
-     * This is an OGC DGIWG extension tag.
+     * This is an <abbr>OGC</abbr> <abbr>DGIWG</abbr> extension tag.
      */
     public static final short GEO_METADATA = (short) 0xC6DD;
 
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
index d5d2f35134..603b212edc 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
@@ -33,7 +33,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   0.8
  */
 package org.apache.sis.storage.geotiff;
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
index 7900e1de85..4604c24a3a 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
@@ -82,7 +82,6 @@ import org.apache.sis.util.internal.shared.Strings;
 import org.apache.sis.util.internal.shared.Numerics;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.math.Vector;
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.storage.event.StoreListeners;
@@ -179,14 +178,14 @@ public final class CRSBuilder extends 
ReferencingFactoryContainer {
 
     /**
      * Suggested value for a general description of the transformation form 
grid coordinates to "real world" coordinates.
-     * This is computed by {@link #build(Vector, Vector, String)} and made 
available as additional information to the caller.
+     * This is computed by {@link #build(GeoKeysLoader)} and made available as 
additional information to the caller.
      */
     public String description;
 
     /**
      * {@code POINT} if {@link GeoKeys#RasterType} is {@link 
GeoCodes#RasterPixelIsPoint},
      * {@code AREA} if it is {@link GeoCodes#RasterPixelIsArea}, or null if 
unspecified.
-     * This is computed by {@link #build(Vector, Vector, String)} and made 
available to the caller.
+     * This is computed by {@link #build(GeoKeysLoader)} and made available to 
the caller.
      */
     public CellGeometry cellGeometry;
 
@@ -512,6 +511,7 @@ public final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @throws ClassCastException if an object defined by an EPSG code is not 
of the expected type.
      * @throws FactoryException if an error occurred during objects creation 
with the factories.
      */
+    @SuppressWarnings("UseSpecificCatch")
     public CoordinateReferenceSystem build(final GeoKeysLoader source) throws 
FactoryException {
         try {
             source.logger = this;
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
index 68d704b4f8..ec1d0d93ed 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.geotiff.reader;
 
+import java.time.Instant;
 import java.util.NoSuchElementException;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
@@ -25,12 +26,17 @@ import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
 import 
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.storage.base.MetadataBuilder;
 import org.apache.sis.storage.event.StoreListeners;
@@ -78,13 +84,9 @@ import org.apache.sis.math.Vector;
  */
 public final class GridGeometryBuilder extends GeoKeysLoader {
     /**
-     * Default scale factory to apply if a row in the model transformation 
contains only zero values.
-     * The matrix in a GeoTIFF file is always of size 4×4 even if the 
<abbr>CRS</abbr> is two-dimensional.
-     * In the latter case, the matrix row for the third dimension has only 
zero values. That row should be
-     * discarded when building the final {@link GridGeometry}, but there is 
sometime inconsistency between
-     * the number of <abbr>CRS</abbr> dimensions and which matrix rows have 
been assigned non-zero values.
+     * Number of dimensions of the horizontal part.
      */
-    private static final double DEFAULT_SCALE_FACTOR = 1;
+    public static final int BIDIMENSIONAL = 2;
 
     //  
╔════════════════════════════════════════════════════════════════════════════════╗
     //  ║                                                                      
          ║
@@ -108,7 +110,7 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
     public Vector modelTiePoints;
 
     /**
-     * The conversion from grid coordinates to CRS coordinates as an affine 
transform.
+     * The conversion from grid coordinates to <abbr>CRS</abbr> coordinates as 
an affine transform.
      * The "grid to CRS" transform can be determined in different ways, from 
simpler to more complex:
      *
      * <ul>
@@ -174,14 +176,18 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
 
     /**
      * Suggested value for a general description of the transformation form 
grid coordinates to "real world" coordinates.
-     * This information is obtained as a side-effect of {@link 
#build(StoreListeners, long, long)} call.
+     * This information is obtained as a side-effect of {@link 
#build(StoreListeners, long, long, Instant)} call.
+     *
+     * @see #completeMetadata(GridGeometry, MetadataBuilder)
      */
     private String description;
 
     /**
      * {@code POINT} if {@link GeoKeys#RasterType} is {@link 
GeoCodes#RasterPixelIsPoint},
      * {@code AREA} if it is {@link GeoCodes#RasterPixelIsArea}, or null if 
unspecified.
-     * This information is obtained as a side-effect of {@link 
#build(StoreListeners, long, long)} call.
+     * This information is obtained as a side-effect of {@link 
#build(StoreListeners, long, long, Instant)} call.
+     *
+     * @see #completeMetadata(GridGeometry, MetadataBuilder)
      */
     private CellGeometry cellGeometry;
 
@@ -236,7 +242,7 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
              * Grid to CRS conversion:  crs = grid × scale + translation
              * We rearrange as:         translation = crs - grid × scale
              * where:                   grid   =  modelTiePoints[i]
-             *                          crs    =  modelTiePoints[i + 
RECORD_LENGTH/2]
+             *                          crs    =  modelTiePoints[i + 
RECORD_LENGTH / BIDIMENSIONAL]
              *                          scale  =  affine(i,i)  —  on the 
diagonal
              */
             if (distance != Double.POSITIVE_INFINITY) {
@@ -245,7 +251,7 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
                 final int trCol  = affine.getNumCol() - 1;
                 for (int j=0; j<numDim; j++) {
                     final double src = -modelTiePoints.doubleValue(nearest + 
j);
-                    final double tgt =  modelTiePoints.doubleValue(nearest + j 
+ Localization.RECORD_LENGTH / 2);
+                    final double tgt =  modelTiePoints.doubleValue(nearest + j 
+ Localization.RECORD_LENGTH / BIDIMENSIONAL);
                     var t = DoubleDouble.of(src, 
decimal).multiply(affine.getNumber(j,j), decimal).add(tgt, decimal);
                     affine.setNumber(j, trCol, t);
                 }
@@ -264,14 +270,17 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
      * @param  listeners  the listeners where to report warnings.
      * @param  width      the image width in pixels.
      * @param  height     the image height in pixels.
+     * @param  imageDate  the date/time found in the {@code DATE_TIME} tag, or 
{@code null} if none.
      * @return the grid geometry, guaranteed non-null.
      * @throws FactoryException if an error occurred while creating a CRS or a 
transform.
      */
     @SuppressWarnings("fallthrough")
-    public GridGeometry build(final StoreListeners listeners, final long 
width, final long height) throws FactoryException {
+    public GridGeometry build(final StoreListeners listeners, final long 
width, final long height, final Instant imageDate)
+            throws FactoryException
+    {
         CoordinateReferenceSystem crs = null;
         if (keyDirectory != null) {
-            final CRSBuilder helper = new CRSBuilder(listeners);
+            final var helper = new CRSBuilder(listeners);
             try {
                 crs = helper.build(this);
                 description  = helper.description;
@@ -289,47 +298,90 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
             }
         }
         /*
-         * If the CRS is non-null, then it is either two- or three-dimensional.
-         * The `affine` matrix may be for a greater number of dimensions, so it
-         * may need to be reduced.
+         * If the CRS is non-null, then the spatial part is either two- or 
three-dimensional.
+         * A temporal axis may be added to the non-null CRS.
          */
-        int n = (crs != null) ? crs.getCoordinateSystem().getDimension() : 2;
-        final var axisTypes = new DimensionNameType[n];
-        final var high = new long[n];
-        switch (n) {
+        final double timeCoordinate;
+        final TemporalCRS temporalCRS;
+        final int spatialDimension = (crs != null) ? 
crs.getCoordinateSystem().getDimension() : BIDIMENSIONAL;
+        final int dimension;
+        if (imageDate != null) {
+            dimension = spatialDimension + 1;
+            temporalCRS = CommonCRS.defaultTemporal();
+            timeCoordinate = 
DefaultTemporalCRS.castOrCopy(temporalCRS).toValue(imageDate);
+            if (crs != null) {
+                crs = CRS.compound(crs, temporalCRS);
+            }
+        } else {
+            dimension = spatialDimension;
+            timeCoordinate = Double.NaN;
+            temporalCRS = null;
+        }
+        final var axisTypes = new DimensionNameType[dimension];
+        final var high = new long[dimension];
+        if (temporalCRS != null) {
+            axisTypes[spatialDimension] = DimensionNameType.TIME;
+        }
+        switch (spatialDimension) {
             default: axisTypes[2] = DimensionNameType.VERTICAL; // Fallthrough 
everywhere.
             case 2:  axisTypes[1] = DimensionNameType.ROW;      high[1] = 
height - 1;
             case 1:  axisTypes[0] = DimensionNameType.COLUMN;   high[0] = 
width  - 1;
             case 0:  break;
         }
         final var extent = new GridExtent(axisTypes, null, high, true);
-        boolean pixelIsPoint = (cellGeometry == CellGeometry.POINT);
         final MathTransformFactory factory = 
DefaultMathTransformFactory.provider();
+        PixelInCell anchor = (cellGeometry == CellGeometry.POINT) ? 
PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER;
         GridGeometry gridGeometry;
         try {
             MathTransform gridToCRS = null;
             if (affine != null) {
-                final Matrix m = Matrices.resizeAffine(affine, ++n, n);
-                Matrices.forceNonZeroScales(m, DEFAULT_SCALE_FACTOR);
+                /*
+                 * The `affine` matrix is always 4×4 in a GeoTIFF file, which 
may be larger than requested.
+                 * Resize the matrix to the size that we need. Maybe the last 
dimension, initially ignored,
+                 * become used by the temporal dimension, so we need to clear 
that dimension for safety.
+                 */
+                final Matrix m = Matrices.resizeAffine(affine, dimension + 1, 
dimension + 1);
+                if (temporalCRS != null) {
+                    for (int i=0; i <= dimension; i++) {
+                        m.setElement(spatialDimension, i, 0);
+                        m.setElement(i, spatialDimension, 0);
+                    }
+                    m.setElement(spatialDimension, dimension, timeCoordinate);
+                    m.setElement(spatialDimension, spatialDimension, 
Double.NaN);   // Unknown duration.
+                }
+                /*
+                 * If the CRS has no vertical component, then the matrix row 
and column for the vertical coordinates
+                 * should be ignored. However, we observed inconsistency in 
some GeoTIFF files between the number of
+                 * CRS dimensions and the matrix rows which have been assigned 
non-zero values. Because rows of only
+                 * zero values cause problems, we assign a NaN value in one of 
their columns.
+                 */
+                Matrices.forceNonZeroScales(m, Double.NaN);
                 gridToCRS = factory.createAffineTransform(m);
             } else if (modelTiePoints != null) {
-                pixelIsPoint = true;
+                anchor    = PixelInCell.CELL_CENTER;
                 gridToCRS = Localization.nonLinear(modelTiePoints);
-                gridToCRS = factory.createPassThroughTransform(0, gridToCRS, n 
- 2);
+                gridToCRS = factory.createPassThroughTransform(0, gridToCRS, 
spatialDimension - BIDIMENSIONAL);
+                if (temporalCRS != null) {
+                    gridToCRS = MathTransforms.compound(gridToCRS, 
MathTransforms.linear(Double.NaN, timeCoordinate));
+                }
             }
-            gridGeometry = new GridGeometry(extent, pixelIsPoint ? 
PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER, gridToCRS, crs);
+            gridGeometry = new GridGeometry(extent, anchor, gridToCRS, crs);
         } catch (TransformException e) {
+            /*
+             * Note: we catch TransformExceptions because they may be caused 
by erroneous data in the GeoTIFF file,
+             * but let FactoryExceptions propagate because they are more 
likely to be a SIS configuration problem.
+             */
             GeneralEnvelope envelope = null;
             if (crs != null) {
                 envelope = new GeneralEnvelope(crs);
                 envelope.setToNaN();
+                if (temporalCRS != null && anchor == PixelInCell.CELL_CENTER) {
+                    // The coordinate is the lower value (start) of the time 
range.
+                    envelope.setRange(spatialDimension, timeCoordinate, 
Double.NaN);
+                }
             }
-            gridGeometry = new GridGeometry(extent, envelope, 
GridOrientation.HOMOTHETY);
+            gridGeometry = new GridGeometry(extent, envelope, 
GridOrientation.UNKNOWN);
             canNotCreate(listeners, e);
-            /*
-             * Note: we catch TransformExceptions because they may be caused 
by erroneous data in the GeoTIFF file,
-             * but let FactoryExceptions propagate because they are more 
likely to be a SIS configuration problem.
-             */
         }
         keyDirectory      = null;            // Not needed anymore, so let GC 
do its work.
         numericParameters = null;
@@ -344,7 +396,7 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
      *
      * <h4>Prerequisite</h4>
      * <ul>
-     *   <li>{@link #build(StoreListeners, long, long)} must have been invoked 
successfully before this method.</li>
+     *   <li>{@link #build(StoreListeners, long, long, Instant)} must have 
been invoked successfully before this method.</li>
      *   <li>{@link ImageFileDirectory} must have filled its part of metadata 
before to invoke this method.</li>
      * </ul>
      *
@@ -358,7 +410,7 @@ public final class GridGeometryBuilder extends 
GeoKeysLoader {
      *   <li>{@code metadata/referenceSystemInfo}</li>
      * </ul>
      *
-     * @param  gridGeometry  the grid geometry computed by {@link 
#build(StoreListeners, long, long)}.
+     * @param  gridGeometry  the grid geometry computed by {@link 
#build(StoreListeners, long, long, Instant)}.
      * @param  metadata      the helper class where to write metadata values.
      * @throws NumberFormatException if a numeric value was stored as a string 
and cannot be parsed.
      */
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
index 093f27c106..89532b5401 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
@@ -615,6 +615,8 @@ public enum Type {
 
     /**
      * Reads the value as strings. There is usually exactly one string, but an 
arbitrary amount is allowed.
+     * The default implementation assumes that the vector contains numerical 
data, which is the case of all
+     * types except {@link #ASCII}. This method is overridden for handling any 
text in the {@code ASCII} case.
      *
      * @param  input    the input from where to read the value.
      * @param  length   the string length, including the final NUL byte.
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
index 7a5a64e482..9daaeb1969 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
@@ -76,7 +76,7 @@ public final class XMLMetadata implements Filter {
 
     /**
      * The bytes to decode as an XML document.
-     * DGIWG specification mandates UTF-8 encoding.
+     * <abbr>DGIWG</abbr> specification mandates UTF-8 encoding.
      */
     private byte[] bytes;
 
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index 03b2f6b0d7..12f9f057e7 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -20,6 +20,10 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.EnumMap;
 import java.util.logging.Level;
+import java.time.Instant;
+import java.time.Duration;
+import java.time.temporal.Temporal;
+import java.time.format.DateTimeFormatter;
 import static javax.imageio.plugins.tiff.GeoTIFFTagSet.TAG_GEO_ASCII_PARAMS;
 import static javax.imageio.plugins.tiff.GeoTIFFTagSet.TAG_GEO_DOUBLE_PARAMS;
 import javax.measure.Unit;
@@ -55,6 +59,7 @@ import org.opengis.parameter.ParameterValue;
 import org.apache.sis.measure.Units;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.StringBuilders;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.internal.shared.Strings;
 import org.apache.sis.util.internal.shared.CollectionsExt;
@@ -120,6 +125,14 @@ public final class GeoEncoder {
      */
     private String citation;
 
+    /**
+     * The temporal coordinate of the image, or {@code null} if none.
+     * This is extracted from the temporal dimension of the grid geometry.
+     *
+     * @see #imageDate()
+     */
+    private Instant imageDate;
+
     /**
      * Whether the map projection is a pseudo-projection. The latter has no 
GeoTIFF code.
      * Therefore, they need to be replaced by the non-pseudo variant with 
adjustments of
@@ -257,7 +270,7 @@ public final class GeoEncoder {
      * @throws IncompleteGridGeometryException if the grid geometry is 
incomplete.
      * @throws IncompatibleResourceException if the grid geometry cannot be 
encoded.
      */
-    public void write(GridGeometry grid, final MetadataFetcher<?> metadata)
+    public void write(GridGeometry grid, final MetadataFetcher metadata)
             throws FactoryException, TransformException, 
IncommensurableException, IncompatibleResourceException
     {
         grid = grid.shiftGridToZeros();
@@ -265,8 +278,8 @@ public final class GeoEncoder {
         isPoint  = CollectionsExt.first(metadata.cellGeometry) == 
CellGeometry.POINT;
         final var anchor = isPoint ? PixelInCell.CELL_CENTER : 
PixelInCell.CELL_CORNER;
         /*
-         * Get the dimension indices of the two-dimensional slice to write. 
They should be the first dimensions,
-         * but we allow those dimensions to appear elsewhere.
+         * Get the dimension indices of the two-dimensional slice to write.
+         * They should be the first dimensions, but we allow those dimensions 
to appear elsewhere.
          */
         final int[] dimensions = 
grid.getExtent().getSubspaceDimensions(BIDIMENSIONAL);
         final GridGeometry horizontal = grid.selectDimensions(dimensions);
@@ -276,6 +289,20 @@ public final class GeoEncoder {
                 String message = 
resources().getString(Resources.Keys.CanNotEncodeNonLinearModel);
                 throw new 
IncompatibleResourceException(message).addAspect("gridToCRS");
             }
+            /*
+             * Extract the temporal coordinate. This information is stored for 
restitution by the
+             * `imageDate()` method. This information, even absent, shall 
unconditionally replace
+             * the information obtained from metadata for avoiding 
misinterpretation at read time.
+             */
+            final Instant[] time = grid.getTemporalExtent();
+            if (time.length != 0) {
+                imageDate = time[0];
+                if (isPoint && time.length > 1) {
+                    // TODO: replace by the following when allowed to compile 
for JDK23:
+                //  imageDate = 
imageDate.plus(imageDate.until(t[1]).dividedBy(2));
+                    imageDate = imageDate.plus(Duration.between(imageDate, 
time[1]).dividedBy(2));
+                }
+            }
         }
         /*
          * Write the horizontal component of the CRS. We need to take the CRS
@@ -861,6 +888,45 @@ public final class GeoEncoder {
         return JDK15.isEmpty(asciiParams) ? null : 
List.of(asciiParams.toString());
     }
 
+    /**
+     * Formats the given date in the way that it should be encoded in the 
{@code DATE_TIME} tag.
+     * According the <abbr>TIFF</abbr> specification, this tag contains the 
image creation date.
+     * But the <abbr>DGIWG</abbr> specification reinterprets that information 
as the time when
+     * the imagery values were collected, which is a point on the 
<abbr>CRS</abbr> temporal axis.
+     *
+     * <p>The list given to this method should be the list returned by {@link 
#imageDate()} when
+     * the image contains spatiotemporal referencing information. But the 
argument may also be a
+     * list obtained from other source when the image to write has no 
spatiotemporal coordinates,
+     * in which case we are writing a plain <abbr>TIFF</abbr> image and the 
date may be obtained
+     * from ISO 19115 metadata.</p>
+     *
+     * @param  imageDate  the date to encode, or {@code null} or empty if none.
+     * @return the first date encoded as a string, or {@code null} if none.
+     */
+    public static List<String> creationDates(final List<Temporal> imageDate) {
+        if (imageDate == null || imageDate.isEmpty()) {
+            return null;
+        }
+        var value = new 
StringBuilder(DateTimeFormatter.ISO_INSTANT.format(imageDate.get(0)));
+        int s = value.lastIndexOf(".");
+        if (s >= 0) value.setLength(s);
+        StringBuilders.replace(value, "-", ":");
+        StringBuilders.replace(value, "T", " ");
+        StringBuilders.replace(value, "Z", "");
+        return List.of(value.toString());
+    }
+
+    /**
+     * Returns the temporal coordinate of the image, or an empty list if none.
+     * This information shall unconditionally replace the information obtained 
from metadata, even when
+     * the list is empty, for avoiding misinterpretation of the spatiotemporal 
location at reading time.
+     *
+     * @return the temporal coordinate of the image as a list of 0 or 1 
element.
+     */
+    public List<Temporal> imageDate() {
+        return (imageDate != null) ? List.of(imageDate) : List.of();
+    }
+
     /**
      * Returns the coefficients of the affine transform, or {@code null} if 
none.
      * Array length is fixed to 16 elements, for a 4×4 matrix in row-major 
order.
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java 
b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
index c34df3d4f4..bb0104a6d9 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
@@ -42,7 +42,7 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   1.0
  */
 module org.apache.sis.storage.sql {
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
index ec2b149779..c764390369 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
@@ -56,7 +56,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   1.0
  */
 package org.apache.sis.storage.sql;
diff --git a/endorsed/src/org.apache.sis.storage/main/module-info.java 
b/endorsed/src/org.apache.sis.storage/main/module-info.java
index 527a1e2d5e..8b7fe8b1c5 100644
--- a/endorsed/src/org.apache.sis.storage/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage/main/module-info.java
@@ -20,7 +20,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   0.3
  */
 module org.apache.sis.storage {
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
index 22b3c11e6b..5de1f71862 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
@@ -370,7 +370,7 @@ search: synchronized (members) {
          * but a future version may use the state of this 
`CoverageAggregator`, for example making a better
          * effort to align the resources on the same "gridToCRS" transform.
          */
-        final var crs = 
DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+        final var crs = 
DefaultTemporalCRS.castOrCopy(CommonCRS.defaultTemporal());
         double scale  = crs.toValue(span);
         double offset = crs.toValue(lower);
         long   index  = Numerics.roundAndClamp(offset / scale);             // 
See comment in above method.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
index b9d249cb25..02d0a6715a 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
@@ -17,7 +17,6 @@
 package org.apache.sis.storage.base;
 
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -60,10 +59,8 @@ import org.opengis.metadata.citation.Responsibility;
  * API of this class may change in any future SIS versions.
  *
  * @author  Martin Desruisseaux (Geomatys)
- *
- * @param  <T>  type of temporal objects.
  */
-public abstract class MetadataFetcher<T> {
+public class MetadataFetcher {
     /**
      * Types of date to accept as a date of last update, in preference order.
      */
@@ -112,7 +109,7 @@ public abstract class MetadataFetcher<T> {
      *
      * <p>Path: {@code metadata/identificationInfo/citation/date}</p>
      */
-    public List<T> creationDate;
+    public List<Temporal> creationDate;
 
     /**
      * Dates of the last update, or {@code null} if none.
@@ -121,7 +118,7 @@ public abstract class MetadataFetcher<T> {
      *
      * @see #lastUpdate(Metadata)
      */
-    public List<T> lastUpdate;
+    public List<Temporal> lastUpdate;
 
     /**
      * Type of the {@link #lastUpdate} values as an index in the {@link 
#LAST_UPDATE_TYPES} array.
@@ -186,7 +183,7 @@ public abstract class MetadataFetcher<T> {
      * @param  accept    the method to invoke for each element.
      * @param  elements  the collection of elements, or {@code null} if none.
      */
-    private <E> void forEach(final BiPredicate<MetadataFetcher<T>,E> accept, 
final Iterable<? extends E> elements) {
+    private <E> void forEach(final BiPredicate<MetadataFetcher, E> accept, 
final Iterable<? extends E> elements) {
         if (elements != null) {
             for (final E info : elements) {
                 if (info != null && accept.test(this, info)) break;
@@ -434,30 +431,19 @@ public abstract class MetadataFetcher<T> {
      * @param  clear   whether to clear the list before to add the date.
      * @return the collection where the date was added.
      */
-    private List<T> addDate(List<T> target, final CitationDate value, final 
boolean clear) {
-        @SuppressWarnings("deprecation")
-        final Date date = value.getDate();
+    private List<Temporal> addDate(List<Temporal> target, final CitationDate 
value, final boolean clear) {
+        final Temporal date = value.getReferenceDate();
         if (date != null) {
             if (target == null) {
                 target = new ArrayList<>(2);        // We will usually have 
only one element.
             } else if (clear) {
                 target.clear();
             }
-            target.add(convertDate(date));
+            target.add(date);
         }
         return target;
     }
 
-    /**
-     * Converts the given date into the object to store.
-     * The {@code <T>} type may be for example {@code <String>}
-     * with a string representation specified by the format implemented by the 
store.
-     *
-     * @param  date  the date to convert.
-     * @return subclass-dependent object representing the given date.
-     */
-    protected abstract T convertDate(final Date date);
-
     /**
      * Returns the first date of type {@link DateType#LAST_UPDATE}.
      * If there is no last update, then this method fallbacks on {@link 
DateType#LAST_REVISION}.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 4d190f4c48..f0b43e0d6d 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@ -441,7 +441,7 @@ final class Store extends URIDataStore implements 
FeatureSet {
             if (startTime != null) {
                 final TemporalCRS temporal;
                 if (isTimeAbsolute) {
-                    temporal = TimeEncoding.DEFAULT.crs();
+                    temporal = CommonCRS.defaultTemporal();
                     timeEncoding = TimeEncoding.ABSOLUTE;
                 } else {
                     temporal = builder.createTemporalCRS(startTime, timeUnit);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
index d48618192a..bcf7fe7164 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
@@ -36,15 +36,10 @@ import org.apache.sis.measure.Units;
  * @author  Martin Desruisseaux (Geomatys)
  */
 class TimeEncoding extends SurjectiveConverter<String,Instant> {
-    /**
-     * The temporal coordinate reference system to use for {@link #ABSOLUTE} 
time encoding.
-     */
-    static final CommonCRS.Temporal DEFAULT = 
CommonCRS.Temporal.TRUNCATED_JULIAN;
-
     /**
      * Times are formatted as ISO dates.
      */
-    static final TimeEncoding ABSOLUTE = new TimeEncoding(DEFAULT.datum(), 
Units.DAY) {
+    static final TimeEncoding ABSOLUTE = new 
TimeEncoding(CommonCRS.defaultTemporal().getDatum(), Units.DAY) {
         @Override public Instant apply(final String time) {
             return LenientDateFormat.parseInstantUTC(time);
         }
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java 
b/endorsed/src/org.apache.sis.util/main/module-info.java
index 23e4d4def6..84025a789f 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -22,7 +22,7 @@
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   0.3
  */
 module org.apache.sis.util {
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
index e9e287a54e..677458deaf 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
@@ -391,7 +391,7 @@ public class Statistics implements DoubleConsumer, 
LongConsumer, Cloneable, Seri
     }
 
     /**
-     * Multiplies the statistics by the given factor. The given scale factory 
is also applied
+     * Multiplies the statistics by the given factor. The given scale factor 
is also applied
      * recursively on the {@linkplain #differences() differences} statistics, 
if any.
      * Invoking this method transforms the statistics as if every values given 
to the
      * {@code accept(…)} had been first multiplied by the given factor.

Reply via email to