-1

Given a date and a number of hours, I want to calculate a new date if I subtract the hours using only weekday hours (no weekend hours (no Saturday or Sunday hours)).

I have the following two functions to calculate a date when subtracting weekday hours. Is there a better way?

static DateTime SubtractWeekdayHours
    (
        DateTime date2subtractFrom
        , int hours2subtract
    )
    {
        if (hours2subtract < 1 || hours2subtract > 120)
            throw new Exception("SubtractWeekdayHours() only subtracts hours between 1 and 120 inclusive.");

        var weekdayDateTime = DateTime.Now;

        //  Check if the end of the span touches the weekend:
        if (date2subtractFrom.DayOfWeek == DayOfWeek.Saturday || date2subtractFrom.DayOfWeek == DayOfWeek.Sunday)
        {
            Debug.Write(", End of span EXCEPTION: " + date2subtractFrom.ToString("F"));
            var SaturdayStartOf = date2subtractFrom.DayOfWeek == DayOfWeek.Saturday ? date2subtractFrom.Date : date2subtractFrom.AddDays(-1).Date;
            weekdayDateTime = SaturdayStartOf.AddHours(-hours2subtract);
        }
        else
        {   //  Check if the start of the span touches the weekend:
            var possibleWeekendDate = date2subtractFrom.AddHours(-hours2subtract);
            if (possibleWeekendDate.DayOfWeek == DayOfWeek.Saturday || possibleWeekendDate.DayOfWeek == DayOfWeek.Sunday)
            {
                Debug.Write(", Start of span EXCEPTION: " + possibleWeekendDate.ToString("F"));
                weekdayDateTime = calculateWeekdayDateTimeWhenSpanTouchesWeekend(possibleWeekendDate, date2subtractFrom, hours2subtract);
            }
            else
            {
                var simpleSubtraction = false;
                var daysToCheck = hours2subtract / 24;
                if (daysToCheck > 0)
                {   //  Check if any part of the span touches the weekend:
                    var weekendTouched = false;
                    for (var x = -daysToCheck; x < 0; x++)
                    {
                        possibleWeekendDate = date2subtractFrom.AddDays(x);
                        if (possibleWeekendDate.DayOfWeek == DayOfWeek.Saturday || possibleWeekendDate.DayOfWeek == DayOfWeek.Sunday)
                        {
                            weekendTouched = true;
                            Debug.Write(", Span EXCEPTION:" + possibleWeekendDate.ToString("F"));
                            weekdayDateTime = calculateWeekdayDateTimeWhenSpanTouchesWeekend(possibleWeekendDate, date2subtractFrom, hours2subtract);
                            break;
                        }
                    }

                    if (!weekendTouched)
                    {   //  Span start and end do not touch a weekend and do not span a weekend, so it is just simple subtraction:
                        simpleSubtraction = true;
                    }
                }
                else
                {   //  Span start and end do not touch a weekend and the number of hours are less than 24, so it is just simple subtraction:
                    simpleSubtraction = true;
                }

                if (simpleSubtraction)
                {
                    Debug.Write(", Simple subtraction:");
                    weekdayDateTime = date2subtractFrom.AddHours(-hours2subtract);
                }
            }
        }

        return weekdayDateTime;
    }

    private static DateTime calculateWeekdayDateTimeWhenSpanTouchesWeekend
    (
        DateTime weekendDate
        , DateTime date2subtractFrom
        , int hours2subtract
    )
    {
        var MondayStartOf = weekendDate.DayOfWeek == DayOfWeek.Saturday ? weekendDate.AddDays(2).Date : weekendDate.AddDays(1).Date;
        var timeSpan = date2subtractFrom - MondayStartOf;
        var hoursLeft = hours2subtract - timeSpan.TotalHours;
        var SaturdayStartOf = MondayStartOf.AddDays(-2);
        return SaturdayStartOf.AddHours(-hoursLeft);
    }
Ashwin Nirmul
  • 61
  • 1
  • 8
  • I would iterate back from day to day ... just because I would expect the next todo: Include holidays - But this is a question for http://codereview.stackexchange.com – Sir Rufo Jun 27 '17 at 15:53
  • I would honestly create my own exception to handle this. That way I could just wrap the date calculation in a try-catch block. Call it a WeekendSpanException or something. Probably help prevent the soon to be maintenance nightmare. – Ingenioushax Jun 27 '17 at 16:05

1 Answers1

1

I believe this works as you describe, but you should throw some tests at it. Basically what I would do is just continually subtract an hour from our start date in a loop and, if the resulting date is not one we should ignore, decrement our "hours" counter until we reach zero.

This method allows the caller to pass in a list of DayOfWeek (days) and a list of DateTime (dates) that should be ignored. Below it is the implementation that reflects what you're doing (ignoring weekends).

static DateTime SubtractHours(DateTime startDate, int hours, 
    List<DayOfWeek> daysToIgnore = null, List<DateTime> datesToIgnore = null)
{
    if (hours < 1) throw new ArgumentOutOfRangeException(nameof(hours), 
        "hours must be a positive integer");
    if (daysToIgnore == null) daysToIgnore = new List<DayOfWeek>();
    if (datesToIgnore == null) datesToIgnore = new List<DateTime>();
    var endDate = startDate;

    do
    {
        // In this loop, we continually subtract an hour from our start date
        endDate = endDate.AddHours(-1);

        // If that does not result in a day of week or date that 
        // we should ignore, then subtract one from our hours
        if (!daysToIgnore.Any(d => d.Equals(endDate.DayOfWeek)) &&
            !datesToIgnore.Any(d => d.Date.Equals(endDate.Date)))
        {
            hours--;
        }
    } while (hours > 0);

    return endDate;
}

Here's the method that would replace the one you created:

static DateTime SubtractWeekdayHours(DateTime startDate, int hours)
{
    var daysOfWeekToIgnore = new List<DayOfWeek> {DayOfWeek.Saturday, DayOfWeek.Sunday};
    return SubtractHours(startDate, hours, daysOfWeekToIgnore);
}

Note: I did not add your restriction of 120 hours max, but you can add that part!

Rufus L
  • 36,127
  • 5
  • 30
  • 43