2

given a date, and a list of holidays, how do i add the given number of workdays to this date? there are a lot of solutions to the smaller problem which does not take account of holidays (see for example Adding Days to a Date but Excluding Weekends).

EDIT: i am looking for a O(1) or at least linear (on the number of holidays) solution.

please help thanks konstantin

Community
  • 1
  • 1
akonsu
  • 28,824
  • 33
  • 119
  • 194

5 Answers5

1

Approaching O(1) (without init time)

If you really want an O(1) solution, I hope you do not take into account the initialization fase.

To initialize:

  • Build sorted list of all dates in queryable range that are valid return values. (using something like the code in the answer from Rik Garner)
  • Build hashtable with dates from the above list, with the date as key, index in the list as value

The initialization code, you need only once, and you cache the result.

To query/calculate

 List<DateTime> validWorkdays = // ;
 Dictionary<DateTime, int> lookupIndexOfValidWorkday = // ;

 DateTime AddWorkdays(DateTime start, int count) {
    var startIndex = lookupIndexOfValidWorkday[start];
    return validWorkDays[startIndex + count];
 }

Concerning the retrieve from the dictionary:

Getting or setting the value of this property approaches an O(1) operation.

O(n) on number of holidays

Under the assumption that the list of holidays is sorted from oldest to newest. (Credits for weekdays formula)

DateTime AddBusinessDay(DateTime start, int count, IEnumerable<DateTime> holidays) {
   int daysToAdd = count + ((count/ 5) * 2) + ((((int)start.DayOfWeek + (count % 5)) >= 5) ? 2 : 0);
   var end = start.AddDays(daysToAdd);
   foreach(var dt in holidays) {
     if (dt >= start && dt <= end) {
        end = end.AddDays(1);
        if (end.DayOfWeek == DayOfWeek.Saterday) {
          end = end.AddDays(2);
        }
     }
   }
   return end;
}    

This method can be optimized.

It's very hard to create a simple formula that just calculates the result, like the answers in the question you linked to. Because when you adjust for holidays you need to take into account new holidays that might fall into your range. For weekends, you know there is a fixed interval between them.

Community
  • 1
  • 1
GvS
  • 52,015
  • 16
  • 101
  • 139
0

//Below code is working...please let me know if you have any concern

DateTime AddBusinessDays(int noofDays, DateTime dtCurrent)
    {
        var holidays = new List<DateTime>() { new DateTime(2013, 10, 22), new DateTime(2013, 10, 28)};

        DateTime tempdt = new DateTime(dtCurrent.Year, dtCurrent.Month, dtCurrent.Day);
        // if starting day is non working day adjust to next working day
        tempdt = ExcludeNotWorkingDay(tempdt, holidays);

        // if starting day is non working day adjust to next working day then minus 1 day in noofadding days
        if (tempdt.Date > dtCurrent.Date && !(noofDays == 0))
            noofDays = noofDays - 1;

        while (noofDays > 0)
        {   
            tempdt = tempdt.AddDays(1);
            // if day is non working day adjust to next working day
            tempdt = ExcludeNotWorkingDay(tempdt, holidays);
            noofDays = noofDays - 1;
        }

        return tempdt;
    }
    DateTime ExcludeNotWorkingDay(DateTime dtCurrent, List<DateTime> holidays)
    {
        while (!IsWorkDay(dtCurrent, holidays))
        {
            dtCurrent = dtCurrent.AddDays(1);
        }
        return dtCurrent;
    }
    bool IsWorkDay(DateTime dtCurrent, List<DateTime> holidays)
    {
        if ((dtCurrent.DayOfWeek == DayOfWeek.Saturday || dtCurrent.DayOfWeek == DayOfWeek.Sunday ||
             holidays.Contains(dtCurrent)))
        {
            return false;
        }
        else
        {
            return true;
        }
    }
0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace DateThing
{
    class Program
    {
        static void Main(string[] args)
        {
            var holidays = new List<DateTime>()
                               {
                                   new DateTime(2010, 12, 25),
                                   new DateTime(2010, 12, 26)
                               };


            var futureDate = CalculateFutureDate(DateTime.Today, 20, holidays);

        }

        static DateTime CalculateFutureDate(DateTime fromDate, int numberofWorkDays, ICollection<DateTime> holidays)
        {
            var futureDate = fromDate;

            for (var i = 0; i < numberofWorkDays; i++ )
            {
                if (futureDate.DayOfWeek == DayOfWeek.Saturday || futureDate.DayOfWeek == DayOfWeek.Sunday ||
                    (holidays != null && holidays.Contains(futureDate)))
                    futureDate = futureDate.AddDays(1); // Increase FutureDate by one because of condition

                futureDate = futureDate.AddDays(1); // Add a working day
            }
            return futureDate;
        }
    }
}
jvanrhyn
  • 2,804
  • 19
  • 14
0

Try this......

 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;
    }
Rikalous
  • 4,514
  • 1
  • 40
  • 52
  • thanks. suppose the collection of holidays contains numberofWorkDays * 2 holidays side by side starting from the fromDate... – akonsu Dec 08 '10 at 13:58
  • You can adjust the value in the Range to suit; in the code I grabbed this from we are always adding lots of workdays, i.e. numberOfWorkDays >> holidays[between from and to]. In your case you could do (numberOfWorkDays*2)+holidays.Count perhaps? – Rikalous Dec 08 '10 at 14:06
-1

Something like this perhaps

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DateThing
{
    class Program
    {
        static void Main(string[] args)
        {
            var holidays = new List<DateTime>()
                               {
                                   new DateTime(2010, 12, 25),
                                   new DateTime(2010, 12, 26)
                               };


            var workDays = GetNumberOfWorkDays(DateTime.Today, new DateTime(2011, 1, 1), holidays);
        }

        static int GetNumberOfWorkDays(DateTime fromDate, DateTime toDate, ICollection<DateTime> holidays)
        {
            var days = 0;
            for (var i = fromDate; i < toDate; )
            {
                if (i.DayOfWeek != DayOfWeek.Saturday && i.DayOfWeek != DayOfWeek.Sunday && 
                    (holidays != null && !holidays.Contains(i)))
                    days++;

                i = i.AddDays(1);
            }
            return days;
        }
    }


}
jvanrhyn
  • 2,804
  • 19
  • 14
  • i am looking for a method that does reverse: fromDate + GetNumberOfWorkDays should yield toDate. – akonsu Dec 08 '10 at 08:47