Search code examples
c#date-comparisontimeofday

Detecting work shift based on DateTime.Now and predefined hours comparison. c#


I want my program to reset statistics when new work shift starts. My shift hours are predefined as:

  • Day shift 4:00 - 16:25 Mon-Thu (4:00 - 15:55 Fri-Sun)
  • Eve shift 16:25 - 4:00 Mon-Thu (15:55 - 4:00 Fri-Sun)

This is what I'm doing at the moment, it works, but I can't figure out how to do a Time comparison when end of shift is in the AM, so right now it works when end of shift is set to 23:59:59, but when I change it to 4AM, it falls on its ass...

        DateTime d1 = new DateTime(2020, 01, 01, 16, 25, 00); //Mon-Thur shift change
        DateTime d2 = new DateTime(2020, 01, 01, 15, 55, 00); //Fri-Sun shift change
        DateTime d3 = new DateTime(2020, 01, 01, 23, 59, 59); //Evening shift end
        DateTime d4 = new DateTime(2020, 01, 01, 04, 00, 00); //Morning shift start
        DateTime d5 = DateTime.Now;


        if (d5.DayOfWeek == DayOfWeek.Monday || d5.DayOfWeek == DayOfWeek.Tuesday || d5.DayOfWeek == DayOfWeek.Wednesday || d5.DayOfWeek == DayOfWeek.Thursday)
        {
            if (d5.TimeOfDay >= d4.TimeOfDay && d5.TimeOfDay < d1.TimeOfDay)
            {
                dayShift = true;
                eveShift = false;
            }
        }
        if (d5.DayOfWeek == DayOfWeek.Friday || d5.DayOfWeek == DayOfWeek.Saturday || d5.DayOfWeek == DayOfWeek.Sunday)
        {
            if (d5.TimeOfDay >= d4.TimeOfDay && d5.TimeOfDay < d2.TimeOfDay)
            {
                dayShift = true;
                eveShift = false;
            }
        }
        if (d5.DayOfWeek == DayOfWeek.Monday || d5.DayOfWeek == DayOfWeek.Tuesday || d5.DayOfWeek == DayOfWeek.Wednesday || d5.DayOfWeek == DayOfWeek.Thursday)
        {
            if (d5.TimeOfDay >= d1.TimeOfDay && d5.TimeOfDay < d3.TimeOfDay)
            {
                dayShift = false;
                eveShift = true;
            }
        }
        if (d5.DayOfWeek == DayOfWeek.Friday || d5.DayOfWeek == DayOfWeek.Saturday || d5.DayOfWeek == DayOfWeek.Sunday)
        {
            if (d5.TimeOfDay >= d2.TimeOfDay && d5.TimeOfDay < d3.TimeOfDay)
            {
                dayShift = false;
                eveShift = true;
            }
        }

What could I do to be able to compare the TimeOfDay to AM hours?

I'd like to check if the time is between d1 and d4. Any ideas?


