计算两个日期之间的工作日时间差

前言

工作日的计算是一个大的问题, 要考虑假期, 而且每年的假期也不一样。假期完还有工作日调休等。
所以必须要将假期存储在硬盘上的数据(数据库或xml)。
然后计算时间差。
比如, 2018年5月11日星期五, 需要过3个工作日, 那么就是2018年5月16日星期三。

假期的获取

假期每年都是不一样的, 所以

  1. 通过人工查看政策文件手动写入硬盘, 如节假日安排
  2. 通过Api定时任务Quartz进行获取, 如聚合数据Api

以xml形式存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<xml>
<holidays>
<day value="2018-01-01" desc="元旦"/>
<day value="2018-02-15" desc="春节"/>
<day value="2018-02-16" desc="春节"/>
<day value="2018-02-17" desc="春节"/>
<day value="2018-02-18" desc="春节"/>
<day value="2018-02-19" desc="春节"/>
<day value="2018-02-20" desc="春节"/>
<day value="2018-02-21" desc="春节"/>
<day value="2018-04-05" desc="清明节"/>
<day value="2018-04-06" desc="清明节"/>
<day value="2018-04-07" desc="清明节"/>
<day value="2018-04-29" desc="劳动节"/>
<day value="2018-04-30" desc="劳动节"/>
<day value="2018-05-01" desc="劳动节"/>
<day value="2018-06-16" desc="端午节"/>
<day value="2018-06-17" desc="端午节"/>
<day value="2018-06-18" desc="端午节"/>
<day value="2018-09-22" desc="中秋节"/>
<day value="2018-09-23" desc="中秋节"/>
<day value="2018-09-24" desc="中秋节"/>
<day value="2018-10-01" desc="国庆节"/>
<day value="2018-10-02" desc="国庆节"/>
<day value="2018-10-03" desc="国庆节"/>
<day value="2018-10-04" desc="国庆节"/>
<day value="2018-10-05" desc="国庆节"/>
<day value="2018-10-06" desc="国庆节"/>
<day value="2018-10-07" desc="国庆节"/>
</holidays>
<workingdays>
<day value="2018-02-11" desc="调休成为工作日"/>
<day value="2018-02-24" desc="调休成为工作日"/>
<day value="2018-04-08" desc="调休成为工作日"/>
<day value="2018-04-28" desc="调休成为工作日"/>
<day value="2018-09-29" desc="调休成为工作日"/>
<day value="2018-09-30" desc="调休成为工作日"/>
</workingdays>
</xml>

以数据库形式存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
id          day        desc
1 2018-01-01 元旦
2 2018-02-15 春节
3 2018-02-16 春节
4 2018-02-17 春节
5 2018-02-18 春节
6 2018-02-19 春节
7 2018-02-20 春节
8 2018-02-21 春节
9 2018-04-05 清明节
10 2018-04-06 清明节
11 2018-04-07 清明节
12 2018-04-29 劳动节
13 2018-04-30 劳动节
14 2018-05-01 劳动节
15 2018-06-16 端午节
16 2018-06-17 端午节
17 2018-06-18 端午节
18 2018-09-22 中秋节
19 2018-09-23 中秋节
20 2018-09-24 中秋节
21 2018-10-01 国庆节
22 2018-10-02 国庆节
23 2018-10-03 国庆节
24 2018-10-04 国庆节
25 2018-10-05 国庆节
26 2018-10-06 国庆节
27 2018-10-07 国庆节
28 2018-02-11 调休成为工作日
29 2018-02-24 调休成为工作日
30 2018-04-08 调休成为工作日
31 2018-04-28 调休成为工作日
32 2018-09-29 调休成为工作日
33 2018-09-30 调休成为工作日

计算工作日的时间差

代码逻辑都写在注释中了。
其中, holidaysMap<年月日, 节假日名称>workingdaysMap<年月日, 调休>两个Map集合读取的数据, 来源于上述的数据集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WorkDateHelper {
private static final Logger logger = LoggerFactory.getLogger(WorkDateHelper.class);
private static final int DAY_TIME = 24 * 60 * 60 * 1000; // 1天的毫秒数
private static final SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyy-MM-dd");

/**
* 计算工作日的时间差
* @param preDate 前一个日期
* @param nextDate 后一个日期
*/
public static long workTimeDiff(Date preDate, Date nextDate){
Calendar calendar = Calendar.getInstance();

// 1. 保证 前一个日期 大于 后一个日期
long preTime = preDate.getTime(), nextTime = nextDate.getTime();
if(preTime > nextTime){
logger.error("前一个日期"+yyyyMMdd.format(preDate)+"不能大于后一个日期"+yyyyMMdd.format(nextDate));
return -1;
}

// 2. 校准 preTime, 调整到明天0点整点
long timeDiff = 0;
calendar.setTimeInMillis(preTime);
calendar.add(Calendar.DATE, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);

// 2.1. 如果是节假日调休或工作日, 就记录校准时间差
if(isWorkDate(preTime)) {
timeDiff += calendar.getTimeInMillis() - preTime;
}
preTime = calendar.getTimeInMillis();
// logger.debug("校准preTime:"+new Date(preTime)+","+DurationFormatUtils.formatDuration(timeDiff, "**dd HH:mm:ss**", true));

// 3. 校准 nextTime, 调整到当天0点整点
calendar.setTimeInMillis(nextTime);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);

// 3.1. 如果是节假日调休或工作日, 就记录校准时间差
if(isWorkDate(nextTime)) {
timeDiff += nextTime - calendar.getTimeInMillis();
}
nextTime = calendar.getTimeInMillis();
// logger.debug("校准nextTime:"+new Date(nextTime)+","+DurationFormatUtils.formatDuration(timeDiff, "**dd HH:mm:ss**", true));


// 4. 模拟时间流逝, 计算在工作日内的时间差
while(preTime < nextTime) {
// 3. 如果是节假日调休或工作日, 就记录校准时间差
if(isWorkDate(preTime)) {
timeDiff += DAY_TIME;
}
preTime += DAY_TIME;
// logger.debug("时间流逝:"+new Date(preTime)+","+DurationFormatUtils.formatDuration(timeDiff, "**dd HH:mm:ss**", true));
}

return timeDiff;
}

/**
* 判断是否为 工作日, 节假日调休或正常工作日
* @param time 计算的日期
*/
public static boolean isWorkDate(long time){
String day = yyyyMMdd.format(new Date(time));

// 1. 是节假日就返回false
if(holidaysMap.containsKey(day)){
return false;
}

// 2. 是节假日调休就返回true
if(workingdaysMap.containsKey(day)){
return true;
}

// 3. 是周末返回false
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(time);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
if(dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY){
return false;
}

// 4. 是正常工作日就返回true
return true;
}
}