sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Improve accuracy of temporal position to date/instant conversions and add tests.
Date Fri, 01 Mar 2019 19:51:14 GMT
This is an automated email from the ASF dual-hosted git repository.

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

commit 2b41224037b92f347231098a9de07cdaafc698c8
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Mar 1 20:50:21 2019 +0100

    Improve accuracy of temporal position to date/instant conversions and add tests.
---
 .../sis/referencing/crs/DefaultTemporalCRS.java    | 80 ++++++++++++++--------
 .../referencing/crs/DefaultTemporalCRSTest.java    | 19 ++++-
 .../sis/internal/util/StandardDateFormat.java      |  5 ++
 3 files changed, 74 insertions(+), 30 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 028758c..0a041d4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -38,6 +38,8 @@ import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Units;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+import static org.apache.sis.internal.util.StandardDateFormat.NANOS_PER_SECOND;
+import static org.apache.sis.internal.util.StandardDateFormat.MILLIS_PER_SECOND;
 
 
 /**
@@ -92,16 +94,18 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
     private TemporalDatum datum;
 
     /**
-     * A converter from values in this CRS to values in milliseconds.
+     * Conversion factor from values in this CRS to values in seconds. We use {@link UnitConverter}
+     * instead than {@code double} because the SIS implementation of {@code UnitConverter}
performs
+     * some extra steps against rounding errors.
      *
      * @see #initializeConverter()
      */
-    private transient UnitConverter toMillis;
+    private transient UnitConverter toSeconds;
 
     /**
-     * The {@linkplain TemporalDatum#getOrigin origin} in milliseconds since January 1st,
1970.
-     * This field could be implicit in the {@link #toMillis} converter, but we still handle
it
-     * explicitly in order to use integer arithmetic.
+     * The {@linkplain TemporalDatum#getOrigin origin} in seconds since January 1st, 1970.
+     * This field could be implicit in the {@link #toSeconds} converter, but we still handle
+     * it explicitly in order to use integer arithmetic.
      */
     private transient long origin;
 
