当前位置: 首页 > news >正文

基于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}
}

在这份数据中,列举了全部的法定节假日调休信息。isOffDaytrue,表示和节假日相关的周六日、非周六周日休假;比如2024-01-01为周一,这里为true,表示休假;再比如2024-02-10,为周六,也表示休假。isOffDayfalse,表示周六周日照常上班(也就是我们说的调休)。

解决思路(此章节不是重点,可掠过)

对于此问题,我觉得可以从数据库的设计入手。数据库设计有如下几个思路:

  • 数据库保存特殊日期的数据。比如本应该工作的日期变成了节假日,本应该休息的日期变成了工作日。
    • 入库逻辑。就拿上面的数据,如果isOffDay为false,我们肯定全部入库。如果isOffDay为true,还需要判断日期是否为周六日,如果不是需要入库。
    • 计算工作日。需要针对每一天都要判断是否异常,首先按照正常逻辑处理,然后查询数据库,如果异常(数据库存在),将结果取反。比如查询2024-4-28以后10个工作日的日期,首先查看2024-4-29是否为周末,这里是周一。然后查询数据库,数据库不存在。所以为工作日,计1天,由此向后推10个工作日。
  • 数据库保存放假的数据
    • 入库逻辑。首先通过Java提供的日期类,计算出周六周日的日期列表。然后根据接口提供的数据,如果isOffDayfalse,将此日期在集合中移除;如果isOffDaytrue,判断是否为周六日,如果不是,加入到集合中。最后将集合保存到数据库。
    • 计算工作日。针对每天,需要查询数据库。如果数据库不存在,则工作日+1,否则不变。这里也可以将数据一次性读取,在内存中处理。
  • 数据库保存工作日数据
    • 入库逻辑。这个和存放放假数据相反。
    • 查询工作日。这里可以通过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的工作日间隔计算

背景问题 在我们实际开发过程中&#xff0c;时常会遇到日期的间隔计算&#xff0c;即计算多少工作日之后的日期&#xff0c;在不考虑法定节假日的情况下也不是那么复杂&#xff0c;毕竟周六、周日是相对固定的&#xff0c;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/ 源代码位置&#xff1a;Lib/sqlite3/ SQLite is a C…...

Spring Boot中的安全配置与实现

Spring Boot中的安全配置与实现 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨Spring Boot中的安全配置与实现&#xff0c;看看如何保护你的…...

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)&#xff1a;实现一个Token过滤器配置&#xff0c;用于拦截HTTP请求&#xff0c;从请求头中提取Token&…...

【TOOLS】Chrome扩展开发

Chrome Extension Development 1. 入门教程 入门案例&#xff0c;可以访问【 谷歌插件官网官方文档 】查看官方入门教程&#xff0c;这里主要讲解大概步骤 Chrome Extenson 没有固定的脚手架&#xff0c;所以项目的搭建需要根据开发者自己根据需求搭建项目&#xff08;例如通过…...

分享WPF的UI开源库

文章目录 前言一、HandyControl二、AduSkin三、Adonis UI四、Panuon.WPF.UI五、LayUI-WPF六、MahApps.Metro七、MaterialDesignInXamlToolkit八、FluentWPF九、DMSkin总结 前言 分享WPF的UI开源库。 一、HandyControl HandyControl是一套WPF控件库&#xff0c;它几乎重写了所…...

[ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)

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

JVM:类加载器

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

支持向量机 (support vector machine,SVM)

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

宝塔面板以www用户运行composer

方式一 执行命令时指定www用户 sudo -u www composer update方式二 在网站配置中的composer选项卡中选择配置运行...

昇思25天打卡营-mindspore-ML- Day24-基于 MindSpore 实现 BERT 对话情绪识别

学习笔记&#xff1a;基于MindSpore实现BERT对话情绪识别 算法原理 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是由Google于2018年开发的一种预训练语言表示模型。BERT的核心原理是通过在大量文本上预训练深度双向表示&#xff0…...

【精品资料】模块化数据中心解决方案(33页PPT)

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

N6 word2vec文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 前言 上周学习了训练word2vec模型&#xff0c;这周进行相关实战 1. 导入所需库和设备配置 import torch import torch.nn as nn import torchvision …...

excel、word、ppt 下载安装步骤整理

请按照我的步骤开始操作&#xff0c;注意以下截图红框标记处&#xff08;往往都是需要点击的地方&#xff09; 第一步&#xff1a;下载 首先进入office下载网址&#xff1a; otp.landian.vip 然后点击下载 拉到下方 下载站点&#xff08;这里根据自己的需要选择下载&#x…...

【python学习】标准库之日期和时间库定义、功能、使用场景和示例

引言 datetime模块最初是由 Alex Martelli 在 Python 2.3 版本引入的&#xff0c;目的是为了解决之前版本中处理日期和时间时存在的限制和不便 在datetime模块出现之前&#xff0c;Python 主要使用time模块来处理时间相关的功能&#xff0c;但 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中&#xff0c;nmap模块是一个用于与Nmap安全扫描器交互的库。Nmap&#xff08;Network Mapper&#xff09;是一个开源工具&#xff0c;用于发现网络上的设备和服务。虽然Python的nmap模块可能不是官方的Nmap库&#xff08;因为Nmap本身是用C/C编写的&#xff09;&…...

JVM系列 | 对象的创建与存储

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

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

麒麟系统使用-进行.NET开发

文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的&#xff0c;如果需要进行.NET开发&#xff0c;则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET&#xff0c;所以要进…...

Python的__call__ 方法

在 Python 中&#xff0c;__call__ 是一个特殊的魔术方法&#xff08;magic method&#xff09;&#xff0c;它允许一个类的实例像函数一样被调用。当你在一个对象后面加上 () 并执行时&#xff08;例如 obj()&#xff09;&#xff0c;Python 会自动调用该对象的 __call__ 方法…...

简约商务通用宣传年终总结12套PPT模版分享

IOS风格企业宣传PPT模版&#xff0c;年终工作总结PPT模版&#xff0c;简约精致扁平化商务通用动画PPT模版&#xff0c;素雅商务PPT模版 简约商务通用宣传年终总结12套PPT模版分享:商务通用年终总结类PPT模版https://pan.quark.cn/s/ece1e252d7df...