Skip to content

Instantly share code, notes, and snippets.

@anth12
Last active June 19, 2018 12:58
Show Gist options
  • Save anth12/401a0413a212025486daeb543ed66722 to your computer and use it in GitHub Desktop.
Save anth12/401a0413a212025486daeb543ed66722 to your computer and use it in GitHub Desktop.
/*
MIT License
Copyright (c) 2018 Anthony Halliday
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace PixelForge
{
[DebuggerDisplay("{DebuggerDisplay}")]
public struct DateRange
{
public DateRange(DateTime start, DateTime end)
{
if (start > end)
throw new ArgumentException("start must be less the end date");
Start = start;
End = end;
}
public DateTime Start { get; set; }
public DateTime End { get; set; }
public static DateRange Create(DateTime start, DateTime end)
{
return new DateRange(
start,
end
);
}
public static DateRange Create(string start, string end)
{
return new DateRange(
DateTime.Parse(start),
DateTime.Parse(end)
);
}
public bool Intersects(DateRange other)
{
DateRange lower, higher;
if (this.Start < other.Start)
{
lower = this;
higher = other;
}
else
{
lower = other;
higher = this;
}
if (lower.End < higher.Start)
return false;
if (lower.Start > higher.End)
return false;
return true;
}
public IEnumerable<DateTime> GetDates()
{
var date = Start.Date;
while (date < End)
{
yield return date;
date = date.AddDays(1);
}
}
[ExcludeFromCodeCoverage]
private string DebuggerDisplay => $"Range: {ToString()}";
public override string ToString()
{
return $"{Start:d} - {End:d}";
}
}
public static class DateRangeCollectionExtensions
{
public static IEnumerable<DateRange> MissingRanges(this IList<DateRange> ranges)
{
ranges = ranges.OrderBy(s => s.Start).ThenBy(s => s.End).ToList();
var current = ranges.First();
for (int i = 0; i < ranges.Count(); i++)
{
var next = ranges[i];
if (current.End < next.Start)
yield return DateRange.Create(current.End, next.Start);
if (current.End < next.End)
current = next;
}
}
}
}
/*
MIT License
Copyright (c) 2018 Anthony Halliday
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
namespace PixelForge
{
public class DateRangeTests
{
#region MissingRanges Test(s)
[Test]
public void Ctor_GivenInvalidParameters_ThenExceptionThrown()
{
Assert.Throws<ArgumentException>(()=>
new DateRange(DateTime.Today, DateTime.Today.AddDays(-1)));
}
#endregion
#region MissingRanges Test(s)
[Test, TestCaseSource(nameof(SmallToNoRangeSource))]
public void MissingRanges_GivenSmallToNoRange_ThenExpectedRangesReturned(DateRange[] ranges, DateRange[] expectedMissingRanges)
{
var missingRanges = ranges.MissingRanges();
missingRanges.Should().BeEquivalentTo(expectedMissingRanges);
}
private static IEnumerable<object[]> SmallToNoRangeSource()
{
yield return new[]
{
new []
{
DateRange.Create("2018-03-01", "2018-05-01"),
DateRange.Create("2018-05-01", "2018-09-30"),
DateRange.Create("2018-09-30", "2019-04-30")
},
new DateRange[]
{
}
};
// Regular short missing ranges
yield return new[]
{
new []
{
DateRange.Create("2018-03-01", "2018-04-30"),
DateRange.Create("2018-05-05", "2018-10-30"),
DateRange.Create("2018-11-01", "2019-04-30")
},
new []
{
DateRange.Create("2018-04-30", "2018-05-05"),
DateRange.Create("2018-10-30", "2018-11-01")
}
};
// Unordered with long spanning range
yield return new[]
{
new []
{
DateRange.Create("2018-05-05", "2018-10-30"),
DateRange.Create("2018-04-01", "2018-05-02"),
DateRange.Create("2018-03-10", "2018-12-30"),
DateRange.Create("2018-03-01", "2018-04-30"),
DateRange.Create("2019-01-01", "2019-01-30"),
},
new []
{
DateRange.Create("2018-12-30", "2019-01-01")
}
};
}
#endregion
#region GetDates Test(s)
[Test]
public void GetDates_GivenOnlyScenario_ThenCorrectDatesReturned()
{
var range = DateRange.Create("2018-12-28", "2019-01-02");
var datesInRange = range.GetDates();
datesInRange.Count().Should().Be(5);
datesInRange.Should().Contain(new DateTime(2018, 12, 28));
datesInRange.Should().Contain(new DateTime(2018, 12, 29));
datesInRange.Should().Contain(new DateTime(2018, 12, 30));
datesInRange.Should().Contain(new DateTime(2018, 12, 31));
datesInRange.Should().Contain(new DateTime(2019, 1, 1));
}
#endregion
#region Intersects Test(s)
[Test, TestCaseSource(nameof(IntersectsSource))]
public void Intersects_GivenIntersectingRanges_ThenReturnsTrue(bool expectedResult, DateRange rangeA, DateRange rangeB)
{
var actualResult = rangeA.Intersects(rangeB);
Assert.AreEqual(expectedResult, actualResult, $"{rangeA} - {rangeB}");
}
private static IEnumerable<object[]> IntersectsSource()
{
// Intersecting ranges
yield return new object[]
{
true,
DateRange.Create("2018-03-01", "2018-05-01"),
DateRange.Create("2018-05-01", "2018-09-30")
};
yield return new object[]
{
true,
DateRange.Create("2018-03-01", "2018-03-10"),
DateRange.Create("2018-03-09", "2018-03-10")
};
yield return new object[]
{
true,
DateRange.Create("2019-01-02", "2019-03-10"),
DateRange.Create("2018-12-09", "2019-01-10")
};
yield return new object[]
{
true,
DateRange.Create("2019-01-02", "2019-01-09"),
DateRange.Create("2018-12-09", "2019-01-10")
};
// Not intersecting ranges
yield return new object[]
{
false,
DateRange.Create("2018-03-01", "2018-05-01"),
DateRange.Create("2018-05-02", "2018-09-30")
};
yield return new object[]
{
false,
DateRange.Create("2018-05-01", "2018-05-10"),
DateRange.Create("2018-03-11", "2018-03-15")
};
yield return new object[]
{
false,
DateRange.Create("2019-01-02", "2019-03-10"),
DateRange.Create("2018-12-09", "2019-01-01")
};
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment