基于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类型指针…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
