So I have all these travel dates, from-date and to-date. I want to add up all the travel days, and sort them by year. However if one travel for period which spans two years, my code will reach the wrong sum :-(
Given
From date To date Number of days
01.01.2001 01.02.2001 32
01.01.2002 01.02.2002 32
01.05.2002 01.08.2002 93
20.12.2002 01.03.2003 72
01.02.2009 01.02.2010 366
01.01.2013 02.02.2015 763
Sum 1358
My code produces this. However, it makes a mistake:
Year Total days
2001 32
2002 137
2003 60
2009 334
2010 32
2013 365
2014 398 <---- here is a case where my code is wrong
Sum 1358
Code
var dates = new Dictionary<int, int>();
var stays = GetStays();
var returnString = "Year, Total days<br><br>";
foreach (var stay in stays)
{
var totalTravelDays = stay.ToDate.Value.AddDays(1) - stay.FromDate;
var currentYear = stay.FromDate.Value.Year;
var nextYear = stay.FromDate.Value.AddYears(1).Year;
var nextYearDate = new DateTime(stay.FromDate.Value.Year, 1, 1).AddYears(1);
var daysInThisYear = new TimeSpan?();
var daysInNextYear = new TimeSpan?();
if (stay.FromDate.Value.Year != stay.ToDate.Value.Year)
{
daysInThisYear = nextYearDate - stay.FromDate;
daysInNextYear = totalTravelDays - daysInThisYear;
}
else
{
daysInThisYear = totalTravelDays;
daysInNextYear = new TimeSpan(0);
}
if (dates.ContainsKey(currentYear))
dates[currentYear] += daysInThisYear.Value.Days;
else
dates[currentYear] = daysInThisYear.Value.Days;
if (dates.ContainsKey(nextYear))
dates[nextYear] += daysInNextYear.Value.Days;
else
dates[nextYear] = daysInNextYear.Value.Days;
}
Help appreciated :)
Assuming that var stays = List<Stay>();
, you may try this:
var days = stays.SelectMany(s =>
Enumerable
.Range(0, (s.ToDate - s.FromDate).Days + 1)
.Select(d => s.FromDate.AddDays(d)))
.GroupBy(d => d.Year)
.Select(s => new { Year = s.Key, TotalDays = s.Count() })
.ToList();
days.ForEach(d =>
{
Console.WriteLine("{0} {1}", d.Year, d.TotalDays);
});
The output of the above is:
2001 32
2002 137
2003 60
2009 334
2010 32
2013 365
2014 365
2015 33
If you write a helper method to split date ranges into several ranges partitioned by year:
IEnumerable<Tuple<DateTime,DateTime>>
SplitDateRangeByYear(DateTime fromDate, DateTime toDate)
{
var start = fromDate;
for(var y = fromDate.Year; y < toDate.Year; ++y)
{
var nextYear = y + 1;
var nextYearStartDate = new DateTime(nextYear, 1, 1);
yield return Tuple.Create(start, nextYearStartDate);
start = nextYearStartDate;
}
yield return Tuple.Create(start, toDate);
}
Then you can write some handy Linq to do your bidding:
var yearlyTotals = stays
.SelectMany(s => SplitDateRangeByYear(s.FromDate, s.ToDate))
.GroupBy(x => x.Item1.Year)
.Select(g => new{
Year = g.Key,
NumDays= g.Sum(x => (x.Item2 - x.Item1).TotalDays)});
This is a more general solution that you requested because it will properly deal with sub-day TimeSpan components (i.e. your ranges include times of day).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With