From 655f4d0221dec8af5f97e200d008f0f5e83f1c9f Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 22 May 2024 13:07:27 -0600 Subject: [PATCH] Add durations. Add periods. Subtract durations. Subtract periods. Multiply durations by doubles. Multiply periods by longs. Divide durations. --- .../java/io/deephaven/time/DateTimeUtils.java | 186 +++++++++++++++++- .../io/deephaven/time/TestDateTimeUtils.java | 151 ++++++++++++++ 2 files changed, 331 insertions(+), 6 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/DateTimeUtils.java b/engine/time/src/main/java/io/deephaven/time/DateTimeUtils.java index ea06842f56e..9a8ba781f85 100644 --- a/engine/time/src/main/java/io/deephaven/time/DateTimeUtils.java +++ b/engine/time/src/main/java/io/deephaven/time/DateTimeUtils.java @@ -24,8 +24,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static io.deephaven.util.QueryConstants.NULL_INT; -import static io.deephaven.util.QueryConstants.NULL_LONG; +import static io.deephaven.util.QueryConstants.*; import static java.time.format.DateTimeFormatter.*; /** @@ -1652,6 +1651,44 @@ public static ZonedDateTime plus(@Nullable final ZonedDateTime dateTime, @Nullab } } + /** + * Adds two durations. + * + * @param duration1 first duration + * @param duration2 second duration + * @return {@code null} if either input is {@code null}; otherwise the sum of the two durations + */ + public static Duration plus(@Nullable final Duration duration1, @Nullable final Duration duration2) { + if (duration1 == null || duration2 == null) { + return null; + } + + try { + return duration1.plus(duration2); + } catch (Exception ex) { + throw new DateTimeOverflowException(ex); + } + } + + /** + * Adds two periods. + * + * @param period1 first period + * @param period2 second period + * @return {@code null} if either input is {@code null}; otherwise the sum of the two periods + */ + public static Period plus(@Nullable final Period period1, @Nullable final Period period2) { + if (period1 == null || period2 == null) { + return null; + } + + try { + return period1.plus(period2); + } catch (Exception ex) { + throw new DateTimeOverflowException(ex); + } + } + /** * Subtracts days from a {@link LocalDate}. * @@ -1871,6 +1908,44 @@ public static long minus(@Nullable final ZonedDateTime dateTime1, @Nullable fina return checkUnderflowMinus(epochNanos(dateTime1), epochNanos(dateTime2), true); } + /** + * Subtracts two durations. + * + * @param duration1 first duration + * @param duration2 second duration + * @return {@code null} if either input is {@code null}; otherwise the difference of the two durations + */ + public static Duration minus(@Nullable final Duration duration1, @Nullable final Duration duration2) { + if (duration1 == null || duration2 == null) { + return null; + } + + try { + return duration1.minus(duration2); + } catch (Exception ex) { + throw new DateTimeOverflowException(ex); + } + } + + /** + * Subtracts two periods. + * + * @param period1 first period + * @param period2 second period + * @return {@code null} if either input is {@code null}; otherwise the difference of the two periods + */ + public static Period minus(@Nullable final Period period1, @Nullable final Period period2) { + if (period1 == null || period2 == null) { + return null; + } + + try { + return period1.minus(period2); + } catch (Exception ex) { + throw new DateTimeOverflowException(ex); + } + } + /** * Multiply a duration by a scalar. * @@ -1894,11 +1969,43 @@ public static Duration multiply(final Duration duration, final long scalar) { * @return {@code null} if either input is {@code null}; otherwise the duration multiplied by the scalar */ public static Duration multiply(final long scalar, final Duration duration) { - if (duration == null || scalar == NULL_LONG) { + return multiply(duration, scalar); + } + + /** + * Multiply a duration by a scalar. + * + * @param duration the duration to multiply + * @param scalar the scalar to multiply by + * @return {@code null} if either input is {@code null}; otherwise the duration multiplied by the scalar + */ + public static Duration multiply(final Duration duration, final double scalar) { + if (duration == null || scalar == NULL_DOUBLE) { return null; } - return duration.multipliedBy(scalar); + if(Double.isNaN(scalar)) { + throw new DateTimeOverflowException("Scalar value is NaN"); + } + + final double product = duration.toNanos() * scalar; + + if(product > Long.MAX_VALUE || product < Long.MIN_VALUE) { + throw new DateTimeOverflowException("Product value is too large to be cast to a long"); + } + + return Duration.ofNanos((long) product); + } + + /** + * Multiply a duration by a scalar. + * + * @param duration the duration to multiply + * @param scalar the scalar to multiply by + * @return {@code null} if either input is {@code null}; otherwise the duration multiplied by the scalar + */ + public static Duration multiply(final double scalar, final Duration duration) { + return multiply(duration, scalar); } /** @@ -1924,13 +2031,80 @@ public static Period multiply(final Period period, final int scalar) { * @return {@code null} if either input is {@code null}; otherwise the period multiplied by the scalar */ public static Period multiply(final int scalar, final Period period) { - if (period == null || scalar == NULL_INT) { + return multiply(period, scalar); + } + + /** + * Multiply a period by a scalar. + * + * @param period the period to multiply + * @param scalar the scalar to multiply by + * @return {@code null} if either input is {@code null}; otherwise the period multiplied by the scalar + */ + public static Period multiply(final Period period, final long scalar) { + if (period == null || scalar == NULL_LONG) { return null; } - return period.multipliedBy(scalar); + if(scalar > Integer.MAX_VALUE || scalar < Integer.MIN_VALUE) { + throw new DateTimeOverflowException("Scalar value is too large to be cast to an int"); + } + + return period.multipliedBy((int) scalar); + } + + /** + * Multiply a period by a scalar. + * + * @param period the period to multiply + * @param scalar the scalar to multiply by + * @return {@code null} if either input is {@code null}; otherwise the period multiplied by the scalar + */ + public static Period multiply(final long scalar, final Period period) { + return multiply(period, scalar); } + /** + * Divide a duration by a scalar. + * + * @param duration the duration to divide + * @param scalar the scalar to divide by + * @return {@code null} if either input is {@code null}; otherwise the duration divide by the scalar + */ + public static Duration divide(final Duration duration, final long scalar) { + if (duration == null || scalar == NULL_LONG) { + return null; + } + + if(scalar == 0) { + throw new DateTimeOverflowException("Scalar value is zero"); + } + + return duration.dividedBy(scalar); + } + + /** + * Divide a duration by a scalar. + * + * @param duration the duration to divide + * @param scalar the scalar to divide by + * @return {@code null} if either input is {@code null}; otherwise the duration divide by the scalar + */ + public static Duration divide(final Duration duration, final double scalar) { + if (duration == null || scalar == NULL_DOUBLE) { + return null; + } + + if(Double.isNaN(scalar)) { + throw new DateTimeOverflowException("Scalar value is NaN"); + } + + if(scalar == 0) { + throw new DateTimeOverflowException("Scalar value is zero"); + } + + return Duration.ofNanos((long) (duration.toNanos() / scalar)); + } /** * Returns the difference in nanoseconds between two instant values. diff --git a/engine/time/src/test/java/io/deephaven/time/TestDateTimeUtils.java b/engine/time/src/test/java/io/deephaven/time/TestDateTimeUtils.java index b3d583b3e98..bbb61f2f505 100644 --- a/engine/time/src/test/java/io/deephaven/time/TestDateTimeUtils.java +++ b/engine/time/src/test/java/io/deephaven/time/TestDateTimeUtils.java @@ -2012,6 +2012,40 @@ public void testPlusLocalDate() { // ok } } + + public void testPlusDuration() { + final Duration d1 = Duration.ofSeconds(1); + final Duration d2 = Duration.ofSeconds(2); + final Duration d3 = Duration.ofSeconds(3); + TestCase.assertEquals(d3, DateTimeUtils.plus(d1, d2)); + TestCase.assertEquals(d3, DateTimeUtils.plus(d2, d1)); + TestCase.assertNull(DateTimeUtils.plus(d1, null)); + TestCase.assertNull(DateTimeUtils.plus((Duration) null, d2)); + + try { + DateTimeUtils.plus(d1, Duration.ofSeconds(Long.MAX_VALUE)); + TestCase.fail("This should have overflowed"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } + + public void testPlusPeriod() { + final Period d1 = Period.ofDays(1); + final Period d2 = Period.ofDays(2); + final Period d3 = Period.ofDays(3); + TestCase.assertEquals(d3, DateTimeUtils.plus(d1, d2)); + TestCase.assertEquals(d3, DateTimeUtils.plus(d2, d1)); + TestCase.assertNull(DateTimeUtils.plus(d1, null)); + TestCase.assertNull(DateTimeUtils.plus((Period) null, d2)); + + try { + DateTimeUtils.plus(d1, Period.ofDays(Integer.MAX_VALUE)); + TestCase.fail("This should have overflowed"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } public void testPlus() { final Instant instant = DateTimeUtils.parseInstant("2010-01-01T12:13:14.999123456 JP"); @@ -2188,6 +2222,38 @@ public void testMinusLocalDate() { } } + public void testMinusDuration() { + final Duration d1 = Duration.ofSeconds(3); + final Duration d2 = Duration.ofSeconds(1); + TestCase.assertEquals(Duration.ofSeconds(2), DateTimeUtils.minus(d1, d2)); + TestCase.assertEquals(Duration.ofSeconds(-2), DateTimeUtils.minus(d2, d1)); + TestCase.assertNull(DateTimeUtils.minus(d1, null)); + TestCase.assertNull(DateTimeUtils.minus((Duration) null, d2)); + + try { + DateTimeUtils.minus(d1, Duration.ofSeconds(Long.MIN_VALUE)); + TestCase.fail("This should have overflowed"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } + + public void testMinusPeriod() { + final Period d1 = Period.ofDays(3); + final Period d2 = Period.ofDays(1); + TestCase.assertEquals(Period.ofDays(2), DateTimeUtils.minus(d1, d2)); + TestCase.assertEquals(Period.ofDays(-2), DateTimeUtils.minus(d2, d1)); + TestCase.assertNull(DateTimeUtils.minus(d1, null)); + TestCase.assertNull(DateTimeUtils.minus((Period) null, d2)); + + try { + DateTimeUtils.minus(d1, Period.ofDays(Integer.MIN_VALUE)); + TestCase.fail("This should have overflowed"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } + public void testMinus() { final Instant instant1 = DateTimeUtils.parseInstant("2010-01-01T12:13:14.999123456 JP"); final Instant instant2 = DateTimeUtils.parseInstant("2010-01-01T13:13:14.999123456 JP"); @@ -2352,6 +2418,7 @@ public void testMultiply() { final Duration d = Duration.ofNanos(123456789L); TestCase.assertEquals(Period.ofDays(6), DateTimeUtils.multiply(p, 2)); + TestCase.assertEquals(Period.ofDays(6), DateTimeUtils.multiply(2, p)); TestCase.assertEquals(Period.ofDays(9), DateTimeUtils.multiply(p, 3)); TestCase.assertEquals(Period.ofDays(3), DateTimeUtils.multiply(p, 1)); TestCase.assertEquals(Period.ofDays(0), DateTimeUtils.multiply(p, 0)); @@ -2361,7 +2428,26 @@ public void testMultiply() { TestCase.assertNull(DateTimeUtils.multiply((Period) null, 3)); TestCase.assertNull(DateTimeUtils.multiply(p, NULL_INT)); + TestCase.assertEquals(Period.ofDays(6), DateTimeUtils.multiply(p, 2L)); + TestCase.assertEquals(Period.ofDays(6), DateTimeUtils.multiply(2L, p)); + TestCase.assertEquals(Period.ofDays(9), DateTimeUtils.multiply(p, 3L)); + TestCase.assertEquals(Period.ofDays(3), DateTimeUtils.multiply(p, 1L)); + TestCase.assertEquals(Period.ofDays(0), DateTimeUtils.multiply(p, 0L)); + TestCase.assertEquals(Period.ofDays(-3), DateTimeUtils.multiply(p, -1L)); + TestCase.assertEquals(Period.ofDays(-6), DateTimeUtils.multiply(p, -2L)); + TestCase.assertEquals(Period.ofDays(-9), DateTimeUtils.multiply(p, -3L)); + TestCase.assertNull(DateTimeUtils.multiply((Period) null, 3L)); + TestCase.assertNull(DateTimeUtils.multiply(p, NULL_LONG)); + + try{ + DateTimeUtils.multiply(p, Long.MAX_VALUE); + TestCase.fail("This should have overflowed"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + TestCase.assertEquals(Duration.ofNanos(246913578L), DateTimeUtils.multiply(d, 2)); + TestCase.assertEquals(Duration.ofNanos(246913578L), DateTimeUtils.multiply(2, d)); TestCase.assertEquals(Duration.ofNanos(370370367L), DateTimeUtils.multiply(d, 3)); TestCase.assertEquals(Duration.ofNanos(123456789L), DateTimeUtils.multiply(d, 1)); TestCase.assertEquals(Duration.ofNanos(0), DateTimeUtils.multiply(d, 0)); @@ -2370,6 +2456,71 @@ public void testMultiply() { TestCase.assertEquals(Duration.ofNanos(-370370367L), DateTimeUtils.multiply(d, -3)); TestCase.assertNull(DateTimeUtils.multiply((Duration) null, 3)); TestCase.assertNull(DateTimeUtils.multiply(d, NULL_LONG)); + + TestCase.assertEquals(Duration.ofNanos(246913578L), DateTimeUtils.multiply(d, 2.0)); + TestCase.assertEquals(Duration.ofNanos(246913578L), DateTimeUtils.multiply(2.0, d)); + TestCase.assertNull(DateTimeUtils.multiply((Duration) null, 3.0)); + TestCase.assertNull(DateTimeUtils.multiply(d, NULL_DOUBLE)); + + try{ + DateTimeUtils.multiply(d, Double.NaN); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + + try{ + DateTimeUtils.multiply(d, Double.POSITIVE_INFINITY); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + + try{ + DateTimeUtils.multiply(d, Double.NEGATIVE_INFINITY); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } + + public void testDivideDuration() { + final Duration d = Duration.ofNanos(123456789L); + + TestCase.assertEquals(Duration.ofNanos(61728394L), DateTimeUtils.divide(d, 2)); + TestCase.assertEquals(Duration.ofNanos(123456789L), DateTimeUtils.divide(d, 1)); + TestCase.assertEquals(Duration.ofNanos(-123456789L), DateTimeUtils.divide(d, -1)); + TestCase.assertEquals(Duration.ofNanos(-61728394L), DateTimeUtils.divide(d, -2)); + TestCase.assertNull(DateTimeUtils.divide((Duration) null, 3)); + TestCase.assertNull(DateTimeUtils.divide(d, NULL_LONG)); + + try{ + DateTimeUtils.divide(d, 0); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + + TestCase.assertEquals(Duration.ofNanos(61728394L), DateTimeUtils.divide(d, 2.0)); + TestCase.assertEquals(Duration.ofNanos(0L), DateTimeUtils.divide(d, POS_INFINITY_DOUBLE)); + TestCase.assertEquals(Duration.ofNanos(0L), DateTimeUtils.divide(d, NEG_INFINITY_DOUBLE)); + TestCase.assertNull(DateTimeUtils.divide((Duration) null, 3.0)); + TestCase.assertNull(DateTimeUtils.divide(d, NULL_DOUBLE)); + + try{ + DateTimeUtils.divide(d, 0.0); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + + try{ + DateTimeUtils.divide(d, Double.NaN); + TestCase.fail("This should have excepted"); + } catch (DateTimeUtils.DateTimeOverflowException e) { + // ok + } + } public void testDiffNanos() {