From 112ff4131d4a8a2969be30cf2b57df61f1ba3e5a Mon Sep 17 00:00:00 2001 From: lutovich Date: Mon, 7 May 2018 14:23:18 +0200 Subject: [PATCH 1/2] Create random durations with positive nanoseconds Previously, test code that generated random durations could create a duration with negative amount of nanoseconds. Such duration is now normalized by the database so that nanoseconds value is always in range from 0 to 999_999_999. This commit makes tests only create random durations with nanoseconds in that range. Otherwise some test assertions fail because they expect non-normalized value to be returned back. --- driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java index ebe059987e..88b8c973c3 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java @@ -81,7 +81,7 @@ public static ZonedDateTime randomZonedDateTimeWithZoneId() public static IsoDuration randomDuration() { int sign = random().nextBoolean() ? 1 : -1; // duration can be negative - return new InternalIsoDuration( sign * randomInt(), sign * randomInt(), sign * randomInt(), sign * Math.abs( random( NANO_OF_SECOND ) ) ); + return new InternalIsoDuration( sign * randomInt(), sign * randomInt(), sign * randomInt(), Math.abs( random( NANO_OF_SECOND ) ) ); } private static ZonedDateTime randomZonedDateTime( ZoneId zoneId ) From edc09ce2c21efeab3d7282d8b3a9332d1a0f9a5a Mon Sep 17 00:00:00 2001 From: lutovich Date: Mon, 7 May 2018 15:02:00 +0200 Subject: [PATCH 2/2] Handle nanosecond normalization in IsoDuration Database normalizes nanoseconds to be in range from 0 to 999_999_999. `IsoDuration` previously did no calculations in it's `#toString()` and returned incorrect result. This commit makes it return correct string representation despite normalized second and nanosecond components. It will also do normalization in the constructor using `java.time.Duration#ofSeconds(long, long)` in order to return correct strings for durations created on the client side and not returned from the database. --- .../driver/internal/InternalIsoDuration.java | 56 +++++++++++++++---- .../internal/InternalIsoDurationTest.java | 33 ++++++++--- .../v1/integration/TemporalTypesIT.java | 10 ++++ 3 files changed, 80 insertions(+), 19 deletions(-) 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 c5a8e09243..dbc0e129bb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java @@ -36,6 +36,7 @@ public class InternalIsoDuration implements IsoDuration { + private static final long NANOS_PER_SECOND = 1_000_000_000; private static final List SUPPORTED_UNITS = unmodifiableList( asList( MONTHS, DAYS, SECONDS, NANOS ) ); private final long months; @@ -45,25 +46,25 @@ public class InternalIsoDuration implements IsoDuration public InternalIsoDuration( Period period ) { - this( period.toTotalMonths(), period.getDays(), 0, 0 ); + this( period.toTotalMonths(), period.getDays(), Duration.ZERO ); } public InternalIsoDuration( Duration duration ) { - this( 0, 0, duration.getSeconds(), duration.getNano() ); + this( 0, 0, duration ); } public InternalIsoDuration( long months, long days, long seconds, int nanoseconds ) { - this.months = months; - this.days = days; - this.seconds = seconds; - this.nanoseconds = nanoseconds; + this( months, days, Duration.ofSeconds( seconds, nanoseconds ) ); } - public InternalIsoDuration( Period period, Duration duration ) + InternalIsoDuration( long months, long days, Duration duration ) { - this( period.toTotalMonths(), period.getDays(), duration.getSeconds(), duration.getNano() ); + this.months = months; + this.days = days; + this.seconds = duration.getSeconds(); // normalized value of seconds + this.nanoseconds = duration.getNano(); // normalized value of nanoseconds in [0, 999_999_999] } @Override @@ -192,8 +193,41 @@ public int hashCode() @Override public String toString() { - String secondsSign = seconds < 0 || nanoseconds < 0 ? "-" : ""; - String nanosecondsString = nanoseconds == 0 ? "" : String.format( ".%09d", Math.abs( nanoseconds ) ); - return String.format( "P%sM%sDT%s%s%sS", months, days, secondsSign, Math.abs( seconds ), nanosecondsString ); + StringBuilder sb = new StringBuilder(); + sb.append( 'P' ); + sb.append( months ).append( 'M' ); + sb.append( days ).append( 'D' ); + sb.append( 'T' ); + if ( seconds < 0 && nanoseconds > 0 ) + { + if ( seconds == -1 ) + { + sb.append( "-0" ); + } + else + { + sb.append( seconds + 1 ); + } + } + else + { + sb.append( seconds ); + } + if ( nanoseconds > 0 ) + { + int pos = sb.length(); + // append nanoseconds as a 10-digit string with leading '1' that is later replaced by a '.' + if ( seconds < 0 ) + { + sb.append( 2 * NANOS_PER_SECOND - nanoseconds ); + } + else + { + sb.append( NANOS_PER_SECOND + nanoseconds ); + } + sb.setCharAt( pos, '.' ); // replace '1' with '.' + } + sb.append( 'S' ); + return sb.toString(); } } 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 3f4f8f8d59..1fbbd95f10 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java @@ -153,19 +153,36 @@ public void shouldCreateFromDuration() } @Test - public void toStringShouldPrintInIsoStandardFormat() throws Throwable + public void toStringShouldPrintInIsoStandardFormat() { - assertThat( new InternalIsoDuration( 0, 0, 0, 0 ).toString(), equalTo( "P0M0DT0S" ) ); - assertThat( new InternalIsoDuration( 2, 45, 59, 11 ).toString(), equalTo( "P2M45DT59.000000011S" ) ); - assertThat( new InternalIsoDuration( 4, -101, 1, 999 ).toString(), equalTo( "P4M-101DT1.000000999S" ) ); - assertThat( new InternalIsoDuration( -1, 12, -19, 1 ).toString(), equalTo( "P-1M12DT-19.000000001S" ) ); + assertThat( newDuration( 0, 0, 0, 0 ).toString(), equalTo( "P0M0DT0S" ) ); + assertThat( newDuration( 2, 45, 59, 11 ).toString(), equalTo( "P2M45DT59.000000011S" ) ); + assertThat( newDuration( 4, -101, 1, 999 ).toString(), equalTo( "P4M-101DT1.000000999S" ) ); + assertThat( newDuration( -1, 12, -19, 1 ).toString(), equalTo( "P-1M12DT-18.999999999S" ) ); + assertThat( newDuration( 0, 0, -1, 1 ).toString(), equalTo( "P0M0DT-0.999999999S" ) ); assertThat( new InternalIsoDuration( Period.parse( "P356D" ) ).toString(), equalTo( "P0M356DT0S" ) ); assertThat( new InternalIsoDuration( Duration.parse( "PT45S" ) ).toString(), equalTo( "P0M0DT45S" ) ); - assertThat( new InternalIsoDuration( Period.parse( "P14D" ), Duration.parse( "PT16H12M" ) ).toString(), equalTo( "P0M14DT58320S" ) ); - assertThat( new InternalIsoDuration( Period.parse( "P5M1D" ), Duration.parse( "PT12H" ) ).toString(), equalTo( "P5M1DT43200S" ) ); - assertThat( new InternalIsoDuration( Period.parse( "P2W3D" ), Duration.parse( "PT2H0.111222333S" ) ).toString(), equalTo( "P0M17DT7200.111222333S" ) ); + assertThat( new InternalIsoDuration( 0, 14, Duration.parse( "PT16H12M" ) ).toString(), equalTo( "P0M14DT58320S" ) ); + assertThat( new InternalIsoDuration( 5, 1, Duration.parse( "PT12H" ) ).toString(), equalTo( "P5M1DT43200S" ) ); + assertThat( new InternalIsoDuration( 0, 17, Duration.parse( "PT2H0.111222333S" ) ).toString(), equalTo( "P0M17DT7200.111222333S" ) ); + + assertThat( newDuration( 42, 42, 42, 0 ).toString(), equalTo( "P42M42DT42S" ) ); + assertThat( newDuration( 42, 42, -42, 0 ).toString(), equalTo( "P42M42DT-42S" ) ); + + assertThat( newDuration( 42, 42, 0, 5 ).toString(), equalTo( "P42M42DT0.000000005S" ) ); + assertThat( newDuration( 42, 42, 0, -5 ).toString(), equalTo( "P42M42DT-0.000000005S" ) ); + + assertThat( newDuration( 42, 42, 1, 5 ).toString(), equalTo( "P42M42DT1.000000005S" ) ); + assertThat( newDuration( 42, 42, -1, 5 ).toString(), equalTo( "P42M42DT-0.999999995S" ) ); + assertThat( newDuration( 42, 42, 1, -5 ).toString(), equalTo( "P42M42DT0.999999995S" ) ); + assertThat( newDuration( 42, 42, -1, -5 ).toString(), equalTo( "P42M42DT-1.000000005S" ) ); + + assertThat( newDuration( 42, 42, 28, 9 ).toString(), equalTo( "P42M42DT28.000000009S" ) ); + assertThat( newDuration( 42, 42, -28, 9 ).toString(), equalTo( "P42M42DT-27.999999991S" ) ); + assertThat( newDuration( 42, 42, 28, -9 ).toString(), equalTo( "P42M42DT27.999999991S" ) ); + assertThat( newDuration( 42, 42, -28, -9 ).toString(), equalTo( "P42M42DT-28.000000009S" ) ); } private static IsoDuration newDuration( long months, long days, long seconds, int 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 baadcd528b..16bbafcc40 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 @@ -268,6 +268,16 @@ public void shouldFormatDurationToString() testDurationToString( -1, 999999999, "P0M0DT-0.000000001S" ); testDurationToString( -78036, -143000000, "P0M0DT-78036.143000000S" ); + + testDurationToString( 0, 1_000_000_000, "P0M0DT1S" ); + testDurationToString( 0, -1_000_000_000, "P0M0DT-1S" ); + testDurationToString( 0, 1_000_000_007, "P0M0DT1.000000007S" ); + testDurationToString( 0, -1_000_000_007, "P0M0DT-1.000000007S" ); + + testDurationToString( 40, 2_123_456_789, "P0M0DT42.123456789S" ); + testDurationToString( -40, 2_123_456_789, "P0M0DT-37.876543211S" ); + testDurationToString( 40, -2_123_456_789, "P0M0DT37.876543211S" ); + testDurationToString( -40, -2_123_456_789, "P0M0DT-42.123456789S" ); } private void testSendAndReceiveRandomValues( Supplier supplier, Function converter )