基于BitMap的工作日间隔计算
背景问题
在我们实际开发过程中,时常会遇到日期的间隔计算,即计算多少工作日之后的日期,在不考虑法定节假日的情况下也不是那么复杂,毕竟周六、周日是相对固定的,Java语言也提供了丰富的类来处理此问题。
然而,当考虑法定节假日,原先的工作日也许变成了休息日,同样原先的休息日变成了工作日,再加上大多数客户是内网环境,节假日信息不得不维护到数据库,所以复杂度立马提升了N个档次。
由此,这里提供一些思路仅供参考。
准备工作
我们可以通过如下网址获取法定节假日的信息法定节假日。返回数据如下,这里只截取部分数据
{"2024-01-01": {"date": "2024-01-01","name": "元旦","isOffDay": true},"2024-02-04": {"date": "2024-02-04","name": "春节","isOffDay": false},"2024-02-10": {"date": "2024-02-10","name": "春节","isOffDay": true},"2024-02-11": {"date": "2024-02-11","name": "春节","isOffDay": true}
}
在这份数据中,列举了全部的法定节假日调休信息。isOffDay
为true
,表示和节假日相关的周六日、非周六周日休假;比如2024-01-01
为周一,这里为true,表示休假;再比如2024-02-10
,为周六,也表示休假。isOffDay
为false
,表示周六周日照常上班(也就是我们说的调休)。
解决思路(此章节不是重点,可掠过)
对于此问题,我觉得可以从数据库的设计入手。数据库设计有如下几个思路:
- 数据库保存特殊日期的数据。比如本应该工作的日期变成了节假日,本应该休息的日期变成了工作日。
- 入库逻辑。就拿上面的数据,如果
isOffDay
为false,我们肯定全部入库。如果isOffDay
为true,还需要判断日期是否为周六日,如果不是需要入库。 - 计算工作日。需要针对每一天都要判断是否异常,首先按照正常逻辑处理,然后查询数据库,如果异常(数据库存在),将结果取反。比如查询
2024-4-28
以后10个工作日的日期,首先查看2024-4-29
是否为周末,这里是周一。然后查询数据库,数据库不存在。所以为工作日,计1天,由此向后推10个工作日。
- 入库逻辑。就拿上面的数据,如果
- 数据库保存放假的数据。
- 入库逻辑。首先通过Java提供的日期类,计算出周六周日的日期列表。然后根据接口提供的数据,如果
isOffDay
为false
,将此日期在集合中移除;如果isOffDay
为true
,判断是否为周六日,如果不是,加入到集合中。最后将集合保存到数据库。 - 计算工作日。针对每天,需要查询数据库。如果数据库不存在,则工作日+1,否则不变。这里也可以将数据一次性读取,在内存中处理。
- 入库逻辑。首先通过Java提供的日期类,计算出周六周日的日期列表。然后根据接口提供的数据,如果
- 数据库保存工作日数据。
- 入库逻辑。这个和存放放假数据相反。
- 查询工作日。这里可以通过sql就可以查询。比如查询
2024-04-28
后10个工作日日期。
最后一条数据就是指定的工作日。select * from t_work_date where f_date > '2024-04-28' order by f_date limit 10
当然,也可以将所有的数据存放到数据库。增加一个是否工作日的标识。同样可以通过sql搞定。
基于BitMap
上面思路仅供我们了解,不是这次重点。下面我们重点说明BitMap怎么计算工作日指定天数后的日期。我们知道,对于一个日期,它要么是工作,要么休息,我们很容易想到0和1。我们可以将1代表工作日,将0代表休息日。所以针对一年的数据,我们只用365(或者366)个0和1表示就行。接下来,我们同样按照入库逻辑和计算工作日两个方面说明此问题。
入库逻辑
数据库设计
这里我们创建一张表包含两个字段,f_year和f_data。 这里基于postgresql
存储,sql语句如下:
create table t_date(f_year int2,data BYTEA
);
主要逻辑
由于下面代码注释很全面,这里就不写处理逻辑了。需要说明的是这里用到了Java提供的BitSet
类。
这个类和其他数组一样,索引也是从0开始的
。
/*** 填充数据。* @param year 计算的年份*/
private static BitSet fillData(Integer year){//返回一年多少天int days = Year.of(year).length();//初始化这一年多少天。BitSet bitSet = new BitSet(days);//默认0,这里反转,全部变为1bitSet.flip(0,days);//计算当前年第一个周六的日期LocalDate firstSaturday = LocalDate.of(year, 1, 1).with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY));//计算第一个周六在这个月是第几天 int dayOfMonth = firstSaturday.getDayOfMonth();//如果是第7天,说明1月1日是周日。所以先将第一天放假。if(dayOfMonth == 7){bitSet.set(0,false);}//当前周六,7天往后循环加,知道当期年最后一天。for (int i = dayOfMonth; i <= days; i=i+7) {//由于索引从0开始,所以这里-1,//周六放假bitSet.set(i-1,false);//周日放假bitSet.set(i,false);}//解析接口的数据为JSON。这里需要自行调用接口获取json数据JSONObject jsonObject = JSONObject.parseObject(json);jsonObject.forEach((k,v)->{//k为日期,v:日期信息{"date": "2024-01-01","name": "元旦","isOffDay": true}LocalDate k1 = LocalDate.parse(k);//获取当前日期在年份是第几天int dayOfYear = k1.getDayOfYear();JSONObject dataInfo = (JSONObject) v;//当前日期是否放假。true:放假。false:不放假Boolean isOffDay = dataInfo.getBoolean("isOffDay");//由于bitSet索引是从0开始,所以这里要减1.//我们这里存储的刚好和是否放假相反,所以这里取反bitSet.set(dayOfYear-1,!isOffDay);});return bitSet;
}
这里我们计算得到的BitMap数据,并将其打印:
private static void printBitSet(BitSet bitSet){for (int i = 0; i < bitSet.length(); i++) {if(i % 8 == 0){System.out.println();}else if(i % 4 == 0){System.out.print(" ");}System.out.print(bitSet.get(i)?1:0);}
}
截取部分数据,下面双斜线后面的不是输出内容。
0111 1001 //1.8
1111 0011 //1.16
1110 0111 //1.24
1100 1111 //2.1
1011 1111 //2.9
0000 0000 //2.17
1111 1100 //2.25
我们拿到结果,那么怎么将数据存放的数据库呢?只要将BitSet转换为二进制就可以:
byte[] byteArray = bitSet.toByteArray();
这里我们顺便看一下长度:46
,也就是46个字节。
计算工作日
加载到JVM缓存中
//byteArray为数据库查询到的f_data二进制数据
BitSet bitSet = BitSet.valueOf(byteArray);
计算工作日
/*** 计算指定日期的工作日* @param currentDate 当前日期* @param workDay 工作日长度* @param bitSet 计算数据* @return 计算结果*/
private static LocalDate calWorkData(String currentDate,int workDay,BitSet bitSet){LocalDate parse = LocalDate.parse(currentDate);//获取当前日在在一年的第几天int begin = parse.getDayOfYear();//将计算结果先赋值当前日期int last = begin;//workDay个工作日,这里循环workDay此//对于其他的算法,这里循环的次数为工作日的次数+放假的次数for (int i = 0; i < workDay; i++) {//找到下一天后的第一个设置为1的位置。//注意nextSetBit这个方法,从索引值(包括索引值)开始计算,所以这里要先+1。//还有一个方法nextClearBit,表示下一个0的位置。last = bitSet.nextSetBit(++last);}//last就是索引位置,用最后的索引位置-开始的索引的位置,然后将当前日期推后此天数,就是要计算的日期。LocalDate localDate = parse.plusDays(last - (long)begin);System.out.println(localDate);return localDate;
}
存在问题
这里并没有考虑到跨年,有一种思路。由于次年的法定节假日一般是在当年的11月份左右发布。所以在计算下一年记录的时候,将下一年的数据追加到2024年后面。这样,每一条数据的长度就变成92个字节,按照utf8编码,也就是30来个汉字,我们是可以接受的。
存储以及计算复杂度分析
通过上面提供的几种思路,所占用数据库的大小,这里我们做一个对比:
- BitMap:上面我们也计算了。f_year为2个字节,f_data的92个字节 ,共 94个字节。
- 数据库保存特殊日期:一个日期记录是
2024-04-04
,为10个字节,特殊日期这里至少11个。再加上各种调休。按照平均20天算,需要220个字节。 - 数据库保存放假的数据:周六日(52*2) + 11 = 115天,那么存放字节:115 * 10 = 1150个字节。
- 数据库保存工作日的数据:(365-115) * 10 = 2500个字节
虽然数据库保存特殊日期
与BitMap
差不多,保存放假数据
和保存工作日数据
存储上分别是BitMap
的10倍和20倍。针对计算工作日复杂度,我觉得数据库保存工作日的数据
通过一条sql语句搞定,算是最简单的,另外也没有BitMap
跨年的问题。
总结
BitMap
无论在存储和计算工作日的复杂度上都占有明显的优势。数据库保存工作日的数据
方式,虽然占用空间是BitMap的20多倍,2000个字节也可以忽略不计,由于它计算工作日算是最简单的,也不失为采纳的思路。
思考
上面休假与工作的最小单位为一天,如果为半天,上面又该如何计算求取?
相关文章:
基于BitMap的工作日间隔计算
背景问题 在我们实际开发过程中,时常会遇到日期的间隔计算,即计算多少工作日之后的日期,在不考虑法定节假日的情况下也不是那么复杂,毕竟周六、周日是相对固定的,Java语言也提供了丰富的类来处理此问题。 然而&#x…...
sqlite3 — DB-API 2.0 interface for SQLite databases
sqlite3 — DB-API 2.0 interface for SQLite databases — Python 3.12.4 documentation sqlite3 — DB-API 2.0 interface for SQLite databasessqlite3 — SQLite数据库的DB-API 2.0接口 Source code: Lib/sqlite3/ 源代码位置:Lib/sqlite3/ SQLite is a C…...
Spring Boot中的安全配置与实现
Spring Boot中的安全配置与实现 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨Spring Boot中的安全配置与实现,看看如何保护你的…...

