时间查重方案的设计
- 项目场景:
- 需求描述:
- 解决方案:
- 1.处理流程
- 2.周计划的时间段处理
- 3.时间段处理和比较
- 总结
项目场景:
项目场景:上层下发任务计划(包括周计划和日计划),每个计划都有一个对应的时间段,设备要去校验这些时间段是否有重复的地方,如果有两个任务计划之间存在交叉的时间,设备端应当报错,不予保存
需求描述:
单从需求来看,单个日计划的查重校验比较容易,但是任务计划里还包含有周计划,每个周计划可以选其中某几个周X,根据任务选择的有效期不同,对应时间段中的日期是不确定的。比如指定2021.5.31-2021.6.3的周计划,可以选择周一到周四每一天,也可以选择其中某几天,这就提升了时间段查重的困难性,因为不能直接将任务计划中的时间段拿出来作比较
解决方案:
1.处理流程
考虑到周计划的特殊性,因为不能直接跟日计划进行比较,所以我们需要对周计划进行拆解,现将周计划转换成对应的日计划,然后再逐一比较,最后再去判断是否存在交叉的时间段。具体实现流程如下:
2.周计划的时间段处理
其中又得着重说下周计划的转换流程,是如何将计划中的每个周X转换成对应的日期与天计划来进行比较的,这里得经过好几个步骤,首先是得判断制定的周计划中有几个对应的周X,然后再将有效期内的周X转换成对应的日期
具体的实现代码如下:
/** @fn : CaculateWeekDay
* @brief : 计算当前日期是周几
* @param date : 待计算的日期 %Y-%m-%d
* @return : # 周X
* @author :
*/
int CaculateWeekDay(char *date)
{
int y = 0,m = 0, d = 0;
sscanf(date, "%d-%d-%d", &y,&m,&d);
if(m==1||m==2) {
m+=12;
y--;
}
int iWeek=(d+2*m+3*(m+1)/5+y+y/4-y/100+y/400)%7;
switch(iWeek)
{
case 0: return 1; break;
case 1: return 2; break;
case 2: return 3; break;
case 3: return 4; break;
case 4: return 5; break;
case 5: return 6; break;
case 6: return 7; break;
default: return 0;break;
}
}
/** @fn : day_diff
* @brief : 计算两个日期之间的天数
* @param start_date : 开始日期 %Y-%m-%d
* @param end_date : 结束日期 %Y-%m-%d
* @param day : 当前周几 1-7
* @return : # 开始日期和结束日期之间的天数 2021-03-25 - 2021-03-26 -> 两天
* @author :
*/
int day_diff(char *start_date, char *end_date)
{
int y2, m2, d2;
int y1, m1, d1;
int year_start, month_start, day_start;
int year_end, month_end, day_end;
sscanf(start_date, "%d-%d-%d", &year_start,&month_start,&day_start);
sscanf(end_date, "%d-%d-%d", &year_end,&month_end,&day_end);
m1 = (month_start + 9) % 12;
y1 = year_start - m1/10;
d1 = 365*y1 + y1/4 - y1/100 + y1/400 + (m1*306 + 5)/10 + (day_start - 1);
m2 = (month_end + 9) % 12;
y2 = year_end - m2/10;
d2 = 365*y2 + y2/4 - y2/100 + y2/400 + (m2*306 + 5)/10 + (day_end - 1);
return (d2 - d1)+1;
}
/** @fn : get_dayofweek_num
* @brief : 获取当前时间段中有几个周X
* @param start_date : 开始日期 %Y-%m-%d
* @param end_date : 结束日期 %Y-%m-%d
* @param day : 当前周几 1-7
* @return : # 当前日期内的周几的个数
* @author :
*/
int get_dayofweek_num(char *start_date, char *end_date,char day)
{
int dayNum = day_diff(start_date,end_date); //计算总天数
int sDateSpare,eDateSpare;
sDateSpare = CaculateWeekDay(start_date); //计算起始日期是周几
eDateSpare = sDateSpare + dayNum%7 - 1; //计算多余的日期结束是周几
if((eDateSpare > 7) && (day < sDateSpare)) //结束和开始的相对日期跨周 且比开始日期小才需要加一周
{
return dayNum/7 + ((day+7 >= sDateSpare && day+7 <= eDateSpare) ? 1:0);
}
return dayNum/7 + ((day >= sDateSpare && day <= eDateSpare) ? 1:0);
}
3.时间段处理和比较
周计划转换成对应的日计划后用,还需要与之前的日计划进行比较,当计划比较多的时候就需要全都记录下来,逐个去比较。这里采用链表的方式记录每个时间段的节点,然后再去遍历判断是否存在交叉的时间段,如果存在则记录错误节点。
/*********************************************************************************
* Description: 从链表中搜索当前时间段是否冲突
* Input:
* Output: 无
* Return: 未冲突 HPR_TRUE 冲突 HPR_ERROR
* Others: 无
*******************************************************************************/
HPR_INT32 search_list_for_time_is_conflict(T_DATE_NODE *pDate)
{
isapi_check(pDate, HPR_ERROR);
T_DATE_NODE *pNode = NULL;
LIST_FOR_EACH(T_DATE_NODE, pNode, &tDateList)
{
if(pDate->level == pNode->level)
{
if((pDate->start_time <= pNode->stop_time) && (pDate->stop_time >= pNode->start_time)
&&(pDate->begin_time <= pNode->end_time)&& (pDate->end_time >= pNode->begin_time))
{
return HPR_ERROR;
}
}
}
return HPR_TRUE;
}
/**@brief 校验计划任务中是否存在时间段冲突
* @param[in] start_time 任务有效期开始时间
stop_time 任务有效期结束时间
begin_time 任务执行的开始时间
end_time 任务执行的结束时间
weekday 周计划中的星期X
* @param[out]
* @return 校验结果 HPR_ERROR 时间冲突
HPR_OK 时间段未冲突
*/
INT32 check_time_is_conflict(char level,char *start_time,char *stop_time,char *begin_time,char *end_time,char weekday)
{
INT32 iRet = HPR_OK;
T_DATE_NODE tmpResult = {0};
T_DATE_NODE *tPlanTime = NULL;
INT32 count=0, dayOfWeek=0, dayNum=0, date=0, i=0;
INT32 year=0, month=0, day=0, hour=0, minute=0, second=0;
char tmpDate[16] = {0};
// 转换传入的日期数据
tmpResult.level = level;
sscanf(start_time, "%d-%d-%d", &year,&month,&day);
tmpResult.start_time = year*10000 + month*100 + day;
sscanf(stop_time, "%d-%d-%d", &year,&month,&day);
tmpResult.stop_time = year*10000 + month*100 + day;
sscanf(begin_time, "%d:%d:%d", &hour,&minute,&second);
tmpResult.begin_time = hour*10000 + minute*100 + second;
sscanf(end_time, "%d:%d:%d", &hour,&minute,&second);
tmpResult.end_time = hour*10000 + minute*100 + second;
if(false != weekday)
{
count = get_dayofweek_num(start_time,stop_time,weekday); //计算当前有效期段内周X有多少个
if(count == 0)
{
ISAPI_INFO("the weekday(%d) does not exist in the period(%s---%s) \n",weekday,start_time,stop_time)
iRet = HPR_NOSUPPORT;
goto exit;
}
dayOfWeek = CaculateWeekDay(start_time); //计算起始日期是周几
dayNum = weekday + (weekday < dayOfWeek ? 7 : 0) - dayOfWeek; //计算一个周期内周X与起始日期的间隔天数
(void)memcpy_s(tmpDate,MAX_TIME_LEN,start_time,MAX_TIME_LEN);
for(i=0;i<count;i++)
{
tPlanTime = malloc_node_from_date_pool();
if(NULL == tPlanTime)
{
ISAPI_ERR("node pool malloc error\n");
return HPR_ERROR;
}
tPlanTime->level = tmpResult.level;
tPlanTime->begin_time = tmpResult.begin_time;
tPlanTime->end_time = tmpResult.end_time;
date_convert(tmpDate,&date,dayNum+i*7); //转换当前有效期内对应周X的日期
tPlanTime->start_time = date;
tPlanTime->stop_time = date;
if(HPR_ERROR == search_list_for_time_is_conflict(tPlanTime))
{
ISAPI_INFO("the plan time '%d' is conflict\n",tPlanTime->begin_time)
iRet = HPR_ERROR;
goto exit;
}
lstAdd(&tDateList,&tPlanTime->node);
}
}
else
{
tPlanTime = malloc_node_from_date_pool();
if(NULL == tPlanTime)
{
ISAPI_ERR("node pool malloc error\n");
return HPR_ERROR;
}
tPlanTime->level = tmpResult.level;
tPlanTime->begin_time = tmpResult.begin_time;
tPlanTime->end_time = tmpResult.end_time;
tPlanTime->start_time = tmpResult.start_time;
tPlanTime->stop_time = tmpResult.stop_time;
if(HPR_ERROR == search_list_for_time_is_conflict(tPlanTime))
{
ISAPI_INFO("the plan time '%d' is conflict\n",tPlanTime->begin_time)
iRet = HPR_ERROR;
goto exit;
}
lstAdd(&tDateList,&tPlanTime->node);
}
exit:
return iRet;
}
总结
本文仅仅简单实现了时间查重的比较方法,可以对下发的任务计划进行时间段的查重校验,实际使用中功能是都可以基本满足,就是在效率上还应该进一步优化,由于是遍历查询,当任务计划到达上限时,效率比较低。在时间段转换上或许也可能存在更优的方法