diff --git a/changelog.md b/changelog.md
index ebd0a053..f867f868 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,11 +1,10 @@
### Revision History
-* 2.4.2
- * Fixed `SafeSimpleDateFormat` to properly format dates having years with fewer than four digits.
* 2.4.1
- * `Converter` has had significant expansion in the types that it can convert between, greater than 500 combinations. In addition, you can add your own conversions to it as well. Call the `Converter.getSupportedConversions()` to see all the combinations supported. Also, you can use `Converter` instance-based now, allowing it to have different conversion tables if needed.
- * `DateUtilities` has had performance improvements (> 35%), and adds a new `.parseDate()` API that allows it to return a `ZonedDateTime.` See the updated Javadoc on the class for a complete description of all of the formats it supports. Normally, you do not need to use this class directly, as you can use `Converter` to convert between `Dates`, `Calendars`, and the new Temporal classes like `ZonedDateTime,` `Duration,` `Instance,` as well as `long,` `BigInteger,` etc.
+ * `Converter` has had significant expansion in the types that it can convert between, about 670 combinations. In addition, you can add your own conversions to it as well. Call the `Converter.getSupportedConversions()` to see all the combinations supported. Also, you can use `Converter` instance-based now, allowing it to have different conversion tables if needed.
+ * `DateUtilities` has had performance improvements (> 35%), and adds a new `.parseDate()` API that allows it to return a `ZonedDateTime.` See the updated Javadoc on the class for a complete description of all the formats it supports. Normally, you do not need to use this class directly, as you can use `Converter` to convert between `Dates`, `Calendars`, and the new Temporal classes like `ZonedDateTime,` `Duration,` `Instance,` as well as Strings.
* `FastByteArrayOutputStream` updated to match `ByteArrayOutputStream` API. This means that `.getBuffer()` is `.toByteArray()` and `.clear()` is now `.reset().`
* `FastByteArrayInputStream` added. Matches `ByteArrayInputStream` API.
+ * Bug fix: `SafeSimpleDateFormat` to properly format dates having years with fewer than four digits.
* Bug fix: SafeSimpleDateFormat .toString(), .hashCode(), and .equals() now delegate to the contain SimpleDataFormat instance. We recommend using the newer DateTimeFormatter, however, this class works well for Java 1.8+ if needed.
* 2.4.0
* Added ClassUtilities. This class has a method to get the distance between a source and destination class. It includes support for Classes, multiple inheritance of interfaces, primitives, and class-to-interface, interface-interface, and class to class.
diff --git a/pom.xml b/pom.xml
index 25f6ee50..241dc2b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.cedarsoftware
java-util
jar
- 2.5.0-SNAPSHOT
+ 2.4.1
Java Utilities
https://github.com/jdereg/java-util
diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java
index a91af93a..bb23773b 100644
--- a/src/main/java/com/cedarsoftware/util/convert/Converter.java
+++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java
@@ -182,6 +182,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(java.sql.Date.class, Long.class), DateConversions::toLong);
CONVERSION_DB.put(pair(Timestamp.class, Long.class), DateConversions::toLong);
CONVERSION_DB.put(pair(Instant.class, Long.class), InstantConversions::toLong);
+ CONVERSION_DB.put(pair(Duration.class, Long.class), DurationConversions::toLong);
CONVERSION_DB.put(pair(LocalDate.class, Long.class), LocalDateConversions::toLong);
CONVERSION_DB.put(pair(LocalDateTime.class, Long.class), LocalDateTimeConversions::toLong);
CONVERSION_DB.put(pair(OffsetDateTime.class, Long.class), OffsetDateTimeConversions::toLong);
@@ -406,6 +407,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(java.sql.Date.class, AtomicLong.class), DateConversions::toAtomicLong);
CONVERSION_DB.put(pair(Timestamp.class, AtomicLong.class), DateConversions::toAtomicLong);
CONVERSION_DB.put(pair(Instant.class, AtomicLong.class), InstantConversions::toAtomicLong);
+ CONVERSION_DB.put(pair(Duration.class, AtomicLong.class), DurationConversions::toAtomicLong);
CONVERSION_DB.put(pair(LocalDate.class, AtomicLong.class), LocalDateConversions::toAtomicLong);
CONVERSION_DB.put(pair(LocalDateTime.class, AtomicLong.class), LocalDateTimeConversions::toAtomicLong);
CONVERSION_DB.put(pair(ZonedDateTime.class, AtomicLong.class), ZonedDateTimeConversions::toAtomicLong);
@@ -616,6 +618,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Map.class, OffsetDateTime.class), MapConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(String.class, OffsetDateTime.class), StringConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(Long.class, OffsetDateTime.class), NumberConversions::toOffsetDateTime);
+ CONVERSION_DB.put(pair(AtomicLong.class, OffsetDateTime.class), NumberConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(Double.class, OffsetDateTime.class), DoubleConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(BigInteger.class, OffsetDateTime.class), BigIntegerConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(BigDecimal.class, OffsetDateTime.class), BigDecimalConversions::toOffsetDateTime);
@@ -729,7 +732,9 @@ 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(Long.class, Duration.class), NumberConversions::toDuration);
CONVERSION_DB.put(pair(Double.class, Duration.class), DoubleConversions::toDuration);
+ CONVERSION_DB.put(pair(AtomicLong.class, Duration.class), NumberConversions::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);
diff --git a/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java b/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java
index 5fd1f140..b316fd6a 100644
--- a/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java
@@ -6,6 +6,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
import com.cedarsoftware.util.CompactLinkedMap;
@@ -39,6 +40,15 @@ static Map toMap(Object from, Converter converter) {
return target;
}
+ static long toLong(Object from, Converter converter) {
+ return ((Duration) from).toMillis();
+ }
+
+ static AtomicLong toAtomicLong(Object from, Converter converter) {
+ Duration duration = (Duration) from;
+ return new AtomicLong(duration.toMillis());
+ }
+
static BigInteger toBigInteger(Object from, Converter converter) {
Duration duration = (Duration) from;
BigInteger epochSeconds = BigInteger.valueOf(duration.getSeconds());
diff --git a/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java b/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java
index 0d96f2ac..6ab697a7 100644
--- a/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java
@@ -3,6 +3,7 @@
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;
@@ -174,7 +175,11 @@ static char toCharacter(Object from, Converter converter) {
static Date toDate(Object from, Converter converter) {
return new Date(toLong(from, converter));
}
-
+
+ static Duration toDuration(Object from, Converter converter) {
+ return Duration.ofMillis(toLong(from, converter));
+ }
+
static Instant toInstant(Object from, Converter converter) {
return Instant.ofEpochMilli(toLong(from, converter));
}
diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
index 5069abe2..8e873933 100644
--- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
+++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
@@ -113,8 +113,36 @@ public ZoneId getZoneId() {
loadZoneDateTimeTests();
loadZoneOffsetTests();
loadStringTests();
+ loadAtomicLongTests();
}
+ /**
+ * AtomicLong
+ */
+ private static void loadAtomicLongTests() {
+ TEST_DB.put(pair(Void.class, AtomicLong.class), new Object[][]{
+ {null, null}
+ });
+ TEST_DB.put(pair(Instant.class, AtomicLong.class), new Object[][]{
+ { Instant.parse("0000-01-01T00:00:00Z"), new AtomicLong(-62167219200000L), true},
+ { Instant.parse("0000-01-01T00:00:00.001Z"), new AtomicLong(-62167219199999L), true},
+ { Instant.parse("1969-12-31T23:59:59Z"), new AtomicLong(-1000L), true},
+ { Instant.parse("1969-12-31T23:59:59.999Z"), new AtomicLong(-1L), true},
+ { Instant.parse("1970-01-01T00:00:00Z"), new AtomicLong(0L), true},
+ { Instant.parse("1970-01-01T00:00:00.001Z"), new AtomicLong(1L), true},
+ { Instant.parse("1970-01-01T00:00:00.999Z"), new AtomicLong(999L), true},
+ });
+ TEST_DB.put(pair(Duration.class, AtomicLong.class), new Object[][]{
+ { Duration.ofMillis(Long.MIN_VALUE / 2), new AtomicLong(Long.MIN_VALUE / 2), true },
+ { Duration.ofMillis(Integer.MIN_VALUE), new AtomicLong(Integer.MIN_VALUE), true },
+ { Duration.ofMillis(-1), new AtomicLong(-1), true },
+ { Duration.ofMillis(0), new AtomicLong(0), true },
+ { Duration.ofMillis(1), new AtomicLong(1), true },
+ { Duration.ofMillis(Integer.MAX_VALUE), new AtomicLong(Integer.MAX_VALUE), true },
+ { Duration.ofMillis(Long.MAX_VALUE / 2), new AtomicLong(Long.MAX_VALUE / 2), true },
+ });
+ }
+
/**
* String
*/
@@ -811,6 +839,24 @@ private static void loadDurationTests() {
{"PT16M40S", Duration.ofSeconds(1000), true},
{"PT2H46M40S", Duration.ofSeconds(10000), true},
});
+ TEST_DB.put(pair(Long.class, Duration.class), new Object[][]{
+ { Long.MIN_VALUE / 2, Duration.ofMillis(Long.MIN_VALUE / 2), true },
+ { (long)Integer.MIN_VALUE, Duration.ofMillis(Integer.MIN_VALUE), true },
+ { -1L, Duration.ofMillis(-1), true },
+ { 0L, Duration.ofMillis(0), true },
+ { 1L, Duration.ofMillis(1), true },
+ { (long)Integer.MAX_VALUE, Duration.ofMillis(Integer.MAX_VALUE), true },
+ { Long.MAX_VALUE / 2, Duration.ofMillis(Long.MAX_VALUE / 2), true },
+ });
+ TEST_DB.put(pair(AtomicLong.class, Duration.class), new Object[][]{
+ { new AtomicLong(Long.MIN_VALUE / 2), Duration.ofMillis(Long.MIN_VALUE / 2), true },
+ { new AtomicLong(Integer.MIN_VALUE), Duration.ofMillis(Integer.MIN_VALUE), true },
+ { new AtomicLong(-1), Duration.ofMillis(-1), true },
+ { new AtomicLong(0), Duration.ofMillis(0), true },
+ { new AtomicLong(1), Duration.ofMillis(1), true },
+ { new AtomicLong(Integer.MAX_VALUE), Duration.ofMillis(Integer.MAX_VALUE), true },
+ { new AtomicLong(Long.MAX_VALUE / 2), Duration.ofMillis(Long.MAX_VALUE / 2), true },
+ });
TEST_DB.put(pair(Double.class, Duration.class), new Object[][]{
{-0.000000001, Duration.ofNanos(-1) }, // IEEE 754 prevents reverse
{0d, Duration.ofNanos(0), true},
@@ -936,6 +982,24 @@ private static void loadInstantTests() {
{ "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(Long.class, Instant.class), new Object[][]{
+ {-62167219200000L, Instant.parse("0000-01-01T00:00:00Z"), true},
+ {-62167219199999L, Instant.parse("0000-01-01T00:00:00.001Z"), true},
+ {-1000L, Instant.parse("1969-12-31T23:59:59Z"), true},
+ {-1L, Instant.parse("1969-12-31T23:59:59.999Z"), true},
+ {0L, Instant.parse("1970-01-01T00:00:00Z"), true},
+ {1L, Instant.parse("1970-01-01T00:00:00.001Z"), true},
+ {999L, Instant.parse("1970-01-01T00:00:00.999Z"), true},
+ });
+ TEST_DB.put(pair(AtomicLong.class, Instant.class), new Object[][]{
+ {new AtomicLong(-62167219200000L), Instant.parse("0000-01-01T00:00:00Z"), true},
+ {new AtomicLong(-62167219199999L), Instant.parse("0000-01-01T00:00:00.001Z"), true},
+ {new AtomicLong(-1000L), Instant.parse("1969-12-31T23:59:59Z"), true},
+ {new AtomicLong(-1L), Instant.parse("1969-12-31T23:59:59.999Z"), true},
+ {new AtomicLong(0L), Instant.parse("1970-01-01T00:00:00Z"), true},
+ {new AtomicLong(1L), Instant.parse("1970-01-01T00:00:00.001Z"), true},
+ {new AtomicLong(999L), Instant.parse("1970-01-01T00:00:00.999Z"), true},
+ });
TEST_DB.put(pair(Double.class, Instant.class), new Object[][]{
{ -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
@@ -2118,28 +2182,37 @@ private static void loadLongTests(long now) {
{Year.of(9999), 9999L},
});
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},
- {new Date(Long.MAX_VALUE), Long.MAX_VALUE},
+ {new Date(Long.MIN_VALUE), Long.MIN_VALUE, true},
+ {new Date(now), now, true},
+ {new Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true},
+ {new Date(0), 0L, true},
+ {new Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true},
+ {new Date(Long.MAX_VALUE), Long.MAX_VALUE, true},
});
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},
+ {new java.sql.Date(Long.MIN_VALUE), Long.MIN_VALUE, true},
+ {new java.sql.Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true},
+ {new java.sql.Date(now), now, true},
+ {new java.sql.Date(0), 0L, true},
+ {new java.sql.Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true},
+ {new java.sql.Date(Long.MAX_VALUE), Long.MAX_VALUE, true},
});
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},
+ {new Timestamp(Long.MIN_VALUE), Long.MIN_VALUE, true},
+ {new Timestamp(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true},
+ {new Timestamp(now), now, true},
+ {new Timestamp(0), 0L, true},
+ {new Timestamp(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true},
+ {new Timestamp(Long.MAX_VALUE), Long.MAX_VALUE, true},
+ });
+ TEST_DB.put(pair(Duration.class, Long.class), new Object[][]{
+ { Duration.ofMillis(Long.MIN_VALUE / 2), Long.MIN_VALUE / 2, true },
+ { Duration.ofMillis(Integer.MIN_VALUE), (long)Integer.MIN_VALUE, true },
+ { Duration.ofMillis(-1), -1L, true },
+ { Duration.ofMillis(0), 0L, true },
+ { Duration.ofMillis(1), 1L, true },
+ { Duration.ofMillis(Integer.MAX_VALUE), (long)Integer.MAX_VALUE, true },
+ { Duration.ofMillis(Long.MAX_VALUE / 2), Long.MAX_VALUE / 2, true },
});
TEST_DB.put(pair(Instant.class, Long.class), new Object[][]{
{Instant.parse("0000-01-01T00:00:00Z"), -62167219200000L, true},
@@ -2864,7 +2937,7 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source,
} else {
assert ClassUtilities.toPrimitiveWrapperClass(sourceClass).isInstance(source) : "source type mismatch ==> Expected: " + shortNameSource + ", Actual: " + Converter.getShortName(source.getClass());
}
- assert target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target) : "target type mismatch ==> " + shortNameTarget + ", actual: " + Converter.getShortName(target.getClass());
+ assert target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target) : "target type mismatch ==> Expected: " + shortNameTarget + ", Actual: " + Converter.getShortName(target.getClass());
// if the source/target are the same Class, then ensure identity lambda is used.
if (sourceClass.equals(targetClass)) {
@@ -2880,7 +2953,9 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source,
// Assert values are equals
Object actual = converter.convert(source, targetClass);
try {
- if (target instanceof BigDecimal) {
+ if (target instanceof AtomicLong) {
+ assertEquals(((AtomicLong) target).get(), ((AtomicLong) actual).get());
+ } else if (target instanceof BigDecimal) {
if (((BigDecimal) target).compareTo((BigDecimal) actual) != 0) {
assertEquals(target, actual);
}