1

I want to bind the correct amount of hours to correct work period. The work week is from Monday to Sunday. Based on the time of the day the work has different price. The work price based on time of the day when is done are called Periods. We are using 24 hours time format.

Example:

  • Period1: Mon - Fri, 00:00 - 07:00
  • Period2: Mon - Fri, 07:00 - 20:00
  • Period3: Mon - Fri, 20:00 - 22:00
  • Period4: Mon - Fri, 22:00 - 23:59 (midnight)
  • Period5: Sat - Sun, 00:00 - 07:00
  • Period6: Sat - Sun, 07:00 - 22:00
  • Period7: Sat - Sun, 22:00 - 23:59 (midnight)
  • Period8: Holiday, 00:00 - 23:59

The Period is represented like this:

public class Period
{
   public Period()
   {
   }

   public string Name { get; set; }
   public int Days { get; set; }
   public bool Holiday { get; set; }
   public TimeSpan Start { get; set; }
   public TimeSpan End { get; set; }
}

Days is int but the value will come from this enum:

[Flags]
public enum Workweek
{
    Sunday = 1,
    Monday = 2,
    Tuesday = 4,
    Wednesday = 8,
    Thursday = 16,
    Friday = 32,
    Saturday = 64
}

When the Period Days property will be 62 It means the Period is valid from Monday - Friday, when Days is: 65 the Period is from Saturday to Sunday. When Days is: 6 the Period is from Monday to Tuesday.

When Holiday is true means that the Period is only valid on holiday days. Holiday day overrides normal day or weekend.

The work shift is represented like this:

public class Work
{
   public Work()
   {
   }

   public string Name { get; set; }
   public DateTime Start { get; set; }
   public DateTime End { get; set; }
}

Assuming I have a List<Work> and a List<Period> how can I bind the hours worked to the correct Period? Each Period has different pricing.

Example:

Case 1: If you do a work shift on Monday between: 15:00 and 23:00, then that work shift will be something like this:

  • Period2: 5 hours
  • Period3: 2 hours
  • Period4: 1 hour

Case 1.1: If that specific Monday is holiday, then it will be:

  • Period8: 8 hours

Case 2: If Instead the work starts on Monday at: 20:00 and ends at 04:00 next day the result will be:

  • Period3: 2 hours
  • Period4: 2 hours
  • Period1: 4 hours

Case 2.2: If that Monday is holiday, the it will be:

  • Period8: 4 hours
  • Period1: 4 hours

The holidays days are as a List<DateTime>.

How can i match the hours to correct period?

What I tried so far:

Work work = new Work()
{
    Name = Demo,
    Star = new DateTime(2017,05,02,15,00,00);
    End = new DateTime(2017,05,02,23,00,00);
};

List<Period> periods = new List<Period>();

foreach (var period in periods)
{
    Workweek shiftDay = (Workweek)Enum.Parse(typeof(Workweek), work.Start.DayOfWeek, true); // i think this is wrong, because in the case where the end date is in the next day


    Workweek periodDay = (Workweek)period.Days;

    if ((shiftDay & periodDay) == shiftDay)
    {
        // the work matches the period
    }

}

I am thinking I should use a foreach and loop through each second between start date and end date and check if that second matches the period day.

Something like this:

  public static IEnumerable<DateTime> EachSecond(this Work work)
    {
        DateTime currentSecond = new DateTime(work.Start.Year, work.Start.Month, work.Start.Day, work.Start.Hour, work.Start.Minute, work.Start.Second, work.Start.Millisecond);
        while (currentSecond <= wor.kEnd)
        {
            yield return currentSecond;
            currentSecond = currentSecond.AddSeconds(1);
        }
    }

   Work work = new Work()
   {
      Name = Demo,
      Star = new DateTime(2017,05,02,15,00,00);
      End = new DateTime(2017,05,02,23,00,00);
   };

    List<Period> periods = new List<Period>();

    foreach (var second in work.EachSecond())
    {
          Workweek shiftSecond = (Workweek)Enum.Parse(typeof(Workweek), second.DayOfWeek, true);

          foreach (var period in periods)
          {
               Workweek periodDay = (Workweek)period.Days;

               if ((shiftSecond & periodDay) == shiftSecond)
               {

               }
          }
    }
