Question

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 :)

Était-ce utile?

La solution

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

Autres conseils

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).

You don't need a so big code to calculate the days between two dates; you can rely on a much simpler/more accurate approach. For example:

DateTime date1 = new DateTime(2013, 1, 1);
DateTime date2 = new DateTime(2015, 2, 2);

int totDays = Math.Abs(date1.Subtract(date2).Days);

Adapted to your specific situation:

int[] days = new int[Math.Abs(date1.Year - date2.Year) + 1];
int curYear = 0;
if (date1.Year != date2.Year)
{
    if(date1.Year > date2.Year)
    {
        DateTime temp = date1;
        date1 = date2;
        date2 = temp;
    }
    int curYearNo = date1.Year - 1;
    curYear = -1;
    do
    {
        curYearNo = curYearNo + 1;
        curYear = curYear + 1;
        if (curYearNo < date2.Year)
        {
            days[curYear] = Math.Abs(new DateTime(curYearNo, 1, 1).Subtract(new DateTime(curYearNo, 12, 31)).Days) + 1; //Without +1 it would output 364/365 (because of not including both 1st January and 31st December)
        }
        else
        {
            days[curYear] = Math.Abs(new DateTime(curYearNo, 1, 1).Subtract(date2).Days);
        }
    } while (curYearNo < date2.Year);
}
else
{
    days[curYear] = Math.Abs(date1.Subtract(date2).Days);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top