前段时间,用户提出一个关于时间段统计的需求,要求:存在多个时间段的任务,有任务的开始时间和结束时间,任务的时间可能是交叉进行,然后统计出所有任务完成的总时间。
一、明确需求
需求出来之后,首先要做的就是需求分析,而该任务的主要是通过时间段来进行统计,统计连续时间段差值的总和, 例如有如下时间段:
开始时间 | 结束时间 |
2016-9-1 | 2016-9-1-15 |
2016-9-1 | 2016-9-1-13 |
2016-9-3 | 2016-9-1-17 |
2016-9-19 | 2016-9-1-21 |
2016-9-27 | 2016-9-1-29 |
分析该5个时间段,发现前三个属于连续时间段,可以合并为一个时间段:2016-9-1~2016-9-17,这样我们可以很容易看出该5个时间段的总时间为:16+2+2=20。
二、解决思路及过程:
需求确定之后,开始着手解决。首先,对于时间差值计算,存在三种情况:交叉、相离、包含。如下图:
1)数据查询处理:
每一种情形又分成了两种情况,这样很容易给我们的程序处理增加判断条件,所以我们可以在查询数据时做一些简化工作,比如我们可以让时间以开始时间、结束时间做升序排序,这样就可以使每种情形只保留一种情况(图中第②中情况就不存在了)。
2)数据处理:
在三种情形中,“包含”和“相交”这两种的时间差都属于最大时间-最小时间,而“分离”属于两者各自的时间差相加。涉及到多个时间段时,可以使用分组操作,先将前两种情形处理并相加,然后把第三种情况临时存放,最后进行递归调用。
具体代码如下:
public int getDays(DataTable dt) {
/*
* 需求说明:
*获取多个时间段的时间差:差值是连续时间段的差值。
*比如:2016-9-1~2016-9-1-15、2016-9-1~2016-9-1-13、2016-9-3~2016-9-1-17、
*2016-9-19~2016-9-1-21、2016-9-27~2016-9-1-29 求该5个时间段的差值
*前3个时间段称为连续时间段,可以合并成2016-9-1~2016-9-17
*所以总的时间差是:16+2+2=20
*
*/
/*
* 解决思路:
* 1、首先从数据库查询数据(开始时间与结束时间是两个字段),并以开始时间、结束时间从小到大排序,例如:select * from test_date order by begin_date ,end_date
* 2、时间段分析:分为三种情况
* (1)相交
* (2)包含
* (3)相离
* 其中“相交”和“包含”都表示时间段为连续时间段,时间差=最大结束时间-最小结束时间
* 3、递归 使用递归法来处理“相离”的情况
*
*/
//定义并创建table,用于存放相离的数据,进行递归
DataTable dt1 = new DataTable();
DataRow dr ;
//定义列,跟查询出来的table列及列名一致
dt1.Columns.Add("ID");
dt1.Columns.Add("BEGIN_DATE");
dt1.Columns.Add("END_DATE");
//取出第一个时间段(本实例采用dataTable接收查询数据)
//开始时间
DateTime d = Convert.ToDateTime(dt.Rows[0][1].ToString());
//结束时间
DateTime d1 = Convert.ToDateTime(dt.Rows[0][2].ToString());
//假设第一个时间段为最小开始时间和最大结束时间
DateTime max = d1;
DateTime min = d;
//第一个时间段的时间差
int days = d1.Subtract(d).Days;
//循环求值
for (int i = 1; i < dt.Rows.Count; i++) {
//s表示开始时间,e表示结束时间
DateTime s = Convert.ToDateTime(dt.Rows[i ][1]);
DateTime e = Convert.ToDateTime(dt.Rows[i ][2]);
//相交
if (s < max && e >max)
{
//天数=上一次天数+(本次最大结束时间-上一最大结束时间)
days += e.Subtract(max).Days;
//本次最大结束时间更换为最大结束时间
max = e;
}
//包含
else if (s < max && e <= max)
{
//此时无需增加
days += 0;
}
//相离
else if (s >= max)
{
//将相离的数据存入table
dr = dt1.NewRow();
dr["ID"] = i;
dr["BEGIN_DATE"] = s;
dr["END_DATE"] = e;
dt1.Rows.Add(dr);
}
}
//判断相离的table中是否有数据
if (dt1.Rows.Count > 0) {
//存在相离数据,进行递归调用
days += getDays(dt1);
}
return days;
}
三、优化
功能实现之后,还得去考虑性能上的事情。在上述的实现过程中,在处理“相离”数据时,使用了递归调用。这样会增加内存的消耗,占用额外资源,给性能带来一定的负担。所以,需要对其进行优化。
优化后代码:
public int getDay(DataTable dt) {
//开始时间
DateTime d = Convert.ToDateTime(dt.Rows[0][1].ToString());
//结束时间
DateTime d1 = Convert.ToDateTime(dt.Rows[0][2].ToString());
//假设第一个时间段为最小开始时间和最大结束时间
DateTime max = d1;
DateTime min = d;
//第一个时间段的时间差
int days = d1.Subtract(d).Days;
//循环求值
for (int i = 1; i < dt.Rows.Count; i++)
{
//s表示开始时间,e表示结束时间
DateTime s = Convert.ToDateTime(dt.Rows[i][1]);
DateTime e = Convert.ToDateTime(dt.Rows[i][2]);
//相交
if (s < max && e > max)
{
//天数=上一次天数+(本次最大结束时间-上一最大结束时间)
days += e.Subtract(max).Days;
//本次最大结束时间更换为最大结束时间
max = e;
}
//包含
else if (s < max && e <= max)
{
//此时无需增加
days += 0;
}
//相离
else if (s >= max)
{
days += e.Subtract(s).Days;
max = e;
}
}
return days;
}
小结:
在解决该需求时,开始的时候有点蒙,不知道从何入手。然后找到分情况进行处理,再到对其性能进行优化。这一过程其实就是一个很重要的学习历程,从迷茫到有思路,从解决到优化,跟我们现在的学习是一样的。问题总是耐不住琢磨的,琢磨透原理之后,在实现上就简单多了。