diff --git a/pom.xml b/pom.xml
index b60f67d..481d452 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,7 +38,7 @@
com.typesafe
config
- 1.4.5
+ 1.4.6
org.slf4j
@@ -121,7 +121,7 @@
nl.jqno.equalsverifier
equalsverifier
- 4.2.5
+ 4.4.1
test
@@ -149,14 +149,14 @@
io.dropwizard
dropwizard-bom
- 5.0.0
+ 5.0.1
pom
import
io.dropwizard
dropwizard-dependencies
- 5.0.0
+ 5.0.1
pom
import
@@ -172,19 +172,19 @@
maven-resources-plugin
- 3.4.0
+ 3.5.0
maven-compiler-plugin
- 3.14.1
+ 3.15.0
maven-surefire-plugin
- 3.5.4
+ 3.5.5
maven-jar-plugin
- 3.4.2
+ 3.5.0
maven-install-plugin
@@ -197,16 +197,21 @@
org.codehaus.mojo
versions-maven-plugin
- 2.20.1
+ 2.21.0
maven-dependency-plugin
- 3.9.0
+ 3.10.0
maven-enforcer-plugin
3.6.2
+
+ com.github.siom79.japicmp
+ japicmp-maven-plugin
+ 0.25.4
+
diff --git a/src/main/java/no/digipost/jackson/JsonDuration.java b/src/main/java/no/digipost/jackson/JsonDuration.java
index 84a28fb..9097260 100644
--- a/src/main/java/no/digipost/jackson/JsonDuration.java
+++ b/src/main/java/no/digipost/jackson/JsonDuration.java
@@ -29,12 +29,20 @@
import java.util.stream.Stream;
import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MICROS;
+import static java.time.temporal.ChronoUnit.MILLIS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static java.time.temporal.ChronoUnit.NANOS;
+import static java.time.temporal.ChronoUnit.SECONDS;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
public final class JsonDuration implements TemporalAmount, Serializable {
+ private static final long serialVersionUID = 7565437081331942214L;
+
public static final List supportedUnits = unmodifiableList(Stream.of(ChronoUnit.values()).filter(u -> !u.isDurationEstimated() || u == DAYS).collect(toList()));
public final long amount;
@@ -42,22 +50,51 @@ public final class JsonDuration implements TemporalAmount, Serializable {
public final Duration duration;
private final String stringRepresentation;
- @JsonCreator
+ @Deprecated
public static JsonDuration of(String jsonString) {
- return new JsonDuration(jsonString);
+ return parse(jsonString);
}
- private JsonDuration(String jsonString) {
+ @JsonCreator
+ public static JsonDuration parse(String jsonString) {
try {
String[] amountAndUnit = jsonString.split("\\s+");
- amount = Long.parseLong(amountAndUnit[0]);
- ChronoUnit chronoUnit = ChronoUnit.valueOf(amountAndUnit[1].toUpperCase());
- unit = chronoUnit;
- duration = Duration.of(amount, unit);
- stringRepresentation = amount + " " + chronoUnit.name();
+ long amount = Long.parseLong(amountAndUnit[0]);
+ ChronoUnit unit = ChronoUnit.valueOf(amountAndUnit[1].toUpperCase());
+ return new JsonDuration(amount, unit, null);
} catch (Exception e) {
throw new CannotConvertToJsonDuration(jsonString, e);
}
+
+ }
+
+ public static JsonDuration from(Duration duration) {
+ long nanosPart = duration.toNanosPart();
+ if (nanosPart != 0) {
+ long totalNanos = duration.toNanos();
+ if (totalNanos % 1000 != 0) {
+ return new JsonDuration(totalNanos, NANOS, duration);
+ } else if (totalNanos % 1_000_000 != 0) {
+ return new JsonDuration(totalNanos / 1000, MICROS, duration);
+ } else {
+ return new JsonDuration(duration.toMillis(), MILLIS, duration);
+ }
+ } else if (duration.toSecondsPart() != 0) {
+ return new JsonDuration(duration.toSeconds(), SECONDS, duration);
+ } else if (duration.toMinutesPart() != 0) {
+ return new JsonDuration(duration.toMinutes(), MINUTES, duration);
+ } else if (duration.toHoursPart() != 0) {
+ return new JsonDuration(duration.toHours(), HOURS, duration);
+ } else {
+ return new JsonDuration(duration.toDays(), DAYS, duration);
+ }
+ }
+
+ private JsonDuration(long amount, ChronoUnit unit, Duration duration) {
+ this.amount = amount;
+ this.unit = unit;
+ this.stringRepresentation = amount + " " + unit.name();
+ this.duration = duration != null ? duration : Duration.of(amount, unit);
}
@@ -110,4 +147,5 @@ public boolean equals(Object obj) {
public int hashCode() {
return Objects.hash(duration);
}
+
}
diff --git a/src/test/java/no/digipost/jackson/JsonDurationTest.java b/src/test/java/no/digipost/jackson/JsonDurationTest.java
index 766d83c..24968c2 100644
--- a/src/test/java/no/digipost/jackson/JsonDurationTest.java
+++ b/src/test/java/no/digipost/jackson/JsonDurationTest.java
@@ -16,57 +16,97 @@
package no.digipost.jackson;
import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.time.Duration;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MICROS;
+import static java.time.temporal.ChronoUnit.MILLIS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static java.time.temporal.ChronoUnit.NANOS;
+import static java.time.temporal.ChronoUnit.SECONDS;
import static no.digipost.jackson.JsonDuration.supportedUnits;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.quicktheories.QuickTheory.qt;
import static org.quicktheories.generators.SourceDSL.arbitrary;
import static org.quicktheories.generators.SourceDSL.integers;
+import static org.quicktheories.generators.SourceDSL.longs;
import static org.quicktheories.generators.SourceDSL.strings;
+import static uk.co.probablyfine.matchers.Java8Matchers.where;
-public class JsonDurationTest {
+class JsonDurationTest {
@Test
- public void correctEqualsAndHashcode() {
+ void correctEqualsAndHashcode() {
EqualsVerifier
- .forRelaxedEqualExamples(JsonDuration.of("1 days"), JsonDuration.of("24 hours"), JsonDuration.of("1440 minutes"))
- .andUnequalExamples(JsonDuration.of("4 days"), JsonDuration.of("5 days"), JsonDuration.of("1337 nanos"))
+ .forRelaxedEqualExamples(JsonDuration.parse("1 days"), JsonDuration.parse("24 hours"), JsonDuration.parse("1440 minutes"))
+ .andUnequalExamples(JsonDuration.parse("4 days"), JsonDuration.parse("5 days"), JsonDuration.parse("1337 nanos"))
.verify();
- assertThat(JsonDuration.of("1 days"), is(JsonDuration.of("24 hours")));
- assertThat(JsonDuration.of("42 minutes"), not("42 minutes"));
+ assertThat(JsonDuration.parse("1 days"), is(JsonDuration.parse("24 hours")));
+ assertThat(JsonDuration.parse("42 minutes"), not("42 minutes"));
}
@Test
- public void parsesUnitsSupportedByJavaTimeDuration() {
+ void parsesUnitsSupportedByJavaTimeDuration() {
qt()
.forAll(integers().all(), arbitrary().pick(supportedUnits))
.checkAssert((amount, unit) -> {
- JsonDuration parsed = JsonDuration.of(amount + " " + unit.name().toLowerCase());
+ JsonDuration parsed = JsonDuration.parse(amount + " " + unit.name().toLowerCase());
assertThat(parsed.duration.toMillis(), is(Duration.of(amount, unit).toMillis()));
});
}
@Test
- public void stringRepresentationOfItselfIsParsable() {
+ void stringRepresentationOfItselfIsParsable() {
qt()
.forAll(integers().all(), arbitrary().pick(supportedUnits))
- .as((amount, unit) -> JsonDuration.of(amount + " " + unit.name()))
- .checkAssert(parsed -> assertThat(JsonDuration.of(parsed.toString()), is(parsed)));
+ .as((amount, unit) -> JsonDuration.parse(amount + " " + unit.name()))
+ .checkAssert(parsed -> assertThat(JsonDuration.parse(parsed.toString()), is(parsed)));
}
@Test
- public void unableToParseMalforedStrings() {
+ void unableToParseMalforedStrings() {
qt()
.forAll(strings().allPossible().ofLengthBetween(0, 100).assuming(s -> !s.matches("\\d+ \\w\\w+")))
- .checkAssert(notParseable -> assertThrows(JsonDuration.CannotConvertToJsonDuration.class, () -> JsonDuration.of(notParseable)));
+ .checkAssert(notParseable -> assertThrows(JsonDuration.CannotConvertToJsonDuration.class, () -> JsonDuration.parse(notParseable)));
}
+
+ @Nested
+ class ConvertFromDuration {
+ @Test
+ void resolvedAmountAndUnitIsEquivalentToTheDuration() {
+ qt()
+ .forAll(longs().all().map(Duration::ofNanos).map(JsonDuration::from))
+ .checkAssert(jsonDuration -> assertThat(Duration.of(jsonDuration.amount, jsonDuration.unit), is(jsonDuration.duration)));
+ }
+
+ @Test
+ void useASensibleLargestPossibleUnit() {
+ assertAll(
+ () -> assertThat(JsonDuration.from(Duration.ofHours(24)), where(d -> d.unit, is(DAYS))),
+ () -> assertThat(JsonDuration.from(Duration.ofHours(25)), where(d -> d.unit, is(HOURS))),
+ () -> assertThat(JsonDuration.from(Duration.ofHours(25).plusSeconds(42)), where(d -> d.unit, is(SECONDS))),
+ () -> assertThat(JsonDuration.from(Duration.ofHours(24).plusMinutes(10)), where(d -> d.unit, is(MINUTES))));
+ }
+
+ @Test
+ void handlesSubSecondUnits() {
+ assertAll(
+ () -> assertThat(JsonDuration.from(Duration.ofNanos(1_000_000_000)), where(d -> d.unit, is(SECONDS))),
+ () -> assertThat(JsonDuration.from(Duration.ofNanos(1_001_000_000)), where(d -> d.unit, is(MILLIS))),
+ () -> assertThat(JsonDuration.from(Duration.ofNanos(1_001_001_000)), where(d -> d.unit, is(MICROS))),
+ () -> assertThat(JsonDuration.from(Duration.ofNanos(1_001_001_001)), where(d -> d.unit, is(NANOS))));
+ }
+ }
+
}