Aks
Aks

Reputation: 5236

c# DateTime to Add/Subtract Working Days

I have a scenario where given a date(DateTime),that date plus/minus x days(achieved with DateTime.AddDays) must add or subtract x working days, i.e., skip weekends and holidays. How can I get it to do this? Should I implement my own version and attach it to a calendar or something?

Upvotes: 49

Views: 87121

Answers (10)

Daniel Rusnok
Daniel Rusnok

Reputation: 489

    public static DateTime AddBussinessDays(this DateTime originalDate, int workDays)
    {
        if (workDays == 0)
        {
            return originalDate;
        }

        originalDate = originalDate.AddDays(workDays);

        if (originalDate.IsHoliday() || originalDate.DayOfWeek == DayOfWeek.Saturday || originalDate.DayOfWeek == DayOfWeek.Sunday)
        {
            return originalDate.AddBussinessDays(workDays < 0 ? -1 : 1);
        }

        return originalDate;
    }

I raise you a recursive version of solution which also can subtract days by passing a negative integer value into method exactly like method AddDays.

Upvotes: -1

Rikalous
Rikalous

Reputation: 4574

I did this recently with a bit of LINQ:

private DateTime CalculateFutureDate(DateTime fromDate, int numberofWorkDays, ICollection<DateTime> holidays)
{
    var futureDate = fromDate;
    var daterange = Enumerable.Range(1, numberofWorkDays * 2);
    var dateSet = daterange.Select (d => futureDate.AddDays(d));
    var dateSetElim = dateSet.Except(holidays).Except(dateSet.Where( s =>s.DayOfWeek == DayOfWeek.Sunday).Except(dateSet.Where  (s=>s.DayOfWeek==DayOfWeek.Saturday) ));

    //zero-based array
    futureDate = dateSetElim.ElementAt(numberofWorkDays-1);
    return futureDate;
}

Upvotes: 10

Hardik Dudhaiya
Hardik Dudhaiya

Reputation: 249

simple solution is here Try it and enjoy coding

public class Program
{
    public static void Main(string[] args)
    {
        Double days= 7;
        string s=DateTime.Now.AddDays(7).ToString("dd/MM/yyyy");  
        DateTime dt= OrderDeliveryDate(days);
        Console.WriteLine("dt"+dt.ToString("dd/MM/yyyy"));


    }

    public static DateTime OrderDeliveryDate(Double days)
    {

        Double count=0;
        for(int i=0;i<days;i++)
        {
            if(DateTime.Now.AddDays(i).DayOfWeek.ToString() == "Saturday")
            {
               count= count+1;
            }
            else if(DateTime.Now.AddDays(i).DayOfWeek.ToString() == "Sunday")
            {
                count=count+1;
            }

        }
        days=days+count;
        return DateTime.Now.AddDays(days);
    }
}

Upvotes: -1

Naburi
Naburi

Reputation: 31

I was looking to add and subtract dates skipping weekends and this is the first entry in Google. I didn't like any of the answers here so I will add the one i made myself in case someone ends here like me:

public static DateTime AddExcludingWeekends(this DateTime dateTime, int nDays)
{
  var wholeWeeks = nDays / 5; //since nDays does not include weekdays every week is considered as 5 days
  var absDays = Math.Abs(nDays);
  var remaining = absDays % 5; //results in the number remaining days to add or substract excluding the whole weeks
  var direction = nDays / absDays;//results in 1 if nDays is posisive or -1 if it's negative
  while (dateTime.DayOfWeek == DayOfWeek.Saturday || dateTime.DayOfWeek == DayOfWeek.Sunday)
    dateTime = dateTime.AddDays(direction); //If we are already in a weekend, get out of it
  while (remaining-- > 0)
  {//add remaining days...
    dateTime = dateTime.AddDays(direction);
    if (dateTime.DayOfWeek == DayOfWeek.Saturday)
      dateTime = dateTime.AddDays(direction * 2);//...skipping weekends
  }
  return dateTime.AddDays(wholeWeeks * 7); //Finally add the whole weeks as 7 days, thus skipping the weekends without checking for DayOfWeek
}

I hope that this helps someone.

Upvotes: 1

I have modified the previous answers to more Functional approaches. I have provided two solutions below, one using IEnumerable and one using IObservable and Reactive Extensions

Using IObservable

public static class DateTimeExtensions
{
    public static DateTime AddWorkDays(this DateTime date, int workingDays)
    {
        return Observable
            .Generate
                (date, arg => true, arg => arg.AddDays(workingDays < 0 ? -1 : 1), arg => arg)
            .Where(newDate =>
                (newDate.DayOfWeek != DayOfWeek.Saturday &&
                 newDate.DayOfWeek != DayOfWeek.Sunday &&
                 !newDate.IsHoliday()))
            .Take(Math.Abs(workingDays) + 1)
            .LastAsync()
            .Wait();
    }

    public static bool IsHoliday(this DateTime date)
    {
        return false;
    }
}

Using IEnumerable

public static class DateTimeExtensions
{
    public static DateTime AddWorkDays(this DateTime date, int workingDays)
    {
        return date.GetDates(workingDays < 0)
            .Where(newDate =>
                (newDate.DayOfWeek != DayOfWeek.Saturday &&
                 newDate.DayOfWeek != DayOfWeek.Sunday &&
                 !newDate.IsHoliday()))
            .Take(Math.Abs(workingDays))
            .Last();
    }

