Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.4.5</version>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -121,7 +121,7 @@
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>4.2.5</version>
<version>4.4.1</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -149,14 +149,14 @@
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-bom</artifactId>
<version>5.0.0</version>
<version>5.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId>
<version>5.0.0</version>
<version>5.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand All @@ -172,19 +172,19 @@
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.4.0</version>
<version>3.5.0</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<version>3.15.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<version>3.5.5</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<version>3.5.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
Expand All @@ -197,16 +197,21 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.20.1</version>
<version>2.21.0</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.9.0</version>
<version>3.10.0</version>
</plugin>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
</plugin>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<version>0.25.4</version>
</plugin>
</plugins>
</pluginManagement>
</build>
Expand Down
54 changes: 46 additions & 8 deletions src/main/java/no/digipost/jackson/JsonDuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,72 @@
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<ChronoUnit> supportedUnits = unmodifiableList(Stream.of(ChronoUnit.values()).filter(u -> !u.isDurationEstimated() || u == DAYS).collect(toList()));

public final long amount;
public final TemporalUnit unit;
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);
}


Expand Down Expand Up @@ -110,4 +147,5 @@ public boolean equals(Object obj) {
public int hashCode() {
return Objects.hash(duration);
}

}
66 changes: 53 additions & 13 deletions src/test/java/no/digipost/jackson/JsonDurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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))));
}
}

}