Search code examples
c#.netdateperiodnodatime

Noda Time: Period.Between returning incorrect value


I have a trouble with NodaTime lib. My goal: compute Year/Month/Date between two dates. So, here is my test example:

    private static void Main()
    {
        var list = new List<Tuple<DateTime, DateTime>>
        {
            new Tuple<DateTime, DateTime>(new DateTime(1980, 1, 1), new DateTime(1983, 12, 31)),
            new Tuple<DateTime, DateTime>(new DateTime(2009, 1, 1), new DateTime(2015, 01, 23))
        };
        var totalPeriod = Period.Zero;
        foreach (var tuple in list)
        {
            var dateFrom = tuple.Item1;
            var dateTo = tuple.Item2;
            var ld1 = new LocalDate(dateFrom.Year, dateFrom.Month, dateFrom.Day);
            var ld2 = new LocalDate(dateTo.Year, dateTo.Month, dateTo.Day);
            var period = Period.Between(ld1, ld2, PeriodUnits.YearMonthDay);
            totalPeriod += period;
        }
        Console.WriteLine("Years: {0}, Months: {1}, Days: {2}", 
            totalPeriod.Years, 
            totalPeriod.Months,
            totalPeriod.Days);
        Console.Read();
    }

The output is: Years: 9, Months: 11, Days: 52

It's wrong for me. I want to get, for example, the next output (Of course, the output depends on number of days in month, assuming that there are 31 days in our month): Years: 10, Months: 0, Days: 21

So, I want that days was rounded to years and month. How I can get this?

The answer: Using Matt's answer I created the next solution:

 foreach (var tuple in list)
        {
            var dateFrom = tuple.Item1;
            var dateTo = tuple.Item2;
            var period = Period.Between(LocalDateTime.FromDateTime(dateFrom).Date, LocalDateTime.FromDateTime(dateTo).Date, PeriodUnits.YearMonthDay);
            totalPeriod += period;
        }
        // trying clarify the period
        while(totalPeriod.Days >= 30)
        {
            totalPeriod = totalPeriod - Period.FromDays(30);
            totalPeriod = totalPeriod + Period.FromMonths(1);
            while (totalPeriod.Months >= 12)
            {
                totalPeriod = totalPeriod - Period.FromMonths(12);
                totalPeriod = totalPeriod + Period.FromYears(1);
            }
        }

Solution

  • Richard was right in his comment on the OP. The problem is that the months and years aren't distinct quantities unto themselves. One must have a frame of reference to "count" them. You have that reference when you do the Period.Between operation, but it's lost by the time you try to add the periods together.

    If you check the periods that are being added, it makes sense:

    First:  Years: 3, Months: 11, Days: 30
    Second: Years: 6, Months: 0,  Days: 22
    Total:  Years: 9, Months: 11, Days: 52
    

    In order to round as you would like, the 22 days being added to the 30 days would somehow have to know which month was being referenced. Even if you retained the original information - which one would you use? You could well have a 28-day month on one side, and a 31-day month on the other.

    The best you could do would be to artificially round the results yourself afterwards, and choose a flat value (such as 30 days) to represent all months.

    Oh, one minor thing (unrelated to your question) - To go from a DateTime to a LocalDate, try LocalDateTime.FromDateTime(dt).Date. :)