Extending the DateTime structure: Part I



Introduction

Although the methods and properties provided by the System.DateTime structure appear comprehensive, judging by the number of questions asked on .NET forums about date manipulations, they're not comprehensive enough!

In this article, I'd therefore like to present some "extension" methods which I hope will be useful for those developers who, like myself, work a lot with dates. Although most of the methods are very easy to implement, it's convenient to have the extra functionality "on tap" without having to think about it.

Source code for the extension methods

using System;

namespace Utilities
{
    public static partial class DateTimeExtensions
    {       
        public static DateTime FirstDayOfYear(this DateTime dt)
        {
            return new DateTime(dt.Year, 1, 1);
        }

        public static DateTime FirstDayOfYear(this DateTime dt, DayOfWeek dow)
        {
            return dt.FirstDayOfYear().NextDay(dow, true);
        }

        public static DateTime LastDayOfYear(this DateTime dt)
        {
            return dt.FirstDayOfYear().AddYears(1).AddDays(-1);
        }

        public static DateTime LastDayOfYear(this DateTime dt, DayOfWeek dow)
        {
            return dt.LastDayOfYear().PreviousDay(dow, true);
        }

        public static DateTime FirstDayOfMonth(this DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, 1);
        }

        public static DateTime FirstDayOfMonth(this DateTime dt, DayOfWeek dow)
        {
            return dt.FirstDayOfMonth().NextDay(dow, true);
        }

        public static DateTime LastDayOfMonth(this DateTime dt)
        {
            return dt.FirstDayOfMonth().AddMonths(1).AddDays(-1);
        }

        public static DateTime LastDayOfMonth(this DateTime dt, DayOfWeek dow)
        {
            return dt.LastDayOfMonth().PreviousDay(dow, true);
        }

        public static DateTime PreviousDay(this DateTime dt)
        {
            return dt.Date.AddDays(-1);
        }

        public static DateTime PreviousDay(this DateTime dt, DayOfWeek dow)
        {
            return dt.PreviousDay(dow, false);
        }

        public static DateTime PreviousDay(this DateTime dt, DayOfWeek dow, bool includeThis)
        {
            int diff = dt.DayOfWeek - dow;
            if ((includeThis && diff < 0) || (!includeThis && diff <= 0)) diff += 7;
            return dt.Date.AddDays(-diff);
        }

        public static DateTime NextDay(this DateTime dt)
        {
            return dt.Date.AddDays(1);
        }

        public static DateTime NextDay(this DateTime dt, DayOfWeek dow)
        {
            return dt.NextDay(dow, false);
        }

        public static DateTime NextDay(this DateTime dt, DayOfWeek dow, bool includeThis)
        {
            int diff = dow - dt.DayOfWeek;
            if ((includeThis && diff < 0) || (!includeThis && diff <= 0)) diff += 7;
            return dt.Date.AddDays(diff);
        }

        public static int DaysInYear(this DateTime dt)
        {
            return (dt.LastDayOfYear() - dt.FirstDayOfYear()).Days + 1;
        }

        public static int DaysInYear(this DateTime dt, DayOfWeek dow)
        {
            return (dt.LastDayOfYear(dow).DayOfYear - dt.FirstDayOfYear(dow).DayOfYear) / 7 + 1;
        }

        public static int DaysInMonth(this DateTime dt)
        {
            return (dt.LastDayOfMonth() - dt.FirstDayOfMonth()).Days + 1;
        }

        public static int DaysInMonth(this DateTime dt, DayOfWeek dow)
        {
            return (dt.LastDayOfMonth(dow).Day - dt.FirstDayOfMonth(dow).Day) / 7 + 1;
        }

        public static bool IsLeapYear(this DateTime dt)
        {
            return dt.DaysInYear() == 366;
        }

        public static DateTime AddWeeks(this DateTime dt, int weeks)
        {
            return dt.AddDays(7 * weeks);
        }
    }
}

Notes on usage

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

Please note that all the methods produce results which are relative to the DateTime instance on which they are called. For example the FirstDayOfMonth() method returns the first day of the month in which the current instance occurs.