Solution

  • I would change the approach here. rather than a set of times, I'd instead define what constitutes a 'shift' definition in an object, have a list of those objects and then iterate through them and test the current date/time against them...

    This ended up being far more complicated than I thought at first glance, because 2:33AM on Tuesday would actually be towards the end of Monday's Evening shift I guess...

    But I'd do this:

    public enum ShiftType
    {
        Unknown, //default..
        Day,
        Night
    }
    
    public class DailyShiftDetail
    {
    
        public IEnumerable<DayOfWeek> DaysOfWeek {get;set;}
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        public ShiftType Type { get; set; }
    }
    public class ShiftCheck
    {
        //define shifts on each day.  This data could come from a database or something
        private static IEnumerable<DailyShiftDetail> shifts = new List<DailyShiftDetail>
        {
            new DailyShiftDetail
            {
                //Mon-Thu daytime shift
                DaysOfWeek=new[]
                {
                    DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday 
                },
                Start = new DateTime(2000,1,1,4,0,0), //4AM
                End = new DateTime(2000,1,1,16,25,0),  //4:25PM
                Type=ShiftType.Day
            },
            new DailyShiftDetail
            {
                //Fri-Sun daytime shift
                DaysOfWeek=new[]
                {
                    DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday 
                },
                Start = new DateTime(2000,1,1,04,0,0), //4AM
                End = new DateTime(2000,1,1,15,55,0),  //3:55PM
                Type=ShiftType.Day
            },            
            new DailyShiftDetail
            {
                //Mon-Thu Evening shift
                DaysOfWeek=new[]
                {
                    DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday 
                },
                Start = new DateTime(2000,1,1,16,25,0),
                End = new DateTime(2000,1,1,4,0,0),
                Type=ShiftType.Night
            },
            new DailyShiftDetail
            {
                //Fri-Sun Evening shift
                DaysOfWeek=new[]
                {
                    DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday 
                },
                Start = new DateTime(2000,1,1,15,55,0),
                End = new DateTime(2000,1,1,4,0,0),
                Type=ShiftType.Night
            }
        };
    
        public static ShiftType GetShiftTypeForDate(DateTime testDate)
        {                        
            //you could probably do this all with a .Where() or
            //.FirstOrDefault, but the logic in the LINQ stuff
            //would end up being a bit painful.  This is probably
            //a little less efficient but will perform fine,
            //and the logic is easier to follow.
            DailyShiftDetail matchingShift = null;
    
            foreach(var testShift in shifts)
            {
                var shiftStart = testShift.Start.TimeOfDay;
                var shiftEnd = testShift.End.TimeOfDay;
    
                //check logic differently for evening shifts,
                //as not only are the times going over midnight,
                //but in fact 2AM on a tuesday is a *Monday EVENING* shift...
                //So we need to check the following days too..               
                if (shiftStart > shiftEnd)
                {                    
                    //if the testing time is earlier than the end of shift, then it's between
                    //midnight and the shift time.  
                    if (testDate.TimeOfDay < shiftEnd)                        
                    {
                        //we need to work out what day this 'shift' would have started on...
                        //this will not be the DayOfWeek of the passed-in time, but the day before...
                        //just cast to int, subtract one, account for rollover and cast back to DayOfWeek...
                        int dayBefore = (int)testDate.DayOfWeek - 1;
                        if (dayBefore < 0) 
                        {
                            dayBefore = 6;
                        }
                        var dayOfWeekBefore = (DayOfWeek)dayBefore;
    
                        //now we've worked out the theoretical day the shift starts, we can test that.
                        if (testShift.DaysOfWeek.Contains(dayOfWeekBefore))
                        {
                            matchingShift = testShift;
                        }
                    }
                    //otherwise, if the test time of day is after the start of the shift then we can check the
                    //current day and if that matches the shift we can return it too
                    else if (
                        testDate.TimeOfDay > shiftStart
                        && testShift.DaysOfWeek.Contains(testDate.DayOfWeek))
                    {
                        matchingShift = testShift;
                    }
                }
                else
                {
                    //daytime shift.
                    //simple logic.  Day matches and test time between start and end.
                    //Assume end time is EXCLUSIVE as otherwise 4:00AM EXACTLY is in two different shifts..
                    if (
                        testShift.DaysOfWeek.Contains(testDate.DayOfWeek)
                        && testShift.Start.TimeOfDay <= testDate.TimeOfDay
                        && testShift.End.TimeOfDay > testDate.TimeOfDay
                        )
                    {
                        matchingShift = testShift;                    
                    }
                }
    
                //if we found a match, stop looping through shifts.
                if (matchingShift != null)
                {
                    break;
                }                                    
            }
    
            if (matchingShift == null)
            {
                //couldn't work it out, so return this...
                return ShiftType.Unknown;
            }
            //found a shift record, return its type.
            return matchingShift.Type;
        }
    }
    

    To use, you can test any date/time and get back either 'Day' or 'Night'

    //Today, tuesday 22nd @ 5:55pm.. would be an evening shift?
    var nightShift = ShiftCheck.GetShiftTypeForDate(
        new DateTime(2020, 9, 22, 17, 55, 0));
    //is ShiftType.Night
    
    //today, tuesday 22nd @ 6AM... would be a day shift?
    var dayShift = ShiftCheck.GetShiftTypeForDate(
        new DateTime(2020, 9, 22, 6, 0, 0));
    //is ShiftType.Day
    
    //a friday at 15:57 would be a night shift
    var friNightShift = ShiftCheck.GetShiftTypeForDate(
        new DateTime(2020, 9, 18, 15, 57, 0));
    //is ShiftType.Night
    
    //a Wednesday at 15:57 would be a DAY shift though
    var wedDayShift = ShiftCheck.GetShiftTypeForDate(
        new DateTime(2020, 9, 16, 15, 57, 0));
    //is ShiftType.Day.
    
    

    I've only tested briefly so possibly some logic errors in there somewhere, but should be fairly easy to debug....