Skip to content

Commit b580b1d

Browse files
Unify duration string representation and add tests (#885)
1 parent 6099e0c commit b580b1d

File tree

4 files changed

+73
-44
lines changed

4 files changed

+73
-44
lines changed

Neo4j.Driver/Neo4j.Driver.Tests/Types/DurationTests.cs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Collections;
18+
using System.Globalization;
1819
using FluentAssertions;
1920
using Xunit;
2021

@@ -67,18 +68,17 @@ public void ShouldCreateDurationWithDaysSecondsAndNanoseconds()
6768
}
6869

6970
[Theory]
70-
[InlineData(15, 32, 785, 789215800, "P15M32DT785.789215800S")]
71-
[InlineData(0, 32, 785, 789215800, "P0M32DT785.789215800S")]
72-
[InlineData(0, 0, 785, 789215800, "P0M0DT785.789215800S")]
73-
[InlineData(0, 0, 0, 789215800, "P0M0DT0.789215800S")]
74-
[InlineData(0, 0, -1, 0, "P0M0DT-1S")]
75-
[InlineData(0, 0, 0, 999999999, "P0M0DT0.999999999S")]
76-
[InlineData(0, 0, -1, 5, "P0M0DT-0.999999995S")]
77-
[InlineData(0, 0, -1, 999999999, "P0M0DT-0.000000001S")]
78-
[InlineData(500, 0, 0, 0, "P500M0DT0S")]
79-
[InlineData(0, 0, 0, 5, "P0M0DT0.000000005S")]
80-
[InlineData(0, 0, -500, 1, "P0M0DT-499.999999999S")]
81-
[InlineData(0, 0, -500, 0, "P0M0DT-500S")]
71+
[InlineData(15, 32, 785, 789215800, "P1Y3M32DT13M5.789215800S")] // 15M -> 1Y3M, 785S -> 13M5S
72+
[InlineData(0, 32, 785, 789215800, "P32DT13M5.789215800S")] // 785S -> 13M5S
73+
[InlineData(0, 0, 785, 789215800, "PT13M5.789215800S")] // 785S -> 13M5S
74+
[InlineData(0, 0, 0, 789215800, "PT0.789215800S")]
75+
[InlineData(0, 0, -1, 0, "PT-1S")]
76+
[InlineData(0, 0, 0, 999999999, "PT0.999999999S")]
77+
[InlineData(0, 0, -1, 5, "PT-0.999999995S")]
78+
[InlineData(0, 0, -1, 999999999, "PT-0.000000001S")]
79+
[InlineData(0, 0, 0, 5, "PT0.000000005S")]
80+
[InlineData(0, 0, -500, 1, "PT-8M19.999999999S")] // -500S -> -8M20S, then +1NS
81+
[InlineData(0, 0, -500, 0, "PT-8M20S")] // -500S -> -8M20S
8282
[InlineData(-10, 5, -2, 500, "P-10M5DT-1.999999500S")]
8383
[InlineData(-10, -5, -2, 500, "P-10M-5DT-1.999999500S")]
8484
public void ShouldGenerateCorrectString(int months, int days, int seconds, int nanoseconds, string expected)
@@ -233,11 +233,13 @@ public void ShouldReportSmallerOnCompareToAbsolute()
233233
public void ShouldBeConvertableToString()
234234
{
235235
var duration = new Duration(12, 15, 59, 660000999);
236-
var durationStr1 = Convert.ToString(duration);
236+
var durationStr1 = Convert.ToString(duration, CultureInfo.InvariantCulture);
237237
var durationStr2 = Convert.ChangeType(duration, typeof(string));
238238

239-
durationStr1.Should().Be("P12M15DT59.660000999S");
240-
durationStr2.Should().Be("P12M15DT59.660000999S");
239+
const string expected = "P1Y15DT59.660000999S";
240+
241+
durationStr1.Should().Be(expected);
242+
durationStr2.Should().Be(expected);
241243
}
242244

243245
[Fact]

Neo4j.Driver/Neo4j.Driver/Internal/Helpers/TemporalHelpers.cs

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ internal static class TemporalHelpers
4242
public const long NanosPerSecond = 1_000_000_000;
4343
public const long NanosPerDay = NanosPerHour * HoursPerDay;
4444

45+
private const int MonthsPerYear = 12;
4546
private const int HoursPerDay = 24;
4647
private const int MinutesPerHour = 60;
4748
private const int SecondsPerMinute = 60;
4849
private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour;
4950
private const int SecondsPerDay = SecondsPerHour * HoursPerDay;
50-
private const long NanosPerMinute = NanosPerSecond * SecondsPerMinute;
51+
private const long NanosPerMinute = (long)NanosPerSecond * SecondsPerMinute;
5152
private const long NanosPerHour = NanosPerMinute * MinutesPerHour;
5253

5354
private const long Days0000To1970 = DaysPerCycle * 5L - (30L * 365L + 7L);
@@ -270,29 +271,61 @@ public static void AssertNoTruncation(TimeSpan offset, string target)
270271