All methods return a pure date (i.e. the time component is always midnight) except AddWeeks() which, to be consistent with the existing "Add" methods, preserves the time component of the current instance.

The purpose of the methods should be self-explanatory apart, perhaps, from those which have overloads which take parameters:

  • Those which take a DayOfWeek parameter only consider that particular day when returning a result.

  • Those which take a bool parameter, take into account the day of the week on which the current DateTime instance falls if "true" but ignore it if "false".

So, for example, the NextDay(DayOfWeek) method returns the next date on which that particular day of the week occurs. If the current instance occurs on that day, then it returns the date one week hence.

However, the NextDay(DayOfWeek, bool) method would in that same scenario return the date of the current instance if the bool parameter were "true" but the date one week hence if it were "false".

There are already static (rather than instance) methods called "DaysInMonth" and "IsLeapYear" in the DateTime structure. However, there is no conflict between these and the extension methods because the latter have different signatures.

Example of usage

using System;
using Utilities;

class Test
{
    static void Main()
    {
        string format = "dddd, d MMMM yyyy";
        DateTime dt = new DateTime(2011, 4, 11);
        Console.WriteLine(dt.ToString(format));
        DateTime[] dta = new DateTime[16];
        int[] nda = new int[4];
        dta[0] = dt.FirstDayOfYear();
        dta[1] = dt.FirstDayOfYear(DayOfWeek.Thursday); // first Thurday of the year
        dta[2] = dt.LastDayOfYear();
        dta[3] = dt.LastDayOfYear(DayOfWeek.Sunday); // last Sunday of year
        dta[4] = dt.FirstDayOfMonth();
        dta[5] = dt.FirstDayOfMonth(DayOfWeek.Wednesday); // first Wednesday of the month
        dta[6] = dt.LastDayOfMonth();
        dta[7] = dt.LastDayOfMonth(DayOfWeek.Saturday); // last Saturday of the month
        dta[8] = dt.PreviousDay();
        dta[9] = dt.PreviousDay(DayOfWeek.Monday); // previous Monday (excluding this day)
        dta[10] = dt.PreviousDay(DayOfWeek.Monday, true); // previous Monday (including this day)
        dta[11] = dt.NextDay();
        dta[12] = dt.NextDay(DayOfWeek.Friday); // next Friday (excluding this day)
        dta[13] = dt.NextDay(DayOfWeek.Friday, true); // next Friday (including this day)
        dta[14] = dt.AddWeeks(3); // three weeks hence
        dta[15] = dt.AddWeeks(-2); // a fortnight ago
        foreach(DateTime d in dta) Console.WriteLine(d.ToString(format));
        nda[0] = dt.DaysInYear();
        nda[1] = dt.DaysInYear(DayOfWeek.Tuesday); // number of Tuesdays in year
        nda[2] = dt.DaysInMonth();
        nda[3] = dt.DaysInMonth(DayOfWeek.Monday); // number of Mondays in month
        foreach(int i in nda) Console.WriteLine(i);
        bool b = dt.IsLeapYear();
        Console.WriteLine(b);
        Console.ReadKey(true);
    }  
}

The output of this program is as follows:

Monday, 11 April 2011
Saturday, 1 January 2011
Thursday, 6 January 2011
Saturday, 31 December 2011
Sunday, 25 December 2011
Friday, 1 April 2011
Wednesday, 6 April 2011
Saturday, 30 April 2011
Saturday, 30 April 2011
Sunday, 10 April 2011
Monday, 4 April 2011
Monday, 11 April 2011
Tuesday, 12 April 2011
Friday, 15 April 2011
Friday, 15 April 2011
Monday, 2 May 2011
Monday, 28 March 2011
365
52
30
4
False

Conclusion

You may be wondering why I've declared DateTimeExtensions to be a "partial" static class. The reason is that I'll be adding another partial class in a subsequent article which, unlike this one, will deal with calculating durations between dates rather than actual dates.


Similar Articles