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 2022/09/07 13:43:34 UTC

[sis] branch geoapi-4.0 updated: Allow instantiation of temporal CRS by identifiers.

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 9df7ce65de Allow instantiation of temporal CRS by identifiers.
9df7ce65de is described below

commit 9df7ce65de1454489f54eac5795e2b562c38fb10
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 7 15:42:51 2022 +0200

    Allow instantiation of temporal CRS by identifiers.
    
    https://issues.apache.org/jira/browse/SIS-558
---
 .../main/java/org/apache/sis/referencing/CRS.java  |  52 ++---
 .../java/org/apache/sis/referencing/CommonCRS.java | 109 +++++++---
 .../referencing/factory/CommonAuthorityCode.java   | 159 +++++++++++++++
 .../factory/CommonAuthorityFactory.java            | 220 +++++++++++----------
 .../factory/GeodeticAuthorityFactory.java          |   9 +-
 .../factory/MultiAuthoritiesFactory.java           |   4 +-
 .../sis/referencing/factory/package-info.java      |   2 +-
 .../java/org/apache/sis/referencing/CRSTest.java   |  46 ++++-
 .../org/apache/sis/referencing/CommonCRSTest.java  |  37 +++-
 .../factory/CommonAuthorityFactoryTest.java        |  32 ++-
 .../apache/sis/internal/util/DefinitionURI.java    |  14 +-
 .../java/org/apache/sis/util/resources/Errors.java |   5 +
 .../apache/sis/util/resources/Errors.properties    |   1 +
 .../apache/sis/util/resources/Errors_fr.properties |   1 +
 14 files changed, 518 insertions(+), 173 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
