Как обрабатывать временные интервалы, когда день начинается в разные часы?

У меня есть проблема с обработкой периодов даты и времени, когда день делится на смены, которые начинаются в разные часы. Я ищу, чтобы решить эту проблему в C#

Пример 1. У меня есть событие, которое начинается с 20150901 с 06:00 до 20150902 с 03:00. У меня есть рабочие смены: D (с 06:00 до 18:00) и N (с 18:00 до 06:00). Я хочу назначить событие для значений даты и времени смен.

START DATETIME      END DATETIME
20150901 0600       20150902 0300

SHIFT  START  END
D     0600   1800
N     1800   0600

Временные интервалы, которые я должен получить:

DATE     SHIFT    STARTTIME    ENDTIME
20150901 D        0600         1800
20150901 N        1800         0300

Пример 2: день делится на четыре смены, но день начинается в 06:00 (01: 0600-1200, 02: 1200-1800, 03: 1800-2400, 04: 0000-0600). Мероприятие проходит с 20150901 08:00 до 20150903 10:00 утра.

START DATETIME      END DATETIME
20150901 0800       20150903 1000

SHIFT  START  END
01     0600   1159
02     1200   1759
03     1800   2359
04     0000   0559

Временные интервалы, которые я должен получить:

DATE     SHIFT    STARTTIME   ENDTIME
20150901 01       0800        1200
20150901 02       1200        1800
20150901 03       1800        0000
20150901 04       0000        0600
20150902 01       0600        1200
20150902 02       1200        1800
20150902 03       1800        0000
20150902 04       0000        0600
20150903 01       0600        1000

Пример 3. День делится на четыре смены, но день начинается в 06:00 (01: 0600-1200, 02: 1200-1800, 03: 1800-2400, 04: 0000-0600). Мероприятие проходит с 20150901 02:00 до 20150902 10:00 утра.

START DATETIME      END DATETIME
20150901 0200       20150902 1000

SHIFT  START  END
01     0600   1159
02     1200   1759
03     1800   2359
04     0000   0559

Временные интервалы, которые я должен получить:

DATE     SHIFT    STARTTIME   ENDTIME
20150831 04       0200        0600
20150901 01       0800        1200
20150901 02       1200        1800
20150901 03       1800        0000
20150901 04       0000        0600
20150902 01       0600        1000

Отредактировано 1: добавить исходный код, который у меня есть.

Отредактировано 2: добавить пример 3 и исправить некоторые замечания Мэтта Джонсона

Код работает в некоторых случаях, но у меня все еще возникают ошибки, когда событие как-то выходит за границы сдвига (например, событие начинается до первого поворота смены).

    public class slot
    {
        public DateTime date;
        public TimeSpan startHour;
        public TimeSpan endHour;
        public string shiftCode;
    }


    public List<slot> getSlots()
    {
        slot[] shifts = new slot[4];
        shifts[0] = new slot();
        shifts[0].startHour = new TimeSpan(06, 00, 00);
        shifts[0].endHour = new TimeSpan(12, 00, 00);   
        shifts[0].shiftCode = "01";
        shifts[1] = new slot();
        shifts[1].startHour = new TimeSpan(12, 00, 00);
        shifts[1].endHour = new TimeSpan(18, 00, 00);
        shifts[1].shiftCode = "02";
        shifts[2] = new slot();
        shifts[2].startHour = new TimeSpan(18, 00, 00);
        shifts[2].endHour = new TimeSpan(00, 00, 00);
        shifts[2].shiftCode = "03";
        shifts[3] = new slot();
        shifts[3].startHour = new TimeSpan(00, 00, 00);
        shifts[3].endHour = new TimeSpan(06, 00, 00);
        shifts[3].shiftCode = "04";

        DateTime startEvent = new DateTime(2015, 09, 01, 08, 00, 00);
        DateTime endEvent = new DateTime(2015, 09, 03, 10, 00, 00);

        int i = 0;
        //To find the starting shift for the event
        while (!(
                shifts[i].startHour <= shifts[i].endHour
                    ? (startEvent.TimeOfDay >= shifts[i].startHour && startEvent.TimeOfDay < shifts[i].endHour)
                    : (startEvent.TimeOfDay >= shifts[i].startHour || startEvent.TimeOfDay < shifts[i].endHour)
                ))
            i++;

        DateTime slotStart = startEvent;
        DateTime slotEnd = slotStart.Date + shifts[i].endHour;

        List<slot> slotList = new List<slot>();

        while(endEvent >= slotEnd)
        {
            slot newSlot = new slot();
            newSlot.date = slotStart;
            newSlot.startHour = slotStart.TimeOfDay;
            newSlot.endHour = shifts[i].endHour;
            newSlot.shiftCode = shifts[i].shiftCode;
            slotList.Add(newSlot);
            i++;

            if (i >= shifts.Length)
                i = 0;

            if (shifts[i].startHour < newSlot.endHour)
                slotStart = slotStart.Date.AddDays(1);

            slotEnd = slotStart.Date + shifts[i].endHour;
            slotStart = slotStart.Date + shifts[i].startHour;
        }

        slot lastSlot = new slot();
        lastSlot.date = slotStart;
        lastSlot.startHour = slotStart.TimeOfDay;
        lastSlot.endHour = endEvent.TimeOfDay;
        lastSlot.shiftCode = shifts[i].shiftCode;
        slotList.Add(lastSlot);

        return slotList;
    }

