Skip to content

Commit

Permalink
BigDecimal to/from Duration, BigDecimal to/from Instant - code and te…
Browse files Browse the repository at this point in the history
…sts.
  • Loading branch information
jdereg committed Feb 26, 2024
1 parent 0e2dd2a commit 474baae
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
Expand Down Expand Up @@ -33,9 +35,19 @@ final class BigDecimalConversions {
private BigDecimalConversions() { }

static Instant toInstant(Object from, Converter converter) {
BigDecimal bigDec = (BigDecimal) from;
BigDecimal fractionalPart = bigDec.remainder(BigDecimal.ONE);
return Instant.ofEpochSecond(bigDec.longValue(), fractionalPart.movePointRight(9).longValue());
BigDecimal seconds = (BigDecimal) from;
BigDecimal nanos = seconds.remainder(BigDecimal.ONE);
return Instant.ofEpochSecond(seconds.longValue(), nanos.movePointRight(9).longValue());
}

static Duration toDuration(Object from, Converter converter) {
BigDecimal seconds = (BigDecimal) from;
BigDecimal nanos = seconds.remainder(BigDecimal.ONE);
return Duration.ofSeconds(seconds.longValue(), nanos.movePointRight(9).longValue());
}

static LocalDate toLocalDate(Object from, Converter converter) {
return toZonedDateTime(from, converter).toLocalDate();
}

static LocalDateTime toLocalDateTime(Object from, Converter converter) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(java.sql.Date.class, BigDecimal.class), DateConversions::toBigDecimal);
CONVERSION_DB.put(pair(Timestamp.class, BigDecimal.class), TimestampConversions::toBigDecimal);
CONVERSION_DB.put(pair(Instant.class, BigDecimal.class), InstantConversions::toBigDecimal);
CONVERSION_DB.put(pair(Duration.class, BigDecimal.class), DurationConversions::toBigDecimal);
CONVERSION_DB.put(pair(LocalDate.class, BigDecimal.class), LocalDateConversions::toBigDecimal);
CONVERSION_DB.put(pair(LocalDateTime.class, BigDecimal.class), LocalDateTimeConversions::toBigDecimal);
CONVERSION_DB.put(pair(ZonedDateTime.class, BigDecimal.class), ZonedDateTimeConversions::toBigDecimal);
Expand Down Expand Up @@ -520,7 +521,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Long.class, LocalDate.class), NumberConversions::toLocalDate);
CONVERSION_DB.put(pair(Double.class, LocalDate.class), DoubleConversions::toLocalDate);
CONVERSION_DB.put(pair(BigInteger.class, LocalDate.class), NumberConversions::toLocalDate);
CONVERSION_DB.put(pair(BigDecimal.class, LocalDate.class), NumberConversions::toLocalDate);
CONVERSION_DB.put(pair(BigDecimal.class, LocalDate.class), BigDecimalConversions::toLocalDate);
CONVERSION_DB.put(pair(AtomicInteger.class, LocalDate.class), UNSUPPORTED);
CONVERSION_DB.put(pair(AtomicLong.class, LocalDate.class), NumberConversions::toLocalDate);
CONVERSION_DB.put(pair(java.sql.Date.class, LocalDate.class), DateConversions::toLocalDate);
Expand Down Expand Up @@ -730,6 +731,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Duration.class, Duration.class), Converter::identity);
CONVERSION_DB.put(pair(Double.class, Duration.class), DoubleConversions::toDuration);
CONVERSION_DB.put(pair(BigInteger.class, Duration.class), BigIntegerConversions::toDuration);
CONVERSION_DB.put(pair(BigDecimal.class, Duration.class), BigDecimalConversions::toDuration);
CONVERSION_DB.put(pair(Timestamp.class, Duration.class), TimestampConversions::toDuration);
CONVERSION_DB.put(pair(String.class, Duration.class), StringConversions::toDuration);
CONVERSION_DB.put(pair(Map.class, Duration.class), MapConversions::toDuration);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cedarsoftware.util.convert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.Duration;
Expand Down Expand Up @@ -40,19 +41,27 @@ static Map toMap(Object from, Converter converter) {

static BigInteger toBigInteger(Object from, Converter converter) {
Duration duration = (Duration) from;
BigInteger seconds = BigInteger.valueOf(duration.getSeconds());
BigInteger epochSeconds = BigInteger.valueOf(duration.getSeconds());
BigInteger nanos = BigInteger.valueOf(duration.getNano());

// Convert seconds to nanoseconds and add the nanosecond part
return seconds.multiply(BigIntegerConversions.BILLION).add(nanos);
return epochSeconds.multiply(BigIntegerConversions.BILLION).add(nanos);
}

static double toDouble(Object from, Converter converter) {
Duration duration = (Duration) from;
// Convert to seconds with nanosecond precision
return duration.getSeconds() + duration.getNano() / 1_000_000_000d;
}

static BigDecimal toBigDecimal(Object from, Converter converter) {
Duration duration = (Duration) from;
BigDecimal seconds = new BigDecimal(duration.getSeconds());

// Convert nanoseconds to fractional seconds and add to seconds
BigDecimal fracSec = BigDecimal.valueOf(duration.getNano(), 9);
return seconds.add(fracSec);
}

static Timestamp toTimestamp(Object from, Converter converter) {
Duration duration = (Duration) from;
Instant epoch = Instant.EPOCH;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ static BigInteger toBigInteger(Object from, Converter converter) {
}

static BigDecimal toBigDecimal(Object from, Converter converter) {
// TODO: Upgrade precision
return BigDecimal.valueOf(toLong(from, converter));
Instant instant = toInstant(from, converter);
BigDecimal epochSeconds = BigDecimal.valueOf(instant.getEpochSecond());
BigDecimal nanos = new BigDecimal(BigInteger.valueOf(instant.getNano()), 9);

// Add the nanos to the whole seconds
return epochSeconds.add(nanos);
}

static String toString(Object from, Converter converter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ static BigInteger toBigInteger(Object from, Converter converter) {
}

static BigDecimal toBigDecimal(Object from, Converter converter) {
return BigDecimal.valueOf(toLong(from, converter));
Instant instant = toInstant(from, converter);
return InstantConversions.toBigDecimal(instant, converter);
}

static String toString(Object from, Converter converter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,6 @@ public ZoneId getZoneId() {
TEST_DB.put(pair(Duration.class, Double.class), new Object[][] {
{ Duration.ofSeconds(-1, -1), -1.000000001, true },
{ Duration.ofSeconds(-1), -1d, true },
{ Duration.ofSeconds(-1), -1d, true },
{ Duration.ofSeconds(0), 0d, true },
{ Duration.ofSeconds(1), 1d, true },
{ Duration.ofNanos(1), 0.000000001, true },
Expand Down Expand Up @@ -1749,6 +1748,25 @@ public ZoneId getZoneId() {
{ Timestamp.from(ZonedDateTime.parse("1970-01-01T00:00:00Z").toInstant()), new BigDecimal("0"), true },
{ Timestamp.from(ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").toInstant()), new BigDecimal("0.000000001"), true },
});
TEST_DB.put(pair(LocalDate.class, BigDecimal.class), new Object[][]{
{ LocalDate.parse("0000-01-01"), new BigDecimal("-62167252739"), true}, // Proves it always works from "startOfDay", using the zoneId from options
{ LocalDate.parse("1969-12-31"), new BigDecimal("-118800"), true},
{ LocalDate.parse("1970-01-01"), new BigDecimal("-32400"), true},
{ LocalDate.parse("1970-01-02"), new BigDecimal("54000"), true},
{ ZonedDateTime.parse("1969-12-31T00:00:00Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDate(), new BigDecimal("-118800"), true}, // Proves it always works from "startOfDay", using the zoneId from options
{ ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDate(), new BigDecimal("-118800"), true}, // Proves it always works from "startOfDay", using the zoneId from options
{ ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDate(), new BigDecimal("-32400"), true}, // Proves it always works from "startOfDay", using the zoneId from options
{ ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDate(), new BigDecimal("-32400"), true},
{ ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z).toLocalDate(), new BigDecimal("-32400"), true},
});
TEST_DB.put(pair(LocalDateTime.class, BigDecimal.class), new Object[][]{
{ ZonedDateTime.parse("0000-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), new BigDecimal("-62167219200.0"), true},
{ ZonedDateTime.parse("0000-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), new BigDecimal("-62167219199.999999999"), true},
{ ZonedDateTime.parse("1969-12-31T00:00:00.999999999Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), new BigDecimal("-86399.000000001"), true},
{ ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime(), new BigDecimal("-32400"), true}, // Time portion affects the answer unlike LocalDate
{ ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), BigDecimal.ZERO, true},
{ ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), new BigDecimal("0.000000001"), true},
});
TEST_DB.put(pair(ZonedDateTime.class, BigDecimal.class), new Object[][] { // no reverse due to .toString adding zone offset
{ ZonedDateTime.parse("0000-01-01T00:00:00Z"), new BigDecimal("-62167219200") },
{ ZonedDateTime.parse("0000-01-01T00:00:00.000000001Z"), new BigDecimal("-62167219199.999999999") },
Expand All @@ -1763,6 +1781,28 @@ public ZoneId getZoneId() {
{ OffsetDateTime.parse("1970-01-01T00:00:00Z"), new BigDecimal("0") },
{ OffsetDateTime.parse("1970-01-01T00:00:00.000000001Z"), new BigDecimal("0.000000001") },
});
TEST_DB.put(pair(Duration.class, BigDecimal.class), new Object[][] {
{ Duration.ofSeconds(-1, -1), new BigDecimal("-1.000000001"), true },
{ Duration.ofSeconds(-1), new BigDecimal("-1"), true },
{ Duration.ofSeconds(0), new BigDecimal("0"), true },
{ Duration.ofSeconds(1), new BigDecimal("1"), true },
{ Duration.ofNanos(1), new BigDecimal("0.000000001"), true },
{ Duration.ofNanos(1_000_000_000), new BigDecimal("1"), true },
{ Duration.ofNanos(2_000_000_001), new BigDecimal("2.000000001"), true },
{ Duration.ofSeconds(10, 9), new BigDecimal("10.000000009"), true },
{ Duration.ofDays(1), new BigDecimal("86400"), true},
});
TEST_DB.put(pair(Instant.class, BigDecimal.class), new Object[][]{ // JDK 1.8 cannot handle the format +01:00 in Instant.parse(). JDK11+ handles it fine.
{ ZonedDateTime.parse("0000-01-01T00:00:00Z").toInstant(), new BigDecimal("-62167219200.0"), true},
{ ZonedDateTime.parse("0000-01-01T00:00:00.000000001Z").toInstant(), new BigDecimal("-62167219199.999999999"), true},
{ ZonedDateTime.parse("1969-12-31T00:00:00Z").toInstant(), new BigDecimal("-86400"), true},
{ ZonedDateTime.parse("1969-12-31T00:00:00.999999999Z").toInstant(), new BigDecimal("-86399.000000001"), true },
{ ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").toInstant(), new BigDecimal("-0.000000001"), true },
{ ZonedDateTime.parse("1970-01-01T00:00:00Z").toInstant(), BigDecimal.ZERO, true},
{ ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").toInstant(), new BigDecimal("0.000000001"), true},
{ ZonedDateTime.parse("1970-01-02T00:00:00Z").toInstant(), new BigDecimal("86400"), true},
{ ZonedDateTime.parse("1970-01-02T00:00:00.000000001Z").toInstant(), new BigDecimal("86400.000000001"), true},
});

/////////////////////////////////////////////////////////////
// Instant
Expand All @@ -1771,15 +1811,23 @@ public ZoneId getZoneId() {
{ null, null }
});
TEST_DB.put(pair(String.class, Instant.class), new Object[][]{
{"", null},
{" ", null},
{"1980-01-01T00:00:00Z", Instant.parse("1980-01-01T00:00:00Z"), true},
{"2024-12-31T23:59:59.999999999Z", Instant.parse("2024-12-31T23:59:59.999999999Z")},
{ "", null},
{ " ", null},
{ "1980-01-01T00:00:00Z", Instant.parse("1980-01-01T00:00:00Z"), true},
{ "2024-12-31T23:59:59.999999999Z", Instant.parse("2024-12-31T23:59:59.999999999Z")},
});
TEST_DB.put(pair(Double.class, Instant.class), new Object[][]{
{-0.000000001, Instant.parse("1969-12-31T23:59:59.999999999Z")}, // IEEE-754 precision not good enough for reverse
{0d, Instant.parse("1970-01-01T00:00:00Z"), true},
{0.000000001, Instant.parse("1970-01-01T00:00:00.000000001Z"), true},
{ -62167219200d, Instant.parse("0000-01-01T00:00:00Z"), true},
{ -0.000000001, Instant.parse("1969-12-31T23:59:59.999999999Z")}, // IEEE-754 precision not good enough for reverse
{ 0d, Instant.parse("1970-01-01T00:00:00Z"), true},
{ 0.000000001, Instant.parse("1970-01-01T00:00:00.000000001Z"), true},
});
TEST_DB.put(pair(BigDecimal.class, Instant.class), new Object[][]{
{ new BigDecimal("-62167219200"), Instant.parse("0000-01-01T00:00:00Z"), true},
{ new BigDecimal("-62167219199.999999999"), Instant.parse("0000-01-01T00:00:00.000000001Z"), true},
{ new BigDecimal("-0.000000001"), Instant.parse("1969-12-31T23:59:59.999999999Z"), true},
{ BigDecimal.ZERO, Instant.parse("1970-01-01T00:00:00Z"), true},
{ new BigDecimal("0.000000001"), Instant.parse("1970-01-01T00:00:00.000000001Z"), true},
});

/////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1842,6 +1890,16 @@ public ZoneId getZoneId() {
{100d, Duration.ofSeconds(100), true},
{3.000000006d, Duration.ofSeconds(3, 6) }, // IEEE 754 prevents reverse
});
TEST_DB.put(pair(BigDecimal.class, Duration.class), new Object[][]{
{new BigDecimal("-0.000000001"), Duration.ofNanos(-1), true },
{BigDecimal.ZERO, Duration.ofNanos(0), true},
{new BigDecimal("0.000000001"), Duration.ofNanos(1), true },
{new BigDecimal("100"), Duration.ofSeconds(100), true},
{new BigDecimal("1"), Duration.ofSeconds(1), true},
{new BigDecimal("100"), Duration.ofSeconds(100), true},
{new BigDecimal("100"), Duration.ofSeconds(100), true},
{new BigDecimal("3.000000006"), Duration.ofSeconds(3, 6), true },
});

/////////////////////////////////////////////////////////////
// OffsetDateTime
Expand Down Expand Up @@ -2084,6 +2142,16 @@ public ZoneId getZoneId() {
{ 53999.999, LocalDate.parse("1970-01-01") }, // Showing that there is a wide range of numbers that will convert to this date
{ 54000d, LocalDate.parse("1970-01-02"), true },
});
TEST_DB.put(pair(BigDecimal.class, LocalDate.class), new Object[][] { // options timezone is factored in (86,400 seconds per day)
{ new BigDecimal("-62167219200"), LocalDate.parse("0000-01-01") },
{ new BigDecimal("-62167219200"), ZonedDateTime.parse("0000-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDate() },
{ new BigDecimal("-118800"), LocalDate.parse("1969-12-31"), true },
// These 4 are all in the same date range
{ new BigDecimal("-32400"), LocalDate.parse("1970-01-01"), true },
{ BigDecimal.ZERO, ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDate() },
{ new BigDecimal("53999.999"), LocalDate.parse("1970-01-01") },
{ new BigDecimal("54000"), LocalDate.parse("1970-01-02"), true },
});

/////////////////////////////////////////////////////////////
// LocalDateTime
Expand All @@ -2099,6 +2167,13 @@ public ZoneId getZoneId() {
{ 0d, LocalDateTime.parse("1970-01-01T00:00:00").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ 0.000000001, LocalDateTime.parse("1970-01-01T00:00:00.000000001").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
});
TEST_DB.put(pair(BigDecimal.class, LocalDateTime.class), new Object[][] {
{ new BigDecimal("-62167219200"), LocalDateTime.parse("0000-01-01T00:00:00").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ new BigDecimal("-62167219199.999999999"), LocalDateTime.parse("0000-01-01T00:00:00.000000001").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ new BigDecimal("-0.000000001"), LocalDateTime.parse("1969-12-31T23:59:59.999999999").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ BigDecimal.valueOf(0), LocalDateTime.parse("1970-01-01T00:00:00").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ new BigDecimal("0.000000001"), LocalDateTime.parse("1970-01-01T00:00:00.000000001").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
});

/////////////////////////////////////////////////////////////
// ZonedDateTime
Expand All @@ -2123,7 +2198,7 @@ public ZoneId getZoneId() {
{ BigDecimal.valueOf(0), ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z), true},
{ new BigDecimal("0.000000001"), ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true},
{ BigDecimal.valueOf(86400), ZonedDateTime.parse("1970-01-02T00:00:00Z").withZoneSameInstant(TOKYO_Z), true},
{ new BigDecimal("86400.000000001"), ZonedDateTime.parse("1970-01-02T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true},
{ new BigDecimal("86400.000000001"), ZonedDateTime.parse("1970-01-02T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true},
});

/////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit 474baae

Please sign in to comment.