271272
public static string ToIsoDurationString(long months, long days, long seconds, int nanoseconds)
272273
{
273-
var timePart = string.Empty;
274-
274+
// carry the excess up each level
275+
seconds += nanoseconds / NanosPerSecond;
276+
nanoseconds %= (int)NanosPerSecond;
277+
var minutes = seconds / SecondsPerMinute;
278+
seconds %= SecondsPerMinute;
279+
var hours = minutes / MinutesPerHour;
280+
minutes %= MinutesPerHour;
281+
days += hours / HoursPerDay;
282+
hours %= HoursPerDay;
283+
var years = months / MonthsPerYear;
284+
months %= MonthsPerYear;
285+
286+
// do negative second/nanosecond handling
287+
var negativeTime = hours < 0 || minutes < 0 || seconds < 0 || nanoseconds < 0;
288+
var timeSign = negativeTime ? "-" : "";
275289
if (seconds < 0 && nanoseconds > 0)
276290
{
277-
seconds = seconds + 1;
291+
seconds++;
278292
nanoseconds = (int)NanosPerSecond - nanoseconds;
279-
280-
if (seconds == 0)
281-
{
282-
timePart = "-";
283-
}
284293
}
285-
286-
if (nanoseconds == 0)
294+
295+
hours = Math.Abs(hours);
296+
minutes = Math.Abs(minutes);
297+
seconds = Math.Abs(seconds);
298+
nanoseconds = Math.Abs(nanoseconds);
299+
300+
var dateComponent = years != 0 || months != 0 || days != 0
301+
? $"{IfNonZero(years, 'Y')}{IfNonZero(months, 'M')}{IfNonZero(days, 'D')}"
302+
: "";
303+
304+
string timeComponent = hours != 0 || minutes != 0 || seconds != 0 || nanoseconds != 0
305+
? $"T{timeSign}{IfNonZero(hours, 'H')}{IfNonZero(minutes, 'M')}{Seconds()}"
306+
: "";
307+
308+
// if both empty, return P0D
309+
if (dateComponent == "" && timeComponent == "")
287310
{
288-
timePart = $"{timePart}{seconds}";
289-
}
290-
else
311+
return "P0D";
312+
}
313+
314+
return $"P{dateComponent}{timeComponent}";
315+
316+
string IfNonZero(long amount, char identifier) => amount != 0 ? $"{amount}{identifier}" : "";
317+
318+
string Seconds()
291319
{
292-
timePart = $"{timePart}{seconds}.{nanoseconds:D9}";
293-
}
320+
if (seconds == 0 && nanoseconds == 0)
321+
{
322+
return "";
323+
}
294324

295-
return $"P{months}M{days}DT{timePart}S";
325+
var secstr = $"{seconds}";
326+
var nanosecstr = nanoseconds > 0 ? $".{nanoseconds:D9}" : "";
327+
return secstr + nanosecstr + "S";
328+
}
296329
}
297330

298331
public static string ToIsoDateString(int year, int month, int day)

Neo4j.Driver/Neo4j.Driver/Public/Types/Duration.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,12 @@ public override bool Equals(object obj)
215215
/// <returns>A 32-bit signed integer hash code.</returns>
216216
public override int GetHashCode()
217217
{
218-
unchecked
219-
{
220-
var hashCode = Months.GetHashCode();
221-
hashCode = (hashCode * 397) ^ Days.GetHashCode();
222-
hashCode = (hashCode * 397) ^ Seconds.GetHashCode();
223-
hashCode = (hashCode * 397) ^ Nanos;
224-
return hashCode;
225-
}
218+
return HashCode.Combine(Months, Days, Seconds, Nanos);
226219
}
227220

228-
/// <summary>Converts the value of the current <see cref="Duration"/> object to its equivalent string representation.</summary>
229-
/// <returns>String representation of this Point.</returns>
221+
/// <summary>Converts the value of the current <see cref="Duration"/> object to its equivalent string representation
222+
/// as an ISO 8601 duration string.</summary>
223+
/// <returns>ISO 8601 duration string representation of this Duration.</returns>
230224
public override string ToString()
231225
{
232226
return TemporalHelpers.ToIsoDurationString(Months, Days, Seconds, Nanos);

Neo4j.Driver/Neo4j.Driver/Public/Types/OffsetTime.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ public int CompareTo(OffsetTime other)
170170
return 1;
171171
}
172172

173-
var thisNanoOfDay = this.ToNanoOfDay() - OffsetSeconds * TemporalHelpers.NanosPerSecond;
174-
var otherNanoOfDay = other.ToNanoOfDay() - other.OffsetSeconds * TemporalHelpers.NanosPerSecond;
173+
var thisNanoOfDay = this.ToNanoOfDay() - (long)OffsetSeconds * TemporalHelpers.NanosPerSecond;
174+
var otherNanoOfDay = other.ToNanoOfDay() - (long)other.OffsetSeconds * TemporalHelpers.NanosPerSecond;
175175

176176
if (thisNanoOfDay < 0)
177177
{

0 commit comments

Comments
 (0)