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 WorkingDayCultureInfo
s, 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);
var friday_plus_two_working_days = friday.AddWorkingDays(2);
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);
var friday_plus_two_working_days =
friday.AddWorkingDays(2, dateTimeCultureInfo);
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