如果没有节日放假调休的话,工作日很好计算,周一到周五就是工作日,但因为有节日放假调休,使得这个计算需要外部放假安排数据来支持。计算原理: 先按照放假安排数据计算,再按照周一周五计算。

下面以LocalDateTime 为例。

1.第一版,没有使用缓存

    /**
* 判断是否中国工作日,包含法定节假日调整日期,节假日数据holidayData,如果节假日数据不支持年份,将使用周一到周五为工作日来判断。
* @param localDateTime LocalDateTime
* @param holidayData 放假信息0表示放假,1表示工作日,如:2021-01-01:0,2021-02-07:1
* @return boolean
*/
public static boolean isChineseWorkDay(LocalDateTime localDateTime, String holidayData){
Objects.requireNonNull(holidayData, "holidayData");
Map<String, Integer> dateTypeMap = StringUtil.convertHolidayDataToMap(holidayData);
Integer dateType = dateTypeMap.get(DateTimeFormatterUtil.formatToDateStr(localDateTime));
if(dateType != null){
return dateType == 1 ? true : false;
}
return isWorkDay(localDateTime);
} // StringUtil.convertHolidayDataToMap /**
* 转换节日数据为map
* @param holidayData 节日map
* @return 返回节日map
*/
public static Map<String, Integer> convertHolidayDataToMap(String holidayData){
Map<String, Integer> dateTypeMap = new HashMap<>();
if(isEmpty(holidayData)){
return dateTypeMap;
} String[] dateTypeArr = holidayData.replace(" ", "").split(",");
for(String dateType : dateTypeArr){
String[] arr = dateType.split(":");
dateTypeMap.put(arr[0], Integer.valueOf(arr[1]));
}
return dateTypeMap;
} /**
* 判断是否工作日 (周一到周五)
* @param localDateTime LocalDateTime
* @return boolean
*/
public static boolean isWorkDay(LocalDateTime localDateTime){
int dayOfWeek = getDayOfWeek(localDateTime);
if(dayOfWeek == 6 || dayOfWeek == 7){
return false;
}else{
return true;
}
}

这个方法,先将放假安排数据解析成Map,然后对比,最后使用周一到周五判断。

2.第二版,使用缓存优化

第一版中,每次调用都先将放假安排数据解析成Map,但其实是不需要的,因为放假安排数据每年只发布一次(特殊情况除外),一年都不需要变化,这些数据第一次调用时放进缓存,后面直接使用,有变化时再更新缓存。

缓存使用本地缓存和Redis缓存都可以,本地缓存速度更快一些,下面使用本地缓存。

    public static boolean isChineseWorkDay2(LocalDateTime localDateTime, String holidayData){
Objects.requireNonNull(holidayData, "holidayData");
Map<String, Integer> dateTypeMap = StringUtil.convertHolidayDataToMapUseCache(holidayData);
Integer dateType = dateTypeMap.get(DateTimeFormatterUtil.formatToDateStr(localDateTime));
if(dateType != null){
return dateType == 1 ? true : false;
}
return isWorkDay(localDateTime);
} //StringUtil.convertHolidayDataToMapUseCache /**
* 转换节日数据为map,使用缓存提高性能
* @param holidayData 节日map
* @return 返回节日map
*/
@SuppressWarnings("unchecked")
public static Map<String, Integer> convertHolidayDataToMapUseCache(String holidayData){
Map<String, Integer> dateTypeMap = new HashMap<>();
//参数为空,直接返回
if(isEmpty(holidayData)){
return dateTypeMap;
} //查询缓存
dateTypeMap = (Map<String, Integer>)CommonCache.get(holidayData); //缓存存在,返回缓存
if(CollectionUtil.isNotEmpty(dateTypeMap)){
return dateTypeMap;
} //缓存不存在,先设置缓存然后返回
Supplier<Object> supplier = new Supplier<Object>() {
@Override
public Object get() {
Map<String, Integer> dateTypeMap = new HashMap<>();
String[] dateTypeArr = holidayData.replace(" ", "").split(",");
for(String dateType : dateTypeArr){
String[] arr = dateType.split(":");
dateTypeMap.put(arr[0], Integer.valueOf(arr[1]));
}
return dateTypeMap;
}
};
return (Map<String, Integer>)CommonCache.get(holidayData, supplier);
} /**
* 判断是否工作日 (周一到周五)
* @param localDateTime LocalDateTime
* @return boolean
*/
public static boolean isWorkDay(LocalDateTime localDateTime){
int dayOfWeek = getDayOfWeek(localDateTime);
if(dayOfWeek == 6 || dayOfWeek == 7){
return false;
}else{
return true;
}
}