index 265f31a557..6f11b645a3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
@@ -158,35 +158,37 @@ public final class CRS extends Static {
      * The set of available codes depends on the {@link CRSAuthorityFactory} instances available on the classpath.
      * There is many thousands of <a href="https://sis.apache.org/tables/CoordinateReferenceSystems.html">CRS
      * defined by EPSG authority or by other authorities</a>.
-     * The following table lists a very small subset of codes which are guaranteed to be available
+     * The following table lists a small subset of codes which are guaranteed to be available
      * on any installation of Apache SIS:
      *
      * <blockquote><table class="sis">
      *   <caption>Minimal set of supported authority codes</caption>
-     *   <tr><th>Code</th>      <th>Enum</th>                            <th>CRS Type</th>        <th>Description</th></tr>
-     *   <tr><td>CRS:27</td>    <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:83</td>    <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:84</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td>      <td>European Datum 1950</td></tr>
-     *   <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>North American Datum 1927</td></tr>
-     *   <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>North American Datum 1983</td></tr>
-     *   <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td>      <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td>   <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geocentric</td>      <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geocentric</td>      <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:5041</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS North (E,N)</td></tr>
-     *   <tr><td>EPSG:5042</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS South (E,N)</td></tr>
-     *   <tr><td>EPSG:322##</td><td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##N</td></tr>
-     *   <tr><td>EPSG:323##</td><td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##S</td></tr>
-     *   <tr><td>EPSG:326##</td><td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##N</td></tr>
-     *   <tr><td>EPSG:327##</td><td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##S</td></tr>
-     *   <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
-     *   <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
+     *   <tr><th>Code</th>          <th>Enum</th>                            <th>CRS Type</th>        <th>Description</th></tr>
+     *   <tr><td>CRS:27</td>        <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:83</td>        <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:84</td>        <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>EPSG:4230</td>     <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td>      <td>European Datum 1950</td></tr>
+     *   <tr><td>EPSG:4258</td>     <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4267</td>     <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>North American Datum 1927</td></tr>
+     *   <tr><td>EPSG:4269</td>     <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>North American Datum 1983</td></tr>
+     *   <tr><td>EPSG:4322</td>     <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td>      <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4326</td>     <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4936</td>     <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4937</td>     <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td>   <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4978</td>     <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geocentric</td>      <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4979</td>     <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4984</td>     <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geocentric</td>      <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4985</td>     <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:5041</td>     <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS North (E,N)</td></tr>
+     *   <tr><td>EPSG:5042</td>     <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS South (E,N)</td></tr>
+     *   <tr><td>EPSG:322##</td>    <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##N</td></tr>
+     *   <tr><td>EPSG:323##</td>    <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##S</td></tr>
+     *   <tr><td>EPSG:326##</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##N</td></tr>
+     *   <tr><td>EPSG:327##</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##S</td></tr>
+     *   <tr><td>EPSG:5714</td>     <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
+     *   <tr><td>EPSG:5715</td>     <td>{@link CommonCRS.Vertical#DEPTH  DEPTH}</td>  <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
+     *   <tr><td>OGC:JulianDate</td><td>{@link CommonCRS.Temporal#JULIAN JULIAN}</td> <td>Temporal</td> <td>Julian date (days)</td></tr>
+     *   <tr><td>OGC:UnixTime</td>  <td>{@link CommonCRS.Temporal#UNIX   UNIX}</td>   <td>Unix</td>     <td>Unix time (seconds)</td></tr>
      * </table></blockquote>
      *
      * <h4>URI forms</h4>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
index f9ddfcb9e5..f71dccd8bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
@@ -76,6 +76,7 @@ import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.jdk9.JDK9;
+import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
@@ -456,7 +457,7 @@ public enum CommonCRS {
      * Invoked by when the cache needs to be cleared after a classpath change.
      */
     @SuppressWarnings("NestedSynchronizedStatement")    // Safe because cachedProjections never call any method of 'this'.
-    synchronized void clear() {
+    final synchronized void clear() {
         cached           = null;
         cachedGeo3D      = null;
         cachedNormalized = null;
@@ -1363,7 +1364,7 @@ public enum CommonCRS {
         /**
          * Invoked by when the cache needs to be cleared after a classpath change.
          */
-        synchronized void clear() {
+        final synchronized void clear() {
             cached = null;
         }
 
@@ -1514,21 +1515,22 @@ public enum CommonCRS {
      *   TemporalCRS crs = CommonCRS.Temporal.JULIAN.crs();
      * }
      *
-     * Below is an alphabetical list of object names available in this enumeration:
+     * Below is an alphabetical list of object names available in this enumeration.
+     * Note that the namespace of identifiers ("OGC" versus "SIS") may change in any future version.
      *
      * <blockquote><table class="sis">
      *   <caption>Temporal objects accessible by enumeration constants</caption>
-     *   <tr><th>Name or alias</th>    <th>Object type</th> <th>Enumeration value</th></tr>
-     *   <tr><td>Dublin Julian</td>    <td>CRS, Datum</td>  <td>{@link #DUBLIN_JULIAN}</td></tr>
-     *   <tr><td>Java time</td>        <td>CRS</td>         <td>{@link #JAVA}</td></tr>
-     *   <tr><td>Julian</td>           <td>CRS, Datum</td>  <td>{@link #JULIAN}</td></tr>
-     *   <tr><td>Modified Julian</td>  <td>CRS, Datum</td>  <td>{@link #MODIFIED_JULIAN}</td></tr>
-     *   <tr><td>Truncated Julian</td> <td>CRS, Datum</td>  <td>{@link #TRUNCATED_JULIAN}</td></tr>
-     *   <tr><td>Unix/POSIX time</td>  <td>CRS, Datum</td>  <td>{@link #UNIX}</td></tr>
+     *   <tr><th>Name or alias</th>    <th>Identifier</th>                      <th>Object type</th> <th>Enumeration value</th></tr>
+     *   <tr><td>Dublin Julian</td>    <td>{@code SIS:DublinJulian}</td>        <td>CRS, Datum</td>  <td>{@link #DUBLIN_JULIAN}</td></tr>
+     *   <tr><td>Java time</td>        <td>{@code SIS:JavaTime}</td>            <td>CRS</td>         <td>{@link #JAVA}</td></tr>
+     *   <tr><td>Julian</td>           <td>{@code OGC:JulianDate}</td>          <td>CRS, Datum</td>  <td>{@link #JULIAN}</td></tr>
+     *   <tr><td>Modified Julian</td>  <td>{@code SIS:ModifiedJulianDate}</td>  <td>CRS, Datum</td>  <td>{@link #MODIFIED_JULIAN}</td></tr>
+     *   <tr><td>Truncated Julian</td> <td>{@code OGC:TruncatedJulianDate}</td> <td>CRS, Datum</td>  <td>{@link #TRUNCATED_JULIAN}</td></tr>
+     *   <tr><td>Unix/POSIX time</td>  <td>{@code OGC:UnixTime}</td>            <td>CRS, Datum</td>  <td>{@link #UNIX}</td></tr>
      * </table></blockquote>
      *
      * @author  Martin Desruisseaux (Geomatys)
-     * @version 1.0
+     * @version 1.3
      *
      * @see Engineering#TIME
      *
@@ -1549,14 +1551,16 @@ public enum CommonCRS {
          * the {@link java.text.SimpleDateFormat} class is closer to the common practice (but not ISO 8601
          * compliant).</p>
          */
-        JULIAN(Vocabulary.Keys.Julian, -2440588L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2),
+        JULIAN(Vocabulary.Keys.Julian, -2440588L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2,
+               "JulianDate", true),
 
         /**
          * Time measured as days since November 17, 1858 at 00:00 UTC.
          * A <cite>Modified Julian day</cite> (MJD) is defined relative to
          * <cite>Julian day</cite> (JD) as {@code MJD = JD − 2400000.5}.
          */
-        MODIFIED_JULIAN(Vocabulary.Keys.ModifiedJulian, -40587L * MILLISECONDS_PER_DAY),
+        MODIFIED_JULIAN(Vocabulary.Keys.ModifiedJulian, -40587L * MILLISECONDS_PER_DAY,
+                        "ModifiedJulianDate", false),
 
         /**
          * Time measured as days since May 24, 1968 at 00:00 UTC.
@@ -1564,24 +1568,26 @@ public enum CommonCRS {
          * A <cite>Truncated Julian day</cite> (TJD) is defined relative to
          * <cite>Julian day</cite> (JD) as {@code TJD = JD − 2440000.5}.
          */
-        TRUNCATED_JULIAN(Vocabulary.Keys.TruncatedJulian, -587L * MILLISECONDS_PER_DAY),
+        TRUNCATED_JULIAN(Vocabulary.Keys.TruncatedJulian, -587L * MILLISECONDS_PER_DAY,
+                         "TruncatedJulianDate", true),
 
         /**
          * Time measured as days since December 31, 1899 at 12:00 UTC.
          * A <cite>Dublin Julian day</cite> (DJD) is defined relative to
          * <cite>Julian day</cite> (JD) as {@code DJD = JD − 2415020}.
          */
-        DUBLIN_JULIAN(Vocabulary.Keys.DublinJulian, -25568L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2),
+        DUBLIN_JULIAN(Vocabulary.Keys.DublinJulian, -25568L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2,
+                      "DublinJulian", false),
 
         /**
          * Time measured as seconds since January 1st, 1970 at 00:00 UTC.
          */
-        UNIX(Vocabulary.Keys.Time_1, 0),
+        UNIX(Vocabulary.Keys.Time_1, 0, "UnixTime", true),
 
         /**
          * Time measured as milliseconds since January 1st, 1970 at 00:00 UTC.
          */
-        JAVA(Vocabulary.Keys.Time_1, 0);
+        JAVA(Vocabulary.Keys.Time_1, 0, "JavaTime", false);
 
         /**
          * The resource keys for the name as one of the {@code Vocabulary.Keys} constants.
@@ -1593,6 +1599,18 @@ public enum CommonCRS {
          */
         private final long epoch;
 
+        /**
+         * Identifier in OGC or SIS namespace.
+         *
+         * @see org.apache.sis.referencing.factory.CommonAuthorityFactory#TEMPORAL_NAMES
+         */
+        private final String identifier;
+
+        /**
+         * Whether the identifier is in OGC namespace.
+         */
+        private final boolean isOGC;
+
         /**
          * The cached object. This is initially {@code null}, then set to various kinds of objects depending
          * on which method has been invoked. The kind of object stored in this field may change during the
@@ -1603,9 +1621,11 @@ public enum CommonCRS {
         /**
          * Creates a new enumeration value of the given name with time counted since the given epoch.
          */
-        private Temporal(final short name, final long epoch) {
-            this.key   = name;
-            this.epoch = epoch;
+        private Temporal(final short name, final long epoch, final String identifier, final boolean isOGC) {
+            this.key        = name;
+            this.epoch      = epoch;
+            this.identifier = identifier;
+            this.isOGC      = isOGC;
         }
 
         /**
@@ -1625,10 +1645,39 @@ public enum CommonCRS {
         /**
          * Invoked by when the cache needs to be cleared after a classpath change.
          */
-        synchronized void clear() {
+        final synchronized void clear() {
             cached = null;
         }
 
+        /**
+         * Returns the enumeration value for the given identifier (without namespace).
+         * Identifiers in OGC namespace are {@code "JulianDate"}, {@code "TruncatedJulianDate"} and {@code "UnixTime"}.
+         * Identifiers in SIS namespace are {@code "ModifiedJulianDate"}, {@code "DublinJulian"} and {@code "JavaTime"}.
+         * Note that the content of OGC and SIS namespaces may change in any future version.
+         *
+         * @param  identifier  case-insensitive identifier of the desired temporal CRS, without namespace.
+         * @param  onlyOGC     whether to return the CRS only if its identifier is in OGC namespace.
+         * @return the enumeration value for the given identifier.
+         * @throws IllegalArgumentException if the given identifier is not recognized.
+         *
+         * @see <a href="http://www.opengis.net/def/crs/OGC/0">OGC Definitions Server</a>
+         *
+         * @since 1.3
+         */
+        public static Temporal forIdentifier(final String identifier, final boolean onlyOGC) {
+            ArgumentChecks.ensureNonEmpty("identifier", identifier);
+            for (final Temporal candidate : values()) {
+                if (candidate.identifier.equalsIgnoreCase(identifier)) {
+                    if (onlyOGC & !candidate.isOGC) {
+                        throw new IllegalArgumentException(Errors.format(
+                                Errors.Keys.IdentifierNotInNamespace_2, Constants.OGC, candidate.identifier));
+                    }
+                    return candidate;
+                }
+            }
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownEnumValue_2, Temporal.class, identifier));
+        }
+
         /**
          * Returns the enumeration value for the given epoch, or {@code null} if none.
          * If the epoch is January 1st, 1970, then this method returns {@link #UNIX}.
@@ -1638,6 +1687,7 @@ public enum CommonCRS {
          *
          * @since 1.0
          */
+        @OptionalCandidate
         public static Temporal forEpoch(final Instant epoch) {
             if (epoch != null) {
                 final long e = epoch.toEpochMilli();
@@ -1677,12 +1727,15 @@ public enum CommonCRS {
                     object = crs(cached);
                     if (object == null) {
                         final TemporalDatum datum = datum();
-                        final Map<String,?> properties;
+                        final Map<String,?> source;
                         if (this == JAVA) {
-                            properties = properties(Vocabulary.formatInternational(key, "Java"));
+                            source = properties(Vocabulary.formatInternational(key, "Java"));
                         } else {
-                            properties = IdentifiedObjects.getProperties(datum, exclude());
+                            source = IdentifiedObjects.getProperties(datum, exclude());
                         }
+                        final Map<String,Object> properties = new HashMap<>(source);
+                        properties.put(TemporalCRS.IDENTIFIERS_KEY,
+                                new NamedIdentifier(isOGC ? Citations.OGC : Citations.SIS, identifier));
                         object = new DefaultTemporalCRS(properties, datum, cs());
                         cached = object;
                     }
@@ -1958,14 +2011,14 @@ public enum CommonCRS {
      * @param  key  a constant from {@link org.apache.sis.util.resources.Vocabulary.Keys}.
      * @return the properties to give to the object constructor.
      */
-    static Map<String,?> properties(final short key) {
+    private static Map<String,?> properties(final short key) {
         return properties(Vocabulary.formatInternational(key));
     }
 
     /**
      * Puts the given name in a map of properties to be given to object constructors.
      */
-    static Map<String,?> properties(final InternationalString name) {
+    private static Map<String,?> properties(final InternationalString name) {
         return singletonMap(NAME_KEY, new NamedIdentifier(null, name));
     }
 
@@ -1989,7 +2042,7 @@ public enum CommonCRS {
      * Returns the EPSG factory to use for creating CRS, or {@code null} if none.
      * If this method returns {@code null}, then the caller will silently fallback on hard-coded values.
      */
-    static GeodeticAuthorityFactory factory() {
+    private static GeodeticAuthorityFactory factory() {
         if (!EPSGFactoryFallback.FORCE_HARDCODED) {
             final GeodeticAuthorityFactory factory = AuthorityFactories.EPSG();
             if (!(factory instanceof EPSGFactoryFallback)) {
@@ -2003,7 +2056,7 @@ public enum CommonCRS {
      * Invoked when a factory failed to create an object.
      * After invoking this method, the caller will fallback on hard-coded values.
      */
-    static void failure(final Object caller, final String method, final FactoryException e, final int code) {
+    private static void failure(final Object caller, final String method, final FactoryException e, final int code) {
         String message = Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, (Constants.EPSG + ':') + code);
         message = Exceptions.formatChainedMessages(null, message, e);
         final LogRecord record = new LogRecord(Level.WARNING, message);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java
new file mode 100644
index 0000000000..8aa66bd213
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java
@@ -0,0 +1,159 @@
+/*
+ * 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.referencing.factory;
+
+import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.referencing.Resources;
+
+
+/**
+ * Result of parsing a code in "OGC", "CRS", "AUTO" or "AUTO2" namespace.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.3
+ * @since   0.7
+ * @module
+ */
+final class CommonAuthorityCode {
+    /**
+     * The parameter separator for codes in the {@code "AUTO(2)"} namespace.
+     */
+    static final char SEPARATOR = ',';
+
+    /**
+     * Local part of the code, without the authority part and without the parameters.
+     */
+    final String localCode;
+
+    /**
+     * The remaining part of the code after {@link #localCode}, or {@code null} if none.
+     * This part may exist with codes in the {@code AUTO} or {@code AUTO2} namespace.
+     */
+    private String complement;
+
+    /**
+     * The result of parsing {@link #complement} as numerical parameters.
+     * Computed by {@link #parameters()} when first requested.
+     */
+    private double[] parameters;
+
+    /**
+     * If the authority is {@code "AUTO"}, version of that authority (1 or 2). Otherwise 0.
+     */
+    private int versionOfAuto;
+
+    /**
+     * Whether the first character of {@link #localCode} is a decimal digit, the minus or the plus character.
+     */
+    final boolean isNumeric;
+
+    /**
+     * {@code true} if the "OGC" namespace was explicitly specified.
+     */
+    boolean isOGC;
+
+    /**
+     * Finds the index where the code begins, ignoring spaces and the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"},
+     * {@code "AUTO1"} or {@code "AUTO2"} namespaces if present. If a namespace is found and is a legacy one, then
+     * the {@link #isLegacy} flag will be set.
+     *
+     * @param  code  authority, code and parameters to parse.
+     * @throws NoSuchAuthorityCodeException if an authority is present but is not one of the recognized authorities.
+     */
+    CommonAuthorityCode(final String code) throws NoSuchAuthorityCodeException {
+        int s = code.indexOf(Constants.DEFAULT_SEPARATOR);
+        if (s >= 0) {
+            final int end   = CharSequences.skipTrailingWhitespaces(code, 0, s);
+            final int start = CharSequences.skipLeadingWhitespaces (code, 0, end);
+            isOGC = GeodeticAuthorityFactory.regionMatches(Constants.OGC, code, start, end);
+            if (!isOGC && !GeodeticAuthorityFactory.regionMatches(Constants.CRS, code, start, end)) {
+                if (code.regionMatches(true, start, "AUTO", 0, 4)) {                    // 4 is the length of "AUTO".
+                    switch (end - start) {
+                        case 4: versionOfAuto = 1; break;                               // "AUTO".
+                        case 5: versionOfAuto = (code.charAt(end-1) - '0'); break;      // "AUTO1" or "AUTO2".
+                    }
+                }
+                if (!isAuto(false)) {
+                    throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.UnknownAuthority_1,
+                            CharSequences.trimWhitespaces(code, 0, s)), Constants.OGC, code);
+                }
+            }
+        }
+        final int length = code.length();
+        s = CharSequences.skipLeadingWhitespaces(code, s+1, length);
+        /*
+         * Above code removed the "CRS" part when it is used as a namespace, as in "CRS:84".
+         * The code below removes the "CRS" prefix when it is concatenated within the code,
+         * as in "CRS84". Together, those two checks handle redundant codes like "CRS:CRS84"
+         * (malformed code, but seen in practice).
+         */
+        if (code.regionMatches(true, s, Constants.CRS, 0, Constants.CRS.length())) {
+            s = CharSequences.skipLeadingWhitespaces(code, s + Constants.CRS.length(), length);
+        }
+        if (s >= length) {
+            throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.EmptyArgument_1, "code"), Constants.OGC, code);
+        }
+        /*
+         * Check whether the code has parameters. It should happen only in code in "AUTO" or "AUTO2" namespace,
+         * but we nevertheless check for all authorities.
+         */
+        int end = CharSequences.skipTrailingWhitespaces(code, s, length);
+        final int startOfParameters = code.indexOf(SEPARATOR, s);
+        if (startOfParameters >= 0) {
+            complement = code.substring(startOfParameters + 1, end);
+            end = CharSequences.skipTrailingWhitespaces(code, s, startOfParameters);
+        }
+        localCode = code.substring(s, end);
+        final char c = localCode.charAt(0);
+        isNumeric = (c >= '0' && c <= '9') || (c == '-' || c == '+');
+    }
+
+    /**
+     * Returns whether the authority is "AUTO", "AUTO1" or "AUTO2".
+     *
+     * @param  legacy  whether to returns {@code true} only if the authority is "AUTO" or "AUTO1".
+     * @return whether the authority is some "AUTO" namespace.
+     */
+    final boolean isAuto(final boolean legacy) {
+        return legacy ? (versionOfAuto == 1) : (versionOfAuto >= 1 && versionOfAuto <= 2);
+    }
+
+    /**
+     * Returns the result of parsing the comma-separated list of optional parameters after the code.
+     * If there is no parameter, then this method returns an empty array.
+     * Caller should not modify the returned array.
+     *
+     * @return the parameters after the code, or an empty array if none.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    final double[] parameters() {
+        if (parameters == null) {
+            parameters = CharSequences.parseDoubles(complement, SEPARATOR);     // `parseDoubles(…)` is null-safe.
+        }
+        return parameters;
+    }
+
+    /**
+     * Returns the error message for unexpected parameters after the code.
+     */
+    final String unexpectedParameters() {
+        return Errors.format(Errors.Keys.UnexpectedCharactersAfter_2, localCode, complement);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
index fe06784f67..a80f26a879 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
@@ -26,6 +26,8 @@ import javax.measure.Unit;
 import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
+import org.opengis.util.GenericName;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -35,6 +37,7 @@ import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.cs.CartesianCS;
 import org.apache.sis.internal.referencing.provider.TransverseMercator.Zoner;
@@ -47,8 +50,6 @@ import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.SimpleInternationalString;
@@ -151,6 +152,30 @@ import static java.util.logging.Logger.getLogger;
  *     <td>{@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian}</td>
  *     <td>(east, north)</td>
  *     <td>user-specified</td>
+ *   </tr><tr>
+ *     <td>OGC</td>
+ *     <td>JulianDate</td>
+ *     <td>Julian</td>
+ *     <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ *     <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ *     <td>(future)</td>
+ *     <td>days</td>
+ *   </tr><tr>
+ *     <td>OGC</td>
+ *     <td>TruncatedJulianDate</td>
+ *     <td>Truncated Julian</td>
+ *     <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ *     <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ *     <td>(future)</td>
+ *     <td>days</td>
+ *   </tr><tr>
+ *     <td>OGC</td>
+ *     <td>UnixTime</td>
+ *     <td>Unix Time</td>
+ *     <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ *     <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ *     <td>(future)</td>
+ *     <td>seconds</td>
  *   </tr>
  * </table>
  *
@@ -184,7 +209,7 @@ import static java.util.logging.Logger.getLogger;
  * switching to polar stereographic projections for high latitudes.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see CommonCRS
  *
@@ -195,7 +220,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
     /**
      * The {@value} prefix for a code identified by parameters.
      * This is defined in annexes B.7 to B.11 of WMS 1.3 specification.
-     * The {@code "AUTO(2)"} namespaces are not considered by SIS as real authorities.
+     * The {@code "AUTO(2)"} namespaces are not considered by Apache SIS as real authorities.
      */
     private static final String AUTO2 = "AUTO2";
 
@@ -207,11 +232,6 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
     private static final Set<String> CODESPACES = Collections.unmodifiableSet(
             new LinkedHashSet<>(Arrays.asList(Constants.OGC, Constants.CRS, "AUTO", AUTO2)));
 
-    /**
-     * The bit for saying that a namespace is the legacy {@code "AUTO"} namespace.
-     */
-    private static final int LEGACY_MASK = 0x80000000;
-
     /**
      * First code in the AUTO(2) namespace.
      */
@@ -232,13 +252,22 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
     };
 
     /**
-     * The parameter separator for codes in the {@code "AUTO(2)"} namespace.
+     * Names of temporal CRS in OGC namespace.
+     * Those CRS are defined by {@link CommonCRS.Temporal}.
+     * Codes in Apache SIS namespace are excluded from this list.
+     *
+     * @see CommonCRS.Temporal#identifier
      */
-    static final char SEPARATOR = ',';
+    private static final String[] TEMPORAL_NAMES = {
+        "JulianDate",
+        "TruncatedJulianDate",
+        "UnixTime"
+    };
 
     /**
      * The codes known to this factory, associated with their CRS type. This is set to an empty map
      * at {@code CommonAuthorityFactory} construction time and filled only when first needed.
+     * Keys are of the form "AUTHORITY:IDENTIFIER".
      */
     private final Map<String,Class<?>> codes;
 
@@ -274,79 +303,41 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
     }
 
     /**
-     * Rewrites the given code in a canonical format.
-     * If the code can not be reformatted, then this method returns {@code null}.
+     * Rewrites the given code in a canonical format and without parameters.
+     * If the code is not in a known namespace, then this method returns {@code null}.
      */
-    static String reformat(final String code) {
+    static String reformat(String code) {
         try {
-            return format(Integer.parseInt(code.substring(skipNamespace(code) & ~LEGACY_MASK)));
+            final CommonAuthorityCode parsed = new CommonAuthorityCode(code);
+            code = parsed.localCode;
+            code = parsed.isNumeric ? format(Integer.parseInt(code)) : format(code);
         } catch (NoSuchAuthorityCodeException | NumberFormatException e) {
-            Logging.recoverableException(getLogger(Loggers.CRS_FACTORY), CommonAuthorityFactory.class, "reformat", e);
+            Logging.ignorableException(getLogger(Loggers.CRS_FACTORY), CommonAuthorityFactory.class, "reformat", e);
             return null;
         }
+        return code;
     }
 
     /**
-     * Returns the index where the code begins, ignoring spaces and the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"},
-     * {@code "AUTO1"} or {@code "AUTO2"} namespaces if present. If a namespace is found and is a legacy one, then
-     * this {@link #LEGACY_MASK} bit will be set.
-     *
-     * @return index where the code begin, possibly with the {@link #LEGACY_MASK} bit set.
-     * @throws NoSuchAuthorityCodeException if an authority is present but is not one of the recognized authorities.
+     * Formats the given code with a {@code "CRS:"} or {@code "AUTO2:"} prefix.
+     * This is used for numerical codes such as "CRS:84".
      */
-    private static int skipNamespace(final String code) throws NoSuchAuthorityCodeException {
-        int isLegacy = 0;
-        int s = code.indexOf(Constants.DEFAULT_SEPARATOR);
-        if (s >= 0) {
-            final int end   = CharSequences.skipTrailingWhitespaces(code, 0, s);
-            final int start = CharSequences.skipLeadingWhitespaces (code, 0, end);
-            if (!regionMatches(Constants.CRS, code, start, end) &&
-                !regionMatches(Constants.OGC, code, start, end))
-            {
-                boolean isRecognized = false;
-                final int length = AUTO2.length() - 1;
-                if (code.regionMatches(true, start, AUTO2, 0, length)) {
-                    switch (end - start - length) {         // Number of extra characters after "AUTO".
-                        case 0: {                           // Namespace is exactly "AUTO" (ignoring case).
-                            isRecognized = true;
-                            isLegacy = LEGACY_MASK;
-                            break;
-                        }
-                        case 1: {                           // Namespace has one more character than "AUTO".
-                            final char c = code.charAt(end - 1);
-                            isRecognized = (c >= '1' && c <= '2');
-                            if (c == '1') {
-                                isLegacy = LEGACY_MASK;
-                            }
-                        }
-                    }
-                }
-                if (!isRecognized) {
-                    throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.UnknownAuthority_1,
-                            CharSequences.trimWhitespaces(code, 0, s)), Constants.OGC, code);
-                }
-            }
-        }
-        s = CharSequences.skipLeadingWhitespaces(code, s+1, code.length());
-        /*
-         * Above code removed the "CRS" part when it is used as a namespace, as in "CRS:84".
-         * The code below removes the "CRS" prefix when it is concatenated within the code,
-         * as in "CRS84". Together, those two checks handle redundant codes like "CRS:CRS84"
-         * (malformed code, but seen in practice).
-         */
-        if (code.regionMatches(true, s, Constants.CRS, 0, Constants.CRS.length())) {
-            s = CharSequences.skipLeadingWhitespaces(code, s + Constants.CRS.length(), code.length());
-        }
-        if (s >= code.length()) {
-            throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.EmptyArgument_1, "code"), Constants.OGC, code);
-        }
-        return s | isLegacy;
+    private static String format(final int code) {
+        return ((code >= FIRST_PROJECTION_CODE) ? AUTO2 : Constants.CRS) + Constants.DEFAULT_SEPARATOR + code;
+    }
+
+    /**
+     * Formats the given code with an {@code "OGC:"} prefix.
+     * This is used for non-numerical codes such as "OGC:JulianDate".
+     */
+    private static String format(final String code) {
+        return Constants.OGC + Constants.DEFAULT_SEPARATOR + code;
     }
 
     /**
      * Provides a complete set of the known codes provided by this factory.
-     * The returned set contains a namespace followed by numeric identifiers
-     * like {@code "CRS:84"}, {@code "CRS:27"}, {@code "AUTO2:42001"}, <i>etc</i>.
+     * The returned set contains a namespace followed by identifiers like
+     * {@code "CRS:84"}, {@code "CRS:27"}, {@code "AUTO2:42001"}, <i>etc</i>.
      *
      * @param  type  the spatial reference objects type.
      * @return the set of authority codes for spatial reference objects of the given type.
@@ -368,18 +359,14 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
                 for (int code = FIRST_PROJECTION_CODE; code < FIRST_PROJECTION_CODE + PROJECTION_NAMES.length; code++) {
                     add(code, ProjectedCRS.class);
                 }
+                for (final String name : TEMPORAL_NAMES) {
+                    codes.put(format(name), TemporalCRS.class);
+                }
             }
         }
         return new FilteredCodes(codes, type).keySet();
     }
 
-    /**
-     * Formats the given code with a {@code "CRS:"} or {@code "AUTO2:"} prefix.
-     */
-    private static String format(final int code) {
-        return ((code >= FIRST_PROJECTION_CODE) ? AUTO2 : Constants.CRS) + Constants.DEFAULT_SEPARATOR + code;
-    }
-
     /**
      * Adds an element in the {@link #codes} map, witch check against duplicated values.
      */
@@ -434,27 +421,37 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
      */
     @Override
     public InternationalString getDescriptionText(final String code) throws FactoryException {
-        final int s = skipNamespace(code) & ~LEGACY_MASK;
-        final String localCode = code.substring(s, CharSequences.skipTrailingWhitespaces(code, s, code.length()));
-        if (localCode.indexOf(SEPARATOR) < 0) {
+        final CommonAuthorityCode parsed = new CommonAuthorityCode(code);
+        if (parsed.isNumeric && parsed.parameters().length == 0) {
             /*
              * For codes in the "AUTO(2)" namespace without parameters, we can not rely on the default implementation
-             * since it would fail to create the ProjectedCRS instance. Instead we return a generic description.
+             * because it would fail to create the ProjectedCRS instance. Instead we return a generic description.
              * Note that we do not execute this block if parametes were specified. If there is parameters,
              * then we instead rely on the default implementation for a more accurate description text.
+             * Note also that we do not restrict to "AUTOx" namespaces because erroneous namespaces exist
+             * in practice and the numerical codes are non-ambiguous (at least in current version).
              */
             final int codeValue;
             try {
-                codeValue = Integer.parseInt(localCode);
+                codeValue = Integer.parseInt(parsed.localCode);
             } catch (NumberFormatException exception) {
-                throw noSuchAuthorityCode(localCode, code, exception);
+                throw noSuchAuthorityCode(parsed.localCode, code, exception);
             }
             final int i = codeValue - FIRST_PROJECTION_CODE;
             if (i >= 0 && i < PROJECTION_NAMES.length) {
                 return new SimpleInternationalString(PROJECTION_NAMES[i]);
             }
         }
-        return new SimpleInternationalString(createCoordinateReferenceSystem(localCode).getName().getCode());
+        /*
+         * Fallback on fetching the full CRS, then request its name.
+         * It will include the parsing of parameters if any.
+         */
+        final Identifier name = createCoordinateReferenceSystem(code, parsed).getName();
+        if (name instanceof GenericName) {
+            return ((GenericName) name).tip().toInternationalString();
+        } else {
+            return new SimpleInternationalString(name.getCode());
+        }
     }
 
     /**
@@ -488,28 +485,38 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
     @Override
     public CoordinateReferenceSystem createCoordinateReferenceSystem(final String code) throws FactoryException {
         ArgumentChecks.ensureNonNull("code", code);
-        final String localCode;
-        final boolean isLegacy;
-        String complement = null;
-        { // Block for keeping 'start' and 'end' variables locale.
-            int start = skipNamespace(code);
-            isLegacy = (start & LEGACY_MASK) != 0;
-            start &= ~LEGACY_MASK;
-            final int startOfParameters = code.indexOf(SEPARATOR, start);
-            int end = CharSequences.skipTrailingWhitespaces(code, start, code.length());
-            if (startOfParameters >= 0) {
-                complement = code.substring(startOfParameters + 1);
-                end = CharSequences.skipTrailingWhitespaces(code, start, startOfParameters);
-            }
-            localCode = code.substring(start, end);
+        return createCoordinateReferenceSystem(code, new CommonAuthorityCode(code));
+    }
+
+    /**
+     * Implementation of {@link #createCoordinateReferenceSystem(String)} after the user-supplied code has been parsed.
+     *
+     * @param  code    the user-supplied code of desired CRS.
+     * @param  parsed  result of parsing the supplied {@code code}.
+     * @throws FactoryException if the object creation failed.
+     */
+    private CoordinateReferenceSystem createCoordinateReferenceSystem(final String code, final CommonAuthorityCode parsed)
+            throws FactoryException
+    {
+        /*
+         * First, handled the case of non-numerical parameterless codes ("OGC:JulianDate", etc.)
+         * We accept also SIS-specific codes (e.g. "OGC:ModifiedJulianDate", "OGC:JavaTime") if
+         * the namespace is the more neutral "CRS" instead of "OGC". The SIS-specific codes are
+         * never listed in `getAuthorityCodes(…)`.
+         */
+        final String localCode = parsed.localCode;
+        final double[] parameters = parsed.parameters();
+        if (!parsed.isNumeric && !parsed.isAuto(false) && parameters.length == 0) try {
+            return CommonCRS.Temporal.forIdentifier(localCode, parsed.isOGC).crs();
+        } catch (IllegalArgumentException e) {
+            throw noSuchAuthorityCode(localCode, code, e);
         }
-        int codeValue = 0;
-        double[] parameters = ArraysExt.EMPTY_DOUBLE;
+        /*
+         * In current version, all non-temporal CRS have a numerical code.
+         */
+        final int codeValue;
         try {
             codeValue = Integer.parseInt(localCode);
-            if (complement != null) {
-                parameters = CharSequences.parseDoubles(complement, SEPARATOR);
-            }
         } catch (NumberFormatException exception) {
             throw noSuchAuthorityCode(localCode, code, exception);
         }
@@ -528,6 +535,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
                 errorKey = Errors.Keys.TooManyArguments_2;
             }
             if (errorKey == 0) {
+                final boolean isLegacy = parsed.isAuto(true);
                 return createAuto(code, codeValue, isLegacy,
                         (count > 2) ? parameters[0] : isLegacy ? Constants.EPSG_METRE : 1,
                                       parameters[count - 2],
@@ -536,8 +544,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
             throw new NoSuchAuthorityCodeException(Errors.format(errorKey, expected, count), AUTO2, localCode, code);
         }
         if (count != 0) {
-            throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.UnexpectedCharactersAfter_2,
-                    localCode, complement), Constants.CRS, localCode, code);
+            throw new NoSuchAuthorityCodeException(parsed.unexpectedParameters(), Constants.CRS, localCode, code);
         }
         final CommonCRS crs;
         switch (codeValue) {
@@ -562,7 +569,6 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
      * @param  latitude    a latitude in the desired projection zone.
      * @return the projected CRS for the given projection and parameters.
      */
-    @SuppressWarnings("null")
     private ProjectedCRS createAuto(final String code, final int projection, final boolean isLegacy,
             final double factor, final double longitude, final double latitude) throws FactoryException
     {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index fd11ee22c4..417804cb6d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -1246,9 +1246,16 @@ public abstract class GeodeticAuthorityFactory extends AbstractFactory implement
 
     /**
      * Returns {@code true} if the given portion of the code is equal, ignoring case, to the given namespace.
+     *
+     * @param  namespace  expected namespace (e.g. "OGC" or "EPSG").
+     * @param  code       the code where to check namespace.
+     * @param  start      index of first character of the namespace in the code.
+     * @param  end        index after last character of the namespace in the code.
+     * @return whether the specified region of the code is equal to the namespace.
      */
     static boolean regionMatches(final String namespace, final String code, final int start, final int end) {
-        return (namespace.length() == end - start) && code.regionMatches(true, start, namespace, 0, namespace.length());
+        final int length = namespace.length();
+        return (length == end - start) && code.regionMatches(true, start, namespace, 0, length);
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index 77f579a4d2..a7376779f1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@ -847,12 +847,12 @@ public class MultiAuthoritiesFactory extends GeodeticAuthorityFactory implements
          * depends on whether the authority is "AUTO" or "AUTO2". This works for now, but we may need a more
          * rigorous approach in a future SIS version.
          */
-        if (parameters != null || code.indexOf(CommonAuthorityFactory.SEPARATOR) >= 0) {
+        if (parameters != null || code.indexOf(CommonAuthorityCode.SEPARATOR) >= 0) {
             final StringBuilder buffer = new StringBuilder(authority.length() + code.length() + 1)
                     .append(authority).append(Constants.DEFAULT_SEPARATOR).append(code);
             if (parameters != null) {
                 for (final String p : parameters) {
-                    buffer.append(CommonAuthorityFactory.SEPARATOR).append(p);
+                    buffer.append(CommonAuthorityCode.SEPARATOR).append(p);
                 }
             }
             code = buffer.toString();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
index 0c2c551c70..618fb78fb1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
@@ -56,7 +56,7 @@
  * </table>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   0.6
  * @module
  */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
index b48ee806e1..80421469b2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
@@ -19,6 +19,7 @@ package org.apache.sis.referencing;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Arrays;
+import java.util.List;
 import org.apache.sis.internal.system.Loggers;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -55,7 +56,7 @@ import static org.apache.sis.test.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.4
  * @module
  */
@@ -142,6 +143,19 @@ public final strictfp class CRSTest extends TestCase {
         verifyForCode(CommonCRS.NAD83.normalizedGeographic(), "http://www.opengis.net/gml/srs/crs.xml#83");
     }
 
+    /**
+     * Tests {@link CRS#forCode(String)} with temporal CRS codes.
+     *
+     * @throws FactoryException if a CRS can not be constructed.
+     */
+    @Test
+    public void testForTemporalCode() throws FactoryException {
+        verifyForCode(CommonCRS.Temporal.JULIAN.crs(), "OGC:JulianDate");
+        verifyForCode(CommonCRS.Temporal.UNIX.crs(),   "OGC:UnixTime");
+        verifyForCode(CommonCRS.Temporal.TRUNCATED_JULIAN.crs(),
+                      "http://www.opengis.net/gml/srs/crs.xml#TruncatedJulianDate");
+    }
+
     /**
      * Test {@link CRS#forCode(String)} with values that should be invalid.
      *
@@ -157,6 +171,36 @@ public final strictfp class CRSTest extends TestCase {
         }
     }
 
+    /**
+     * Asserts that the result of {@link CRS#forCode(String)} for a compound CRS are the given components.
+     */
+    private static void verifyForCompoundCode(final String code, final SingleCRS... expected) throws FactoryException {
+        final List<SingleCRS> components = CRS.getSingleComponents(CRS.forCode(code));
+        final int count = Math.min(components.size(), expected.length);
+        for (int i=0; i<count; i++) {
+            assertTrue(String.valueOf(i), Utilities.deepEquals(expected[i], components.get(i), ComparisonMode.DEBUG));
+        }
+        assertEquals(expected.length, components.size());
+    }
+
+    /**
+     * Tests {@link CRS#forCode(String)} with compound CRS codes.
+     *
+     * @throws FactoryException if a CRS can not be constructed.
+     */
+    @Test
+    public void testForCompoundCode() throws FactoryException {
+        verifyForCompoundCode("urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714",
+                CommonCRS.WGS84.geographic(), CommonCRS.Vertical.MEAN_SEA_LEVEL.crs());
+        verifyForCompoundCode("urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714,crs:OGC::TruncatedJulianDate",
+                CommonCRS.WGS84.geographic(), CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(), CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+
+        verifyForCompoundCode("http://www.opengis.net/def/crs-compound?" +
+                            "1=http://www.opengis.net/def/crs/epsg/0/4326&" +
+                            "2=http://www.opengis.net/def/crs/epsg/0/5715",
+                CommonCRS.WGS84.geographic(), CommonCRS.Vertical.DEPTH.crs());
+    }
+
     /**
      * Tests simple WKT parsing. It is not the purpose of this class to test extensively the WKT parser;
      * those tests are rather done by {@link org.apache.sis.io.wkt.GeodeticObjectParserTest}.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
index 547d069c3f..c8d6c17695 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
@@ -20,6 +20,7 @@ import java.util.Date;
 import java.util.Map;
 import java.util.HashMap;
 import java.time.Instant;
+import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.TemporalCRS;
@@ -33,6 +34,7 @@ import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.referencing.VerticalDatumTypes;
 import org.apache.sis.internal.util.Constants;
@@ -52,7 +54,7 @@ import static org.apache.sis.test.TestUtilities.*;
  * Tests the {@link CommonCRS} class.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.4
  * @module
  */
@@ -301,6 +303,39 @@ public final strictfp class CommonCRSTest extends TestCase {
         assertTrue(name, name.contains(word));
     }
 
+    /**
+     * Tests {@link CommonCRS.Temporal#forIdentifier(String, boolean)}.
+     */
+    @Test
+    public void testTemporalForIdentifier() {
+        assertSame(CommonCRS.Temporal.TRUNCATED_JULIAN, CommonCRS.Temporal.forIdentifier("TruncatedJulianDate", false));
+        assertSame(CommonCRS.Temporal.TRUNCATED_JULIAN, CommonCRS.Temporal.forIdentifier("TruncatedJulianDate", true));
+        assertSame(CommonCRS.Temporal.MODIFIED_JULIAN,  CommonCRS.Temporal.forIdentifier("ModifiedJulianDate",  false));
+        try {
+            CommonCRS.Temporal.forIdentifier("ModifiedJulianDate", true);
+            fail("Unexpected because not in OGC namespace.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertTrue(message.contains("ModifiedJulianDate"));
+            assertTrue(message.contains("OGC"));
+        }
+        assertEquals("OGC:TruncatedJulianDate", getSingleton(CommonCRS.Temporal.TRUNCATED_JULIAN.crs().getIdentifiers()).toString());
+        assertEquals("SIS:ModifiedJulianDate",  getSingleton(CommonCRS.Temporal. MODIFIED_JULIAN.crs().getIdentifiers()).toString());
+    }
+
+    /**
+     * Tests the URN lookup on temporal CRS.
+     *
+     * @throws FactoryException if a call to {@link IdentifiedObjects#lookupURN lookupURN(…)} failed.
+     */
+    @Test
+    public void testLookupURN() throws FactoryException {
+        final TemporalCRS crs = CommonCRS.Temporal.TRUNCATED_JULIAN.crs();
+        assertNull(IdentifiedObjects.lookupEPSG(crs));                  // Not an EPSG code.
+        assertNull(IdentifiedObjects.lookupURN(crs, Citations.SIS));    // Not in SIS namespace.
+        assertEquals("urn:ogc:def:crs:OGC::TruncatedJulianDate", IdentifiedObjects.lookupURN(crs, Citations.OGC));
+    }
+
     /**
      * Tests {@link CommonCRS#universal(double, double)} with Universal Transverse Mercator (UTM) projections.
      *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 719fa84b25..310db89267 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.factory;
 
 import java.util.Arrays;
 import java.util.Set;
+import java.util.HashSet;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
 import org.opengis.util.FactoryException;
@@ -29,11 +30,13 @@ import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.datum.Datum;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.io.wkt.Convention;
@@ -47,13 +50,14 @@ import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.apache.sis.test.ReferencingAssert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
 
 
 /**
  * Tests {@link CommonAuthorityFactory}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.3
  * @since   0.7
  * @module
  */
@@ -81,7 +85,8 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
         assertTrue("getAuthorityCodes(Datum.class)",
                 factory.getAuthorityCodes(Datum.class).isEmpty());
         assertSetEquals(Arrays.asList("CRS:1", "CRS:27", "CRS:83", "CRS:84", "CRS:88",
-                                      "AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005"),
+                                      "AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005",
+                                      "OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"),
                 factory.getAuthorityCodes(CoordinateReferenceSystem.class));
         assertSetEquals(Arrays.asList("AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005"),
                 factory.getAuthorityCodes(ProjectedCRS.class));
@@ -89,6 +94,8 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
                 factory.getAuthorityCodes(GeographicCRS.class));
         assertSetEquals(Arrays.asList("CRS:88"),
                 factory.getAuthorityCodes(VerticalCRS.class));
+        assertSetEquals(Arrays.asList("OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"),
+                factory.getAuthorityCodes(TemporalCRS.class));
         assertSetEquals(Arrays.asList("CRS:1"),
                 factory.getAuthorityCodes(EngineeringCRS.class));
 
@@ -103,6 +110,27 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
         assertTrue ("OGC:CRS084", codes.contains("OGC:CRS084"));
     }
 
+    /**
+     * Verifies that the names of temporal CRS (including namespace)
+     * are identical to the one defined by {@link CommonCRS.Temporal}.
+     *
+     * @throws FactoryException if an error occurred while fetching the set of codes.
+     */
+    @Test
+    public void verifyTemporalCodes() throws FactoryException {
+        final Set<String> expected = new HashSet<>();
+        for (final CommonCRS.Temporal e : CommonCRS.Temporal.values()) {
+            assertTrue(expected.add(IdentifiedObjects.toString(getSingleton(e.crs().getIdentifiers()))));
+        }
+        assertFalse(expected.isEmpty());
+        for (final String code : factory.getAuthorityCodes(TemporalCRS.class)) {
+            assertTrue(code, expected.remove(code));
+        }
+        for (final String remaining : expected) {
+            assertFalse(remaining, remaining.startsWith(Constants.OGC));
+        }
+    }
+
     /**
      * Tests {@link CommonAuthorityFactory#getDescriptionText(String)}.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
index 615fdf8572..86d679a1c0 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
@@ -716,14 +716,18 @@ public final class DefinitionURI {
          * and "http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG/9.1/27700&2=(…)/crs/EPSG/9.1/5701".
          */
         if (components != null) {
+            boolean first = true;
             for (int i=0; i<components.length;) {
                 final DefinitionURI c = components[i++];
-                if (isHTTP) {
-                    buffer.append(i == 1 ? COMPONENT_SEPARATOR_1
-                                         : COMPONENT_SEPARATOR_2)
-                          .append(i).append(KEY_VALUE_SEPARATOR);
+                if (c != null) {
+                    if (isHTTP) {
+                        buffer.append(first ? COMPONENT_SEPARATOR_1
+                                            : COMPONENT_SEPARATOR_2)
+                              .append(i).append(KEY_VALUE_SEPARATOR);
+                    }
+                    c.appendStringTo(buffer, COMPONENT_SEPARATOR);
+                    first = false;
                 }
-                c.appendStringTo(buffer, COMPONENT_SEPARATOR);
             }
         }
     }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index de7a0030d2..195d419288 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -328,6 +328,11 @@ public final class Errors extends IndexedResourceBundle {
          */
         public static final short ForbiddenProperty_1 = 41;
 
+        /**
+         * Identifier “{1}” is not in “{0}” namespace.
+         */
+        public static final short IdentifierNotInNamespace_2 = 199;
+
         /**
          * Argument ‘{0}’ can not be an instance of ‘{1}’.
          */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
index ec0853a874..9919d73574 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
@@ -77,6 +77,7 @@ FactoryNotFound_1                 = No factory of kind \u2018{0}\u2019 found.
 FileNotFound_1                    = File \u201c{0}\u201d has not been found.
 ForbiddenAttribute_2              = Attribute \u201c{0}\u201d is not allowed for an object of type \u2018{1}\u2019.
 ForbiddenProperty_1               = Property \u201c{0}\u201d is not allowed.
+IdentifierNotInNamespace_2        = Identifier \u201c{1}\u201d is not in \u201c{0}\u201d namespace.
 IllegalArgumentClass_2            = Argument \u2018{0}\u2019 can not be an instance of \u2018{1}\u2019.
 IllegalArgumentClass_3            = Argument \u2018{0}\u2019 can not be an instance of \u2018{2}\u2019. Expected an instance of \u2018{1}\u2019 or derived type.
 IllegalArgumentValue_2            = Argument \u2018{0}\u2019 can not take the \u201c{1}\u201d value.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
index 305e55dfd6..2f74f10f84 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
@@ -74,6 +74,7 @@ FactoryNotFound_1                 = Aucune fabrique de type \u2018{0}\u2019 n\u2
 FileNotFound_1                    = Le fichier \u00ab\u202f{0}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9.
 ForbiddenAttribute_2              = L\u2019attribut \u00ab\u202f{0}\u202f\u00bb n\u2019est pas autoris\u00e9 pour un objet de type \u2018{1}\u2019.
 ForbiddenProperty_1               = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas autoris\u00e9e.
+IdentifierNotInNamespace_2        = L\u2019identifiant \u201c{1}\u201d n\u2019est pas dans l\u2019espace de noms \u201c{0}\u201d.
 IllegalArgumentClass_2            = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{1}\u2019.
 IllegalArgumentClass_3            = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{2}\u2019. Une instance de \u2018{1}\u2019 ou d\u2019un type d\u00e9riv\u00e9 \u00e9tait attendue.
 IllegalArgumentValue_2            = L\u2019argument \u2018{0}\u2019 n\u2019accepte pas la valeur \u00ab\u202f{1}\u202f\u00bb.