1 背景
一些业务的处理是以工作日为周期进行处理,这就需要判断某个日期是否为工作日,以及计算n个工作日后的日期是多少。
2 工作日判断逻辑
默认情况下周六、日是假期,周一~周五是工作日。
但是国家法定假日和法定假日导致的周、六日调班会打破默认情况。
如果周六、日赶上调班也可以是工作日,周一~周五赶上法定假期也可以是假期
具体判断逻辑如【图1】:
3实现方案
3.1 假期信息配置
如果要判断某个日期是否为工作日,首先要有每年的法定假日和调班数据。
使用如下结构存储以上信息
{
"2020": {
"1": {
"holidays": [
1,
24,
25,
26,
27,
28,
29,
30,
31
],
"shiftDays": [
19
],
"remark": "元旦"
},
"2": {
"holidays": [],
"shiftDays": [],
"remark": "春节"
},
"4": {
"holidays": [
4,
5,
6
],
"shiftDays": [
26
],
"remark": "清明、五一"
},
"5": {
"holidays": [
1,
2,
3,
4,
5
],
"shiftDays": [
9
],
"remark": "五一"
},
"6": {
"holidays": [
25,
26,
27
],
"shiftDays": [
28
],
"remark": "端午"
},
"9": {
"holidays": [],
"shiftDays": [
27
],
"remark": ""
},
"10": {
"holidays": [
1,
2,
3,
4,
5,
6,
7
],
"shiftDays": [
10
],
"remark": "国庆"
}
}
}
3.2 Java结构
/**
* 假期配置
* 从spring配置注入,从db读取配置,从apollo配置注入等
*/
private static Map<Integer, Map<Integer, HolidayConfig>> holidayConfigs;
/**
* 每月的假期配置
*/
@Data
public static class HolidayConfig {
// 假日
private Set<Integer> holidays;
// 调班
private Set<Integer> shiftDays;
// 备注
private String remark;
}
- 第一层Integer为每年的假期配置
- 第二层Integer为没月的假期配置
- 每月的假期配置包含2个List,分别为法定假日和调班的dayOfMonth。
3.3 几个常用的工具方法
/**
* 判断date是否为工作日
*
* @param date
* @return
*/
public static boolean isWorkday(LocalDate date)
/**
* 得到在date上加n个工作日后的日期
*
* @param date 偏移起始日期
* @param n 在起始日期上加n个工作日(n可以为负数)
* @return n个工作日后的日期
* @throws Exception
*/
public static LocalDate plusNWorkDay(LocalDate date, int n)
4 完整代码
package com.xuanfeng.tools.date;
import lombok.Data;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.Map;
import java.util.Set;
/**
* @description: 工作日工具类
* @author: xuanfeng
* @create: 2020/08/21 16:15
*/
public class WorkDayUtil {
/**
* 假期配置
* 从spring配置注入,从db读取配置,从apollo配置注入等
*/
private static Map<Integer, Map<Integer, HolidayConfig>> holidayConfigs;
/**
* 得到在date上加n个工作日后的日期
*
* @param date 偏移起始日期
* @param n 在起始日期上加n个工作日(n可以为负数)
* @return n个工作日后的日期
* @throws Exception
*/
public static LocalDate plusNWorkDay(LocalDate date, int n) {
// 绝对值
int absN = Math.abs(n);
// ±1,向前找或是向后找
int sign = Integer.compare(n, 0);
// 已发现的工作日数量
int foundNum = 0;
for (int i = 1; ; i++) {
// 下一个工作日
LocalDate nextDate = date.plusDays(i * sign);
if (isWorkday(nextDate)) {
// 当找到absN个工作日后返回
if (++foundNum == absN) {
return nextDate;
}
}
}
}
/**
* 判断date是否为工作日
*
* @param date
* @return
*/
public static boolean isWorkday(LocalDate date) {
HolidayConfig holidayConfig = null;
// 如果有假期配置
if (!CollectionUtils.isEmpty(holidayConfigs)) {
// 取出年的配置
Map<Integer, HolidayConfig> yearConfig = holidayConfigs.get(date.getYear());
// 如果有年度配置
if (!CollectionUtils.isEmpty(yearConfig)) {
// 取出月的配置
holidayConfig = yearConfig.get(date.getMonthValue());
}
}
DayOfWeek dayOfWeek = date.getDayOfWeek();
Integer dayOfMonth = date.getDayOfMonth();
// 如果没有本月的假期配置(只判断周六日)
if (holidayConfig == null) {
if (dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY) {
return false;
} else {
return true;
}
}
// 有本月假期配置
else {
// 周六日
if (dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.SATURDAY) {
//周六周日需要判断是否调班
Set<Integer> shiftDays = holidayConfig.getShiftDays();
if (CollectionUtils.isEmpty((shiftDays))) {
return false;
} else {
return shiftDays.contains(dayOfMonth);
}
}
// 非周六日
else {
// 判断是否在假期内
Set<Integer> holidays = holidayConfig.getHolidays();
if (CollectionUtils.isEmpty(holidays)) {
return true;
} else {
return !holidays.contains(dayOfMonth);
}
}
}
}
/**
* 每月的假期配置
*/
@Data
public static class HolidayConfig {
// 假日
private Set<Integer> holidays;
// 调班
private Set<Integer> shiftDays;
// 备注
private String remark;
}
public static void setHolidayConfigs(Map<Integer, Map<Integer, HolidayConfig>> holidayConfigs) {
WorkDayUtil.holidayConfigs = holidayConfigs;
}
}