近期在做一项目的过程中,需要涉及到服务人员班次、请假及服务预定等一系列时间管理问题,最后要提供出人员空闲的时间段以供用户选择,归结到底,涉及到两大时间算法,一是时间过滤,即在原定上班班次安排的基础上扣除休假、请假以及已预定的时间段,另一个是时间段合并,需要将一个服务涉及的多位服务人员的有效时间合并,以便用户先选择时间,而后选择该时间段有空的服务人员。下面将分享一下涉及时间的两个算法。
1、合并集合中的各时间段
原始数据:
startTime=2015-05-26 12:06:00 endTime=2015-05-26 15:06:00
startTime=2015-05-27 16:08:00 endTime=2015-05-27 19:08:00
startTime=2015-05-27 13:10:00 endTime=2015-05-27 16:10:00
startTime=2015-05-27 14:57:00 endTime=2015-05-27 17:57:00
startTime=2015-05-27 10:16:00 endTime=2015-05-27 13:16:00
startTime=2015-05-26 11:50:00 endTime=2015-05-26 13:50:00
startTime=2015-05-26 13:46:00 endTime=2015-05-26 16:46:00
startTime=2015-05-27 9:09:00 endTime=2015-05-27 12:09:00
startTime=2015-05-27 16:36:00 endTime=2015-05-27 18:36:00
startTime=2015-05-26 14:39:00 endTime=2015-05-26 16:39:00
结果:
startTime=2015-05-26 11:50:00 endTime=2015-05-26 16:46:00
startTime=2015-05-27 9:09:00 endTime=2015-05-27 19:08:00
核心代码
public static List<EmptyDuration> MergeDateTime(List<EmptyDuration> emptyDurations)
{
List<EmptyDuration> results = new List<EmptyDuration>();
if (emptyDurations.Count == 0)
{
return results;
}
// 对原始数据进行排序
List<EmptyDuration> em = emptyDurations.OrderBy(p => p.StartTime).ToList<EmptyDuration>();
DateTime startTime = em[0].StartTime;
DateTime endTime = em[0].EndTime;
for (int i = 0; i < em.Count - 1; i++)
{
int j = i + 1;
// 处理前后两个没有交叉重叠的情况
if (em[i].EndTime < em[j].StartTime)
{
results.Add(new EmptyDuration
{
StartTime = startTime,
EndTime = endTime
});
startTime = em[j].StartTime;
endTime = em[j].EndTime;
}
else
{
endTime = em[i].EndTime > em[j].EndTime ? em[i].EndTime : em[j].EndTime;
}
}
// 补上最后剩下的时间段
results.Add(new EmptyDuration
{
StartTime = startTime,
EndTime = endTime
});
return results;
}
其它辅助测试代码
public static void MergeTest(int num, int days)
{
List<EmptyDuration> timeList = GeneralDuration(num, days);
List<EmptyDuration> list = MergeDateTime(timeList);
}
public static List<EmptyDuration> GeneralDuration(int count, int maxDateDiff)
{
Random random = new Random(DateTime.Now.Millisecond);
List<EmptyDuration> list = new List<EmptyDuration>();
for (int i = 0; i < count; i++)
{
int hour = random.Next(9, 20);
int hourDiff = random.Next(2, 4);
int min = random.Next(60);
int dateDiff = random.Next(0, maxDateDiff);
DateTime startTime = DateTime.Now.Date.AddDays(dateDiff).AddHours(hour).AddMinutes(min);
DateTime endTime = DateTime.Now.Date.AddDays(dateDiff).AddHours(hour).AddHours(hourDiff).AddMinutes(min);
list.Add(new EmptyDuration
{
ArtificerId = 10002,
ShopId = 20001,
StartTime = startTime,
EndTime = endTime
});
}
return list;
}
2、时间段过滤
在此代码中,左侧是服务人员原定上班时间集合,右侧是员工请假、预定所占时间集合。
原始数据(左):
startTime=2015-05-26 9:00:00 endTime=2015-05-26 22:00:00
startTime=2015-05-27 9:00:00 endTime=2015-05-27 22:00:00
startTime=2015-05-28 9:00:00 endTime=2015-05-28 22:00:00
原始数据(右):
startTime=2015-05-27 19:39:00 endTime=2015-05-27 21:39:00
startTime=2015-05-28 12:56:00 endTime=2015-05-28 15:56:00
startTime=2015-05-26 17:17:00 endTime=2015-05-26 20:17:00
startTime=2015-05-27 9:45:00 endTime=2015-05-27 12:45:00
startTime=2015-05-27 18:08:00 endTime=2015-05-27 20:08:00
startTime=2015-05-28 19:46:00 endTime=2015-05-28 22:46:00
startTime=2015-05-28 12:09:00 endTime=2015-05-28 15:09:00
startTime=2015-05-27 11:55:00 endTime=2015-05-27 13:55:00
startTime=2015-05-28 17:18:00 endTime=2015-05-28 20:18:00
startTime=2015-05-26 18:09:00 endTime=2015-05-26 20:09:00
结果:
startTime=2015-05-26 9:00:00 endTime=2015-05-26 17:17:00
startTime=2015-05-26 20:17:00 endTime=2015-05-26 22:00:00
startTime=2015-05-27 9:00:00 endTime=2015-05-27 9:45:00
startTime=2015-05-27 13:55:00 endTime=2015-05-27 18:08:00
startTime=2015-05-27 21:39:00 endTime=2015-05-27 22:00:00
startTime=2015-05-28 9:00:00 endTime=2015-05-28 12:09:00
startTime=2015-05-28 15:56:00 endTime=2015-05-28 17:18:00
核心代码
public static List<EmptyDuration> SubtractDateTime(List<EmptyDuration> minuend, List<EmptyDuration> subtrahend)
{
List<EmptyDuration> results = new List<EmptyDuration>();
if (minuend.Count == 0 )
{
return results;
}
if (subtrahend.Count == 0)
{
return minuend;
}
// 对原始数据进行排序
List<EmptyDuration> lefts = minuend.OrderBy(p => p.StartTime).ToList<EmptyDuration>();
List<EmptyDuration> rights = subtrahend.OrderBy(p => p.StartTime).ToList<EmptyDuration>();
// 左右两个集合的指针
int lIndex = 0;
int rIndex = 0;
// 左右两个集合的元素数量
int lMax = lefts.Count;
int rMax = rights.Count;
DateTime startTime = DateTime.MinValue;
DateTime endTime = DateTime.MinValue;
// 左右指针是否要下移一位并获取元素数据
bool lIncrease = true;
while (lIndex < lMax && rIndex < rMax)
{
if (lIncrease)
{
startTime = lefts[lIndex].StartTime;
endTime = lefts[lIndex].EndTime;
}
lIncrease = false;
// 处理两个集合当前元素存在交叉重叠的情况
if (endTime > rights[rIndex].StartTime && startTime < rights[rIndex].EndTime)
{
// 上班开始时间在请假预定时间之前
if (startTime < rights[rIndex].StartTime)
{
results.Add(new EmptyDuration
{
StartTime = startTime,
EndTime = rights[rIndex].StartTime
});
// 上班结束时间在请假预定结束时间之前
if (endTime <= rights[rIndex].EndTime)
{
lIncrease = true;
}
else
{
startTime = rights[rIndex].EndTime;
rIndex++;
}
}
else // 上班开始时间在请假预定时间之后
{
// 上班结束在请假预定时间之后
if (endTime > rights[rIndex].EndTime)
{
startTime = rights[rIndex].EndTime;
rIndex++;
}
else
{
lIncrease = true;
}
}
}
else
{
// 上班时间段在请假预定时间段之前
if (startTime < rights[rIndex].StartTime)
{
results.Add(new EmptyDuration
{
StartTime = startTime,
EndTime = endTime
});
lIncrease = true;
}
else
{
rIndex++;
}
}
if (lIncrease)
{
lIndex++;
}
}
// 处理剩余上班时间段
if (lIndex < lMax)
{
results.Add(new EmptyDuration
{
StartTime = startTime,
EndTime = endTime
});
for (int i = lIndex + 1; i < lMax; i++)
{
results.Add(new EmptyDuration
{
StartTime = lefts[i].StartTime,
EndTime = lefts[i].EndTime
});
}
}
return results;
}
其它辅助测试代码:
public static void SubtractTest(int num, int days)
{
List<EmptyDuration> leftList = GeneralDuration(days);
List<EmptyDuration> rightList = GeneralDuration(num, days);
List<EmptyDuration> list = SubtractDateTime(leftList, rightList);
}
public static List<EmptyDuration> GeneralDuration(int days)
{
List<EmptyDuration> list = new List<EmptyDuration>();
for (int i = 0; i < days; i++)
{
list.Add(new EmptyDuration
{
StartTime = DateTime.Now.Date.AddDays(i).AddHours(9),
EndTime = DateTime.Now.Date.AddDays(i).AddHours(22)
});
}
return list;
}