Extending the DateTime structure in C#: Part II


Introduction

In the conclusion to Part I of this article (http://www.c-sharpcorner.com/UploadFile/b942f9/6714/), I mentioned that I would be adding some further extension methods for the DateTime structure in Part II which deal with the durations between dates.

You might expect that these methods should be extending the TimeSpan rather than the DateTime structure but this in fact is not feasible. Whilst methods and properties in the TimeSpan structure can easily work out the durations between dates, they can do so only in terms of days; weeks, months and years are not covered.

Although weeks can easily be calculated from the duration in days, months and years are more awkward because of differences in the lengths of months and the occurrence of leap years. Also it may not be clear whether the final day of an interval should be taken into account or not. For example:

1 March 2011 to 30 April 2011 equals 1 complete month if the final day is ignored or 2 complete months if it isn't.

29 January 2011 to 28 February 2011 equals 0 complete months if the final day is ignored or 1 complete month if it isn't.

The following extension methods deal with this difficulty by giving the user the option to take into account the final day or to ignore it (the default is to ignore it).

Source code for the extension methods

using System;

namespace Utilities
{
    public static partial class DateTimeExtensions
    {
        private static int DateValue(this DateTime dt)
        {
            return dt.Year * 372 + (dt.Month - 1) * 31 + dt.Day - 1;
        }

        public static int YearsBetween(this DateTime dt, DateTime dt2)
        {
            return dt.MonthsBetween(dt2) / 12;
        }

        public static int YearsBetween(this DateTime dt, DateTime dt2, bool includeLastDay)
        {
            return dt.MonthsBetween(dt2, includeLastDay) / 12;
        }

        public static int YearsBetween(this DateTime dt, DateTime dt2, bool includeLastDay, out int excessMonths)
        {
            int months = dt.MonthsBetween(dt2, includeLastDay);
            excessMonths = months % 12;
            return months / 12;
        }

        public static int MonthsBetween(this DateTime dt, DateTime dt2)
        {
            int months = (dt2.DateValue() - dt.DateValue()) / 31;
            return Math.Abs(months);
        }

        public static int MonthsBetween(this DateTime dt, DateTime dt2, bool includeLastDay)
        {
            if (!includeLastDay) return dt.MonthsBetween(dt2);
            int days;
            if (dt2 >= dt)
                days = dt2.AddDays(1).DateValue() - dt.DateValue();
            else
                days = dt.AddDays(1).DateValue() - dt2.DateValue();
            return days / 31;
        }

        public static int WeeksBetween(this DateTime dt, DateTime dt2)
        {
            return dt.DaysBetween(dt2) / 7;
        }

        public static int WeeksBetween(this DateTime dt, DateTime dt2, bool includeLastDay)
        {
            return dt.DaysBetween(dt2, includeLastDay) / 7;
        }

        public static int WeeksBetween(this DateTime dt, DateTime dt2, bool includeLastDay, out int excessDays)
        {
            int days = dt.DaysBetween(dt2, includeLastDay);
            excessDays = days % 7;
            return days / 7;
        }

        public static int DaysBetween(this DateTime dt, DateTime dt2)
        {
            return (dt2.Date - dt.Date).Duration().Days;
        }

        public static int DaysBetween(this DateTime dt, DateTime dt2, bool includeLastDay)
        {
            int days = dt.DaysBetween(dt2);
            if (!includeLastDay) return days;
            return days + 1;
        }
    }
}


Technical note

To calculate months and years in an efficient manner, I've used an old programming hack which assumes a notional year of 372 days split into 12 equal months of 31 days, whether it's a leap year or not. Unusually, this is presented as a private extension method as I didn't think it would be useful outside this class.

Notes on usage

As in the case of the extension methods in Part I, these extension methods can be used in any of your projects (C# 3.0 or later) by first compiling them to a dynamic link library (dll), adding a reference to the dll to your project and then adding the following "using" directive to the file:

using Utilities; // or any other name you choose for the namespace

All methods work out the absolute value of the duration between the two dates (I didn't think that catering for negative durations would be very useful) and so it doesn't matter in which order they are used.

All methods completely ignore the time components of each DateTime instance (in effect they treat it as midnight) and so it is possible that the result of the DaysBetween() method may differ from the absolute value of the Days component of the TimeSpan between the two dates. Indeed, this is why this method has been included.

As well as overloads to determine whether the last day is included or not, there are also overloads to return the excess months or days when the duration in years or weeks, respectively, is being calculated. The excess is returned in an 'out' parameter.

Example of usage

using System;
using Utilities;


class Test
{
    static void Main()
    {
        DateTime dt = new DateTime(2008, 4, 13);
        DateTime dt2 = new DateTime(2011, 4, 12);
        int[] durations = new int[12];
        int excessMonths;
        int excessDays;
        durations[0] = dt.YearsBetween(dt2);
        durations[1] = dt.YearsBetween(dt2, true);
        durations[2] = dt.YearsBetween(dt2, false, out excessMonths);
        durations[3] = excessMonths;

        dt = new DateTime(2010, 9, 13);
        durations[4] = dt.MonthsBetween(dt2);
        durations[5] = dt.MonthsBetween(dt2, true);

        dt = new DateTime(2011, 1, 5);
        durations[6] = dt.WeeksBetween(dt2);
        durations[7] = dt.WeeksBetween(dt2, true);
        durations[8] = dt.WeeksBetween(dt2, false, out excessDays);
        durations[9] = excessDays;

        dt = new DateTime(2011, 5, 13); // now ahead of dt2
        durations[10] = dt.DaysBetween(dt2);
        durations[11] = dt.DaysBetween(dt2, true);
        foreach (int duration in durations) Console.WriteLine(duration);
        Console.ReadKey(true);
    }
}

The output of this program is as follows:

2
3
2
11
6
7
13
14
13
6
31
32

Conclusion

The extension methods in this article have been provided in a separate partial static class to those in Part I - and do not use the latter - in case some developers would prefer to use one set but not the other.

Recommended Free Ebook
Next Recommended Readings