Skip to content

Commit

Permalink
Double's time related conversions (bi-directional) added, with suppor…
Browse files Browse the repository at this point in the history
…ting tests.
  • Loading branch information
jdereg committed Feb 25, 2024
1 parent b3bb8aa commit 0a82697
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 9 deletions.
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 @@ -224,6 +224,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Double.class, Double.class), Converter::identity);
CONVERSION_DB.put(pair(Boolean.class, Double.class), BooleanConversions::toDouble);
CONVERSION_DB.put(pair(Character.class, Double.class), CharacterConversions::toDouble);
CONVERSION_DB.put(pair(Duration.class, Double.class), DurationConversions::toDouble);
CONVERSION_DB.put(pair(Instant.class, Double.class), InstantConversions::toDouble);
CONVERSION_DB.put(pair(LocalDate.class, Double.class), LocalDateConversions::toDouble);
CONVERSION_DB.put(pair(LocalDateTime.class, Double.class), LocalDateTimeConversions::toDouble);
Expand Down Expand Up @@ -517,7 +518,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Short.class, LocalDate.class), UNSUPPORTED);
CONVERSION_DB.put(pair(Integer.class, LocalDate.class), UNSUPPORTED);
CONVERSION_DB.put(pair(Long.class, LocalDate.class), NumberConversions::toLocalDate);
CONVERSION_DB.put(pair(Double.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(AtomicInteger.class, LocalDate.class), UNSUPPORTED);
Expand Down Expand Up @@ -727,6 +728,7 @@ private static void buildFactoryConversions() {
// Duration conversions supported
CONVERSION_DB.put(pair(Void.class, Duration.class), VoidConversions::toNull);
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(Timestamp.class, Duration.class), TimestampConversions::toDuration);
CONVERSION_DB.put(pair(String.class, Duration.class), StringConversions::toDuration);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.cedarsoftware.util.convert;

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,6 +35,10 @@ static Instant toInstant(Object from, Converter converter) {
return Instant.ofEpochSecond(seconds, nanoAdjustment);
}

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

static LocalDateTime toLocalDateTime(Object from, Converter converter) {
return toZonedDateTime(from, converter).toLocalDateTime();
}
Expand All @@ -46,11 +52,14 @@ static OffsetDateTime toOffsetDateTime(Object from, Converter converter) {
}

static Timestamp toTimestamp(Object from, Converter converter) {
double milliseconds = (Double) from;
long millisPart = (long) milliseconds;
int nanosPart = (int) ((milliseconds - millisPart) * 1_000_000);
Timestamp timestamp = new Timestamp(millisPart);
timestamp.setNanos(timestamp.getNanos() + nanosPart);
return timestamp;
return Timestamp.from(toInstant(from, converter));
}

static Duration toDuration(Object from, Converter converter) {
double d = (Double) from;
// Separate whole seconds and nanoseconds
long seconds = (long) d;
int nanoAdjustment = (int) ((d - seconds) * 1_000_000_000);
return Duration.ofSeconds(seconds, nanoAdjustment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ static BigInteger toBigInteger(Object from, Converter converter) {
return seconds.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_000.0;
}

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 @@ -62,7 +62,7 @@ static ZonedDateTime toZonedDateTime(Object from, Converter converter) {
}

static double toDouble(Object from, Converter converter) {
return toLong(from, converter);
return toInstant(from, converter).toEpochMilli();
}

static AtomicLong toAtomicLong(Object from, Converter converter) {
Expand All @@ -89,10 +89,12 @@ static Date toDate(Object from, Converter converter) {
}

static BigInteger toBigInteger(Object from, Converter converter) {
// TODO: Upgrade precision
return BigInteger.valueOf(toLong(from, converter));
}

static BigDecimal toBigDecimal(Object from, Converter converter) {
// TODO: Upgrade precision
return BigDecimal.valueOf(toLong(from, converter));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,15 @@ public ZoneId getZoneId() {
{(char) 1, 1d},
{(char) 0, 0d},
});
TEST_DB.put(pair(Duration.class, Double.class), new Object[][] {
{ Duration.ofSeconds(-1), -1d, true },
{ Duration.ofSeconds(0), 0d, true },
{ Duration.ofSeconds(1), 1d, true },
{ Duration.ofNanos(1), 0.000000001d, true },
{ Duration.ofNanos(1_000_000_000), 1d, true },
{ Duration.ofNanos(2_000_000_001), 2.000000001d, true },
{ Duration.ofSeconds(10, 9), 10.000000009d, true },
});
TEST_DB.put(pair(Instant.class, Double.class), new Object[][]{ // JDK 1.8 cannot handle the format +01:00 in Instant.parse(). JDK11+ handle it fine.
// {ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").toInstant(), -0.000001d, true}, // IEEE-754 double cannot represent this number precisely
{ZonedDateTime.parse("1970-01-01T00:00:00Z").toInstant(), 0d, true},
Expand All @@ -1072,13 +1081,16 @@ public ZoneId getZoneId() {
{ZonedDateTime.parse("2024-02-12T11:38:00.123937482+01:00").toInstant(), 1707734280123.937482d}, // nano = one-millionth of a milli
});
TEST_DB.put(pair(LocalDate.class, Double.class), new Object[][]{
{LocalDate.parse("1969-12-31"), -118800000d, true}, // Proves it always works from "startOfDay", using the zoneId from options
{LocalDate.parse("1970-01-01"), -32400000d, true}, // Proves it always works from "startOfDay", using the zoneId from options
{LocalDate.parse("1970-01-02"), 54000000d, true}, // Proves it always works from "startOfDay", using the zoneId from options
{ZonedDateTime.parse("1969-12-31T00:00:00Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDate(), -1.188E8, 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(), -1.188E8, true}, // Proves it always works from "startOfDay", using the zoneId from options
{ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(ZoneId.of("UTC")).toLocalDate(), -32400000d, true}, // Proves it always works from "startOfDay", using the zoneId from options
{ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z).toLocalDate(), -32400000d, true},
{ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z).toLocalDate(), -32400000.000000001d, true},
{ZonedDateTime.parse("2024-02-12T11:38:00+01:00").withZoneSameInstant(TOKYO_Z).toLocalDate(), 1.7076636E12, true}, // Epoch millis in Tokyo timezone (at start of day - no time)
{ZonedDateTime.parse("2024-02-12T11:38:00.123456789+01:00").withZoneSameInstant(TOKYO_Z).toLocalDate(), 1.7076636E12}, // Only to start of day resolution
{ZonedDateTime.parse("2024-02-12T11:38:00.123456789+01:00").withZoneSameInstant(TOKYO_Z).toLocalDate(), 1.7076636E12, true}, // Only to start of day resolution
});
TEST_DB.put(pair(LocalDateTime.class, Double.class), new Object[][]{
// {ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").withZoneSameInstant(TOKYO_Z).toLocalDateTime(), -0.000001d, true}, // IEEE-754 double does not quite have the resolution
Expand Down Expand Up @@ -1144,6 +1156,27 @@ public ZoneId getZoneId() {
{new Timestamp(Long.MAX_VALUE), (double) Long.MAX_VALUE},
});
TEST_DB.put(pair(Calendar.class, Double.class), new Object[][]{
{(Supplier<Calendar>) () -> {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.setTimeInMillis(-1);
return cal;
}, -1d},
{(Supplier<Calendar>) () -> {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.setTimeInMillis(0);
return cal;
}, 0d},
{(Supplier<Calendar>) () -> {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.setTimeInMillis(1);
return cal;
}, 1d},
{(Supplier<Calendar>) () -> {
Calendar cal = Calendar.getInstance();
cal.clear();
Expand Down Expand Up @@ -1710,6 +1743,11 @@ public ZoneId getZoneId() {
TEST_DB.put(pair(OffsetDateTime.class, OffsetDateTime.class), new Object[][]{
{OffsetDateTime.parse("2024-02-18T06:31:55.987654321+00:00"), OffsetDateTime.parse("2024-02-18T06:31:55.987654321+00:00"), true },
});
TEST_DB.put(pair(Double.class, OffsetDateTime.class), new Object[][]{
{-0.000001d, OffsetDateTime.parse("1969-12-31T23:59:59.999999999Z").withOffsetSameInstant(ZonedDateTime.now(TOKYO_Z).getOffset()) }, // IEEE-754 resolution prevents perfect symmetry (close)
{0d, OffsetDateTime.parse("1970-01-01T00:00:00Z").withOffsetSameInstant(ZonedDateTime.now(TOKYO_Z).getOffset()), true },
{0.000001d, OffsetDateTime.parse("1970-01-01T00:00:00.000000001Z").withOffsetSameInstant(ZonedDateTime.now(TOKYO_Z).getOffset()), true },
});

/////////////////////////////////////////////////////////////
// MonthDay
Expand Down Expand Up @@ -1868,6 +1906,16 @@ public ZoneId getZoneId() {
/////////////////////////////////////////////////////////////
// Timestamp
/////////////////////////////////////////////////////////////
TEST_DB.put(pair(Void.class, Timestamp.class), new Object[][]{
{ null, null },
});
// No identity test - Timestamp is mutable
TEST_DB.put(pair(Double.class, Timestamp.class), new Object[][]{
{ -0.000001d, Timestamp.from(ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").toInstant())}, // IEEE-754 limit prevents reverse test
{ 0d, Timestamp.from(ZonedDateTime.parse("1970-01-01T00:00:00Z").toInstant()), true},
{ 0.000001d, Timestamp.from(ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").toInstant()), true},
{ (double)now, new Timestamp(now), true},
});
TEST_DB.put(pair(BigDecimal.class, Timestamp.class), new Object[][] {
{ new BigDecimal("-62167219200000.000000"), Timestamp.from(ZonedDateTime.parse("0000-01-01T00:00:00Z").toInstant()), true },
{ new BigDecimal("-62167219199999.999999"), Timestamp.from(ZonedDateTime.parse("0000-01-01T00:00:00.000000001Z").toInstant()), true },
Expand Down Expand Up @@ -1906,6 +1954,53 @@ public ZoneId getZoneId() {
{OffsetDateTime.parse("2024-02-18T06:31:55.987654321+00:00"), Timestamp.from(ZonedDateTime.parse("2024-02-18T06:31:55.987654321+00:00").toInstant()) },
});

/////////////////////////////////////////////////////////////
// LocalDate
/////////////////////////////////////////////////////////////
TEST_DB.put(pair(Void.class, LocalDate.class), new Object[][] {
{ null, null }
});
TEST_DB.put(pair(LocalDate.class, LocalDate.class), new Object[][] {
{ LocalDate.parse("1970-01-01"), LocalDate.parse("1970-01-01"), true }
});
TEST_DB.put(pair(Double.class, LocalDate.class), new Object[][] { // options timezone is factored in (86,400,000 millis per day)
{ -32400001d, LocalDate.parse("1969-12-31") },
{ -32400000d, LocalDate.parse("1970-01-01"), true },
{ 0d, LocalDate.parse("1970-01-01") },
{ 53999999d, LocalDate.parse("1970-01-01") },
{ 54000000d, LocalDate.parse("1970-01-02"), true },
});

/////////////////////////////////////////////////////////////
// LocalDateTime
/////////////////////////////////////////////////////////////
TEST_DB.put(pair(Void.class, LocalDateTime.class), new Object[][] {
{ null, null }
});
TEST_DB.put(pair(LocalDateTime.class, LocalDateTime.class), new Object[][] {
{ LocalDateTime.of(1970, 1, 1, 0,0), LocalDateTime.of(1970, 1, 1, 0,0), true }
});
TEST_DB.put(pair(Double.class, LocalDateTime.class), new Object[][] {
{ -0.000001d, LocalDateTime.parse("1969-12-31T23:59:59.999999999").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime() }, // IEEE-754 prevents perfect symmetry
{ 0d, LocalDateTime.parse("1970-01-01T00:00:00").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
{ 0.000001d, LocalDateTime.parse("1970-01-01T00:00:00.000000001").atZone(ZoneId.of("UTC")).withZoneSameInstant(TOKYO_Z).toLocalDateTime(), true },
});

/////////////////////////////////////////////////////////////
// ZonedDateTime
/////////////////////////////////////////////////////////////
TEST_DB.put(pair(Void.class, ZonedDateTime.class), new Object[][]{
{ null, null },
});
TEST_DB.put(pair(ZonedDateTime.class, ZonedDateTime.class), new Object[][]{
{ ZonedDateTime.parse("1970-01-01T00:00:00.000000000Z").withZoneSameInstant(TOKYO_Z), ZonedDateTime.parse("1970-01-01T00:00:00.000000000Z").withZoneSameInstant(TOKYO_Z) },
});
TEST_DB.put(pair(Double.class, ZonedDateTime.class), new Object[][]{
{ -0.000001d, ZonedDateTime.parse("1969-12-31T23:59:59.999999999+00:00").withZoneSameInstant(TOKYO_Z)}, // IEEE-754 limit prevents reverse test
{ 0d, ZonedDateTime.parse("1970-01-01T00:00:00+00:00").withZoneSameInstant(TOKYO_Z), true},
{ 0.000001d, ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true},
});

/////////////////////////////////////////////////////////////
// ZoneOffset
/////////////////////////////////////////////////////////////
Expand Down

0 comments on commit 0a82697

Please sign in to comment.