缓存使用了WeakHashMap实现缓存自动清理,使用ReentrantReadWriteLock实现读写线程安全。详细代码见com.xkzhangsan.time.utils.CommonCache,核心代码片段如下:

    /**
* 从缓存池中查找值
*
* @param key
* 键
* @return 值
*/
public V get(K key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
} /**
* 从缓存池中查找值,没有时尝试生成
*
* @param key
* 键
* @param supplier 提供者
* @return 值
*/
public V get(K key, Supplier<V> supplier) {
V value = get(key);
if (value == null && supplier != null) {
lock.writeLock().lock();
try {
value = cache.get(key);
// 双重检查,防止在竞争锁的过程中已经有其它线程写入
if (null == value) {
try {
value = supplier.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
cache.put(key, value);
}
} finally {
lock.writeLock().unlock();
}
}
return value;
}


3. 二种实现性能对比

这里以2021年放假信息为例,分别调用100万次。忽略第一次创建缓存的时间,从第二次开始,测试数据如下:

 2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1

    @Test
public void isChineseWorkDay1(){
//2021年放假信息
String holidayData = "2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1";
//指定日期是否是工作日
long s = 0;
for (int i = 0; i < 1000001; i++) {
if(i==1){
s = System.currentTimeMillis();
}
DateTimeCalculatorUtil.isChineseWorkDay(LocalDateTime.now(), holidayData);
}
System.out.println("isChineseWorkDay1 cost1:"+(System.currentTimeMillis()-s));
} @Test
public void isChineseWorkDay2(){
//2021年放假信息
String holidayData = "2021-01-01:0,2021-02-07:1,2021-02-11:0,2021-02-12:0,2021-02-15:0,2021-02-16:0,2021-02-17:0,2021-02-20:1,2021-04-05:0,2021-04-25:1,2021-05-03:0,2021-05-04:0,2021-05-05:0,2021-05-08:1,2021-06-14:0,2021-09-18:1,2021-09-20:0,2021-09-21:0,2021-09-26:1,2021-10-01:0,2021-10-04:0,2021-10-05:0,2021-10-06:0,2021-10-07:0,2021-10-09:1";
//指定日期是否是工作日
long s = 0;
for (int i = 0; i < 1000001; i++) {
if(i==1){
s = System.currentTimeMillis();
}
DateTimeCalculatorUtil.isChineseWorkDay2(LocalDateTime.now(), holidayData);
}
System.out.println("isChineseWorkDay2 cost2:"+(System.currentTimeMillis()-s));
}

结果(单位:ms):

isChineseWorkDay1 cost1:5589
isChineseWorkDay2 cost2:366

可以看到,使用缓存后性能对比 5589/366=15.27 , 速度提高15倍多,代码性能的小优化,大量调用后会被累加放大,优化非常值得!

源代码地址:https://github.com/xkzhangsan/xk-time

Java日期时间API系列38-----一种高效的工作日计算计算方法的更多相关文章

  1. Java日期时间API系列19-----Jdk8中java.time包中的新的日期时间API类,ZonedDateTime与ZoneId和LocalDateTime的关系,ZonedDateTime格式化和时区转换等。

    通过Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类中时间范围示意图:可以很清晰的看出ZonedDateTime相当于LocalDateTime+ZoneI ...

  2. Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析

    目录 0.前言 1.TemporalAccessor源码 2.Temporal源码 3.TemporalAdjuster源码 4.ChronoLocalDate源码 5.LocalDate源码 6.总 ...

  3. Java日期时间API系列11-----Jdk8中java.time包中的新的日期时间API类,使用java8日期时间API重写农历LunarDate

    通过Java日期时间API系列7-----Jdk8中java.time包中的新的日期时间API类的优点,java8具有很多优点,现在网上查到的农历转换工具类都是基于jdk7及以前的类写的,下面使用ja ...

  4. Java日期时间API系列12-----Jdk8中java.time包中的新的日期时间API类,日期格式化,常用日期格式大全

    通过Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter, 可以看出java8的DateTimeFormatter完美解决 ...

  5. Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类

    因为Jdk7及以前的日期时间类的不方便使用问题和线程安全问题等问题,2005年,Stephen Colebourne创建了Joda-Time库,作为替代的日期和时间API.Stephen向JCP提交了 ...

  6. Java日期时间API系列13-----Jdk8中java.time包中的新的日期时间API类,时间类转换,Date转LocalDateTime,LocalDateTime转Date等

    从前面的系列博客中可以看出Jdk8中java.time包中的新的日期时间API类设计的很好,但Date由于使用仍非常广泛,这就涉及到Date转LocalDateTime,LocalDateTime转D ...

  7. Java日期时间API系列3-----Jdk7及以前的日期时间类的不方便使用问题

    使用Java日期时间类,每个人都很熟悉每个项目中必不可少的工具类就是dateutil,包含各种日期计算,格式化等处理,而且常常会遇到找不到可用的处理方法,需要自己新增方法,处理过程很复杂. 1.Dat ...

  8. Java日期时间API系列7-----Jdk8中java.time包中的新的日期时间API类的特点

    1.不变性 新的日期/时间API中,所有的类都是不可变的,这对多线程环境有好处. 比如:LocalDateTime 2.关注点分离 新的API将人可读的日期时间和机器时间(unix timestamp ...

  9. Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter

    1.DateTimeFormatter final修饰,线程安全,用于打印和解析日期-时间对象的格式化程序. 创建DateTimeFormatter: DateTimeFormatter dateTi ...

  10. Java日期时间API系列39-----中文语句中的时间语义识别(time NLP 输入一句话,能识别出话里的时间)原理分析

    NLP (Natural Language Processing) 是人工智能(AI)的一个子领域.自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一(来自百度百科). 其中中文更是 ...

随机推荐

  1. 解决github不能访问的问题

    亲测有效,授之以鱼不如授之以渔,网上看了很多方法,也试着做了,很多都是治标不治本,最后找到个靠谱的方式:利用DNS查询工具,找到最快的IP地址,然后把host地址换成查询到的结果,方法如下: 在系统的 ...

  2. vue2整个项目中,数据请求显示loading图----------未完成阅读,码

    一般项目中,有时候会要求,你在数据请求的时候显示一张gif图片,然后数据加载完后,消失.这个,一般只需要在封装的axios中写入js事件即可.当然,我们首先需要在app.vue中,加入此图片.如下: ...

  3. 【运维--系统】nacos介绍和安装

    目录: 简介 安装java 安装mysql 安装nacos 附录 简介 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服 ...

  4. 病毒木马查杀实战第023篇:MBR病毒之引导区的解析

    前言 引导型病毒指寄生在磁盘引导区或主引导区的计算机病毒.这种病毒利用系统引导时,不对主引导区的内容正确与否进行判别的缺点,在引导系统的过程中入侵系统,驻留内存,监视系统运行,伺机传染和破坏.按照引导 ...

  5. POJ1328贪心放雷达

    题意:        有一个二维坐标,y>0是海,y<=0是陆地,然后只能在y=0的岸边上放雷达,有n个城市需要被监控,问最少放多少个雷达. 思路:       贪心去做就行了,其实题目不 ...

  6. Hydra暴力破解工具的用法

    目录 Hydra 常见参数 破解SSH 破解FTP 破解HTTP 破解3389远程登录 Kali自带密码字典 dirb dirbuster fern-wifi metasploit wfuzz Hyd ...

  7. Intel汇编程序设计-整数算术指令(中)

    7.3  移位和循环移位的应用 7.3.1  多双字移位 要对扩展精度整数(长整数)进行移位操作,可把它划分为字节数组.字数组或双字数组,然后再对该数组进行移位操作.在内存中存储数字时通常采用的方式是 ...

  8. postman使用教程7-参数化引用外部文件测试数据

    前言 当同一个接口需要测试不同的参数时,需用到参数化的概念.postman支持从外部文件读取测试数据参数化 设置参数化变量 新建一个collections 专门测试登录接口 设置集合变量usernam ...

  9. Sublime 快捷生成HTML 插件安装

    更多精彩关注公众号 1 安装 Package Control1.1 ctrl + ` 呼出控制台1.2 复制(不要带最外层的双引号,该代码仅适用于sublime text 3)"import ...

  10. Salsa20算法介绍

    简介 Salsa20是一种流式对称加密算法,类似于Chacha20,算法性能相比AES能够快3倍以上. Salsa20算法通过将32 Byte的key和8 Byte的随机数nonce扩展为2^70 B ...