user2818430
  • 5,853
  • 21
  • 82
  • 148
  • 2
    You need to show at least some attempt to solve this, stackoverflow is not a code writing service after all. – Evk Jun 02 '17 at 10:29
  • @Evk: Updated the question – user2818430 Jun 02 '17 at 10:42
  • I think the approach is not too far fetched. From the top of my head I see two things: 1. Do you really need to iterate through seconds? The period is minute based, so I would also use minutes in the matching process. 2. You could also look at [DateTime.CompareTo](https://msdn.microsoft.com/de-de/library/system.datetime.compare(v=vs.110).aspx) and then use save the duration in between which would save the looping, but require a bit more code probably. I can't produce an example at the moment :/ – Markus Deibel Jun 02 '17 at 11:55
  • 2
    Something like [this](https://dotnetfiddle.net/ScpGiu)? – Sir Rufo Jun 02 '17 at 11:58
  • @SirRufo: Please add your comment as a reply so I can accept it as answer. Appreciated! – user2818430 Jun 02 '17 at 17:32

2 Answers2

1

Try something like this. I don't know why you need a custom Workweek when you can use the standard DayOfWeek. Put I followed your request anyway.

using System.Collections.ObjectModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleApplication57
{
    class Program
    {
        static void Main(string[] args)
        {

        }

    }
    public class Period
    {
        public static List<Period> periods = new List<Period>() {
            new Period() { Name = "Period1", Days =  new Workweek[] { Workweek.Monday, Workweek.Tuesday, Workweek.Wednesday, Workweek.Thursday, Workweek.Friday}, Holiday = false, Start = new TimeSpan(0,0,0), End = new TimeSpan(7,0,0)},
            new Period() { Name = "Period2", Days =  new Workweek[] { Workweek.Monday, Workweek.Tuesday, Workweek.Wednesday, Workweek.Thursday, Workweek.Friday}, Holiday = false, Start = new TimeSpan(7,0,0), End = new TimeSpan(20,0,0)},
            new Period() { Name = "Period3", Days =  new Workweek[] { Workweek.Monday, Workweek.Tuesday, Workweek.Wednesday, Workweek.Thursday, Workweek.Friday}, Holiday = false, Start = new TimeSpan(20,0,0), End = new TimeSpan(22,0,0)},
            new Period() { Name = "Period4", Days =  new Workweek[] { Workweek.Monday, Workweek.Tuesday, Workweek.Wednesday, Workweek.Thursday, Workweek.Friday}, Holiday = false, Start = new TimeSpan(22,0,0), End = new TimeSpan(24,0,0)},
            new Period() { Name = "Period5", Days =  new Workweek[] { Workweek.Saturday, Workweek.Sunday}, Holiday = false, Start = new TimeSpan(0,0,0), End = new TimeSpan(7,0,0)},
            new Period() { Name = "Period6", Days =  new Workweek[] { Workweek.Saturday, Workweek.Sunday}, Holiday = false, Start = new TimeSpan(7,0,0), End = new TimeSpan(22,0,0)},
            new Period() { Name = "Period7", Days =  new Workweek[] { Workweek.Saturday, Workweek.Sunday}, Holiday = false, Start = new TimeSpan(22,0,0), End = new TimeSpan(24,0,0)},
            new Period() { Name = "Period8", Days = null, Holiday = true, Start = new TimeSpan(0,0,0), End = new TimeSpan(24,0,0)},
        };

        public string Name { get; set; }
        public Workweek[] Days { get; set; }
        public bool Holiday { get; set; }
        public TimeSpan Start { get; set; }
        public TimeSpan End { get; set; }

        public static string GetName(DateTime startTime, DateTime endTime, Boolean holiday)
        {
            string name = "";

            if (holiday)
            {
                name = "Period8";
            }
            else
            {

                foreach (Period period in periods)
                {
                    Boolean dayMatch = period.Days.Select(x => x == (Workweek)(2 ^ (int)startTime.DayOfWeek)).Any();
                    if (dayMatch)
                    {
                        if ((startTime.TimeOfDay > period.Start) && (endTime.TimeOfDay < period.End))
                        {
                            name = period.Name;
                            break;
                        }
                    }

                }
            }

            return name;
        }
    }
    public enum Workweek
    {
        Sunday = 1,
        Monday = 2,
        Tuesday = 4,
        Wednesday = 8,
        Thursday = 16,
        Friday = 32,
        Saturday = 64
    }
    public class Work
    {

        public string Name { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }


}
jdweng
  • 33,250
  • 2
  • 15
  • 20
1

Working example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

public class Program
{
    public static void Main( string[] args )
    {
        var periods = new List<Period> {
            new Period( "1", Days.Workdays, TimeSpan.FromHours(0), TimeSpan.FromHours(7) ),
            new Period( "2", Days.Workdays, TimeSpan.FromHours(7), TimeSpan.FromHours(20) ),
            new Period( "3", Days.Workdays, TimeSpan.FromHours(20), TimeSpan.FromHours(22) ),
            new Period( "4", Days.Workdays, TimeSpan.FromHours(22), TimeSpan.FromHours(24) ),
            new Period( "5", Days.Weekend, TimeSpan.FromHours(0), TimeSpan.FromHours(7) ),
            new Period( "6", Days.Weekend, TimeSpan.FromHours(7), TimeSpan.FromHours(22) ),
            new Period( "7", Days.Weekend, TimeSpan.FromHours(22), TimeSpan.FromHours(24) ),
            new Period( "8", Days.Holiday, TimeSpan.FromHours(0), TimeSpan.FromHours(24) ),
        };
        var holidays = new List<DateTime> {
            new DateTime( 2017, 1, 1 ),
            new DateTime( 2017, 1, 3 ),
            new DateTime( 2017, 1, 6 ),
        };

        var sc = new ShiftCalculator( periods, holidays );

        var shiftperiods = sc.GetShiftPeriods( new DateTime( 2016, 12, 31, 22, 00, 00 ), new DateTime( 2017, 01, 07, 08, 00, 00 ) ).ToList();

        foreach ( var sp in shiftperiods )
        {
            Console.WriteLine( "{0} - {1} - {2} - {3:00.00}h", sp.Period.Name, sp.Period.Days, sp.Start, sp.Duration.TotalHours );
        }

    }
}

[Flags]
enum Days : byte
{
    Sunday = 1,
    Monday = 2,
    Tuesday = 4,
    Wednesday = 8,
    Thursday = 16,
    Friday = 32,
    Saturday = 64,
    Holiday = 128,

    Workdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekend = Saturday | Sunday,
}

[DebuggerDisplay("{Name}: {Days} ({Start}-{End})")]
class Period
{
    public Period( string name, Days days, TimeSpan start, TimeSpan end )
    {
        if ( days.HasFlag( Days.Holiday ) && days != Days.Holiday )
            throw new ArgumentException( "days" );

        if ( start > end )
            throw new ArgumentException( "end" );

        Name = name;
        Days = days;
        Start = start;
        End = end;
    }

    public string Name { get; private set; }
    public Days Days { get; private set; }
    public TimeSpan Start { get; private set; }
    public TimeSpan End { get; private set; }
}

class ShiftPeriod
{
    public Period Period { get; set; }
    public DateTime Start { get; set; }
    public TimeSpan Duration { get; set; }
}

class ShiftCalculator
{
    private readonly List<Period> _periods;
    private readonly List<DateTime> _holidays;

    public ShiftCalculator( IEnumerable<Period> periods, IEnumerable<DateTime> holidays )
    {
        _periods = periods.ToList();
        _holidays = holidays.Select( e => e.Date ).ToList();
    }

    public IEnumerable<ShiftPeriod> GetShiftPeriods( DateTime start, DateTime end )
    {
        if ( start > end ) throw new ArgumentException( "end" );

        var current = start;

        while ( current < end )
        {
            var period = GetPeriodByDateTime( current );

            var next = current.Date + period.End;

            if ( next > end )
            {
                next = end;
            }

            yield return new ShiftPeriod
            {
                Period = period,
                Start = current,
                Duration = next - current,
            };

            current = next;
        }

    }

    private Days GetDayFromDateTime( DateTime datetime )
    {
        Days day;
        if ( _holidays.Contains( datetime.Date ) )
        {
            day = Days.Holiday;
        }
        else
        {
            switch ( datetime.DayOfWeek )
            {
                case DayOfWeek.Sunday:
                day = Days.Sunday;
                break;
                case DayOfWeek.Monday:
                day = Days.Monday;
                break;
                case DayOfWeek.Tuesday:
                day = Days.Tuesday;
                break;
                case DayOfWeek.Wednesday:
                day = Days.Wednesday;
                break;
                case DayOfWeek.Thursday:
                day = Days.Thursday;
                break;
                case DayOfWeek.Friday:
                day = Days.Friday;
                break;
                case DayOfWeek.Saturday:
                day = Days.Saturday;
                break;
                default:
                throw new InvalidOperationException();
            }
        }
        return day;
    }

    private Period GetPeriodByDateTime( DateTime datetime )
    {
        var day = GetDayFromDateTime( datetime );
        var timeOfDay = datetime.TimeOfDay;
        var period = _periods.Where(
            e => e.Days.HasFlag( day ) && e.Start <= timeOfDay && e.End > timeOfDay )
            .FirstOrDefault();
        if ( period == null )
        {
            throw new InvalidOperationException();
        }
        return period;
    }
}

.net fiddle

Generated output

7 - Weekend - 12/31/2016 10:00:00 PM - 02.00h
8 - Holiday - 1/1/2017 12:00:00 AM - 24.00h
1 - Workdays - 1/2/2017 12:00:00 AM - 07.00h
2 - Workdays - 1/2/2017 7:00:00 AM - 13.00h
3 - Workdays - 1/2/2017 8:00:00 PM - 02.00h
4 - Workdays - 1/2/2017 10:00:00 PM - 02.00h
8 - Holiday - 1/3/2017 12:00:00 AM - 24.00h
1 - Workdays - 1/4/2017 12:00:00 AM - 07.00h
2 - Workdays - 1/4/2017 7:00:00 AM - 13.00h
3 - Workdays - 1/4/2017 8:00:00 PM - 02.00h
4 - Workdays - 1/4/2017 10:00:00 PM - 02.00h
1 - Workdays - 1/5/2017 12:00:00 AM - 07.00h
2 - Workdays - 1/5/2017 7:00:00 AM - 13.00h
3 - Workdays - 1/5/2017 8:00:00 PM - 02.00h
4 - Workdays - 1/5/2017 10:00:00 PM - 02.00h
8 - Holiday - 1/6/2017 12:00:00 AM - 24.00h
5 - Weekend - 1/7/2017 12:00:00 AM - 07.00h
6 - Weekend - 1/7/2017 7:00:00 AM - 01.00h
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • how can we make it work if we add a period that spans over the midnight Like this: `new Period( "100", Days.Workdays, TimeSpan.FromHours(22), TimeSpan.FromHours(07) ` Notice the Start Time is 22:00 and End Time is: 07:00. – user2818430 Sep 05 '17 at 21:16