diff --git a/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java b/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java index 0c239533..acf843fb 100644 --- a/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java @@ -57,8 +57,18 @@ static float toFloat(Object from, Converter converter) { return toLong(from, converter); } + /** + * @return double number of milliseconds. When integerized, the number returned is always the number of epoch + * milliseconds. If the Instant specified resolution further than milliseconds, the double returned captures + * that as fractional milliseconds. + * Example 1: "2024-02-12T11:38:00.123937482+01:00" (as an Instant) = 1707734280123.937482d + * Example 2: "2024-02-12T11:38:00.1239+01:00" (as an Instant) = 1707734280123.9d + */ static double toDouble(Object from, Converter converter) { - return toLong(from, converter); + Instant instant = (Instant) from; + long millis = instant.toEpochMilli(); + int nanos = instant.getNano(); + return millis + (nanos % 1_000_000) / 1_000_000.0d; } static AtomicLong toAtomicLong(Object from, Converter converter) { diff --git a/src/main/java/com/cedarsoftware/util/convert/LocalDateTimeConversions.java b/src/main/java/com/cedarsoftware/util/convert/LocalDateTimeConversions.java index 2260f5d0..eece425c 100644 --- a/src/main/java/com/cedarsoftware/util/convert/LocalDateTimeConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/LocalDateTimeConversions.java @@ -48,7 +48,8 @@ static long toLong(Object from, Converter converter) { } static double toDouble(Object from, Converter converter) { - return toInstant(from, converter).toEpochMilli(); + Instant instant = toInstant(from, converter); + return InstantConversions.toDouble(instant, converter); } static LocalDateTime toLocalDateTime(Object from, Converter converter) { diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index b5a35d4b..899bea19 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -89,6 +89,9 @@ public ZoneId getZoneId() { // ... // {source-n, answer-n} + // Useful values for input + long now = System.currentTimeMillis(); + ///////////////////////////////////////////////////////////// // Byte/byte ///////////////////////////////////////////////////////////// @@ -777,6 +780,7 @@ public ZoneId getZoneId() { }); TEST_DB.put(pair(Date.class, Long.class), new Object[][]{ {new Date(Long.MIN_VALUE), Long.MIN_VALUE}, + {new Date(now), now}, {new Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, {new Date(0), 0L}, {new Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, @@ -785,6 +789,7 @@ public ZoneId getZoneId() { TEST_DB.put(pair(java.sql.Date.class, Long.class), new Object[][]{ {new java.sql.Date(Long.MIN_VALUE), Long.MIN_VALUE}, {new java.sql.Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, + {new java.sql.Date(now), now}, {new java.sql.Date(0), 0L}, {new java.sql.Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, {new java.sql.Date(Long.MAX_VALUE), Long.MAX_VALUE}, @@ -792,11 +797,14 @@ public ZoneId getZoneId() { TEST_DB.put(pair(Timestamp.class, Long.class), new Object[][]{ {new Timestamp(Long.MIN_VALUE), Long.MIN_VALUE}, {new Timestamp(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, + {new Timestamp(now), now}, {new Timestamp(0), 0L}, {new Timestamp(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, {new Timestamp(Long.MAX_VALUE), Long.MAX_VALUE}, }); TEST_DB.put(pair(Instant.class, Long.class), new Object[][]{ + {ZonedDateTime.parse("2024-02-12T11:38:00.123456789+01:00").toInstant(), 1707734280123L}, // maintains millis (best long can do) + {ZonedDateTime.parse("2024-02-12T11:38:00.123999+01:00").toInstant(), 1707734280123L}, // maintains millis (best long can do) {ZonedDateTime.parse("2024-02-12T11:38:00+01:00").toInstant(), 1707734280000L}, }); TEST_DB.put(pair(LocalDate.class, Long.class), new Object[][]{ @@ -812,6 +820,16 @@ public ZoneId getZoneId() { zdt = zdt.withZoneSameInstant(TOKYO_Z); return zdt.toLocalDateTime(); }, 1707734280000L}, // Epoch millis in Tokyo timezone + {(Supplier) () -> { + ZonedDateTime zdt = ZonedDateTime.parse("2024-02-12T11:38:00.123+01:00"); // maintains millis (best long can do) + zdt = zdt.withZoneSameInstant(TOKYO_Z); + return zdt.toLocalDateTime(); + }, 1707734280123L}, // Epoch millis in Tokyo timezone + {(Supplier) () -> { + ZonedDateTime zdt = ZonedDateTime.parse("2024-02-12T11:38:00.12399+01:00"); // maintains millis (best long can do) + zdt = zdt.withZoneSameInstant(TOKYO_Z); + return zdt.toLocalDateTime(); + }, 1707734280123L}, // Epoch millis in Tokyo timezone }); TEST_DB.put(pair(ZonedDateTime.class, Long.class), new Object[][]{ {ZonedDateTime.parse("2024-02-12T11:38:00+01:00"), 1707734280000L}, @@ -823,10 +841,19 @@ public ZoneId getZoneId() { cal.setTimeZone(TOKYO_TZ); cal.set(2024, Calendar.FEBRUARY, 12, 11, 38, 0); return cal; - }, 1707705480000L} + }, 1707705480000L}, + {(Supplier) () -> { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeZone(TOKYO_TZ); + cal.setTimeInMillis(now); // Calendar maintains time to millisecond resolution + return cal; + }, now} }); TEST_DB.put(pair(OffsetDateTime.class, Long.class), new Object[][]{ {OffsetDateTime.parse("2024-02-12T11:38:00+01:00"), 1707734280000L}, + {OffsetDateTime.parse("2024-02-12T11:38:00.123+01:00"), 1707734280123L}, // maintains millis (best long can do) + {OffsetDateTime.parse("2024-02-12T11:38:00.12399+01:00"), 1707734280123L}, // maintains millis (best long can do) }); TEST_DB.put(pair(Year.class, Long.class), new Object[][]{ {Year.of(2024), 2024L}, @@ -1049,6 +1076,10 @@ public ZoneId getZoneId() { }); TEST_DB.put(pair(Instant.class, Double.class), new Object[][]{ {ZonedDateTime.parse("2024-02-12T11:38:00+01:00").toInstant(), 1707734280000d}, + {ZonedDateTime.parse("2024-02-12T11:38:00.123+01:00").toInstant(), 1707734280123d}, + {ZonedDateTime.parse("2024-02-12T11:38:00.1234+01:00").toInstant(), 1707734280123.4d}, // fractional milliseconds (nano support) + {ZonedDateTime.parse("2024-02-12T11:38:00.1239+01:00").toInstant(), 1707734280123.9d}, + {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[][]{ {(Supplier) () -> { @@ -1063,10 +1094,26 @@ public ZoneId getZoneId() { zdt = zdt.withZoneSameInstant(TOKYO_Z); return zdt.toLocalDateTime(); }, 1.70773428E12}, // Epoch millis in Tokyo timezone + {(Supplier) () -> { + ZonedDateTime zdt = ZonedDateTime.parse("2024-02-12T11:38:00.123+01:00"); + zdt = zdt.withZoneSameInstant(TOKYO_Z); + return zdt.toLocalDateTime(); + }, 1.707734280123E12}, // Epoch millis in Tokyo timezone + {(Supplier) () -> { + ZonedDateTime zdt = ZonedDateTime.parse("2024-02-12T11:38:00.1239+01:00"); + zdt = zdt.withZoneSameInstant(TOKYO_Z); + return zdt.toLocalDateTime(); + }, 1.7077342801239E12}, // Epoch millis in Tokyo timezone + {(Supplier) () -> { + ZonedDateTime zdt = ZonedDateTime.parse("2024-02-12T11:38:00.123937482+01:00"); + zdt = zdt.withZoneSameInstant(TOKYO_Z); + return zdt.toLocalDateTime(); + }, 1707734280123.937482d}, // Epoch millis in Tokyo timezone }); TEST_DB.put(pair(ZonedDateTime.class, Double.class), new Object[][]{ {ZonedDateTime.parse("2024-02-12T11:38:00+01:00"), 1707734280000d}, }); + // Left off here (need to fix ZoneDateTime and Timestamp) TEST_DB.put(pair(Date.class, Double.class), new Object[][]{ {new Date(Long.MIN_VALUE), (double) Long.MIN_VALUE, true}, {new Date(Integer.MIN_VALUE), (double) Integer.MIN_VALUE, true}, @@ -1131,7 +1178,14 @@ public ZoneId getZoneId() { cal.setTimeZone(TOKYO_TZ); cal.set(2024, Calendar.FEBRUARY, 12, 11, 38, 0); return cal; - }, 1707705480000d} + }, 1707705480000d}, + {(Supplier) () -> { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeZone(TOKYO_TZ); + cal.setTimeInMillis(now); // Calendar maintains time to millisecond resolution + return cal; + }, (double)now} }); TEST_DB.put(pair(Number.class, Double.class), new Object[][]{ {2.5f, 2.5d} @@ -1424,7 +1478,6 @@ public ZoneId getZoneId() { ///////////////////////////////////////////////////////////// // BigInteger ///////////////////////////////////////////////////////////// - long now = System.currentTimeMillis(); TEST_DB.put(pair(Void.class, BigInteger.class), new Object[][]{ { null, null }, }); @@ -1498,18 +1551,42 @@ public ZoneId getZoneId() { { new AtomicInteger(Integer.MIN_VALUE), BigInteger.valueOf(Integer.MIN_VALUE) }, { new AtomicInteger(Integer.MAX_VALUE), BigInteger.valueOf(Integer.MAX_VALUE) }, }); - // Left off here TEST_DB.put(pair(AtomicLong.class, BigInteger.class), new Object[][]{ - {new AtomicLong(0), BigInteger.ZERO}, + { new AtomicLong(-1), BigInteger.valueOf(-1) }, + { new AtomicLong(0), BigInteger.ZERO }, + { new AtomicLong(Long.MIN_VALUE), BigInteger.valueOf(Long.MIN_VALUE) }, + { new AtomicLong(Long.MAX_VALUE), BigInteger.valueOf(Long.MAX_VALUE) }, }); TEST_DB.put(pair(Date.class, BigInteger.class), new Object[][]{ - {new Date(now), BigInteger.valueOf(now)}, + { new Date(0), BigInteger.valueOf(0), true }, + { new Date(now), BigInteger.valueOf(now), true }, + { new Date(Long.MIN_VALUE), BigInteger.valueOf(Long.MIN_VALUE), true }, + { new Date(Long.MAX_VALUE), BigInteger.valueOf(Long.MAX_VALUE), true }, }); TEST_DB.put(pair(java.sql.Date.class, BigInteger.class), new Object[][]{ - {new java.sql.Date(now), BigInteger.valueOf(now)}, + { new java.sql.Date(0), BigInteger.valueOf(0), true }, + { new java.sql.Date(now), BigInteger.valueOf(now), true }, + { new java.sql.Date(Long.MIN_VALUE), BigInteger.valueOf(Long.MIN_VALUE), true }, + { new java.sql.Date(Long.MAX_VALUE), BigInteger.valueOf(Long.MAX_VALUE), true }, }); TEST_DB.put(pair(Timestamp.class, BigInteger.class), new Object[][]{ - {new Timestamp(now), BigInteger.valueOf(now)}, + { new Timestamp(0), BigInteger.valueOf(0), true }, + { new Timestamp(now), BigInteger.valueOf(now), true }, +// { (Supplier) () -> { +// Timestamp ts = new Timestamp(now); +// ts.setNanos(1); +// return ts; +// }, (Supplier) () -> { +// Timestamp ts = new Timestamp(now); +// long milliseconds = ts.getTime(); +// int nanoseconds = ts.getNanos(); +// BigInteger nanos = BigInteger.valueOf(milliseconds).multiply(BigInteger.valueOf(1000000)) +// .add(BigInteger.valueOf(nanoseconds)); +// return nanos; +// } +// }, + { new Timestamp(Long.MIN_VALUE), BigInteger.valueOf(Long.MIN_VALUE), true }, + { new Timestamp(Long.MAX_VALUE), BigInteger.valueOf(Long.MAX_VALUE), true }, }); TEST_DB.put(pair(Instant.class, BigInteger.class), new Object[][]{ {Instant.ofEpochMilli(now), BigInteger.valueOf(now)},