Search code examples
c#datetime.net-coregroup-byintervals

Date time range of shift in a day split into intervals of 10 minutes C#


I am wondering if anyone can help me, I have a collection of start times and end times for a work shift of a day. the hours can be spread across the day. I am trying group by the hour in day (like 0, 1, 2...23, 24) and show that hour in intervals of 10 minutes or worked or not worked. So, I would like to get the end result like below:

I want to able distinguish between worked and not on a hourly basis, the input provides the worked but I have calculate the not worked, I created a method for handling if a time falls outside a 10 minute interval it will set to the nearest one. Method called DoRounding:

Example:

9 am => 0 - 10 Worked 
       10 - 20 Not Worked
       20 - 30 Worked
       30 - 40 Worked
       40 - 50 Worked
       50 - 60 Worked

The time that fails out of the period can be be handled like so

private static int DoRounding(DateTime date)
{

    if (Enumerable.Range(0, 10).Contains(date.Minute))
        return 0;

    if (Enumerable.Range(10, 20).Contains(date.Minute))
        return 20;

    if (Enumerable.Range(20, 30).Contains(date.Minute))
        return 30;

    if (Enumerable.Range(30, 40).Contains(date.Minute))
        return 40;

    if (Enumerable.Range(40, 50).Contains(date.Minute))
        return 50;

    return 60;
}

My method to explode the workblock (I was trying to break down the work period into hours here so I could add the missing parts in another method)

    public static IEnumerable<Tuple<int, DateTime>> CalculateIntervals(WorkPeriod workBlock)
    {
        yield return new Tuple<int, DateTime>(workBlock.StartTime.Hour, workBlock.StartTime);

        var dateTime = new DateTime(workBlock.StartTime.Year, workBlock.StartTime.Month, workBlock.StartTime.Day, workBlock.StartTime.Hour, workBlock.StartTime.Minute, 0, workBlock.StartTime.Kind).AddHours(1);

        while (dateTime < workBlock.EndTime)
        {
            yield return new Tuple<int, DateTime>(dateTime.Hour, dateTime);
            dateTime = dateTime.AddHours(1);
        }

        yield return new Tuple<int, DateTime>(workBlock.EndTime.Hour, workBlock.EndTime);
    }

My attempt at grouping (I want to group into the time slots here to the hour and the intervals such as 1 pm, 0 - 10 minutes and mark it as worked but if an interval was missing from here add it as not worked)

    public static void WorkingHourIntervalStrings(List<WorkPeriod> WorkingHours)
    {
        var output = new List<Tuple<int, DateTime>>();

        foreach (var result in WorkingHours.Select(CalculateIntervals))
            output.AddRange(result);

        output = output.OrderBy(x => x.Item2).ToList();

        var test = output.GroupBy(
            p => p.Item1,
            p => p.Item2.Minute,
            (key, g) => new { Worked = key, Minute = g.ToList() });
    }

Class

public class WorkPeriod 
{
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
}

Calling

 var input = new List<WorkPeriod>
 {
   new WorkPeriod { StartTime  = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
   new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
   new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
   new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
};

  TimeIntervals.WorkingHourIntervalStrings(input);

Possible output structure:

public class Interval
{
    public Interval() => Contents = new List<Contents>();

    public int Hour { get; set; }
    public List<Contents> Contents { get; set; }
}

public class Contents
{
    public bool Worked { get; set; }
    public int Start { get; set; }
    public int End { get; set; }
}

 

Solution

  • Based on your above explanations I would do the following:

    public class Interval
    {
        public Interval() => Contents = new List<Contents>();
    
        public int Hour { get; set; }
        public List<Contents> Contents { get; set; }
    }
    
    public class Contents
    {
        public bool Worked { get; set; }
        public int Start { get; set; }
        //public int End { get; set; }
        public int End => Start + 10;
    }
    
    public class WorkPeriod
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }
    

    Look at the Contents class. The End property is autocalculated from the Start one. Then I would create the following Calculator class:

    public class Calculator
    {
        public bool[] WorkedIntervals = new bool[24 * 6];
    
        private void SetWork(int Hour, int Min)
        {
            int pos = Hour * 6 + Min / 10;
            WorkedIntervals[pos] = true;
        }
    
        private void UpdateIntervals(WorkPeriod period)
        {
            var cur = period.StartTime;
            while (cur < period.EndTime)
            {
                SetWork(cur.Hour, cur.Minute);
                cur = cur.AddMinutes(10);
            }
        }
    
        private void UpdateIntervals(List<WorkPeriod> periods)
        {
            foreach (var l in periods)
                UpdateIntervals(l);
        }
        
        public IEnumerable<Interval> CalcIntervals(List<WorkPeriod> periods)
        {
            var minTime = (from p in periods
                           select p.StartTime).Min();
            var maxTime = (from p in periods
                           select p.EndTime).Max();
    
            UpdateIntervals(periods);
    
            for(int h=minTime.Hour; h<=maxTime.Hour; ++h)
            {
                int pos = h * 6;
                var intrvl = new Interval() { Hour = h };
                for (int m=0; m<=5; m++)
                {
                    if (WorkedIntervals[pos + m])
                        intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = true });
                    else
                        intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = false });
                }
                yield return intrvl;
            }
        }
    }
    

    The idea is that you have to flatten all your time intervals to an array of 144 boolean values (24*6) that represents if each of this 10 minute time interval has been worked or not. eg. if the 7th index of the array is true then it means that at Hour 1 (hour 0 is in indexes 0-5) the 10-20 min interval has been worked.

    Then, on your main function you do the following.

    var input = new List<WorkPeriod>
    {
       new WorkPeriod { StartTime  = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
       new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
       new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
       new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
    };
    
    Calculator ints = new Calculator();
    var res = ints.CalcIntervals(input).ToList();
    

    The res list should contain the hour-intervals from the minimum StartTime to the maximum EndTime with their respected sub-lists.