3 ответа

Несколько вещей:

  1. Вам нужно отделить приращение даты от сдвига-сброса:

    У тебя есть:

    if (i >= shifts.Length)
    {
        i = 0;
        slotStart = slotStart.AddDays(1);
    }
    

    Который должен быть заменен на:

    if (i >= shifts.Length)
    {
        i = 0;
    }
    
    if (shifts[i].startHour < newSlot.startHour)
    {
        slotStart = slotStart.Date.AddDays(1);
    }
    

    Это был главный виновник.

  2. В вашем тесте на время запуска логика просто немного отключена. Так должно быть:

    while (!(
        shifts[i].startHour <= shifts[i].endHour
            ? (startEvent.TimeOfDay >= shifts[i].startHour && startEvent.TimeOfDay < shifts[i].endHour)
            : (startEvent.TimeOfDay >= shifts[i].startHour || startEvent.TimeOfDay < shifts[i].endHour)
        ))
        i++;
    

    То, что вы написали, немного отличается. Вы использовали && объединить первый тест с истинной частью, но это не логично, когда смешивается с остальной частью уравнения.

  3. В общем, вы должны рассмотреть возможность настройки вашего кода для использования полуоткрытых диапазонов. Вместо смены с 06:00 до 17:59 она должна идти с 06:00 до 18:00. Затем вы используете эксклюзивные операторы при тестировании конечной точки. Другими словами start <= testValue < end, Это имеет несколько преимуществ, в том числе упрощение вычитания для получения длительности и предотвращение ошибок точности (так как 17:59:59.999 все еще находится в диапазоне). ПРИМЕЧАНИЕ: этот пункт основан на исходном вопросе перед редактированием.,

  4. Вы могли бы, вероятно, сэкономить много проблем, используя LINQ вместо этих циклов. Кроме того, ваши соглашения об именах не соответствуют обычному стилю C#.

  5. Эти типы проблем легче рассуждать при использовании типов Noda Time, таких как LocalDate, LocalTime, а также LocalDateTime, Если позже вам потребуется учесть часовые пояса, переход на летнее время и т. Д., Вы будете в гораздо более выгодном положении для сопоставления с ZonedDateTime значения, рассчитать Duration ценности или проект для Instant ценности.

  6. Задумывались ли вы над тем, что делать, если весь день не сменяется сменой? Возможно, вы захотите рассчитать отсутствующий период и показать его как вывод ошибки или с кодом сдвига "UNASSIGNED".

Чтобы помочь вам, верхний цикл может выглядеть примерно так:

var currentTime = startTime;
while(currentTime < endTime) {
    var shift = FindShift(currentTime);
    if (shift.EndTime > endTime) {
        shift.EndTime = endTime;
    }
    Print(shift);
    currentTime = shift.EndTime;
}

Это результирующий код, который я сейчас использую. До сих пор я тестировал его с несколькими данными и он работал нормально, даже во временных рамках.

Я следовал некоторым указаниям, которые дал мне Мэтт Джонсон, чтобы заставить его работать, так что спасибо, Мэтт.

Следует иметь в виду, что дни смены начинаются в час первой смены. Это означает, что если сменная группа начинается в 06:00 утра, предыдущие часы дня принадлежат предыдущему дню.

Пример: день делится на четыре смены. Мероприятие проходит с 20150901 02:00 до 20150902 04:00 утра. Таким образом, даже если кажется, что он начинается с 20150901 и заканчивается в 20150902 в соответствии с днями смены, это будет 20150931 и 20150901

START DATETIME      END DATETIME
20150901 0200       20150902 0400

SHIFT  START  END
01     0600   1159
02     1200   1759
03     1800   2359
04     0000   0559

Временные интервалы, которые я должен получить:

DATE     SHIFT    STARTTIME   ENDTIME
20150831 04       0200        0600
20150901 01       0800        1200
20150901 02       1200        1800
20150901 03       1800        0000
20150901 04       0000        0400

Исходный код:

    public class Slot
    {
        public DateTime Date;
        public TimeSpan StartHour;
        public TimeSpan EndHour;
        public string ShiftCode;
    }

    public List<Slot> GetSlots()
    {
        //Shifts Array
        Slot[] shifts = new Slot[4];
        shifts[0] = new Slot();
        shifts[0].StartHour = new TimeSpan(06, 00, 00);
        shifts[0].EndHour = new TimeSpan(12, 00, 00);   
        shifts[0].ShiftCode = "01";
        shifts[1] = new Slot();
        shifts[1].StartHour = new TimeSpan(12, 00, 00);
        shifts[1].EndHour = new TimeSpan(18, 00, 00);
        shifts[1].ShiftCode = "02";
        shifts[2] = new Slot();
        shifts[2].StartHour = new TimeSpan(18, 00, 00);
        shifts[2].EndHour = new TimeSpan(24, 00, 00);
        shifts[2].ShiftCode = "03";
        shifts[3] = new Slot();
        shifts[3].StartHour = new TimeSpan(00, 00, 00);
        shifts[3].EndHour = new TimeSpan(06, 00, 00);
        shifts[3].ShiftCode = "04";

        //Event TimeStamps
        DateTime startEvent = new DateTime(2015, 09, 01, 02, 00, 00);
        DateTime endEvent = new DateTime(2015, 09, 03, 08, 00, 00);

        if (endEvent < startEvent)
            return null;

        int i = 0;
        //To find the starting shift for the event
        while (!(
                shifts[i].StartHour <= shifts[i].EndHour
                    ? (startEvent.TimeOfDay >= shifts[i].StartHour && startEvent.TimeOfDay < shifts[i].EndHour)
                    : (startEvent.TimeOfDay >= shifts[i].StartHour || startEvent.TimeOfDay < shifts[i].EndHour)
                ))
            i++;

        //to establish the date part of the datetime according to the starttime of the shift day
        startEvent = startEvent.AddTicks(-shifts[0].StartHour.Ticks).Date + startEvent.TimeOfDay;
        endEvent = endEvent.AddTicks(-shifts[0].StartHour.Ticks).Date + endEvent.TimeOfDay;

        DateTime slotStart = startEvent;
        DateTime slotEnd = slotStart.Date + shifts[i].EndHour;

        List<Slot> slotList = new List<Slot>();

        while (endEvent.Date > slotEnd.Date || (endEvent.Date == slotEnd.Date && !(
                shifts[i].StartHour <= shifts[i].EndHour
                    ? (endEvent.TimeOfDay >= shifts[i].StartHour && endEvent.TimeOfDay < shifts[i].EndHour)
                    : (endEvent.TimeOfDay >= shifts[i].StartHour || endEvent.TimeOfDay < shifts[i].EndHour)
               )))
        {
            Slot newSlot = new Slot();
            newSlot.Date = slotStart.Date;
            newSlot.StartHour = slotStart.TimeOfDay;
            newSlot.EndHour = shifts[i].EndHour;
            newSlot.ShiftCode = shifts[i].ShiftCode;
            slotList.Add(newSlot);
            i++;

            if (i >= shifts.Length)
            {
                i = 0;
                slotStart = slotStart.Date.AddDays(1);
            }

            slotEnd = slotStart.Date + shifts[i].EndHour;
            slotStart = slotStart.Date + shifts[i].StartHour;
        }

        Slot lastSlot = new Slot();
        lastSlot.Date = slotStart;
        lastSlot.StartHour = slotStart.TimeOfDay;
        lastSlot.EndHour = endEvent.TimeOfDay;
        lastSlot.ShiftCode = shifts[i].ShiftCode;
        slotList.Add(lastSlot);

        return slotList;
    }
Другие вопросы по тегам