@@ -214,8 +218,8 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
      * Initialize the fields required for {@link #toInstant(double)} and {@link #toValue(Instant)}
operations.
      */
     private void initializeConverter() {
-        origin   = datum.getOrigin().getTime();
-        toMillis = super.getCoordinateSystem().getAxis(0).getUnit().asType(Time.class).getConverterTo(Units.MILLISECOND);
+        origin    = datum.getOrigin().getTime() / MILLIS_PER_SECOND;
+        toSeconds = super.getCoordinateSystem().getAxis(0).getUnit().asType(Time.class).getConverterTo(Units.SECOND);
     }
 
     /**
@@ -275,8 +279,8 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
     }
 
     /**
-     * Convert the given value into an instant object.
-     * If the given value is {@link Double#NaN NaN} or infinite, then this method returns
{@code null}.
+     * Converts the given value into an instant object.
+     * If the given value {@linkplain Double#isNaN is NaN} or infinite, then this method
returns {@code null}.
      * This method is the converse of {@link #toValue(Instant)}.
      *
      * @param  value  a value in this axis unit.
@@ -284,16 +288,20 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
      *
      * @since 1.0
      */
-    public Instant toInstant(final double value) {
-        if (Double.isNaN(value) || Double.isInfinite(value)) {
+    public Instant toInstant(double value) {
+        if (Double.isFinite(value)) {
+            value = toSeconds.convert(value);
+            final long t = Math.round(value);                           // In seconds.
+            return Instant.ofEpochSecond(Math.addExact(t, origin),      // Number of seconds
since epoch.
+                    Math.round((value - t) * NANOS_PER_SECOND));        // Nanoseconds adjustment.
+        } else {
             return null;
         }
-        return Instant.ofEpochMilli(Math.round(toMillis.convert(value)) + origin);
     }
 
     /**
-     * Convert the given value into a {@link Date} object.
-     * If the given value is {@link Double#NaN NaN} or infinite, then this method returns
{@code null}.
+     * Converts the given value into a {@link Date} object.
+     * If the given value is {@linkplain Double#isNaN is NaN} or infinite, then this method
returns {@code null}.
      * This method is the converse of {@link #toValue(Date)}.
      *
      * <p>This method is provided for interoperability with legacy {@code java.util.Date}
object.
@@ -302,46 +310,60 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
      * @param  value  a value in this axis unit.
      * @return the value as a {@linkplain Date date}, or {@code null} if the given value
is NaN or infinite.
      */
-    public Date toDate(final double value) {
-        if (Double.isNaN(value) || Double.isInfinite(value)) {
+    public Date toDate(double value) {
+        if (Double.isFinite(value)) {
+            value = toSeconds.convert(value);
+            final long t = Math.round(value);                                       // In
seconds.
+            long ms = Math.addExact(t, origin);                                     // Number
of seconds since epoch.
+            ms = Math.multiplyExact(ms, MILLIS_PER_SECOND);                         // Number
of milliseconds since epoch.
+            ms = Math.addExact(Math.round((value - t) * MILLIS_PER_SECOND), ms);    // Milliseconds
adjustment.
+            return new Date(ms);
+        } else {
             return null;
         }
-        return new Date(Math.round(toMillis.convert(value)) + origin);
     }
 
     /**
-     * Convert the given instant into a value in this axis unit.
-     * If the given instant is {@code null}, then this method returns {@link Double#NaN NaN}.
+     * Converts the given instant into a value in this axis unit.
+     * If the given instant is {@code null}, then this method returns {@link Double#NaN}.
      * This method is the converse of {@link #toInstant(double)}.
      *
      * @param  time  the value as an instant, or {@code null}.
-     * @return the value in this axis unit, or {@link Double#NaN NaN} if the given instant
is {@code null}.
+     * @return the value in this axis unit, or {@link Double#NaN} if the given instant is
{@code null}.
      *
      * @since 1.0
      */
     public double toValue(final Instant time) {
-        if (time == null) {
+        if (time != null) {
+            double t = Math.subtractExact(time.getEpochSecond(), origin);
+            t += time.getNano() / (double) NANOS_PER_SECOND;
+            return toSeconds.inverse().convert(t);
+        } else {
             return Double.NaN;
         }
-        return toMillis.inverse().convert(time.toEpochMilli() - origin);
     }
 
     /**
-     * Convert the given {@linkplain Date date} into a value in this axis unit.
-     * If the given time is {@code null}, then this method returns {@link Double#NaN NaN}.
+     * Converts the given {@linkplain Date date} into a value in this axis unit.
+     * If the given time is {@code null}, then this method returns {@link Double#NaN}.
      * This method is the converse of {@link #toDate(double)}.
      *
      * <p>This method is provided for interoperability with legacy {@code java.util.Date}
object.
-     * New code should use {@link #toValue(Date)} instead.</p>
+     * New code should use {@link #toValue(Instant)} instead.</p>
      *
      * @param  time  the value as a {@linkplain Date date}, or {@code null}.
-     * @return the value in this axis unit, or {@link Double#NaN NaN} if the given time is
{@code null}.
+     * @return the value in this axis unit, or {@link Double#NaN} if the given time is {@code
null}.
      */
     public double toValue(final Date time) {
-        if (time == null) {
+        if (time != null) {
+            long ms = time.getTime();                       // Number of milliseconds since
epoch.
+            long t = ms / MILLIS_PER_SECOND;                // Number of seconds since epoch.
+            ms %= MILLIS_PER_SECOND;                        // Milliseconds adjustment.
+            t = Math.subtractExact(t, origin);              // Time in seconds.
+            return toSeconds.inverse().convert(t + ms / (double) MILLIS_PER_SECOND);
+        } else {
             return Double.NaN;
         }
-        return toMillis.inverse().convert(time.getTime() - origin);
     }
 
     /**
@@ -414,7 +436,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS
{
      */
     private void setCoordinateSystem(final TimeCS cs) {
         setCoordinateSystem("timeCS", cs);
-        if (toMillis == null && datum != null) {
+        if (toSeconds == null && datum != null) {
             initializeConverter();
         }
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultTemporalCRSTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultTemporalCRSTest.java
index 07d1eba..5729528 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultTemporalCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultTemporalCRSTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.referencing.crs;
 
+import java.time.Instant;
+import java.util.Date;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -27,7 +29,7 @@ import static org.apache.sis.test.MetadataAssert.*;
  * Tests {@link DefaultTemporalCRS}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.0
  * @since   0.5
  * @module
  */
@@ -73,4 +75,19 @@ public final strictfp class DefaultTemporalCRSTest extends TestCase {
                 "    TimeUnit[“day”, 86400]]",      // ISO 19162 does not allow "Unit"
keyword here.
                 HardCodedCRS.TIME);
     }
+
+    /**
+     * Tests {@link DefaultTemporalCRS#toDate(double)} and its converse.
+     * Also compares with {@link DefaultTemporalCRS#toInstant(double)}.
+     */
+    @Test
+    public void testDateConversion() {
+        final double  value   = 58543.25;                               // 3 march 2019.
+        final Date    date    = HardCodedCRS.TIME.toDate(value);
+        final Instant instant = HardCodedCRS.TIME.toInstant(value);
+        assertEquals("toInstant", Instant.ofEpochSecond(1551420000L), instant);
+        assertEquals("toDate",    instant, date.toInstant());
+        assertEquals("toValue",   value, HardCodedCRS.TIME.toValue(instant), STRICT);
+        assertEquals("toValue",   value, HardCodedCRS.TIME.toValue(date), STRICT);
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/StandardDateFormat.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/StandardDateFormat.java
index fcf137e..f347c34 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/StandardDateFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/StandardDateFormat.java
@@ -230,6 +230,11 @@ replace:    if (Character.isWhitespace(c)) {
     public static final int MILLISECONDS_PER_DAY = 24*60*60*1000;
 
     /**
+     * Number of milliseconds in one second.
+     */
+    public static final long MILLIS_PER_SECOND = 1000;
+
+    /**
      * Number of nanoseconds in one millisecond.
      */
     public static final long NANOS_PER_MILLISECOND = 1000000;


Mime
View raw message