diff --git a/src/main/java/org/threeten/extra/scale/MispInstant.java b/src/main/java/org/threeten/extra/scale/MispInstant.java new file mode 100644 index 00000000..55feb27c --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/MispInstant.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.threeten.extra.scale; + +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; + +/** + * An instantaneous point on the time-line measured in the MISP time-scale. + *

+ * The java.time classes use the Java time-scale for simplicity. + * That scale works on the assumption that the time-line is simple, there are no + * leap-seconds and there are always 24 * 60 * 60 seconds in a day. + * Unfortunately, the Earth's rotation is not straightforward, and a solar day + * does not match this definition. + *

+ * This class is an alternative representation based on the MISP time-scale. The + * MISP time-scale is a single incrementing count of SI seconds. There are no + * leap seconds or other discontinuities. + *

+ * The duration between two points on the MISP time-scale is calculated solely + * using this class. Do not use the {@code between} method on {@code Duration} + * as that will lose information. Instead use + * {@link #durationUntil(MispInstant)} on this class. + *

+ * It is intended that most applications will use the {@code Instant} class + * which uses the UTC-SLS mapping from UTC to guarantee 86400 seconds per day. + * + *

Time-scale

+ *

+ * The scale is explained in Chapter 6 of the MISB Motion Imagery Handbook. The + * Motion Imagery Handbook can be downloaded from the NSG Registry + * (https://nsgreg.nga.mil). The MISP Time System clock counts SI-Seconds since + * the Epoch of 1970-01-01T00:00:00.0Z (UTC). The MISP Time System is locked + * with TAI with a Leap Second offset of 8.000082 seconds. There are no leap + * seconds or other discontinuities. + * + *

+ * This class may be used for instants in the far past and far future. Since + * some instants will be prior to 1970, it is not strictly an implementation of + * the MISP Time System. However it is equivalent to it since 1970. + * + *

Implementation Requirements:

+ * This class is immutable and thread-safe. + *

+ * This class must be treated as a value type. Do not synchronize, rely on the + * identity hash code or use the distinction between equals() and ==. + */ +public final class MispInstant + implements Comparable, Serializable { + // does not implement Temporal as that would enable methods like + // Duration.between which gives the wrong answer due to lossy conversion + + /** + * Constant for nanos per second. + */ + private static final int NANOS_PER_SECOND = 1000000000; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 5965510986576614924L; + + /** + * Seconds part of the difference between TAI and MISP. + * + *

+ * This is the number of seconds between 1958-01-01T00:00:00(TAI) and + * 1970-01-01T00:00.00(UTC). + */ + private static final long TAI_OFFSET_SEC = 378691208; + + /** + * Nanoseconds part of the difference between TAI and MISP. + * + *

+ * This is the fractional part of the number of seconds between + * 1958-01-01T00:00:00(TAI) and 1970-01-01T00:00.00(UTC). + */ + private static final long TAI_OFFSET_NANOS = 82000; + + /** + * The number of seconds from the epoch of 1970-01-01T00:00:00(UTC). + */ + private final long seconds; + + /** + * The number of nanoseconds, later along the time-line, from the seconds + * field. This is always positive, and never exceeds 999,999,999. + */ + private final int nanos; + + /** + * Obtains an instance of {@code MispInstant} from the number of seconds + * from the MISP epoch of 1970-01-01T00:00:00(UTC) with a nanosecond + * fraction of second. + *

+ * This method allows an arbitrary number of nanoseconds to be passed in. + * The factory will alter the values of the second and nanosecond in order + * to ensure that the stored nanosecond is in the range 0 to 999,999,999. + * For example, the following will result in the exactly the same instant: + *

+     *  MispInstant.ofMispSeconds(3, 1);
+     *  MispInstant.ofMispSeconds(4, -999999999);
+     *  MispInstant.ofMispSeconds(2, 1000000001);
+     * 
+ * + * @param mispSeconds the number of seconds from the epoch of 1970-01-01T00:00:00(UTC) + * @param nanoAdjustment the nanosecond adjustment to the number of seconds, + * positive or negative + * @return the MISP instant, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public static MispInstant ofMispSeconds(long mispSeconds, long nanoAdjustment) { + long secs = Math.addExact(mispSeconds, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + int nos = (int) Math.floorMod(nanoAdjustment, NANOS_PER_SECOND); // safe cast + return new MispInstant(secs, nos); + } + + /** + * Obtains an instance of {@code MispInstant} from a {@code TaiInstant}. + * + * @param tai the TAI instant to convert, not null + * @return the MISP instant, not null + */ + public static MispInstant of(TaiInstant tai) { + long secs = tai.getTaiSeconds() - TAI_OFFSET_SEC; + long nos = tai.getNano() - TAI_OFFSET_NANOS; + return ofMispSeconds(secs, nos); + } + + /** + * Obtains an instance of {@code MispInstant} from an {@code Instant}. + *

+ * Converting a UTC-SLS instant to the MISP Time System requires leap second + * rules. This method uses the latest available system rules. + *

+ * Conversion from an {@link Instant} will not be completely accurate near a + * leap second in accordance with UTC-SLS. + * + * @param instant the instant to convert, not null + * @return the MISP instant, not null + * @throws DateTimeException if the range of {@code MispInstant} is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public static MispInstant of(Instant instant) { + return UtcRules.system().convertToMisp(instant); + } + + /** + * Obtains an instance of {@code MispInstant} from a {@code UtcInstant}. + *

+ * Converting a UTC instant to MISP requires leap second rules. This method + * uses the latest available system rules. + *

+ * The {@code MispInstant} will represent exactly the same point on the + * time-line as per the available leap-second rules. If the leap-second + * rules change then conversion back to UTC may result in a different + * instant. + * + * @param instant the instant to convert, not null + * @return the MISP instant, not null + * @throws DateTimeException if the range of {@code MispInstant} is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public static MispInstant of(UtcInstant instant) { + return UtcRules.system().convertToMisp(instant); + } + + /** + * Constructs an instance. + * + * @param mispSeconds the number of MISP seconds from the epoch + * @param nanoOfSecond the nanoseconds within the second, from 0 to + * 999,999,999 + */ + private MispInstant(long mispSeconds, int nanoOfSecond) { + super(); + this.seconds = mispSeconds; + this.nanos = nanoOfSecond; + } + + /** + * Gets the number of seconds from the MISP epoch of 1970-01-01T00:00:00(UTC). + *

+ * The MISP second count is a simple incrementing count of seconds where + * second 0 is 1970-01-01T00:00:00(UTC). The nanosecond part of the second + * is returned by {@link getNano}. + * + * @return the seconds from the epoch of 1970-01-01T00:00:00(UTC) + */ + public long getMispSeconds() { + return seconds; + } + + /** + * Returns a copy of this {@code MispInstant} with the number of seconds from the MISP epoch of 1970-01-01T00:00:00(UTC). + *

+ * The MISP second count is a simple incrementing count of seconds where + * second 0 is 1970-01-01T00:00:00(UTC). The nanosecond part of the second + * is returned by {@link getNano}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param mispSeconds the number of seconds from the epoch of + * 1970-01-01T00:00:00(UTC) + * @return a {@code MispInstant} based on this instant with the requested + * second, not null + */ + public MispInstant withMispSeconds(long mispSeconds) { + return ofMispSeconds(mispSeconds, nanos); + } + + /** + * Gets the number of nanoseconds, later along the time-line, from the start of the second. + *

+ * The nanosecond-of-second value measures the total number of nanoseconds + * from the second returned by {@link getMispSeconds()}. + * + * @return the nanoseconds within the second, from 0 to 999,999,999 + */ + public int getNano() { + return nanos; + } + + /** + * Returns a copy of this {@code MispInstant} with the nano-of-second value changed. + *

+ * The nanosecond-of-second value measures the total number of nanoseconds + * from the second returned by {@link getMispSeconds()}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second, from 0 to 999,999,999 + * @return a {@code MispInstant} based on this instant with the requested nano-of-second, not null + * @throws IllegalArgumentException if nanoOfSecond is out of range + */ + public MispInstant withNano(int nanoOfSecond) { + if (nanoOfSecond < 0 || nanoOfSecond >= NANOS_PER_SECOND) { + throw new IllegalArgumentException("NanoOfSecond must be from 0 to 999,999,999"); + } + return ofMispSeconds(seconds, nanoOfSecond); + } + + /** + * Returns a copy of this instant with the specified duration added. + *

+ * The duration is added using simple addition of the seconds and + * nanoseconds in the duration to the seconds and nanoseconds of this + * instant. As a result, the duration is treated as being measured in SI + * seconds for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to add, not null + * @return a {@code MispInstant} based on this instant with the duration added, not null + * @throws ArithmeticException if the calculation exceeds the supported + * range + */ + public MispInstant plus(Duration duration) { + long secsToAdd = duration.getSeconds(); + int nanosToAdd = duration.getNano(); + if ((secsToAdd | nanosToAdd) == 0) { + return this; + } + long secs = Math.addExact(seconds, secsToAdd); + long nanoAdjustment = ((long) nanos) + nanosToAdd; // safe int+int + return ofMispSeconds(secs, nanoAdjustment); + } + + /** + * Returns a copy of this instant with the specified duration subtracted. + *

+ * The duration is subtracted using simple subtraction of the seconds and + * nanoseconds in the duration from the seconds and nanoseconds of this + * instant. As a result, the duration is treated as being measured in SI + * seconds for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to subtract, not null + * @return a {@code MispInstant} based on this instant with the duration subtracted, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public MispInstant minus(Duration duration) { + long secsToSubtract = duration.getSeconds(); + int nanosToSubtract = duration.getNano(); + if ((secsToSubtract | nanosToSubtract) == 0) { + return this; + } + long secs = Math.subtractExact(seconds, secsToSubtract); + long nanoAdjustment = ((long) nanos) - nanosToSubtract; // safe int+int + return ofMispSeconds(secs, nanoAdjustment); + } + + /** + * Returns the duration between this instant and the specified instant. + *

+ * This calculates the duration between this instant and another based on + * the MISP time scale. Adding the duration to this instant using + * {@link #plus} will always result in an instant equal to the specified + * instant. + * + * @param otherInstant the instant to calculate the duration until, not null + * @return the duration until the specified instant, may be negative, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public Duration durationUntil(MispInstant otherInstant) { + long durSecs = Math.subtractExact(otherInstant.seconds, seconds); + long durNanos = otherInstant.nanos - nanos; + return Duration.ofSeconds(durSecs, durNanos); + } + + /** + * Converts this instant to an {@code Instant}. + *

+ * Converting a MISP instant to UTC-SLS requires leap second rules. This + * method uses the latest available system rules. The conversion first maps + * from the MISP time system to UTC, then converts to UTC-SLS. + *

+ * Conversion to an {@link Instant} will not be completely accurate near a + * leap second in accordance with UTC-SLS. + * + * @return an {@code Instant} representing the best approximation of this instant, not null + * @throws DateTimeException if the range of {@code Instant} is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant toInstant() { + return UtcRules.system().convertToInstant(this); + } + + /** + * Converts this instant to a {@code UtcInstant}. + *

+ * Converting a MISP instant to UTC requires leap second rules. This method + * uses the latest available system rules. + *

+ * The {@link UtcInstant} will represent exactly the same point on the + * time-line as per the available leap-second rules. If the leap-second + * rules change then conversion back to the MISP time system may result in a + * different instant. + * + * @return a {@code UtcInstant} representing the same instant, not null + * @throws DateTimeException if the range of {@code UtcInstant} is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public UtcInstant toUtcInstant() { + return UtcRules.system().convertToUtc(this); + } + + /** + * Converts this instant to a {@code TaiInstant}. + * + * @return a {@code TaiInstant} representing the same instant, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public TaiInstant toTaiInstant() { + long secs = seconds + TAI_OFFSET_SEC; + long nos = nanos + TAI_OFFSET_NANOS; + return TaiInstant.ofTaiSeconds(secs, nos); + } + + /** + * Compares this instant to another based on the time-line. + * + * @param otherInstant the other instant to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(MispInstant otherInstant) { + int cmp = Long.compare(seconds, otherInstant.seconds); + if (cmp != 0) { + return cmp; + } + return nanos - otherInstant.nanos; + } + + /** + * Checks if this instant is after the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * + * @param otherInstant the other instant to compare to, not null + * @return true if this instant is after the specified instant + * @throws NullPointerException if otherInstant is null + */ + public boolean isAfter(MispInstant otherInstant) { + return compareTo(otherInstant) > 0; + } + + /** + * Checks if this instant is before the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * + * @param otherInstant the other instant to compare to, not null + * @return true if this instant is before the specified instant + * @throws NullPointerException if otherInstant is null + */ + public boolean isBefore(MispInstant otherInstant) { + return compareTo(otherInstant) < 0; + } + + /** + * Checks if this instant is equal to the specified {@code MispInstant}. + * + * @param otherInstant the other instant, null returns false + * @return true if the other instant is equal to this one + */ + @Override + public boolean equals(Object otherInstant) { + if (this == otherInstant) { + return true; + } + if (otherInstant instanceof MispInstant) { + MispInstant other = (MispInstant) otherInstant; + return this.seconds == other.seconds + && this.nanos == other.nanos; + } + return false; + } + + /** + * Returns a hash code for this instant. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return ((int) (seconds ^ (seconds >>> 32))) + 51 * nanos; + } + +} diff --git a/src/main/java/org/threeten/extra/scale/SystemUtcRules.java b/src/main/java/org/threeten/extra/scale/SystemUtcRules.java index f1ac8dbe..9ceab961 100644 --- a/src/main/java/org/threeten/extra/scale/SystemUtcRules.java +++ b/src/main/java/org/threeten/extra/scale/SystemUtcRules.java @@ -75,6 +75,22 @@ final class SystemUtcRules extends UtcRules implements Serializable { */ static final SystemUtcRules INSTANCE = new SystemUtcRules(); + /** + * Seconds part of the difference between TAI and MISP. + * + *

This is the number of seconds between 1958-01-01T00:00:00(TAI) and + * 1970-01-01T00:00.00(UTC). + */ + private static final long TAI_OFFSET_SEC = 378691208; + + /** + * Nanoseconds part of the difference between TAI and MISP. + * + *

This is the fractional part of the number of seconds between + * 1958-01-01T00:00:00(TAI) and 1970-01-01T00:00.00(UTC). + */ + private static final long TAI_OFFSET_NANOS = 82000; + /** * The table of leap second dates. */ @@ -206,6 +222,14 @@ public UtcInstant convertToUtc(TaiInstant taiInstant) { return UtcInstant.ofModifiedJulianDay(mjd, nod); } + @Override + public UtcInstant convertToUtc(MispInstant mispInstant) { + long secs = mispInstant.getMispSeconds() + TAI_OFFSET_SEC; + long nos = mispInstant.getNano() + TAI_OFFSET_NANOS; + TaiInstant tai = TaiInstant.ofTaiSeconds(secs, nos); + return convertToUtc(tai); + } + //----------------------------------------------------------------------- /** * Loads the rules from files in the class loader, often jar files. diff --git a/src/main/java/org/threeten/extra/scale/UtcRules.java b/src/main/java/org/threeten/extra/scale/UtcRules.java index 212ffc2a..ac185b57 100644 --- a/src/main/java/org/threeten/extra/scale/UtcRules.java +++ b/src/main/java/org/threeten/extra/scale/UtcRules.java @@ -226,6 +226,70 @@ public TaiInstant convertToTai(UtcInstant utcInstant) { */ public abstract UtcInstant convertToUtc(TaiInstant taiInstant); + /** + * Converts a {@code MispInstant} to a {@code UtcInstant}. + *

+ * This method converts from the MISP time scale to the UTC time scale using the + * leap-second rules of the implementation. + * + * @param mispInstant the MISP instant to convert, not null + * @return the converted UTC instant, not null + * @throws DateTimeException if the valid range is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public abstract UtcInstant convertToUtc(MispInstant mispInstant); + + //----------------------------------------------------------------------- + /** + * Converts a {@code UtcInstant} to a {@code MispInstant}. + *

+ * This method converts from the UTC to the MISP time-scale using the + * leap-second rules of the implementation. + * + * @param utcInstant the UTC instant to convert, not null + * @return the converted MISP instant, not null + * @throws DateTimeException if the valid range is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public MispInstant convertToMisp(UtcInstant utcInstant) { + TaiInstant tai = convertToTai(utcInstant); + return MispInstant.of(tai); + } + + /** + * Converts a {@code MispInstant} to an {@code Instant}. + *

+ * This method converts from the MISP time-scale to one with 86400 subdivisions + * per day using the leap-second rules of the implementation. + *

+ * The standard implementation uses UTC-SLS. It uses + * {@link #convertToUtc(MispInstant)} and {@link #convertToInstant(UtcInstant)}. + * + * @param mispInstant the MISP instant to convert, not null + * @return the converted instant, not null + * @throws DateTimeException if the valid range is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant convertToInstant(MispInstant mispInstant) { + return convertToInstant(convertToUtc(mispInstant)); + } + + /** + * Converts an {@code Instant} to a {@code MispInstant}. + *

+ * This method converts from UTC-SLS to the MISP time-scale using the + * leap-second rules of the implementation. + * + * @param instant the Instant to convert, not null + * @return the converted MISP instant, not null + * @throws DateTimeException if the valid range is exceeded + * @throws ArithmeticException if numeric overflow occurs + */ + public MispInstant convertToMisp(Instant instant) { + TaiInstant tai = convertToTai(instant); + return MispInstant.of(tai); + } + //----------------------------------------------------------------------- /** * Converts a {@code UtcInstant} to an {@code Instant}. diff --git a/src/test/java/org/threeten/extra/scale/MockUtcRulesAlwaysLeap.java b/src/test/java/org/threeten/extra/scale/MockUtcRulesAlwaysLeap.java index 51db5187..8397cfce 100644 --- a/src/test/java/org/threeten/extra/scale/MockUtcRulesAlwaysLeap.java +++ b/src/test/java/org/threeten/extra/scale/MockUtcRulesAlwaysLeap.java @@ -66,4 +66,9 @@ public UtcInstant convertToUtc(TaiInstant taiInstant) { return null; } + @Override + public UtcInstant convertToUtc(MispInstant mispInstant) { + return null; + } + } diff --git a/src/test/java/org/threeten/extra/scale/MockUtcRulesLeapOn1000.java b/src/test/java/org/threeten/extra/scale/MockUtcRulesLeapOn1000.java index b492974c..cbfb1bd1 100644 --- a/src/test/java/org/threeten/extra/scale/MockUtcRulesLeapOn1000.java +++ b/src/test/java/org/threeten/extra/scale/MockUtcRulesLeapOn1000.java @@ -66,4 +66,9 @@ public UtcInstant convertToUtc(TaiInstant taiInstant) { return null; } + @Override + public UtcInstant convertToUtc(MispInstant mispInstant) { + return null; + } + } diff --git a/src/test/java/org/threeten/extra/scale/TestMispInstant.java b/src/test/java/org/threeten/extra/scale/TestMispInstant.java new file mode 100644 index 00000000..1fd254f8 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/TestMispInstant.java @@ -0,0 +1,741 @@ +/* + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.threeten.extra.scale; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; + +import com.tngtech.junit.dataprovider.DataProvider; +import com.tngtech.junit.dataprovider.UseDataProvider; + +/** + * Test MispInstant. + */ +public class TestMispInstant { + + @Test + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(MispInstant.class)); + assertTrue(Comparable.class.isAssignableFrom(MispInstant.class)); + } + + @Test + public void test_serialization() throws Exception { + MispInstant test = MispInstant.ofMispSeconds(2, 3); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try ( ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(test); + } + try ( ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { + assertEquals(test, ois.readObject()); + } + } + + @Test + public void factory_ofMispSecondslong_long() { + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + MispInstant t = MispInstant.ofMispSeconds(i, j); + assertEquals(i, t.getMispSeconds()); + assertEquals(j, t.getNano()); + } + for (int j = -10; j < 0; j++) { + MispInstant t = MispInstant.ofMispSeconds(i, j); + assertEquals(i - 1, t.getMispSeconds()); + assertEquals(j + 1000000000, t.getNano()); + } + for (int j = 999999990; j < 1000000000; j++) { + MispInstant t = MispInstant.ofMispSeconds(i, j); + assertEquals(i, t.getMispSeconds()); + assertEquals(j, t.getNano()); + } + } + } + + @Test + public void factory_ofMispSeconds_long_long_nanosNegativeAdjusted() { + MispInstant test = MispInstant.ofMispSeconds(2L, -1); + assertEquals(1, test.getMispSeconds()); + assertEquals(999999999, test.getNano()); + } + + @Test + public void factory_ofMispSeconds_long_long_tooBig() { + assertThrows(ArithmeticException.class, () -> MispInstant.ofMispSeconds(Long.MAX_VALUE, 1000000000)); + } + + @Test + public void factory_of_Instant_null() { + assertThrows(NullPointerException.class, () -> MispInstant.of((Instant) null)); + } + + @Test + public void factory_of_UtcInstant_null() { + assertThrows(NullPointerException.class, () -> MispInstant.of((UtcInstant) null)); + } + + @Test + public void test_fromTaiInstant() { + TaiInstant tai = TaiInstant.parse("378691208.000082000s(TAI)"); + MispInstant misp = MispInstant.of(tai); + assertEquals(0, misp.getMispSeconds()); + assertEquals(0, misp.getNano()); + } + + @Test + public void test_toTaiInstant() { + MispInstant misp = MispInstant.ofMispSeconds(0, 0); + TaiInstant tai = misp.toTaiInstant(); + assertEquals(378691208, tai.getTaiSeconds()); + assertEquals(82000, tai.getNano()); + } + + @Test + public void test_fromInstant() { + Instant instant = Instant.parse("2022-03-05T00:00:08.000082Z"); + MispInstant misp = MispInstant.of(instant); + // 37 seconds for TAI to UTC offset, and 8.000082 for MISP to TAI offset + // 59643L is the MJD for 2022-03-05 + // 40587L is the MJD for 1970-01-01 + assertEquals((59643L - 40587L) * 24 * 60 * 60 + 37, misp.getMispSeconds()); + assertEquals(0, misp.getNano()); + } + + @Test + public void test_fromUtcInstant() { + UtcInstant utcInstant = UtcInstant.parse("2022-03-05T00:00:08.000082Z"); + MispInstant misp = MispInstant.of(utcInstant); + // 37 seconds for TAI to UTC offset, and 8.000082 for MISP to TAI offset + // 59643L is the MJD for 2022-03-05 + // 40587L is the MJD for 1970-01-01 + assertEquals((59643L - 40587L) * 24 * 60 * 60 + 37, misp.getMispSeconds()); + assertEquals(0, misp.getNano()); + } + + @Test + public void test_toUtcInstant() { + MispInstant misp1 = MispInstant.ofMispSeconds((59643L - 40587L) * 24 * 60 * 60, 0); + MispInstant misp2 = misp1.minus(Duration.ofSeconds(8, 82000)); + MispInstant misp3 = misp2.plus(Duration.ofSeconds(37, 0)); + UtcInstant utc = misp3.toUtcInstant(); + assertEquals("2022-03-05T00:00:00Z", utc.toString()); + } + + @Test + public void test_toInstant() { + MispInstant misp1 = MispInstant.ofMispSeconds((59643L - 40587L) * 24 * 60 * 60, 0); + MispInstant misp2 = misp1.minus(Duration.ofSeconds(8, 82000)); + MispInstant misp3 = misp2.plus(Duration.ofSeconds(37, 0)); + Instant instant = misp3.toInstant(); + assertEquals("2022-03-05T00:00:00Z", instant.toString()); + } + + @DataProvider + public static Object[][] data_withMispSeconds() { + return new Object[][]{ + {0L, 12345L, 1L, 1L, 12345L}, + {0L, 12345L, -1L, -1L, 12345L}, + {7L, 12345L, 2L, 2L, 12345L}, + {7L, 12345L, -2L, -2L, 12345L}, + {-99L, 12345L, 3L, 3L, 12345L}, + {-99L, 12345L, -3L, -3L, 12345L},}; + } + + @ParameterizedTest + @UseDataProvider("data_withMispSeconds") + public void test_ofMispSeconds(long secs, long nanos, long newMisp, Long expectedSeconds, Long expectedNanos) { + MispInstant i = MispInstant.ofMispSeconds(secs, nanos).withMispSeconds(newMisp); + assertEquals(expectedSeconds.longValue(), i.getMispSeconds()); + assertEquals(expectedNanos.longValue(), i.getNano()); + } + + @DataProvider + public static Object[][] data_withNano() { + return new Object[][]{ + {0L, 12345L, 1, 0L, 1L}, + {7L, 12345L, 2, 7L, 2L}, + {-99L, 12345L, 3, -99L, 3L}, + {-99L, 12345L, 999999999, -99L, 999999999L}, + {-99L, 12345L, -1, null, null}, + {-99L, 12345L, 1000000000, null, null},}; + } + + @ParameterizedTest + @UseDataProvider("data_withNano") + public void test_withNano(long secs, long nanos, int newNano, Long expectedSeconds, Long expectedNanos) { + MispInstant i = MispInstant.ofMispSeconds(secs, nanos); + if (expectedSeconds != null) { + MispInstant withNano = i.withNano(newNano); + assertEquals(expectedSeconds.longValue(), withNano.getMispSeconds()); + assertEquals(expectedNanos.longValue(), withNano.getNano()); + } else { + assertThrows(IllegalArgumentException.class, () -> i.withNano(newNano)); + } + } + + @DataProvider + public static Object[][] data_plus() { + return new Object[][]{ + {Long.MIN_VALUE, 0, Long.MAX_VALUE, 0, -1, 0}, + {-4, 666666667, -4, 666666667, -7, 333333334}, + {-4, 666666667, -3, 0, -7, 666666667}, + {-4, 666666667, -2, 0, -6, 666666667}, + {-4, 666666667, -1, 0, -5, 666666667}, + {-4, 666666667, -1, 333333334, -4, 1}, + {-4, 666666667, -1, 666666667, -4, 333333334}, + {-4, 666666667, -1, 999999999, -4, 666666666}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666668}, + {-4, 666666667, 0, 333333333, -3, 0}, + {-4, 666666667, 0, 666666666, -3, 333333333}, + {-4, 666666667, 1, 0, -3, 666666667}, + {-4, 666666667, 2, 0, -2, 666666667}, + {-4, 666666667, 3, 0, -1, 666666667}, + {-4, 666666667, 3, 333333333, 0, 0}, + {-3, 0, -4, 666666667, -7, 666666667}, + {-3, 0, -3, 0, -6, 0}, + {-3, 0, -2, 0, -5, 0}, + {-3, 0, -1, 0, -4, 0}, + {-3, 0, -1, 333333334, -4, 333333334}, + {-3, 0, -1, 666666667, -4, 666666667}, + {-3, 0, -1, 999999999, -4, 999999999}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -3, 1}, + {-3, 0, 0, 333333333, -3, 333333333}, + {-3, 0, 0, 666666666, -3, 666666666}, + {-3, 0, 1, 0, -2, 0}, + {-3, 0, 2, 0, -1, 0}, + {-3, 0, 3, 0, 0, 0}, + {-3, 0, 3, 333333333, 0, 333333333}, + {-2, 0, -4, 666666667, -6, 666666667}, + {-2, 0, -3, 0, -5, 0}, + {-2, 0, -2, 0, -4, 0}, + {-2, 0, -1, 0, -3, 0}, + {-2, 0, -1, 333333334, -3, 333333334}, + {-2, 0, -1, 666666667, -3, 666666667}, + {-2, 0, -1, 999999999, -3, 999999999}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -2, 1}, + {-2, 0, 0, 333333333, -2, 333333333}, + {-2, 0, 0, 666666666, -2, 666666666}, + {-2, 0, 1, 0, -1, 0}, + {-2, 0, 2, 0, 0, 0}, + {-2, 0, 3, 0, 1, 0}, + {-2, 0, 3, 333333333, 1, 333333333}, + {-1, 0, -4, 666666667, -5, 666666667}, + {-1, 0, -3, 0, -4, 0}, + {-1, 0, -2, 0, -3, 0}, + {-1, 0, -1, 0, -2, 0}, + {-1, 0, -1, 333333334, -2, 333333334}, + {-1, 0, -1, 666666667, -2, 666666667}, + {-1, 0, -1, 999999999, -2, 999999999}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -1, 1}, + {-1, 0, 0, 333333333, -1, 333333333}, + {-1, 0, 0, 666666666, -1, 666666666}, + {-1, 0, 1, 0, 0, 0}, + {-1, 0, 2, 0, 1, 0}, + {-1, 0, 3, 0, 2, 0}, + {-1, 0, 3, 333333333, 2, 333333333}, + {-1, 666666667, -4, 666666667, -4, 333333334}, + {-1, 666666667, -3, 0, -4, 666666667}, + {-1, 666666667, -2, 0, -3, 666666667}, + {-1, 666666667, -1, 0, -2, 666666667}, + {-1, 666666667, -1, 333333334, -1, 1}, + {-1, 666666667, -1, 666666667, -1, 333333334}, + {-1, 666666667, -1, 999999999, -1, 666666666}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666668}, + {-1, 666666667, 0, 333333333, 0, 0}, + {-1, 666666667, 0, 666666666, 0, 333333333}, + {-1, 666666667, 1, 0, 0, 666666667}, + {-1, 666666667, 2, 0, 1, 666666667}, + {-1, 666666667, 3, 0, 2, 666666667}, + {-1, 666666667, 3, 333333333, 3, 0}, + {0, 0, -4, 666666667, -4, 666666667}, + {0, 0, -3, 0, -3, 0}, + {0, 0, -2, 0, -2, 0}, + {0, 0, -1, 0, -1, 0}, + {0, 0, -1, 333333334, -1, 333333334}, + {0, 0, -1, 666666667, -1, 666666667}, + {0, 0, -1, 999999999, -1, 999999999}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 1}, + {0, 0, 0, 333333333, 0, 333333333}, + {0, 0, 0, 666666666, 0, 666666666}, + {0, 0, 1, 0, 1, 0}, + {0, 0, 2, 0, 2, 0}, + {0, 0, 3, 0, 3, 0}, + {0, 0, 3, 333333333, 3, 333333333}, + {0, 333333333, -4, 666666667, -3, 0}, + {0, 333333333, -3, 0, -3, 333333333}, + {0, 333333333, -2, 0, -2, 333333333}, + {0, 333333333, -1, 0, -1, 333333333}, + {0, 333333333, -1, 333333334, -1, 666666667}, + {0, 333333333, -1, 666666667, 0, 0}, + {0, 333333333, -1, 999999999, 0, 333333332}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333334}, + {0, 333333333, 0, 333333333, 0, 666666666}, + {0, 333333333, 0, 666666666, 0, 999999999}, + {0, 333333333, 1, 0, 1, 333333333}, + {0, 333333333, 2, 0, 2, 333333333}, + {0, 333333333, 3, 0, 3, 333333333}, + {0, 333333333, 3, 333333333, 3, 666666666}, + {1, 0, -4, 666666667, -3, 666666667}, + {1, 0, -3, 0, -2, 0}, + {1, 0, -2, 0, -1, 0}, + {1, 0, -1, 0, 0, 0}, + {1, 0, -1, 333333334, 0, 333333334}, + {1, 0, -1, 666666667, 0, 666666667}, + {1, 0, -1, 999999999, 0, 999999999}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 1, 1}, + {1, 0, 0, 333333333, 1, 333333333}, + {1, 0, 0, 666666666, 1, 666666666}, + {1, 0, 1, 0, 2, 0}, + {1, 0, 2, 0, 3, 0}, + {1, 0, 3, 0, 4, 0}, + {1, 0, 3, 333333333, 4, 333333333}, + {2, 0, -4, 666666667, -2, 666666667}, + {2, 0, -3, 0, -1, 0}, + {2, 0, -2, 0, 0, 0}, + {2, 0, -1, 0, 1, 0}, + {2, 0, -1, 333333334, 1, 333333334}, + {2, 0, -1, 666666667, 1, 666666667}, + {2, 0, -1, 999999999, 1, 999999999}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 2, 1}, + {2, 0, 0, 333333333, 2, 333333333}, + {2, 0, 0, 666666666, 2, 666666666}, + {2, 0, 1, 0, 3, 0}, + {2, 0, 2, 0, 4, 0}, + {2, 0, 3, 0, 5, 0}, + {2, 0, 3, 333333333, 5, 333333333}, + {3, 0, -4, 666666667, -1, 666666667}, + {3, 0, -3, 0, 0, 0}, + {3, 0, -2, 0, 1, 0}, + {3, 0, -1, 0, 2, 0}, + {3, 0, -1, 333333334, 2, 333333334}, + {3, 0, -1, 666666667, 2, 666666667}, + {3, 0, -1, 999999999, 2, 999999999}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 3, 1}, + {3, 0, 0, 333333333, 3, 333333333}, + {3, 0, 0, 666666666, 3, 666666666}, + {3, 0, 1, 0, 4, 0}, + {3, 0, 2, 0, 5, 0}, + {3, 0, 3, 0, 6, 0}, + {3, 0, 3, 333333333, 6, 333333333}, + {3, 333333333, -4, 666666667, 0, 0}, + {3, 333333333, -3, 0, 0, 333333333}, + {3, 333333333, -2, 0, 1, 333333333}, + {3, 333333333, -1, 0, 2, 333333333}, + {3, 333333333, -1, 333333334, 2, 666666667}, + {3, 333333333, -1, 666666667, 3, 0}, + {3, 333333333, -1, 999999999, 3, 333333332}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333334}, + {3, 333333333, 0, 333333333, 3, 666666666}, + {3, 333333333, 0, 666666666, 3, 999999999}, + {3, 333333333, 1, 0, 4, 333333333}, + {3, 333333333, 2, 0, 5, 333333333}, + {3, 333333333, 3, 0, 6, 333333333}, + {3, 333333333, 3, 333333333, 6, 666666666}, + {Long.MAX_VALUE, 0, Long.MIN_VALUE, 0, -1, 0},}; + } + + @ParameterizedTest + @UseDataProvider("data_plus") + public void test_plus(long seconds, int nanos, long plusSeconds, int plusNanos, long expectedSeconds, int expectedNanoOfSecond) { + MispInstant i = MispInstant.ofMispSeconds(seconds, nanos).plus(Duration.ofSeconds(plusSeconds, plusNanos)); + assertEquals(expectedSeconds, i.getMispSeconds()); + assertEquals(expectedNanoOfSecond, i.getNano()); + } + + @Test + public void test_plus_overflowTooBig() { + MispInstant i = MispInstant.ofMispSeconds(Long.MAX_VALUE, 999999999); + assertThrows(ArithmeticException.class, () -> i.plus(Duration.ofSeconds(0, 1))); + } + + @Test + public void test_plus_overflowTooSmall() { + MispInstant i = MispInstant.ofMispSeconds(Long.MIN_VALUE, 0); + assertThrows(ArithmeticException.class, () -> i.plus(Duration.ofSeconds(-1, 999999999))); + } + + @DataProvider + public static Object[][] data_minus() { + return new Object[][]{ + {Long.MIN_VALUE, 0, Long.MIN_VALUE + 1, 0, -1, 0}, + {-4, 666666667, -4, 666666667, 0, 0}, + {-4, 666666667, -3, 0, -1, 666666667}, + {-4, 666666667, -2, 0, -2, 666666667}, + {-4, 666666667, -1, 0, -3, 666666667}, + {-4, 666666667, -1, 333333334, -3, 333333333}, + {-4, 666666667, -1, 666666667, -3, 0}, + {-4, 666666667, -1, 999999999, -4, 666666668}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666666}, + {-4, 666666667, 0, 333333333, -4, 333333334}, + {-4, 666666667, 0, 666666666, -4, 1}, + {-4, 666666667, 1, 0, -5, 666666667}, + {-4, 666666667, 2, 0, -6, 666666667}, + {-4, 666666667, 3, 0, -7, 666666667}, + {-4, 666666667, 3, 333333333, -7, 333333334}, + {-3, 0, -4, 666666667, 0, 333333333}, + {-3, 0, -3, 0, 0, 0}, + {-3, 0, -2, 0, -1, 0}, + {-3, 0, -1, 0, -2, 0}, + {-3, 0, -1, 333333334, -3, 666666666}, + {-3, 0, -1, 666666667, -3, 333333333}, + {-3, 0, -1, 999999999, -3, 1}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -4, 999999999}, + {-3, 0, 0, 333333333, -4, 666666667}, + {-3, 0, 0, 666666666, -4, 333333334}, + {-3, 0, 1, 0, -4, 0}, + {-3, 0, 2, 0, -5, 0}, + {-3, 0, 3, 0, -6, 0}, + {-3, 0, 3, 333333333, -7, 666666667}, + {-2, 0, -4, 666666667, 1, 333333333}, + {-2, 0, -3, 0, 1, 0}, + {-2, 0, -2, 0, 0, 0}, + {-2, 0, -1, 0, -1, 0}, + {-2, 0, -1, 333333334, -2, 666666666}, + {-2, 0, -1, 666666667, -2, 333333333}, + {-2, 0, -1, 999999999, -2, 1}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -3, 999999999}, + {-2, 0, 0, 333333333, -3, 666666667}, + {-2, 0, 0, 666666666, -3, 333333334}, + {-2, 0, 1, 0, -3, 0}, + {-2, 0, 2, 0, -4, 0}, + {-2, 0, 3, 0, -5, 0}, + {-2, 0, 3, 333333333, -6, 666666667}, + {-1, 0, -4, 666666667, 2, 333333333}, + {-1, 0, -3, 0, 2, 0}, + {-1, 0, -2, 0, 1, 0}, + {-1, 0, -1, 0, 0, 0}, + {-1, 0, -1, 333333334, -1, 666666666}, + {-1, 0, -1, 666666667, -1, 333333333}, + {-1, 0, -1, 999999999, -1, 1}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -2, 999999999}, + {-1, 0, 0, 333333333, -2, 666666667}, + {-1, 0, 0, 666666666, -2, 333333334}, + {-1, 0, 1, 0, -2, 0}, + {-1, 0, 2, 0, -3, 0}, + {-1, 0, 3, 0, -4, 0}, + {-1, 0, 3, 333333333, -5, 666666667}, + {-1, 666666667, -4, 666666667, 3, 0}, + {-1, 666666667, -3, 0, 2, 666666667}, + {-1, 666666667, -2, 0, 1, 666666667}, + {-1, 666666667, -1, 0, 0, 666666667}, + {-1, 666666667, -1, 333333334, 0, 333333333}, + {-1, 666666667, -1, 666666667, 0, 0}, + {-1, 666666667, -1, 999999999, -1, 666666668}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666666}, + {-1, 666666667, 0, 333333333, -1, 333333334}, + {-1, 666666667, 0, 666666666, -1, 1}, + {-1, 666666667, 1, 0, -2, 666666667}, + {-1, 666666667, 2, 0, -3, 666666667}, + {-1, 666666667, 3, 0, -4, 666666667}, + {-1, 666666667, 3, 333333333, -4, 333333334}, + {0, 0, -4, 666666667, 3, 333333333}, + {0, 0, -3, 0, 3, 0}, + {0, 0, -2, 0, 2, 0}, + {0, 0, -1, 0, 1, 0}, + {0, 0, -1, 333333334, 0, 666666666}, + {0, 0, -1, 666666667, 0, 333333333}, + {0, 0, -1, 999999999, 0, 1}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, -1, 999999999}, + {0, 0, 0, 333333333, -1, 666666667}, + {0, 0, 0, 666666666, -1, 333333334}, + {0, 0, 1, 0, -1, 0}, + {0, 0, 2, 0, -2, 0}, + {0, 0, 3, 0, -3, 0}, + {0, 0, 3, 333333333, -4, 666666667}, + {0, 333333333, -4, 666666667, 3, 666666666}, + {0, 333333333, -3, 0, 3, 333333333}, + {0, 333333333, -2, 0, 2, 333333333}, + {0, 333333333, -1, 0, 1, 333333333}, + {0, 333333333, -1, 333333334, 0, 999999999}, + {0, 333333333, -1, 666666667, 0, 666666666}, + {0, 333333333, -1, 999999999, 0, 333333334}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333332}, + {0, 333333333, 0, 333333333, 0, 0}, + {0, 333333333, 0, 666666666, -1, 666666667}, + {0, 333333333, 1, 0, -1, 333333333}, + {0, 333333333, 2, 0, -2, 333333333}, + {0, 333333333, 3, 0, -3, 333333333}, + {0, 333333333, 3, 333333333, -3, 0}, + {1, 0, -4, 666666667, 4, 333333333}, + {1, 0, -3, 0, 4, 0}, + {1, 0, -2, 0, 3, 0}, + {1, 0, -1, 0, 2, 0}, + {1, 0, -1, 333333334, 1, 666666666}, + {1, 0, -1, 666666667, 1, 333333333}, + {1, 0, -1, 999999999, 1, 1}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 0, 999999999}, + {1, 0, 0, 333333333, 0, 666666667}, + {1, 0, 0, 666666666, 0, 333333334}, + {1, 0, 1, 0, 0, 0}, + {1, 0, 2, 0, -1, 0}, + {1, 0, 3, 0, -2, 0}, + {1, 0, 3, 333333333, -3, 666666667}, + {2, 0, -4, 666666667, 5, 333333333}, + {2, 0, -3, 0, 5, 0}, + {2, 0, -2, 0, 4, 0}, + {2, 0, -1, 0, 3, 0}, + {2, 0, -1, 333333334, 2, 666666666}, + {2, 0, -1, 666666667, 2, 333333333}, + {2, 0, -1, 999999999, 2, 1}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 1, 999999999}, + {2, 0, 0, 333333333, 1, 666666667}, + {2, 0, 0, 666666666, 1, 333333334}, + {2, 0, 1, 0, 1, 0}, + {2, 0, 2, 0, 0, 0}, + {2, 0, 3, 0, -1, 0}, + {2, 0, 3, 333333333, -2, 666666667}, + {3, 0, -4, 666666667, 6, 333333333}, + {3, 0, -3, 0, 6, 0}, + {3, 0, -2, 0, 5, 0}, + {3, 0, -1, 0, 4, 0}, + {3, 0, -1, 333333334, 3, 666666666}, + {3, 0, -1, 666666667, 3, 333333333}, + {3, 0, -1, 999999999, 3, 1}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 2, 999999999}, + {3, 0, 0, 333333333, 2, 666666667}, + {3, 0, 0, 666666666, 2, 333333334}, + {3, 0, 1, 0, 2, 0}, + {3, 0, 2, 0, 1, 0}, + {3, 0, 3, 0, 0, 0}, + {3, 0, 3, 333333333, -1, 666666667}, + {3, 333333333, -4, 666666667, 6, 666666666}, + {3, 333333333, -3, 0, 6, 333333333}, + {3, 333333333, -2, 0, 5, 333333333}, + {3, 333333333, -1, 0, 4, 333333333}, + {3, 333333333, -1, 333333334, 3, 999999999}, + {3, 333333333, -1, 666666667, 3, 666666666}, + {3, 333333333, -1, 999999999, 3, 333333334}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333332}, + {3, 333333333, 0, 333333333, 3, 0}, + {3, 333333333, 0, 666666666, 2, 666666667}, + {3, 333333333, 1, 0, 2, 333333333}, + {3, 333333333, 2, 0, 1, 333333333}, + {3, 333333333, 3, 0, 0, 333333333}, + {3, 333333333, 3, 333333333, 0, 0}, + {Long.MAX_VALUE, 0, Long.MAX_VALUE, 0, 0, 0},}; + } + + @ParameterizedTest + @UseDataProvider("data_minus") + public void test_minus(long seconds, int nanos, long minusSeconds, int minusNanos, long expectedSeconds, int expectedNanoOfSecond) { + MispInstant i = MispInstant.ofMispSeconds(seconds, nanos).minus(Duration.ofSeconds(minusSeconds, minusNanos)); + assertEquals(expectedSeconds, i.getMispSeconds()); + assertEquals(expectedNanoOfSecond, i.getNano()); + } + + @Test + public void test_minus_overflowTooSmall() { + MispInstant i = MispInstant.ofMispSeconds(Long.MIN_VALUE, 0); + assertThrows(ArithmeticException.class, () -> i.minus(Duration.ofSeconds(0, 1))); + } + + @Test + public void test_minus_overflowTooBig() { + MispInstant i = MispInstant.ofMispSeconds(Long.MAX_VALUE, 999999999); + assertThrows(ArithmeticException.class, () -> i.minus(Duration.ofSeconds(-1, 999999999))); + } + + @Test + public void test_durationUntil_fifteenSeconds() { + MispInstant misp1 = MispInstant.ofMispSeconds(10, 0); + MispInstant misp2 = MispInstant.ofMispSeconds(25, 0); + Duration test = misp1.durationUntil(misp2); + assertEquals(15, test.getSeconds()); + assertEquals(0, test.getNano()); + } + + @Test + public void test_durationUntil_twoNanos() { + MispInstant misp1 = MispInstant.ofMispSeconds(4, 5); + MispInstant misp2 = MispInstant.ofMispSeconds(4, 7); + Duration test = misp1.durationUntil(misp2); + assertEquals(0, test.getSeconds()); + assertEquals(2, test.getNano()); + } + + @Test + public void test_durationUntil_twoNanosNegative() { + MispInstant misp1 = MispInstant.ofMispSeconds(4, 9); + MispInstant misp2 = MispInstant.ofMispSeconds(4, 7); + Duration test = misp1.durationUntil(misp2); + assertEquals(-1, test.getSeconds()); + assertEquals(999999998, test.getNano()); + } + + @Test + public void test_comparisons() { + doTest_comparisons_MispInstant( + MispInstant.ofMispSeconds(-2L, 0), + MispInstant.ofMispSeconds(-2L, 999999998), + MispInstant.ofMispSeconds(-2L, 999999999), + MispInstant.ofMispSeconds(-1L, 0), + MispInstant.ofMispSeconds(-1L, 1), + MispInstant.ofMispSeconds(-1L, 999999998), + MispInstant.ofMispSeconds(-1L, 999999999), + MispInstant.ofMispSeconds(0L, 0), + MispInstant.ofMispSeconds(0L, 1), + MispInstant.ofMispSeconds(0L, 2), + MispInstant.ofMispSeconds(0L, 999999999), + MispInstant.ofMispSeconds(1L, 0), + MispInstant.ofMispSeconds(2L, 0) + ); + } + + void doTest_comparisons_MispInstant(MispInstant... instants) { + for (int i = 0; i < instants.length; i++) { + MispInstant a = instants[i]; + for (int j = 0; j < instants.length; j++) { + MispInstant b = instants[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0); + assertFalse(a.equals(b)); + assertTrue(a.isBefore(b)); + assertFalse(a.isAfter(b)); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0); + assertFalse(a.equals(b)); + assertFalse(a.isBefore(b)); + assertTrue(a.isAfter(b)); + } else { + assertEquals(0, a.compareTo(b)); + assertTrue(a.equals(b)); + assertFalse(a.isBefore(b)); + assertFalse(a.isAfter(b)); + } + } + } + } + + @Test + public void test_compareTo_ObjectNull() { + MispInstant a = MispInstant.ofMispSeconds(0L, 0); + assertThrows(NullPointerException.class, () -> a.compareTo(null)); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void test_compareToNonMispInstant() { + Comparable c = MispInstant.ofMispSeconds(0L, 2); + assertThrows(ClassCastException.class, () -> c.compareTo(new Object())); + } + + @Test + public void test_equals() { + MispInstant test5a = MispInstant.ofMispSeconds(5L, 20); + MispInstant test5b = MispInstant.ofMispSeconds(5L, 20); + MispInstant test5n = MispInstant.ofMispSeconds(5L, 30); + MispInstant test6 = MispInstant.ofMispSeconds(6L, 20); + + assertEquals(true, test5a.equals(test5a)); + assertEquals(true, test5a.equals(test5b)); + assertEquals(false, test5a.equals(test5n)); + assertEquals(false, test5a.equals(test6)); + + assertEquals(true, test5b.equals(test5a)); + assertEquals(true, test5b.equals(test5b)); + assertEquals(false, test5b.equals(test5n)); + assertEquals(false, test5b.equals(test6)); + + assertEquals(false, test5n.equals(test5a)); + assertEquals(false, test5n.equals(test5b)); + assertEquals(true, test5n.equals(test5n)); + assertEquals(false, test5n.equals(test6)); + + assertEquals(false, test6.equals(test5a)); + assertEquals(false, test6.equals(test5b)); + assertEquals(false, test6.equals(test5n)); + assertEquals(true, test6.equals(test6)); + } + + @Test + public void test_equals_null() { + MispInstant test5 = MispInstant.ofMispSeconds(5L, 20); + assertEquals(false, test5.equals(null)); + } + + @Test + public void test_equals_otherClass() { + MispInstant test5 = MispInstant.ofMispSeconds(5L, 20); + assertEquals(false, test5.equals((Object) "")); + } + + @Test + public void test_hashCode() { + MispInstant test5a = MispInstant.ofMispSeconds(5L, 20); + MispInstant test5b = MispInstant.ofMispSeconds(5L, 20); + MispInstant test5n = MispInstant.ofMispSeconds(5L, 30); + MispInstant test6 = MispInstant.ofMispSeconds(6L, 20); + + assertEquals(true, test5a.hashCode() == test5a.hashCode()); + assertEquals(true, test5a.hashCode() == test5b.hashCode()); + assertEquals(true, test5b.hashCode() == test5b.hashCode()); + + assertEquals(false, test5a.hashCode() == test5n.hashCode()); + assertEquals(false, test5a.hashCode() == test6.hashCode()); + } + +}