    private static IEnumerable<DateTime> GetDates(this DateTime date, bool isForward)
    {
        while (true)
        {
            date = date.AddDays(isForward ? -1 : 1);
            yield return date;
        }
    } 

    public static bool IsHoliday(this DateTime date)
    {
        return false;
    }
}

Upvotes: 1

Danimal111
Danimal111

Reputation: 2062

It took me some time to work it out... I have created a DB table which I pull into an array with my holiday days.All Credit to Kev (on this post) .. I had to modify his to work as is for me.

In my case, if the first day was a Saturday, and my workingDayCount = -1, I want to pass back Thursday (as my date cannot fall on a weekend or a holiday... it has to be a workday.. in this case Friday.)

Kev's code could pass back a Sunday ... the code below will take it to the previous workday (usually a Friday -- unless Friday is a holiday, when it would pass back Thursday).

public static DateTime AddWorkDays(this DateTime date, int workingDays, params Holidays[] bankHolidays)
    {
        int direction = workingDays < 0 ? -1 : 1;
        DateTime newDate = date;
        // If a working day count of Zero is passed, return the date passed
        if (workingDays == 0)
        {

            newDate = date;
        }
        else
        {
            while (workingDays != -direction)
            {
                if (newDate.DayOfWeek != DayOfWeek.Saturday &&
                    newDate.DayOfWeek != DayOfWeek.Sunday &&
                    Array.IndexOf(bankHolidays, newDate) < 0)
                {
                    workingDays -= direction;
                }
                // if the original return date falls on a weekend or holiday, this will take it to the previous / next workday, but the "if" statement keeps it from going a day too far.

                if (workingDays != -direction)
                { newDate = newDate.AddDays(direction); }
            }
        }
        return newDate;
    }

Here is my simple Holidays class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Clarity.Utilities
{
    public class Holidays 
    {
        public int Id { get; set; }
        public DateTime dtHoliday  { get; set; }
        public string Desc { get; set; }
        public bool Active { get; set; }

    }
}

Here is how to populate the array:

private Holidays[] PopulateArrayWithDates()
    {
        SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConn"].ConnectionString);
        DateTime[] dtHolidays = new DateTime[] { };
        string sql = @"SELECT HolDate, HolName FROM [Server].DBName.dbo.tblHolidays";
        SqlCommand ADDCmd = new SqlCommand(sql, con);
        DataTable table = new DataTable();
        DataTable tbl = new DataTable();
        Utilities.Holidays[] allRecords = null;

        using (var command = new SqlCommand(sql, con))
        {
            con.Open();
            using (var reader = command.ExecuteReader())
            {
                var list = new List<Holidays>();
                while (reader.Read())
                    list.Add(new Holidays { dtHoliday = reader.GetDateTime(0), Desc = reader.GetString(1) });
                allRecords = list.ToArray();
            }
        }
        return allRecords;
    }

Upvotes: 2

Alexander Schmidt
Alexander Schmidt

Reputation: 5743

I would suggest that you have to implement it by your own, and would do it inside an extension method like this:


public static class DateTimeExtensions
{
    public static DateTime AddWorkdays(this DateTime originalDate, int workDays)
    {
        DateTime tmpDate = originalDate;
        while (workDays > 0)
        {
            tmpDate = tmpDate.AddDays(1);
            if (tmpDate.DayOfWeek < DayOfWeek.Saturday && 
                tmpDate.DayOfWeek > DayOfWeek.Sunday &&
                !tmpDate.IsHoliday())
                workDays--;
        }
        return tmpDate;
    }

    public static bool IsHoliday(this DateTime originalDate)
    {
        // INSERT YOUR HOlIDAY-CODE HERE!
        return false;
    }
}

Upvotes: 69

Kev
Kev

Reputation: 119856

Based on Taz's link:

public static class DateTimeExtensions
{
  public static DateTime AddWorkDays(this DateTime date, int workingDays)
  {
    int direction = workingDays < 0 ? -1 : 1;
    DateTime newDate = date;
    while (workingDays != 0)
    {
      newDate = newDate.AddDays(direction);
      if (newDate.DayOfWeek != DayOfWeek.Saturday && 
          newDate.DayOfWeek != DayOfWeek.Sunday && 
          !newDate.IsHoliday())
      {
        workingDays -= direction;
      }
    }
    return newDate;
  }

  public static bool IsHoliday(this DateTime date)
  {
    // You'd load/cache from a DB or file somewhere rather than hardcode
    DateTime[] holidays = 
    new DateTime[] { 
      new DateTime(2010,12,27),
      new DateTime(2010,12,28),
      new DateTime(2011,01,03),
      new DateTime(2011,01,12),
      new DateTime(2011,01,13)
    };

    return holidays.Contains(date.Date);
  }
}

Upvotes: 73

Brett McCann
Brett McCann

Reputation: 2519

You should probably have a Database of holidays to check against, and if the value of day plus/minus x equals a value in the Database add/subtract another, since not everyone has the same holidays.

Upvotes: 0

Nickolay Olshevsky
Nickolay Olshevsky

Reputation: 14160

You should check if the day is working by yourself, since DateTime class cannot know which days will be holidays this year :)

Upvotes: 0

Related Questions