Click here to Skip to main content
12,450,335 members (43,394 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

10.3K views
25 bookmarked
Posted

Datetime Extensions

, 29 Jan 2015 CPOL
Rate this:
Please Sign up or sign in to vote.
This project is a set of extensions to System.DateTime, including holidays and working days calculations on several culture locales

Introduction

This open source project is a set of extensions to System.DateTime, including holidays and working days calculations on several culture locales

In many businesses, there's the concept of a working day. Either being used to calculate the estimated finishing date of a workflow or to return a phone call, some business processes may use the working day concept to add or subtract days from a date excluding weekends and holidays.

You can find the entire source code on the public project repository.

Background

Since business holidays or even weekends may vary depending on the business politics or localization, there isn't a rule of thumb to make these calculations. The way this is handled is by delegating the definition of working day to a DateTimeCultureInfo.

The DateTimeCultureInfo is the core class on which is based the definition of how a specific culture handles a date. It defines if any given date is a working day or not and how to translate a difference between dates. To handle working days, it relies on two methods:

public bool IsWorkingDay(DayOfWeek dayOfWeek)
public bool IsWorkingDay(DateTime date)

The first one is the one responsible to define the working days of the week, as the second one extends it and uses the holidays exceptions.

Since we can have multiple WorkingDayCultureInfos, there is also a property called Name.

The execution of those methods are relayed to specific strategies, the IWorkingDayOfWeekStrategy and IHolidayStrategy. This is by design so we can improve the extensibility of this package as custom needs.

As a secondary functionality, this class can locate, from conventions, strategies implementations for both the interfaces above. By default, the strategies are located from the current CultureInfo.

How to Install

DateTimeExtensions is available on nuget.

To install DateTime Extensions, run the following command in the Package Manager Console.

Using the Code

The simplest way in which I can show you how to use this extension is by showing you this test:

[Test]  
public void simple_calculation() {
    var friday = new DateTime(2011,5,13); // A friday  
    var friday_plus_two_working_days = friday.AddWorkingDays(2); // friday + 2 working days  

    Assert.IsTrue(friday_plus_two_working_days == friday.AddDays(4));  
    Assert.IsTrue(friday_plus_two_working_days.DayOfWeek == DayOfWeek.Tuesday);  
}

Behind the hood, by calling AddWorkingDays without specifying a WorkingDayCultureInfo will try to locate a WorkingDayCultureInfo for the current CultureInfo in the current thread. This may lead to unexpected results if you are not aware which CultureInfo you're running.

You can always explicitly define explicitly the WorkingDayCultureInfo you wish to make your calculations like this:

[Test]
public void recomended_calculation() {
    var dateTimeCultureInfo = new DateTimeCultureInfo("pt-PT");
    var friday = new DateTime(2011, 5, 13); // A friday
    var friday_plus_two_working_days = 
    friday.AddWorkingDays(2, dateTimeCultureInfo); // friday + 2 working days

    Assert.IsTrue(friday_plus_two_working_days == friday.AddDays(4));
    Assert.IsTrue(friday_plus_two_working_days.DayOfWeek == DayOfWeek.Tuesday);
}

From version 1.1, there's also an extension available to list all year Holidays.

IDictionary<DateTime, Holiday> AllYearHolidays(this DateTime date)

Through this extension on DateTime, it's possible to get all holidays for a given culture and the occurrences on its year, like in the next example:

[Test]
public void get_this_year_holidays_in_portugal() {
    var portugalDateTimeCultureInfo = new DateTimeCultureInfo("pt-PT");
    var today = DateTime.Today;
    var holidays = today.AllYearHolidays();

    Assert.IsTrue(holidays.Count == 13);

    foreach (DateTime holidayDate in holidays.Keys) {
        var holiday = holidays[holidayDate];
        Assert.IsTrue(holidayDate.IsWorkingDay(portugalDateTimeCultureInfo) == false, 
        "holiday {0} shouldn't be working day in Portugal", holiday.Name);
    }
}

Supported Cultures

At the moment of this writing, the following cultures are supported:

pt-PT da-DK
pt-BR fi-FI
en-US is-IS
en-GB nb-NO
fr-FR nl-NL
de-DE sv-SE
es-ES es-AR
es-MX en-AU
en-ZA fr-CA (en-CA)
ar-SA it-IT
en-NZ en-GD
en-IE sl-SL

Extensibility

There are two main points for extensibility. The first one is to implement custom IHolidayStrategy or IWorkingDayOfWeekStrategy. The other one is to implement a full custom IWorkingDayCultureInfo.
The end result should be the same for both.

This is an example of a custom IHolidayStrategy that defines Today always as a holiday.

public class CustomHolidayStrategy : IHolidayStrategy {
    public bool IsHoliDay(DateTime day) {
        if (day.Date == DateTime.Today)
            return true;
        return false;
    }

    public IEnumerable<Holiday> Holidays {
        get { return null;  }
    }
}

[Test]
public void provide_custom_strategies() {
    var customDateTimeCultureInfo = new DateTimeCultureInfo() {
        LocateHolidayStrategy = (name) => new CustomHolidayStrategy() ,
    };

    Assert.IsTrue(DateTime.Today.IsWorkingDay(customDateTimeCultureInfo) == false);
    Assert.IsTrue(DateTime.Today.AddDays(1).IsWorkingDay(customDateTimeCultureInfo) == true);
}

This is another example that defines a 3-day weekend (even if this specific case is not common in a real world application, there are cases of working turns rotations that the day off isn't on weekends):

public class CustomDateTimeCultureInfo : IDateTimeCultureInfo {
    public bool IsWorkingDay(DateTime date) {
        return true;
    }

    public bool IsWorkingDay(DayOfWeek dayOfWeek) {
        switch (dayOfWeek) {
            case DayOfWeek.Sunday:
            case DayOfWeek.Saturday:
            case DayOfWeek.Friday:
            return false;
        default:
            return true;
        }
    }

    public IEnumerable<Holiday> Holidays {
        get {
            return null;
        }
    }

    public string Name {
        get { return "Hello World!"; }
    }
}

[Test]
public void provide_custom_culture() {
    var customDateTimeCultureInfo = new CustomDateTimeCultureInfo();
    var today = DateTime.Today;
    var next_friday = today.NextDayOfWeek(DayOfWeek.Friday);

    Assert.IsTrue(next_friday.IsWorkingDay(customDateTimeCultureInfo) == false);
}

Thanks

I would like to thank the following people for their contributions on this project:

  • Martin Holman @martin308
  • Frank Geerlings @frankgeerlings
  • Simon @sihugh
  • Rick Beerendonk @rickbeerendonk
  • David Smith @snoopydo
  • Justin Basinger @jbasinger

Feedback will be much appreciated. You can check out a sample (WIP) project online on http://datetimeextensions.apphb.com/.

History

  • 2015-01-29 - First version
  • 2015-01-31 - Added How to Install section

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

João Matos Silva
Software Developer Mindbus
Portugal Portugal
João Matos Silva is a Enthusiastic software developer at Mindbus. With 10 years of experience on .Net and experience on team leadership and software architecture, mainly focused on the .Net and Asp.Net platforms..
Adept of open source, has a public profile on github (https://github.com/kappy) with some projects of his authorship and contributions to other main open source projects.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionAn interesting article Pin
Garth J Lancaster29-Jan-15 20:43
memberGarth J Lancaster29-Jan-15 20:43 
AnswerRe: An interesting article Pin
João Matos Silva29-Jan-15 20:57
professionalJoão Matos Silva29-Jan-15 20:57 
SuggestionRe: An interesting article Pin
Richard Deeming6-Feb-15 4:58
mvpRichard Deeming6-Feb-15 4:58 
GeneralRe: An interesting article Pin
Garth J Lancaster6-Feb-15 11:47
memberGarth J Lancaster6-Feb-15 11:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160826.1 | Last Updated 30 Jan 2015
Article Copyright 2015 by João Matos Silva
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid