Как обрабатывать временные интервалы, когда день начинается в разные часы?
У меня есть проблема с обработкой периодов даты и времени, когда день делится на смены, которые начинаются в разные часы. Я ищу, чтобы решить эту проблему в 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 ответа
Несколько вещей:
Вам нужно отделить приращение даты от сдвига-сброса:
У тебя есть:
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); }
Это был главный виновник.
В вашем тесте на время запуска логика просто немного отключена. Так должно быть:
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++;
То, что вы написали, немного отличается. Вы использовали
&&
объединить первый тест с истинной частью, но это не логично, когда смешивается с остальной частью уравнения.В общем, вы должны рассмотреть возможность настройки вашего кода для использования полуоткрытых диапазонов. Вместо смены с 06:00 до 17:59 она должна идти с 06:00 до 18:00. Затем вы используете эксклюзивные операторы при тестировании конечной точки. Другими словами
start <= testValue < end
, Это имеет несколько преимуществ, в том числе упрощение вычитания для получения длительности и предотвращение ошибок точности (так как 17:59:59.999 все еще находится в диапазоне). ПРИМЕЧАНИЕ: этот пункт основан на исходном вопросе перед редактированием.,Вы могли бы, вероятно, сэкономить много проблем, используя LINQ вместо этих циклов. Кроме того, ваши соглашения об именах не соответствуют обычному стилю C#.
Эти типы проблем легче рассуждать при использовании типов Noda Time, таких как
LocalDate
,LocalTime
, а такжеLocalDateTime
, Если позже вам потребуется учесть часовые пояса, переход на летнее время и т. Д., Вы будете в гораздо более выгодном положении для сопоставления сZonedDateTime
значения, рассчитатьDuration
ценности или проект дляInstant
ценности.Задумывались ли вы над тем, что делать, если весь день не сменяется сменой? Возможно, вы захотите рассчитать отсутствующий период и показать его как вывод ошибки или с кодом сдвига "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;
}