Skip to content

Commit 1bda475

Browse files
committed
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.
1 parent 112ff41 commit 1bda475

File tree

2 files changed

+70
-19
lines changed

2 files changed

+70
-19
lines changed

driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
public class InternalIsoDuration implements IsoDuration
3838
{
39+
private static final long NANOS_PER_SECOND = 1_000_000_000;
3940
private static final List<TemporalUnit> SUPPORTED_UNITS = unmodifiableList( asList( MONTHS, DAYS, SECONDS, NANOS ) );
4041

4142
private final long months;
@@ -45,25 +46,25 @@ public class InternalIsoDuration implements IsoDuration
4546

4647
public InternalIsoDuration( Period period )
4748
{
48-
this( period.toTotalMonths(), period.getDays(), 0, 0 );
49+
this( period.toTotalMonths(), period.getDays(), Duration.ZERO );
4950
}
5051

5152
public InternalIsoDuration( Duration duration )
5253
{
53-
this( 0, 0, duration.getSeconds(), duration.getNano() );
54+
this( 0, 0, duration );
5455
}
5556

5657
public InternalIsoDuration( long months, long days, long seconds, int nanoseconds )
5758
{
58-
this.months = months;
59-
this.days = days;
60-
this.seconds = seconds;
61-
this.nanoseconds = nanoseconds;
59+
this( months, days, Duration.ofSeconds( seconds, nanoseconds ) );
6260
}
6361

64-
public InternalIsoDuration( Period period, Duration duration )
62+
InternalIsoDuration( long months, long days, Duration duration )
6563
{
66-
this( period.toTotalMonths(), period.getDays(), duration.getSeconds(), duration.getNano() );
64+
this.months = months;
65+
this.days = days;
66+
this.seconds = duration.getSeconds(); // normalized value of seconds
67+
this.nanoseconds = duration.getNano(); // normalized value of nanoseconds in [0, 999_999_999]
6768
}
6869

6970
@Override
@@ -192,8 +193,41 @@ public int hashCode()
192193
@Override
193194
public String toString()
194195
{
195-
String secondsSign = seconds < 0 || nanoseconds < 0 ? "-" : "";
196-
String nanosecondsString = nanoseconds == 0 ? "" : String.format( ".%09d", Math.abs( nanoseconds ) );
197-
return String.format( "P%sM%sDT%s%s%sS", months, days, secondsSign, Math.abs( seconds ), nanosecondsString );
196+
StringBuilder sb = new StringBuilder();
197+
sb.append( 'P' );
198+
sb.append( months ).append( 'M' );
199+
sb.append( days ).append( 'D' );
200+
sb.append( 'T' );
201+
if ( seconds < 0 && nanoseconds > 0 )
202+
{
203+
if ( seconds == -1 )
204+
{
205+
sb.append( "-0" );
206+
}
207+
else
208+
{
209+
sb.append( seconds + 1 );
210+
}
211+
}
212+
else
213+
{
214+
sb.append( seconds );
215+
}
216+
if ( nanoseconds > 0 )
217+
{
218+
int pos = sb.length();
219+
// append nanoseconds as a 10-digit string with leading '1' that is later replaced by a '.'
220+
if ( seconds < 0 )
221+
{
222+
sb.append( 2 * NANOS_PER_SECOND - nanoseconds );
223+
}
224+
else
225+
{
226+
sb.append( NANOS_PER_SECOND + nanoseconds );
227+
}
228+
sb.setCharAt( pos, '.' ); // replace '1' with '.'
229+
}
230+
sb.append( 'S' );
231+
return sb.toString();
198232
}
199233
}

driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,36 @@ public void shouldCreateFromDuration()
153153
}
154154

