5

I am trying to add hours to a date but want the new date to exclude weekends and only be between 9.00 to 17.00.

example:

first scenario:

if my date is 23/02/2012 16:00:00.
if I add 4 hours to that my new date should be 24/02/2012 12:00:00 (which will be within working hours)

second scenario:

if my date is 24/02/2012 09:00:00 (which is a friday)
if I add 24 hours then the new date should be 27/02/2012 09:00:00 (which would be monday next week at 9am)

so far I got this but am stuck as this will not count for any date that is in past say date passed was 10/02/2012(Friday last week) :

   private static void ExcludeWeekend(Datetime dt)
    {
        DateTime todaysDate = DateTime.Today;
        DateTime dueDate = null;

                if (dueDate.DayOfWeek == DayOfWeek.Friday)
                {
                    dueDate.AddHours(48);
                }
                else if (dueDate.DayOfWeek == DayOfWeek.Saturday)
                {
                    dueDate.AddHours(72);
                }
    }
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Zaki
  • 5,540
  • 7
  • 54
  • 91
  • Related: [C# - Duration between two DateTimes in minutes](http://stackoverflow.com/questions/3835041/c-sharp-duration-between-two-datetimes-in-minutes) – dtb Feb 23 '12 at 13:30
  • If your date is a Monday at 4PM, and you add 10 hours, do you want to get to Wednesday at 10AM? – zmbq Feb 23 '12 at 13:37
  • all the answers are wonderful and for me BlueMonkMN's answer worked like a charm, I will give you all +1 but have to accept BlueMonkMN's answer...Sorry – Zaki Feb 23 '12 at 17:27
  • Just realized my answer isn't as good as I'd hoped despite the fact that it appears to have worked like a charm. Let me know if you want help improving the answer based on the comment I added to my answer. – BlueMonkMN Feb 23 '12 at 18:27
  • ah I guess didnt test fridays. Have answered your question – Zaki Feb 24 '12 at 09:15

5 Answers5

9

You can use the class CalendarDateAdd from the Time Period Library for .NET:

// ----------------------------------------------------------------------
public void CalendarDateAddSample()
{
  CalendarDateAdd calendarDateAdd = new CalendarDateAdd();
  // use only weekdays
  calendarDateAdd.AddWorkingWeekDays();
  // setup working hours
  calendarDateAdd.WorkingHours.Add( new HourRange( new Time( 09 ), new Time( 17 ) ) );

  DateTime start = new DateTime( 2012, 2, 23 ); // start date
  TimeSpan offset = new TimeSpan( 4, 0, 0 ); // 4 hours

  DateTime? end = calendarDateAdd.Add( start, offset ); // end date

  Console.WriteLine( "start: {0}", start );
  Console.WriteLine( "offset: {0}", offset );
  Console.WriteLine( "end: {0}", end );
} // CalendarDateAddSample
  • 1
    This library looks very well done and quite exhaustive. I guess the downside is that if your just looking for a very isolated algorithm for one-off usage, it may be overkill. But very well done by the looks of it! – Tim Friesen Feb 28 '12 at 13:55
  • 2
    @Tim: Many thanks. Developing time calculations can be an error-prone task and is, in my opinion, predestined for a library. –  Mar 02 '12 at 21:39
8
 static DateTime AddWithinWorkingHours(DateTime start, TimeSpan offset)
 {
    const int hoursPerDay = 8;
    const int startHour = 9;
    // Don't start counting hours until start time is during working hours
    if (start.TimeOfDay.TotalHours > startHour + hoursPerDay)
       start = start.Date.AddDays(1).AddHours(startHour);
    if (start.TimeOfDay.TotalHours < startHour)
       start = start.Date.AddHours(startHour);
    if (start.DayOfWeek == DayOfWeek.Saturday)
       start.AddDays(2);
    else if (start.DayOfWeek == DayOfWeek.Sunday)
       start.AddDays(1);
    // Calculate how much working time already passed on the first day
    TimeSpan firstDayOffset = start.TimeOfDay.Subtract(TimeSpan.FromHours(startHour));
    // Calculate number of whole days to add
    int wholeDays = (int)(offset.Add(firstDayOffset).TotalHours / hoursPerDay);
    // How many hours off the specified offset does this many whole days consume?
    TimeSpan wholeDaysHours = TimeSpan.FromHours(wholeDays * hoursPerDay);
    // Calculate the final time of day based on the number of whole days spanned and the specified offset
    TimeSpan remainder = offset - wholeDaysHours;
    // How far into the week is the starting date?
    int weekOffset = ((int)(start.DayOfWeek + 7) - (int)DayOfWeek.Monday) % 7;
    // How many weekends are spanned?
    int weekends = (int)((wholeDays + weekOffset) / 5);
    // Calculate the final result using all the above calculated values
    return start.AddDays(wholeDays + weekends * 2).Add(remainder);
 }
BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
  • I just realized this probably won't work as expected when your timespan crosses a weekend. If you add 8 hours on Friday, you will get Monday, but you will also get the same time on Monday if you add 16 hours. This solution may be helpful, but weekends need to be handled better by adding to wholeDays when the time spans a weekend. In order to provide a proper answer for you, can you confirm that you want Friday + 16 hours to be Tuesday? – BlueMonkMN Feb 23 '12 at 18:24
  • yes you are right it should be Tuesday if Friday + 16 hours, because it is excluding weekends. – Zaki Feb 24 '12 at 09:14
  • 1
    @Sam1 I have re-implemented some better code which will handle weekends better among other things. – BlueMonkMN Feb 24 '12 at 16:08
  • 1
    This seems to throw an incorrect time if the time is beyond the working hours for that day, example 17:30 plus 4 working hours, it shows 4 working hours as 13:30, whereas it ought to be 13:00. – David C Oct 23 '12 at 10:55
  • I need this but in php – user794846 Jun 27 '14 at 11:16
  • The trouble with this is when time periods divisible by 24 hours are expected to add days and not straight hours. A good implementation, but be warned: it can result in undesired bugs using conventional business logic i.e.: 24 hours = 1 day, not = 24 working hours. – David Vogel Oct 21 '15 at 16:23
  • 1
    @DavidVogel The logic for making 24 hours equal one day instead of 24 working hours is trivial. This code goes out of its way to ensure that the hours are always treated only as working hours. To make 24 hours equal one day, you'd need only to handle weekends and the rest is built into the `AddHours` .NET function. – BlueMonkMN Oct 21 '15 at 18:47
2
    var d1 = DateTime.Now;
    var ts = TimeSpan.FromHours(40);
    var d2 = d1 + ts;
    if(d2.DayOfWeek == DayOfWeek.Saturday) {
        d2 = d2.AddDays(2);
    }else if(d2.DayOfWeek == DayOfWeek.Sunday){
        d2 = d2.AddDays(1);
    }

If you really want to make it an extension:

var d2 = DateTime.Now.AddSkipWeekend(TimeSpan.FromHours(40));

static class DateExtensions { 
    public static DateTime AddSkipWeekend(this DateTime date1, TimeSpan ts){
        DateTime d2 = date1 + ts;
        if(d2.DayOfWeek == DayOfWeek.Saturday) {
            d2 = d2.AddDays(2);
        } else if(d2.DayOfWeek == DayOfWeek.Sunday) {
            d2 = d2.AddDays(1);
        }
        return d2;
    }
}

Edit: Just realized your non-working-hours requirement:

var d2 = DateTime.Now.AddSkipWeekend(TimeSpan.FromHours(50),TimeSpan.FromHours(9),TimeSpan.FromHours(17));

public static DateTime AddSkipWeekend(this DateTime date1, TimeSpan addTime, TimeSpan workStart, TimeSpan workEnd)
{
    DateTime d2 = date1 + addTime;
    if(d2.TimeOfDay < workStart) {
        d2 = d2.Add(workStart - d2.TimeOfDay);
    } else if(d2.TimeOfDay > workEnd) {
        d2 = d2.Add(TimeSpan.FromHours(12) - d2.TimeOfDay);
    }
    if(d2.DayOfWeek == DayOfWeek.Saturday) {
        d2 = d2.AddDays(2);
    } else if(d2.DayOfWeek == DayOfWeek.Sunday) {
        d2 = d2.AddDays(1);
    }
    return d2;
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • The hard-coded FromHours(12) doesn't make sense to me. It looks like you're basically setting the time of d2 to 12:00 if the resulting time is after the end of the work day. That 12 should probably be workStart. Also, this solution assumes that adding hours includes off-work hours. So adding 16 hours doesn't mean add two work days, but instead means add 16 hours on the full clock and then move the result if it's not during working hours. Not clear if this is what is wanted. – BlueMonkMN Feb 23 '12 at 16:45
  • @BlueMonkMN: I've used the hard-coded value because that is what the OP has asked for. The method can easily be extended by providing another timespan parameter. Please read again what the OP has asked: "if my date is 23/02/2012 16:00:00 and i add 4 hours, my new date should be 24/02/2012 12:00:00 (which will be within working hours)". On your first comment: That's actually what my code does, so I don't see your point. Nowhere does it mention that the timespan cannot be added intitally. – Tim Schmelter Feb 23 '12 at 17:08
  • @TimSchmelter The OP didn't ask for 12:00 explicitly. The first time results in 12:00 because 9:00 plus the remaining 3 hours carrying over from the previous day yields 12:00. – BlueMonkMN Feb 23 '12 at 18:18
1

Try this:

public static DateTime Add(DateTime dt, TimeSpan t)
{
    while (true)
    {
        dt = Max(dt, dt.Date.AddHours(9));
        DateTime x = Min(dt + t, dt.Date.AddHours(17));
        // Console.WriteLine("{0} -> {1} ({2})", dt, x, x - dt);
        t -= x - dt;
        dt = x;
        if (t == TimeSpan.Zero) { return dt; }
        do { dt = dt.Date.AddDays(1); } while (dt.IsWeekendDay());
    }
}

Helper methods from here.

Example 1:

var result = Add(DateTime.Parse("23/02/2012 16:00:00"), TimeSpan.FromHours(4));
// result == {24/02/2012 12:00:00}

23/02/2012 16:00:00 -> 23/02/2012 17:00:00 (01:00:00)
24/02/2012 09:00:00 -> 24/02/2012 12:00:00 (03:00:00)

Example 2:

var result = Add(DateTime.Parse("24/02/2012 09:00:00"), TimeSpan.FromHours(24));
// result == {28/02/2012 17:00:00}

24/02/2012 09:00:00 -> 24/02/2012 17:00:00 (08:00:00)
27/02/2012 09:00:00 -> 27/02/2012 17:00:00 (08:00:00)
28/02/2012 09:00:00 -> 28/02/2012 17:00:00 (08:00:00)
Community
  • 1
  • 1
dtb
  • 213,145
  • 36
  • 401
  • 431
  • tried this but what do i pass as parameter to while (dt.IsWeekendDay()); – Zaki Feb 23 '12 at 14:45
  • @Sam1: IsWeekendDay is an [extension method](http://msdn.microsoft.com/en-us/library/bb383977.aspx). You could also write `IsWeekendDay(dt)` instead of `dt.IsWeekendDay()`. – dtb Feb 23 '12 at 14:57
0

Not an elegant solution but it seems to work.

DateTime AddHoursIgnoreWeekend(DateTime startDate, int hours)
{
    DateTime endDate = startDate;

    for (int i = 0; i < hours; i++) {

        endDate = endDate.AddHours(1);

        if (endDate.DayOfWeek == DayOfWeek.Saturday || endDate.DayOfWeek == DayOfWeek.Sunday) 
        {
            i--;
        }
    }

    return endDate;
 }
Hank Tuff
  • 185
  • 5
  • 12
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 07 '22 at 01:04