DepthAnything(2): 基于ONNXRuntime在ARM(aarch64)平台部署DepthAnything
DepthAnything(1): 先跑一跑Depth Anything_depth anything离线怎么跑-CSDN博客 目录 1. 写在前面 2. 安装推理组件 3. 生成ONNX 4. 准备ONNXRuntime库 5. API介绍 6. 例程 1. 写在前面 DepthAnything是一种能在任何情况下处理任何图像的简单却又强大的深度估计模型。 …...
JAVA简单封装UserUtil
目录 思路 一、TokenFilterConfiguration 二、FilterConfig 三、TokenContextHolder 四、TokenUtil 五、UserUtil 思路 配置Token过滤器(TokenFilterConfiguration):实现一个Token过滤器配置,用于拦截HTTP请求,从请求头中提取Token&…...

【TOOLS】Chrome扩展开发
Chrome Extension Development 1. 入门教程 入门案例,可以访问【 谷歌插件官网官方文档 】查看官方入门教程,这里主要讲解大概步骤 Chrome Extenson 没有固定的脚手架,所以项目的搭建需要根据开发者自己根据需求搭建项目(例如通过…...
分享WPF的UI开源库
文章目录 前言一、HandyControl二、AduSkin三、Adonis UI四、Panuon.WPF.UI五、LayUI-WPF六、MahApps.Metro七、MaterialDesignInXamlToolkit八、FluentWPF九、DMSkin总结 前言 分享WPF的UI开源库。 一、HandyControl HandyControl是一套WPF控件库,它几乎重写了所…...

[ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)
最新消息ICVISP 2024-已通过ACM出版申请投稿免费参会,口头汇报或海报展示(可获得相应证明证书) ————————————————————————————————————————— [ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议(ICVI…...

JVM:类加载器
文章目录 一、什么是类加载器二、类加载器的应用场景三、类加载器的分类1、分类2、启动类加载器3、Java中的默认类加载器(1)扩展类加载器(2)应用程序类加载器(3)arthas中类加载器相关的功能 四、双亲委派机…...

支持向量机 (support vector machine,SVM)
支持向量机 (support vector machine,SVM) flyfish 支持向量机是一种用于分类和回归的机器学习模型。在分类任务中,SVM试图找到一个最佳的分隔超平面,使得不同类别的数据点在空间中被尽可能宽的间隔分开。 超平面方…...

宝塔面板以www用户运行composer
方式一 执行命令时指定www用户 sudo -u www composer update方式二 在网站配置中的composer选项卡中选择配置运行...
昇思25天打卡营-mindspore-ML- Day24-基于 MindSpore 实现 BERT 对话情绪识别
学习笔记:基于MindSpore实现BERT对话情绪识别 算法原理 BERT(Bidirectional Encoder Representations from Transformers)是由Google于2018年开发的一种预训练语言表示模型。BERT的核心原理是通过在大量文本上预训练深度双向表示࿰…...

【精品资料】模块化数据中心解决方案(33页PPT)
引言:模块化数据中心解决方案是一种创新的数据中心设计和部署策略,旨在提高数据中心的灵活性、可扩展性和效率。这种方案通过将数据中心的基础设施、计算、存储和网络资源封装到标准化的模块中,实现了快速部署、易于管理和高效运维的目标 方案…...

N6 word2vec文本分类
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊# 前言 前言 上周学习了训练word2vec模型,这周进行相关实战 1. 导入所需库和设备配置 import torch import torch.nn as nn import torchvision …...

excel、word、ppt 下载安装步骤整理
请按照我的步骤开始操作,注意以下截图红框标记处(往往都是需要点击的地方) 第一步:下载 首先进入office下载网址: otp.landian.vip 然后点击下载 拉到下方 下载站点(这里根据自己的需要选择下载&#x…...

【python学习】标准库之日期和时间库定义、功能、使用场景和示例
引言 datetime模块最初是由 Alex Martelli 在 Python 2.3 版本引入的,目的是为了解决之前版本中处理日期和时间时存在的限制和不便 在datetime模块出现之前,Python 主要使用time模块来处理时间相关的功能,但 time模块主要基于 Unix 纪元时间&…...
Android --- Kotlin学习之路:基础语法学习笔记
------>可读可写变量 var name: String "Hello World";------>只读变量 val name: String "Hello World"------>类型推断 val name: String "Hello World" 可以写成 val name "Hello World"------>基本数据类型 1…...

嵌入式智能手表项目实现分享
简介 这是一个基于STM32F411CUE6和FreeRTOS和LVGL的低成本的超多功能的STM32智能手表~ 推荐 如果觉得这个手表的硬件难做,又想学习相关的东西,可以试下这个新出的开发板,功能和例程demo更多!FriPi炸鸡派STM32F411开发板: 【STM32开发板】 FryPi炸鸡派 - 嘉立创EDA开源硬件平…...
`nmap`模块是一个用于与Nmap安全扫描器交互的库
在Python中,nmap模块是一个用于与Nmap安全扫描器交互的库。Nmap(Network Mapper)是一个开源工具,用于发现网络上的设备和服务。虽然Python的nmap模块可能不是官方的Nmap库(因为Nmap本身是用C/C编写的)&…...

JVM系列 | 对象的创建与存储
JVM系列 | 对象的生命周期1 对象的创建与存储 文章目录 前言对象的创建过程内存空间的分配方式方式1 | 指针碰撞方式2 | 空闲列表 线程安全问题 | 避免空间冲突的方式方式1 | 同步处理(加锁)方式2 | 本地线程分配缓存 对象的内存布局Part1 | 对象头Mark Word类型指针…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...