前段时间,用户提出一个关于时间段统计的需求,要求:存在多个时间段的任务,有任务的开始时间和结束时间,任务的时间可能是交叉进行,然后统计出所有任务完成的总时间。
一、明确需求
需求出来之后,首先要做的就是需求分析,而该任务的主要是通过时间段来进行统计,统计连续时间段差值的总和, 例如有如下时间段:
开始时间 |
结束时间 |
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; }
小结:
在解决该需求时,开始的时候有点蒙,不知道从何入手。然后找到分情况进行处理,再到对其性能进行优化。这一过程其实就是一个很重要的学习历程,从迷茫到有思路,从解决到优化,跟我们现在的学习是一样的。问题总是耐不住琢磨的,琢磨透原理之后,在实现上就简单多了。