Reputation: 6702
Simple question. I have an ordered collection of dates. They are UK dates btw
01/01/10
01/02/10
01/03/10
01/04/10
02/04/10
03/04/10
04/04/10
And I want to convert this into a collection of date ranges
01/01/10 -> 01/01/10
01/02/10 -> 01/02/10
01/03/10 -> 01/03/10
01/04/10 -> 04/04/10
Just to clarify, I'm trying to convert any consecutive dates into a range. so the first 3 dates are stand alone and the last 4 get converted into a range 1st of April to 4th of April.
Now I can do this using loops but it's not very elegant. Does any one have any solutions out there that are?
Thanks
Upvotes: 1
Views: 2697
Reputation: 1020
var stringDates = new List<string> {"01/09/10", "31/08/10", "01/01/10"};
var dates = stringDates.ConvertAll(DateTime.Parse);
dates.Sort();
var lastDateInSequence = new DateTime();
var firstDateInSequence = new DateTime();
foreach (var range in dates.GroupBy(
d => { if ((d - lastDateInSequence).TotalDays != 1)
firstDateInSequence = d;
lastDateInSequence = d;
return firstDateInSequence;
}))
{
var sb = new StringBuilder();
sb.Append(range.First().ToShortDateString());
sb.Append(" => ");
sb.Append(range.Last().ToShortDateString());
Console.WriteLine(sb.ToString());
}
Upvotes: 0
Reputation: 8972
Given that you want to determine ranges of consecutive date ranges, I think your only option is, as you say a loop. You can do it in a single pass though, and put it in an extension method so it'll operate on any IList<DateTime>
, for example:
// purely an example, chances are this will have actual, y'know logic in live
public class DateRange
{
private List<DateTime> dates = new List<DateTime>();
public void Add(DateTime date)
{
this.dates.Add(date);
}
public IEnumerable<DateTime> Dates
{
get { return this.dates; }
}
}
public static IEnumerable<DateRange> GetRanges(this IList<DateTime> dates)
{
List<DateRange> ranges = new List<DateRange>();
DateRange currentRange = null;
// this presumes a list of dates ordered by day, if not then the list will need sorting first
for( int i = 0; i < dates.Count; ++i )
{
var currentDate = dates[i];
if( i == 0 || dates[i - 1] != currentDate.AddDays(-1))
{
// it's either the first date or the current date isn't consecutive to the previous so a new range is needed
currentRange = new DateRange();
ranges.Add(currentRange);
}
currentRange.Add(currentDate);
}
return ranges;
}
You could also make it even more generic by passing in an IEnumerable<DateTime>
:
public static IEnumerable<DateRange> GetRanges(this IEnumerable<DateTime> dates)
{
List<DateRange> ranges = new List<DateRange>();
DateRange currentRange = null;
DateTime? previousDate = null;
// this presumes a list of dates ordered by day, if not then the list will need sorting first
foreach( var currentDate in dates )
{
if( previousDate == null || previousDate.Value != currentDate.AddDays(-1) )
{
// it's either the first date or the current date isn't consecutive to the previous so a new range is needed
currentRange = new DateRange();
ranges.Add(currentRange);
}
currentRange.Add(currentDate);
previousDate = currentDate;
}
return ranges;
}
Upvotes: 5
Reputation: 17367
dates.Aggregate(new List<DateRange>(), (acc, dt) =>
{
if (acc.Count > 0 && acc.Last().d2 == dt.AddDays(-1))
acc[acc.Count - 1].d2 = dt;
else
acc.Add(new DateRange(dt, dt));
return acc;
}
);
where DateRange
is a class like this:
class DateRange
{
public DateTime d1, d2;
public DateRange(DateTime d1, DateTime d2)
{
this.d1 = d1;
this.d2 = d2;
}
}
Upvotes: 0