155155
@Test
156-
public void toStringShouldPrintInIsoStandardFormat() throws Throwable
156+
public void toStringShouldPrintInIsoStandardFormat()
157157
{
158-
assertThat( new InternalIsoDuration( 0, 0, 0, 0 ).toString(), equalTo( "P0M0DT0S" ) );
159-
assertThat( new InternalIsoDuration( 2, 45, 59, 11 ).toString(), equalTo( "P2M45DT59.000000011S" ) );
160-
assertThat( new InternalIsoDuration( 4, -101, 1, 999 ).toString(), equalTo( "P4M-101DT1.000000999S" ) );
161-
assertThat( new InternalIsoDuration( -1, 12, -19, 1 ).toString(), equalTo( "P-1M12DT-19.000000001S" ) );
158+
assertThat( newDuration( 0, 0, 0, 0 ).toString(), equalTo( "P0M0DT0S" ) );
159+
assertThat( newDuration( 2, 45, 59, 11 ).toString(), equalTo( "P2M45DT59.000000011S" ) );
160+
assertThat( newDuration( 4, -101, 1, 999 ).toString(), equalTo( "P4M-101DT1.000000999S" ) );
161+
assertThat( newDuration( -1, 12, -19, 1 ).toString(), equalTo( "P-1M12DT-18.999999999S" ) );
162+
assertThat( newDuration( 0, 0, -1, 1 ).toString(), equalTo( "P0M0DT-0.999999999S" ) );
162163

163164
assertThat( new InternalIsoDuration( Period.parse( "P356D" ) ).toString(), equalTo( "P0M356DT0S" ) );
164165
assertThat( new InternalIsoDuration( Duration.parse( "PT45S" ) ).toString(), equalTo( "P0M0DT45S" ) );
165166

166-
assertThat( new InternalIsoDuration( Period.parse( "P14D" ), Duration.parse( "PT16H12M" ) ).toString(), equalTo( "P0M14DT58320S" ) );
167-
assertThat( new InternalIsoDuration( Period.parse( "P5M1D" ), Duration.parse( "PT12H" ) ).toString(), equalTo( "P5M1DT43200S" ) );
168-
assertThat( new InternalIsoDuration( Period.parse( "P2W3D" ), Duration.parse( "PT2H0.111222333S" ) ).toString(), equalTo( "P0M17DT7200.111222333S" ) );
167+
assertThat( new InternalIsoDuration( 0, 14, Duration.parse( "PT16H12M" ) ).toString(), equalTo( "P0M14DT58320S" ) );
168+
assertThat( new InternalIsoDuration( 5, 1, Duration.parse( "PT12H" ) ).toString(), equalTo( "P5M1DT43200S" ) );
169+
assertThat( new InternalIsoDuration( 0, 17, Duration.parse( "PT2H0.111222333S" ) ).toString(), equalTo( "P0M17DT7200.111222333S" ) );
170+
171+
assertThat( newDuration( 42, 42, 42, 0 ).toString(), equalTo( "P42M42DT42S" ) );
172+
assertThat( newDuration( 42, 42, -42, 0 ).toString(), equalTo( "P42M42DT-42S" ) );
173+
174+
assertThat( newDuration( 42, 42, 0, 5 ).toString(), equalTo( "P42M42DT0.000000005S" ) );
175+
assertThat( newDuration( 42, 42, 0, -5 ).toString(), equalTo( "P42M42DT-0.000000005S" ) );
176+
177+
assertThat( newDuration( 42, 42, 1, 5 ).toString(), equalTo( "P42M42DT1.000000005S" ) );
178+
assertThat( newDuration( 42, 42, -1, 5 ).toString(), equalTo( "P42M42DT-0.999999995S" ) );
179+
assertThat( newDuration( 42, 42, 1, -5 ).toString(), equalTo( "P42M42DT0.999999995S" ) );
180+
assertThat( newDuration( 42, 42, -1, -5 ).toString(), equalTo( "P42M42DT-1.000000005S" ) );
181+
182+
assertThat( newDuration( 42, 42, 28, 9 ).toString(), equalTo( "P42M42DT28.000000009S" ) );
183+
assertThat( newDuration( 42, 42, -28, 9 ).toString(), equalTo( "P42M42DT-27.999999991S" ) );
184+
assertThat( newDuration( 42, 42, 28, -9 ).toString(), equalTo( "P42M42DT27.999999991S" ) );
185+
assertThat( newDuration( 42, 42, -28, -9 ).toString(), equalTo( "P42M42DT-28.000000009S" ) );
169186
}
170187

171188
private static IsoDuration newDuration( long months, long days, long seconds, int nanoseconds )

0 commit comments

Comments
 (0)