2

Given multiple time span, how can I get an array that gives me number of working hours for a day.

The working hours start at 09:00 and end at 17:00

Examples:

  1. 15-Nov-2017 16:00 - 17-Nov-2017 12:00 -> Should give me an array of [1,8,3]

  2. 15-Nov-2017 09:00 - 17-Nov-2017 12:00 -> Should give me an array of [8,8,1] (This seems easy as I can do Hrs / 8 and Hrs % 8 to get the result.)

  3. 16-Nov-2017 15:00 - 17-Nov-2017 15:00 -> Should give me an array of [2,6]

This is the method that currently gives me total working hours between the start time and end time:

protected double getWorkingHrs(DateTime _dtTaskStart, DateTime _dtTaskEnd)
{
    double count = 0;

    for (var i = _dtTaskStart; i < _dtTaskEnd; i = i.AddHours(1))
    {
        if (i.DayOfWeek != DayOfWeek.Saturday && i.DayOfWeek != DayOfWeek.Sunday)
        {
            if (i.TimeOfDay.Hours >= 9 && i.TimeOfDay.Hours < 17)
            {
                count++;
            }
        }
    }

    return count;
}

Any help would be appreciated.

Task showed like this in 8 hour blocks. So when I add a 20 Hour task, I have to break them down into sets and display them in the grid. I have to split them as chances are the task on different day might get assigned to a different user and may have different notes etc.

UI for placing tasks

Cheers

Amyth
  • 1,367
  • 1
  • 9
  • 15
  • 2
    How is Entity Framework involved here? I doubt that you'll find anything particular in EF about this. – Jon Skeet Nov 15 '17 at 07:50
  • Sorry, was going to add more that later relates to entity, but did not and forgot to remove the tag. The array len later gives me the total number of records to insert using entity. – Amyth Nov 15 '17 at 07:52
  • "Given multiple time span" - it doesn't sound like you actually want to provide a TimeSpan, you want to provide two date/times. Then for each date, return the number of hours work on that day. Is that right? How do the times for the start/end affect other days? It sounds like they shouldn't - every day that's not in the start/end is effectively midnight to midnight? – Jon Skeet Nov 15 '17 at 08:01
  • there are answers to this question here : https://stackoverflow.com/questions/5005983/determine-the-difference-between-two-datetimes-only-counting-opening-hours but the accepted solution is very similar to what OP already posted – Timothy Groote Nov 15 '17 at 08:07
  • So, this for a simple task scheduling app that I am writing. A user can add a task that are more than 8 Hrs long, Say for example 20 Hrs. The task can start at any time of the day. I am displaying them in 8 hour blocks -> See question for updated image. – Amyth Nov 15 '17 at 08:08

3 Answers3

2

Here is a solution for your problem:

    public static double[] GetWorkHours(DateTime start, DateTime end, TimeSpan workStartTime, TimeSpan workEndTime)
    {
        IList<double> result = new List<double>();

        if (start > end)
        {
            throw new ArgumentException("Start date is later than end date");
        }

        DateTime currentDate = start.Date;
        while (currentDate <= end.Date)
        {
            if (currentDate.DayOfWeek != DayOfWeek.Saturday && currentDate.DayOfWeek != DayOfWeek.Sunday)
            {
                TimeSpan realStartTime = GetStartTimeForDay(start, end, workStartTime, currentDate);
                TimeSpan realEndTime = GetEndTimeForDay(start, end, workEndTime, currentDate);

                if (realStartTime >= workEndTime || realEndTime <= workStartTime)
                {
                    result.Add(0);
                }
                else
                {
                    result.Add(GetHoursInTimePeriod(realStartTime, realEndTime));
                }
            }

            currentDate = currentDate.AddDays(1);
        }

        return result.ToArray();
    }

    private static TimeSpan GetStartTimeForDay(DateTime start, DateTime end, TimeSpan workStartTime, DateTime day)
    {
        return day == start.Date ? (start.TimeOfDay < workStartTime ? workStartTime : start.TimeOfDay) : workStartTime;
    }

    private static TimeSpan GetEndTimeForDay(DateTime start, DateTime end, TimeSpan workEndTime, DateTime day)
    {
        return day == end.Date ? (end.TimeOfDay < workEndTime ? end.TimeOfDay : workEndTime) : workEndTime;
    }

    private static double GetHoursInTimePeriod(TimeSpan start, TimeSpan end)
    {
        return (end - start).TotalHours;
    }
Artak
  • 2,819
  • 20
  • 31
  • Thanks a lot, that actually gives the result I am after. The GetStartTimeForDay and GetEndTimeForDay is the piece that was missing. – Amyth Nov 15 '17 at 09:16
1

While not the prettiest (doing a ratcheting loop over datetimes by adding hours is a tad inelegant IMHO.), your function can be adapted to provide an enumerable with the number of working hours (or business hours as they are more commonly called) for each day as follows :

protected IEnumerable<double> getWorkingHrsPerDay(DateTime _dtTaskStart, DateTime _dtTaskEnd)
{
    double count = 0;
    DateTime lastHandledDate = _dtTaskStart.Date;
    for (var i = _dtTaskStart; i < _dtTaskEnd; i = i.AddHours(1))
    {
        if (i.DayOfWeek != DayOfWeek.Saturday && i.DayOfWeek != DayOfWeek.Sunday)
        {
            if (i.TimeOfDay.Hours >= 9 && i.TimeOfDay.Hours < 17)
            {
                count++;
            }
        }
        //if we have started to count the hours for a new day
        if (lastHandledDate.Date != i.Date)
        {
            lastHandledDate = i.Date;
            double hourCount = count;
            //reset our hour count
            count = 0;
            //we return the count of the last day
            yield return hourCount;
        }
        lastHandledDate = i.Date;
    }
}

you could then do the following with LINQ's ToArray method :

double[] hoursPerDay = getWorkingHrsPerDay(beginDate, endDate).ToArray();
Timothy Groote
  • 8,614
  • 26
  • 52
  • also, if you are only counting whole hours anyway, why use `double` to count the hours? i'd say an `int` is more fitting – Timothy Groote Nov 15 '17 at 08:27
  • and in the case of begin friday morning, en on monday night, this would return [8,0,0,8] (because of saturday and sunday). – Timothy Groote Nov 15 '17 at 08:29
  • As the app progresses it will take into consideration .5 Hr blocks, but as of now was keeping it simple and hence used double. – Amyth Nov 15 '17 at 09:02
  • Thanks for the solution, I could have dropped the 0, that would have given me [8,8]. The iteration that adds the task already ignores weekends so should have been fine :) – Amyth Nov 15 '17 at 09:19
0

If you want to have HOUR and Minutes use https://learn.microsoft.com/en-us/dotnet/api/system.timeonly?view=net-7.0
If you use older framework<6.0 install this package https://www.nuget.org/packages/Portable.System.DateTimeOnly

Here is it something which I did for stock market data in "date" is my current datetime the data which I work is from 8:30 to 16:00

        TimeOnly startTime = new TimeOnly(8, 30);
        TimeOnly endTime = new TimeOnly(16, 00);
        TimeOnly currentTime = new TimeOnly(date.Hour, date.Minute); 

        if (currentTime >= startTime && currentTime <= endTime)
        { //only data from 8:30 to 16.00
         
        }
Valentin Petkov
  • 1,570
  • 18
  • 23