Add hours to datetime but exclude weekends and should be between working hours
Asked Answered
W

6

6

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);
                }
    }
Waterlog answered 23/2, 2012 at 13:26 Comment(5)
Related: C# - Duration between two DateTimes in minutesContingent
If your date is a Monday at 4PM, and you add 10 hours, do you want to get to Wednesday at 10AM?Dory
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...SorryWaterlog
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.Bhili
ah I guess didnt test fridays. Have answered your questionWaterlog
B
9
 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);
 }
Bhili answered 23/2, 2012 at 14:26 Comment(7)
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?Bhili
yes you are right it should be Tuesday if Friday + 16 hours, because it is excluding weekends.Waterlog
@Sam1 I have re-implemented some better code which will handle weekends better among other things.Bhili
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.Dutiable
I need this but in phpHaplite
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.Siderosis
@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.Bhili
M
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
Mustache answered 23/2, 2012 at 15:37 Comment(2)
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!Merrifield
@Tim: Many thanks. Developing time calculations can be an error-prone task and is, in my opinion, predestined for a library.Mustache
A
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;
}
Abernathy answered 23/2, 2012 at 13:45 Comment(3)
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.Bhili
@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.Abernathy
@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.Bhili
C
2

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)
Contingent answered 23/2, 2012 at 14:2 Comment(2)
tried this but what do i pass as parameter to while (dt.IsWeekendDay());Waterlog
@Sam1: IsWeekendDay is an extension method. You could also write IsWeekendDay(dt) instead of dt.IsWeekendDay().Contingent
B
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;
 }
Biocatalyst answered 4/10, 2022 at 3:29 Comment(1)
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.Labelle
U
0

@BlueMonkMN 's answer is great. However, I rectified a couple of issues there and now it worked perfectly for me.

Function SLARespondBy(start As DateTime, SLA As Integer) As String
    Const hoursPerDay As Integer = 12
    Const startHour As Integer = 7

    Dim isWholeDayNeeded As Boolean = True
    If start.AddMinutes(SLA).TimeOfDay = TimeSpan.FromHours(19) Then
        isWholeDayNeeded = False
    End If

    Dim offset As TimeSpan = TimeSpan.FromMinutes(SLA)

    ' Don't start counting hours until start time is during working hours
    If start.TimeOfDay.TotalHours > startHour + hoursPerDay Then
        start = start.Date.AddDays(1).AddHours(startHour)
    End If
    If start.TimeOfDay.TotalHours < startHour Then
        start = start.Date.AddHours(startHour)
    End If
    If start.DayOfWeek = DayOfWeek.Saturday Then
        start = start.AddDays(2) ' Move to Monday
        start = New DateTime(start.Year, start.Month, start.Day, 7, 0, 0) ' Set time to 7:00 AM
    ElseIf start.DayOfWeek = DayOfWeek.Sunday Then
        start = start.AddDays(1) ' Move to Monday
        start = New DateTime(start.Year, start.Month, start.Day, 7, 0, 0) ' Set time to 7:00 AM
    End If

    ' Calculate how much working time already passed on the first day
    Dim firstDayOffset As TimeSpan = start.TimeOfDay.Subtract(TimeSpan.FromHours(startHour))

    ' Calculate number of whole days to add
    Dim wholeDays As Integer = 0
    If isWholeDayNeeded Then
        wholeDays = CInt(Math.Truncate(offset.Add(firstDayOffset).TotalHours / hoursPerDay))
    End If

    ' How many hours off the specified offset does this many whole days consume?
    Dim wholeDaysHours As TimeSpan = TimeSpan.FromHours(wholeDays * hoursPerDay)

    ' Calculate the final time of day based on the number of whole days spanned and the specified offset
    Dim remainder As TimeSpan = offset - wholeDaysHours

    ' How far into the week is the starting date?
    Dim weekOffset As Integer = ((CInt(start.DayOfWeek) + 7) - CInt(DayOfWeek.Monday)) Mod 7

    ' How many weekends are spanned?
    Dim weekends As Integer = CInt(Math.Truncate((wholeDays + weekOffset) / 5))

    ' Calculate the final result using all the above calculated values
    Dim finalDateTime As DateTime = start.AddDays(wholeDays + weekends * 2).Add(remainder)
    '
    Return finalDateTime.ToString("MM/dd/yyyy h:mm tt")
End Function
Unction answered 5/5 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.