diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java index 9afd8baef4..8dd8761fac 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java @@ -38,11 +38,13 @@ public class InternalIsoDuration implements IsoDuration { private static final List SUPPORTED_UNITS = unmodifiableList( asList( MONTHS, DAYS, SECONDS, NANOS ) ); + private static final InternalIsoDuration ZERO = new InternalIsoDuration( 0, 0, 0, 0 ); + public static final long NANOS_PER_SECOND = 1_000_000_000L; private final long months; private final long days; private final long seconds; - private final long nanoseconds; + private final int nanoseconds; public InternalIsoDuration( Period period ) { @@ -54,7 +56,7 @@ public InternalIsoDuration( Duration duration ) this( 0, 0, duration.getSeconds(), duration.getNano() ); } - public InternalIsoDuration( long months, long days, long seconds, long nanoseconds ) + public InternalIsoDuration( long months, long days, long seconds, int nanoseconds ) { this.months = months; this.days = days; @@ -62,6 +64,11 @@ public InternalIsoDuration( long months, long days, long seconds, long nanosecon this.nanoseconds = nanoseconds; } + public InternalIsoDuration( Period period, Duration duration ) + { + this( period.toTotalMonths(), period.getDays(), duration.getSeconds(), duration.getNano() ); + } + @Override public long months() { @@ -81,7 +88,7 @@ public long seconds() } @Override - public long nanoseconds() + public int nanoseconds() { return nanoseconds; } @@ -188,11 +195,65 @@ public int hashCode() @Override public String toString() { - return "Duration{" + - "months=" + months + - ", days=" + days + - ", seconds=" + seconds + - ", nanoseconds=" + nanoseconds + - '}'; + // print the duration in iso standard format. + if ( this.equals( ZERO ) ) + { + return "PT0S"; // no need to allocate a string builder if we know the result + } + StringBuilder str = new StringBuilder().append( "P" ); + append( str, months / 12, 'Y' ); + append( str, months % 12, 'M' ); + append( str, days / 7, 'W' ); + append( str, days % 7, 'D' ); + if ( seconds != 0 || nanoseconds != 0 ) + { + str.append( 'T' ); + long s = seconds % 3600; + append( str, seconds / 3600, 'H' ); + append( str, s / 60, 'M' ); + s %= 60; + if ( s != 0 ) + { + str.append( s ); + if ( nanoseconds != 0 ) + { + nanos( str ); + } + str.append( 'S' ); + } + else if ( nanoseconds != 0 ) + { + if ( nanoseconds < 0 ) + { + str.append( '-' ); + } + str.append( '0' ); + nanos( str ); + str.append( 'S' ); + } + } + if ( str.length() == 1 ) + { // this was all zeros (but not ZERO for some reason), ensure well formed output: + str.append( "T0S" ); + } + return str.toString(); + } + + private static void append( StringBuilder str, long quantity, char unit ) + { + if ( quantity != 0 ) + { + str.append( quantity ).append( unit ); + } + } + + private void nanos( StringBuilder str ) + { + str.append( '.' ); + int n = nanoseconds < 0 ? -nanoseconds : nanoseconds; + for ( int mod = (int)NANOS_PER_SECOND; mod > 1 && n > 0; n %= mod ) + { + str.append( n / (mod /= 10) ); + } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java index 480b6b266c..35baaafc23 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -320,7 +320,7 @@ private Value unpackDuration() throws IOException long months = unpacker.unpackLong(); long days = unpacker.unpackLong(); long seconds = unpacker.unpackLong(); - long nanoseconds = unpacker.unpackLong(); + int nanoseconds = Math.toIntExact( unpacker.unpackLong() ); return isoDuration( months, days, seconds, nanoseconds ); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 0868efd307..d4cf3dc4e8 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -323,7 +323,7 @@ public static Value value( Duration duration ) return value( new InternalIsoDuration( duration ) ); } - public static Value isoDuration( long months, long days, long seconds, long nanoseconds ) + public static Value isoDuration( long months, long days, long seconds, int nanoseconds ) { return value( new InternalIsoDuration( months, days, seconds, nanoseconds ) ); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java index 7521d44e09..bd5ce1ee3e 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java @@ -57,5 +57,5 @@ public interface IsoDuration extends TemporalAmount * * @return number of nanoseconds. */ - long nanoseconds(); + int nanoseconds(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java index 438f9d09dc..506b247780 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java @@ -34,7 +34,9 @@ import static java.time.temporal.ChronoUnit.SECONDS; import static java.time.temporal.ChronoUnit.YEARS; import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class InternalIsoDurationTest @@ -151,7 +153,19 @@ public void shouldCreateFromDuration() assertEquals( duration.getNano(), isoDuration.nanoseconds() ); } - private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + @Test + public void toStringShouldPrintInIsoStandardFormat() throws Throwable + { + assertThat( new InternalIsoDuration( 0, 0, 0, 0 ).toString(), equalTo( "PT0S" ) ); + assertThat( new InternalIsoDuration( Period.parse( "P356D" ) ).toString(), equalTo( "P50W6D" ) ); + assertThat( new InternalIsoDuration( Duration.parse( "PT45S" ) ).toString(), equalTo( "PT45S" ) ); + + assertThat( new InternalIsoDuration( Period.parse( "P14D" ), Duration.parse( "PT16H12M" ) ).toString(), equalTo( "P2WT16H12M" ) ); + assertThat( new InternalIsoDuration( Period.parse( "P5M1D" ), Duration.parse( "PT12H" ) ).toString(), equalTo( "P5M1DT12H" ) ); + assertThat( new InternalIsoDuration( Period.parse( "P2W3D" ), Duration.parse( "PT12H" ) ).toString(), equalTo( "P2W3DT12H" ) ); + } + + private static IsoDuration newDuration( long months, long days, long seconds, int nanoseconds ) { return new InternalIsoDuration( months, days, seconds, nanoseconds ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java index 4e812be596..444a3ee7f8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java @@ -70,7 +70,7 @@ public void shouldNotSupportAsLong() } } - private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + private static IsoDuration newDuration( long months, long days, long seconds, int nanoseconds ) { return new InternalIsoDuration( months, days, seconds, nanoseconds ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java index 67a2b14900..b797cba60f 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java @@ -275,7 +275,7 @@ private void testSendAndReceiveValue( T value, Function converter ) assertEquals( value, converter.apply( record.get( 0 ) ) ); } - private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + private static IsoDuration newDuration( long months, long days, long seconds, int nanoseconds ) { return isoDuration( months, days, seconds, nanoseconds ).asIsoDuration(); }