从两组日期范围 C# 中查找间隔日期范围
Posted
技术标签:
【中文标题】从两组日期范围 C# 中查找间隔日期范围【英文标题】:Find Gap Date Ranges from two Set of Date Ranges C# 【发布时间】:2013-03-04 23:21:23 【问题描述】:我有基准日期范围和测试日期范围。我需要在基础和测试之间获取 GAP,这意味着缺少基础但不在测试中的日期范围。最好的方法是什么?
Base Date Ranges
1/1/2012 1/10/2012
1/11/2012 1/25/2012
Test Date Ranges
1/2/2012 1/7/2012
1/8/2012 1/9/2012
1/15/2012 1/30/2012
输出:
Missing Date Ranges that are in Base but not in Test
1/1/2012 1/2/2012
1/7/2012 1/8/2012
1/9/2012 1/10/2012
1/11/2012 1/15/2012
目前已经试过了
static void Main(string[] args)
List<DateRanges> aDateRanges = new List<DateRanges>();
List<DateRanges> bDateRanges = new List<DateRanges>();
aDateRanges.Add(new DateRanges(new DateTime(2012, 1, 1), new DateTime(2012, 1, 10)));
aDateRanges.Add(new DateRanges(new DateTime(2012, 1, 11), new DateTime(2012, 1, 25)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 2), new DateTime(2012, 1, 7)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 8), new DateTime(2012, 1, 9)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 15), new DateTime(2012, 1, 30)));
DisplayDateRanges(GetGaps(aDateRanges, bDateRanges, 0), "Final Gap Fill");
Console.WriteLine("Completed");
Console.Read();
public class DateRanges
public DateTime StartDate get; set;
public DateTime EndDate get; set;
public DateRanges(DateTime Start, DateTime End)
StartDate = Start;
EndDate = End;
public static void DisplayDateRanges(List<DateRanges> dateRanges, string title)
Console.WriteLine("************************0****************************", title);
Console.WriteLine(Environment.NewLine + "New Recursion");
foreach (DateRanges br in dateRanges)
Console.WriteLine("Start Date 0 End Date 1", br.StartDate, br.EndDate);
public static List<DateRanges> GetGaps(List<DateRanges> aDateRanges, List<DateRanges> bDateRanges, int recursionlevel)
List<DateRanges> gapFill = new List<DateRanges>();
List<DateRanges> gapFillTemp = new List<DateRanges>();
List<DateRanges> bDateRangesTemp = new List<DateRanges>(bDateRanges);
Console.WriteLine(Environment.NewLine + "+++++++++++++++++++++++++++++++++++++++++Recursion Level Id 0 +++++++++++++++++++++++++++++++++++++++++", recursionlevel);
DisplayDateRanges(aDateRanges, " A Date Ranges ");
DisplayDateRanges(bDateRanges, " B Date Ranges ");
foreach (DateRanges br in bDateRanges)
if (br.StartDate == br.EndDate)
return gapFill;
foreach (DateRanges ar in aDateRanges)
if (ar.StartDate == ar.EndDate)
return gapFill;
if (br.StartDate == ar.StartDate && br.EndDate == ar.EndDate)
continue;
else if (br.StartDate >= ar.StartDate && br.EndDate <= ar.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(ar.StartDate, br.StartDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
//gapFillTemp.Add(new DateRanges(ar.StartDate, br.StartDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(br.EndDate, ar.EndDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
// gapFillTemp.Add(new DateRanges(br.EndDate, ar.EndDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (br.StartDate < ar.EndDate && br.EndDate >= ar.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(ar.StartDate, br.StartDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
//gapFillTemp.Add(new DateRanges(ar.StartDate, br.StartDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (ar.StartDate >= br.StartDate && ar.StartDate < br.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(br.EndDate, ar.EndDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
// gapFillTemp.Add(new DateRanges(br.EndDate, ar.EndDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (ar.StartDate >= br.StartDate && ar.EndDate <= br.EndDate)
// AS----AE
// BS----------BE
//Do Nothing
else
if (AllowedToAdd(bDateRangesTemp, new DateRanges(ar.StartDate, ar.EndDate)))
bDateRangesTemp.Add(new DateRanges(ar.StartDate, ar.EndDate));
gapFill.Add(new DateRanges(ar.StartDate, ar.EndDate));
return gapFill;
static bool AllowedToAdd(List<DateRanges> bDateRanges, DateRanges newItem)
return !bDateRanges.Any(m =>
(m.StartDate < newItem.StartDate &&
newItem.StartDate < (m.EndDate))
||
(m.StartDate < (newItem.EndDate) &&
(newItem.EndDate) <= (m.EndDate))
||
(newItem.StartDate < m.StartDate &&
m.StartDate < (newItem.EndDate))
||
(newItem.StartDate < (m.EndDate) &&
(m.EndDate) <= (newItem.EndDate))
);
【问题讨论】:
请展示您已经拥有的代码,但感觉它不是“最好的”以及您想要改进代码的方向。否则可能很快就会关闭... 你试过什么?看起来很简单,只需先从范围生成日期,然后运行 Linq except 查询,然后从结果日期重新生成范围 纯SQL可以接受吗?我在您的标签中没有看到它... 是的 sql 也可以 这是一个令人愉快的挑战。 【参考方案1】:您可以使用Time Period Library for .NET 来计算差距:
// ----------------------------------------------------------------------
public void GapFinder()
// base periods
TimePeriodCollection basePeriods = new TimePeriodCollection();
basePeriods.Add( new TimeRange( new DateTime( 2012, 1, 1 ), new DateTime( 2012, 1, 10 ) ) );
basePeriods.Add( new TimeRange( new DateTime( 2012, 1, 11 ), new DateTime( 2012, 1, 25 ) ) );
ITimePeriodCollection combinedBasePeriods = new TimePeriodCombiner<TimeRange>().CombinePeriods( basePeriods );
// test periods
TimePeriodCollection testPeriods = new TimePeriodCollection();
testPeriods.Add( new TimeRange( new DateTime( 2012, 1, 2 ), new DateTime( 2012, 1, 7 ) ) );
testPeriods.Add( new TimeRange( new DateTime( 2012, 1, 8 ), new DateTime( 2012, 1, 9 ) ) );
testPeriods.Add( new TimeRange( new DateTime( 2012, 1, 15 ), new DateTime( 2012, 1, 30 ) ) );
ITimePeriodCollection combinedTestPeriods = new TimePeriodCombiner<TimeRange>().CombinePeriods( testPeriods );
// gaps
TimePeriodCollection gaps = new TimePeriodCollection();
foreach ( ITimePeriod basePeriod in combinedBasePeriods )
gaps.AddAll( new TimeGapCalculator<TimeRange>().GetGaps( combinedTestPeriods, basePeriod ) );
foreach ( ITimePeriod gap in gaps )
Console.WriteLine( "Gap: " + gap );
// GapFinder
【讨论】:
我该如何改变它以显示没有一天重叠的意思,例如上面的答案是 1/1/2012 1/1/2012 和 1/12/2012 1/14/2012。图书馆可以轻松做到这一点吗?【参考方案2】:扩展方法
public static class DateRangeEnumerable
public static IEnumerable<DateTime> GetDates(this IEnumerable<DateRange> source)
var sortedSource = source.OrderBy(r => r.From);
foreach (var range in sortedSource)
var d = range.From;
while (d < range.To)
yield return d;
d = d.AddDays(1);
public static IEnumerable<DateRange> GetRanges(this IEnumerable<DateTime> source)
var sortedSource = source.OrderBy(d => d);
var enumerator = sortedSource.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
DateTime from = enumerator.Current;
DateTime prev = from;
while (true)
while (true)
if (enumerator.MoveNext())
if (enumerator.Current == prev.AddDays(1))
prev = enumerator.Current;
else
break;
else
yield return new DateRange() From = from, To = prev.AddDays(1) ;
yield break;
yield return new DateRange() From = from, To = prev.AddDays(1) ;
from = enumerator.Current;
prev = enumerator.Current;
用法:
var missing = BaseRanges.GetDates().Except(TestRanges.GetDates());
var ranges = missing.GetRanges();
我已经在您的示例数据上对其进行了测试,并返回了它应该返回的结果。
两种扩展方法都是惰性的,一次读取/返回一个元素。
我很确定 GetRanges
方法可以变得更具可读性:)
【讨论】:
我对我的代码进行了测试,它有效。干得好,很酷,我们都想出了(稍微)不同的解决方案。【参考方案3】:以下代码满足问题的给定约束:
单元测试
static void Main(string[] args)
IEnumerable<DateRange> _base = new[] new DateRange() Start = DateTime.Parse("1/1/2012"), End = DateTime.Parse("1/10/2012"),
new DateRange() Start = DateTime.Parse("1/11/2012"), End = DateTime.Parse("1/25/2012") ;
IEnumerable<DateRange> _test = new[] new DateRange() Start = DateTime.Parse("1/2/2012"), End = DateTime.Parse("1/7/2012"),
new DateRange() Start = DateTime.Parse("1/8/2012"), End = DateTime.Parse("1/9/2012"),
new DateRange() Start = DateTime.Parse("1/15/2012"), End = DateTime.Parse("1/30/2012") ;
IEnumerable<DateRange> _theoreticalGaps = new[] new DateRange() Start = DateTime.Parse("1/1/2012"), End = DateTime.Parse("1/2/2012"),
new DateRange() Start = DateTime.Parse("1/7/2012"), End = DateTime.Parse("1/8/2012"),
new DateRange() Start = DateTime.Parse("1/9/2012"), End = DateTime.Parse("1/10/2012"),
new DateRange() Start = DateTime.Parse("1/11/2012"), End = DateTime.Parse("1/15/2012") ;
var gapsInTestNotInBase = FindGaps(_base, _test);
Debug.Assert(Enumerable.SequenceEqual(_theoreticalGaps, gapsInTestNotInBase));
FindGaps 方法
public static IEnumerable<DateRange> FindGaps(IEnumerable<DateRange> baseCollection, IEnumerable<DateRange> testCollection)
var allBaseDates = baseCollection.SelectMany(o => o.GetDiscreetDates())
.Distinct()
.OrderBy(o => o.Ticks);
var missingInTest = (from d in allBaseDates
let inRange = testCollection.Any(o => d.IsInRange(o))
where !inRange
select d).ToArray();
var gaps = missingInTest.Select(o => new DateRange() Start = o, End = o.AddDays(1) );
gaps = gaps.GroupConsecutive();
return gaps;
日期范围类
public class DateRange
protected bool Equals(DateRange other)
return Start.Equals(other.Start) && End.Equals(other.End);
public override int GetHashCode()
unchecked
return (Start.GetHashCode()*397) ^ End.GetHashCode();
public DateTime Start get; set;
public DateTime End get; set;
public IEnumerable<DateTime> GetDiscreetDates()
//Start is not allowed to equal end.
if (Start.Date == End.Date)
throw new ArgumentException("Start cannot equal end.");
var output = new List<DateTime>();
var current = Start.Date;
while (current < End.Date)
output.Add(current);
current = current.AddDays(1);
return output;
public override bool Equals(object obj)
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((DateRange) obj);
扩展方法
public static class Extensions
public static bool IsInRange(this DateTime testDate, DateRange range)
return range.Start <= testDate && range.End > testDate;
public static IEnumerable<DateRange> GroupConsecutive(this IEnumerable<DateRange> input)
var current = input.ToArray();
var nextIndex = 0;
//uses lookahead to figure out if gaps are consecutive.
for (int i = 0; i < current.Length - 1; i++)
//If the next range is consecutive to the current, skip;
if (!current[i].End.IsInRange(current[i + 1]))
yield return new DateRange()
Start = current[nextIndex].Start,
End = current[i].End
;
nextIndex = i + 1;
//If the last elements were consecutive, pull out the final item.
if (nextIndex != current.Length)
yield return new DateRange()
Start = current[nextIndex].Start,
End = current[current.Length - 1].End
;
【讨论】:
【参考方案4】:这也有效
static void Main(string[] args)
List<DateRanges> aDateRanges = new List<DateRanges>();
List<DateRanges> bDateRanges = new List<DateRanges>();
aDateRanges.Add(new DateRanges(new DateTime(2012, 1, 1), new DateTime(2012, 1, 10)));
aDateRanges.Add(new DateRanges(new DateTime(2012, 1, 11), new DateTime(2012, 1, 25)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 2), new DateTime(2012, 1, 7)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 8), new DateTime(2012, 1, 9)));
bDateRanges.Add(new DateRanges(new DateTime(2012, 1, 15), new DateTime(2012, 1, 30)));
DisplayDateRanges(GetGaps(aDateRanges, bDateRanges, 0), "Final Gap Fill");
Console.WriteLine("Completed");
Console.Read();
public class DateRanges
public DateTime StartDate get; set;
public DateTime EndDate get; set;
public DateRanges(DateTime Start, DateTime End)
StartDate = Start;
EndDate = End;
public static void DisplayDateRanges(List<DateRanges> dateRanges, string title)
Console.WriteLine("************************0****************************", title);
Console.WriteLine(Environment.NewLine + "New Recursion");
foreach (DateRanges br in dateRanges)
Console.WriteLine("Start Date 0 End Date 1", br.StartDate, br.EndDate);
public static List<DateRanges> GetGaps(List<DateRanges> aDateRanges, List<DateRanges> bDateRanges, int recursionlevel)
List<DateRanges> gapFill = new List<DateRanges>();
List<DateRanges> gapFillTemp = new List<DateRanges>();
List<DateRanges> bDateRangesTemp = new List<DateRanges>(bDateRanges);
Console.WriteLine(Environment.NewLine + "+++++++++++++++++++++++++++++++++++++++++Recursion Level Id 0 +++++++++++++++++++++++++++++++++++++++++", recursionlevel);
DisplayDateRanges(aDateRanges, " A Date Ranges ");
DisplayDateRanges(bDateRanges, " B Date Ranges ");
foreach (DateRanges br in bDateRanges)
if (br.StartDate == br.EndDate)
return gapFill;
foreach (DateRanges ar in aDateRanges)
if (ar.StartDate == ar.EndDate)
return gapFill;
if (br.StartDate == ar.StartDate && br.EndDate == ar.EndDate)
continue;
else if (br.StartDate >= ar.StartDate && br.EndDate <= ar.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(ar.StartDate, br.StartDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
//gapFillTemp.Add(new DateRanges(ar.StartDate, br.StartDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(br.EndDate, ar.EndDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
// gapFillTemp.Add(new DateRanges(br.EndDate, ar.EndDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (br.StartDate < ar.EndDate && br.EndDate >= ar.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(ar.StartDate, br.StartDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
//gapFillTemp.Add(new DateRanges(ar.StartDate, br.StartDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (ar.StartDate >= br.StartDate && ar.StartDate < br.EndDate)
gapFillTemp.AddRange(GetGaps(new List<DateRanges> new DateRanges(br.EndDate, ar.EndDate) , bDateRangesTemp, recursionlevel + 1));
if (gapFillTemp.Count == 0)
// gapFillTemp.Add(new DateRanges(br.EndDate, ar.EndDate));
bDateRangesTemp.AddRange(gapFillTemp);
gapFill.AddRange(gapFillTemp);
gapFillTemp.Clear();
else if (ar.StartDate >= br.StartDate && ar.EndDate <= br.EndDate)
// AS----AE
// BS----------BE
//Do Nothing
else
if (AllowedToAdd(bDateRangesTemp, new DateRanges(ar.StartDate, ar.EndDate)))
bDateRangesTemp.Add(new DateRanges(ar.StartDate, ar.EndDate));
gapFill.Add(new DateRanges(ar.StartDate, ar.EndDate));
return gapFill;
static bool AllowedToAdd(List<DateRanges> bDateRanges, DateRanges newItem)
return !bDateRanges.Any(m =>
(m.StartDate < newItem.StartDate &&
newItem.StartDate < (m.EndDate))
||
(m.StartDate < (newItem.EndDate) &&
(newItem.EndDate) <= (m.EndDate))
||
(newItem.StartDate < m.StartDate &&
m.StartDate < (newItem.EndDate))
||
(newItem.StartDate < (m.EndDate) &&
(m.EndDate) <= (newItem.EndDate))
);
【讨论】:
以上是关于从两组日期范围 C# 中查找间隔日期范围的主要内容,如果未能解决你的问题,请参考以下文章
pandas使用date_range函数以及datetime函数创建从当前时间为起始时间间隔为天的指定长度时间范围(periods)的时间序列数据设置normalize参数只保留日期信息