Skip to content

Commit

Permalink
Fix LocalInstant.SafeMinus near start/end of time
Browse files Browse the repository at this point in the history
  • Loading branch information
jskeet committed Aug 22, 2017
1 parent 41ae0cf commit 7b2fb0b
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 1 deletion.
53 changes: 53 additions & 0 deletions src/NodaTime.Test/InstantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -417,5 +417,58 @@ public void FromTicksSinceUnixEpoch_Range()
TestHelper.AssertValid(Instant.FromUnixTimeTicks, largestValid);
TestHelper.AssertOutOfRange(Instant.FromUnixTimeTicks, largestValid + 1);
}

[Test]
public void PlusOffset()
{
var localInstant = NodaConstants.UnixEpoch.Plus(Offset.FromHours(1));
Assert.AreEqual(Duration.FromHours(1), localInstant.TimeSinceLocalEpoch);
}

[Test]
public void SafePlus_NormalTime()
{
var localInstant = NodaConstants.UnixEpoch.SafePlus(Offset.FromHours(1));
Assert.AreEqual(Duration.FromHours(1), localInstant.TimeSinceLocalEpoch);
}

[Test]
[TestCase(null, 0, null)]
[TestCase(null, 1, null)]
[TestCase(null, -1, null)]
[TestCase(1, -1, 0)]
[TestCase(1, -2, null)]
[TestCase(2, 1, 3)]
public void SafePlus_NearStartOfTime(int? initialOffset, int offsetToAdd, int? finalOffset)
{
var start = initialOffset == null
? Instant.BeforeMinValue
: Instant.MinValue + Duration.FromHours(initialOffset.Value);
var expected = finalOffset == null
? LocalInstant.BeforeMinValue
: Instant.MinValue.Plus(Offset.FromHours(finalOffset.Value));
var actual = start.SafePlus(Offset.FromHours(offsetToAdd));
Assert.AreEqual(expected, actual);
}

// A null offset indicates "AfterMaxValue". Otherwise, MaxValue.Plus(offset)
[Test]
[TestCase(null, 0, null)]
[TestCase(null, 1, null)]
[TestCase(null, -1, null)]
[TestCase(-1, 1, 0)]
[TestCase(-1, 2, null)]
[TestCase(-2, -1, -3)]
public void SafePlus_NearEndOfTime(int? initialOffset, int offsetToAdd, int? finalOffset)
{
var start = initialOffset == null
? Instant.AfterMaxValue
: Instant.MaxValue + Duration.FromHours(initialOffset.Value);
var expected = finalOffset == null
? LocalInstant.AfterMaxValue
: Instant.MaxValue.Plus(Offset.FromHours(finalOffset.Value));
var actual = start.SafePlus(Offset.FromHours(offsetToAdd));
Assert.AreEqual(expected, actual);
}
}
}
48 changes: 48 additions & 0 deletions src/NodaTime.Test/LocalInstantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,53 @@ public void ToString_Extremes()
Assert.AreEqual(InstantPatternParser.BeforeMinValueText, LocalInstant.BeforeMinValue.ToString());
Assert.AreEqual(InstantPatternParser.AfterMaxValueText, LocalInstant.AfterMaxValue.ToString());
}

[Test]
public void SafeMinus_NormalTime()
{
var start = new LocalInstant(0, 0);
var end = start.SafeMinus(Offset.FromHours(1));
Assert.AreEqual(Duration.FromHours(-1), end.TimeSinceEpoch);
}

// A null offset indicates "BeforeMinValue". Otherwise, MinValue.Plus(offset)
[Test]
[TestCase(null, 0, null)]
[TestCase(null, 1, null)]
[TestCase(null, -1, null)]
[TestCase(1, 1, 0)]
[TestCase(1, 2, null)]
[TestCase(2, 1, 1)]
public void SafeMinus_NearStartOfTime(int? initialOffset, int offsetToSubtract, int? finalOffset)
{
var start = initialOffset == null
? LocalInstant.BeforeMinValue
: Instant.MinValue.Plus(Offset.FromHours(initialOffset.Value));
var expected = finalOffset == null
? Instant.BeforeMinValue
: Instant.MinValue + Duration.FromHours(finalOffset.Value);
var actual = start.SafeMinus(Offset.FromHours(offsetToSubtract));
Assert.AreEqual(expected, actual);
}

// A null offset indicates "AfterMaxValue". Otherwise, MaxValue.Plus(offset)
[Test]
[TestCase(null, 0, null)]
[TestCase(null, 1, null)]
[TestCase(null, -1, null)]
[TestCase(-1, -1, 0)]
[TestCase(-1, -2, null)]
[TestCase(-2, -1, -1)]
public void SafeMinus_NearEndOfTime(int? initialOffset, int offsetToSubtract, int? finalOffset)
{
var start = initialOffset == null
? LocalInstant.AfterMaxValue
: Instant.MaxValue.Plus(Offset.FromHours(initialOffset.Value));
var expected = finalOffset == null
? Instant.AfterMaxValue
: Instant.MaxValue + Duration.FromHours(finalOffset.Value);
var actual = start.SafeMinus(Offset.FromHours(offsetToSubtract));
Assert.AreEqual(expected, actual);
}
}
}
2 changes: 2 additions & 0 deletions src/NodaTime/Instant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ int IComparable.CompareTo(object obj)

/// <summary>
/// Adds the given offset to this instant, to return a <see cref="LocalInstant" />.
/// A positive offset indicates that the local instant represents a "later local time" than the UTC
/// representation of this instant.
/// </summary>
/// <remarks>
/// This was previously an operator+ implementation, but operators can't be internal.
Expand Down
2 changes: 1 addition & 1 deletion src/NodaTime/LocalInstant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ internal Instant SafeMinus(Offset offset)
return Instant.AfterMaxValue;
}
// Okay, do the arithmetic as a Duration, then check the result for overflow, effectively.
var asDuration = duration.PlusSmallNanoseconds(offset.Nanoseconds);
var asDuration = duration.MinusSmallNanoseconds(offset.Nanoseconds);
if (asDuration.FloorDays < Instant.MinDays)
{
return Instant.BeforeMinValue;
Expand Down

0 comments on commit 7b2fb0b

Please sign in to comment.