基于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类型指针…...
[智能体-26]:ollama, 让模型的部署和提供服务(远程或本地)变得异常简单
极简一键部署,自动封装OpenAI 标准 API,本地 / 远程服务秒启用,无需复杂环境编译、配置端口、适配接口。核心亮点安装零门槛跨 Windows/Mac/Linux,一键安装包,无需 CUDA、Python 环境预处理。模型一键拉取运行bash运行…...
PCA-ANN-PWA框架:破解大规模非线性系统全局优化难题
1. 项目概述与核心挑战在化工、能源、材料等过程工业领域,我们工程师经常面临一个头疼的难题:如何对一个包含数百甚至数千个状态变量的大型非线性系统进行高效、可靠的全局优化?这类系统通常由复杂的偏微分方程组描述,比如反应器内…...
构建全球生活便利度指数:多维数据驱动的发展评估框架
1. 项目概述:从数据视角看世界发展作为一名长期和数据打交道的分析师,我常常被问到:如何客观地衡量一个国家或地区的发展水平?是看GDP总量,还是人均收入?是看高楼大厦的数量,还是普通民众的幸福…...
混沌系统预测极限:稀疏观测、数据同化与混沌同步的信息门槛
1. 项目概述:从稀疏观测中预测混沌 在天气预报、湍流模拟乃至金融系统分析中,我们常常面临一个核心难题:如何利用有限、稀疏且带有噪声的观测数据,去准确预测一个高维、非线性的混沌系统未来的演化?这就像试图通过几个…...
服务器被入侵后如何应急响应:安全运维实战指南
1. 这不是演习:当告警邮件凌晨三点弹出来时,你手边该有什么 “服务器CPU持续100%、SSH登录异常增多、/tmp目录下出现陌生可执行文件”——这类告警我见过太多次。不是在靶场演练,不是在CTF赛题里,而是真实发生在某次金融客户核心A…...
Mamba-X:为Vision Mamba模型定制的边缘AI硬件加速器架构解析
1. 项目概述:当视觉Transformer遇上状态空间模型最近在边缘AI硬件加速的圈子里,一个名为“Mamba-X”的设计概念开始被频繁讨论。这名字听起来有点神秘,但核心其实很明确:它瞄准的是当下两个最火热的AI架构趋势——Vision Transfor…...
Burp Suite扫描深度配置指南:被动扫描、主动扫描与自定义插入点协同调优
1. 这不是“点一下就扫完”的配置,而是扫描质量的分水岭 很多人把 Burp Suite Scanner 当成一个“自动漏洞探测器”——填个 URL,点下“Active Scan”,等它跑完弹出一堆高危告警,就以为任务完成了。我见过太多这样的场景ÿ…...
Bittensor:去中心化AI网络的架构、挑战与激励模型优化
1. 项目概述:当AI遇上去中心化,Bittensor在解决什么核心问题?最近几年,AI模型的能力突飞猛进,但一个越来越明显的趋势是,顶尖的AI能力正快速向少数几家科技巨头集中。无论是训练所需的算力、高质量的数据集…...
LeetCode 1314:矩阵区域和 | 二维前缀和
LeetCode 1314:矩阵区域和 | 二维前缀和 引言 矩阵区域和(Matrix Block Sum)是 LeetCode 第 1314 题,难度为 Medium。题目要求计算矩阵中以每个元素为中心、KK 子矩阵区域的元素和。这道题是二维前缀和的经典应用,展…...
WSA-Pacman:让Windows安卓应用管理变得前所未有的简单
WSA-Pacman:让Windows安卓应用管理变得前所未有的简单 【免费下载链接】wsa_pacman A GUI package manager and package installer for Windows Subsystem for Android (WSA) 项目地址: https://gitcode.com/gh_mirrors/ws/wsa_pacman 想要在Windows电脑上安…...
