基于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個字節也可以忽略不計,由于它計算工作日算是最簡單的,也不失為采納的思路。

思考

上面休假與工作的最小單位為一天,如果為半天,上面又該如何計算求取?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45211.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45211.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45211.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MVVM和MVC的原理以及它們的區別

MVVM&#xff08;Model-View-ViewModel&#xff09;和 MVC&#xff08;Model-View-Controller&#xff09;是兩種常見的前端架構模式&#xff0c;它們都旨在幫助組織和管理復雜的前端應用程序邏輯和視圖層。 MVC&#xff08;Model-View-Controller&#xff09; 原理&#xff1…

視圖庫對接系列(GA-T 1400)十七、視圖庫對接系列(本級)采集設備獲取

背景 這一章的話,我們寫寫如何獲取采集設備獲取,之前其實也有說過類似的 就我們訂閱的時候如果subscribeDetail=3的話,下級就會主動給我們推送采集設備。但這里的話,是下級主動推,如果下級平臺不支持,或者說可能因為某個原因推的不全,怎么辦? 我們能否主動獲取采集設備…

WPF學習(4) -- 數據模板

一、DataTemplate 在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;DataTemplate 用于定義數據的可視化呈現方式。它允許你自定義如何展示數據對象&#xff0c;從而實現更靈活和豐富的用戶界面。DataTemplate 通常用于控件&#xff08;如ListBox、…

知識圖譜和 LLM:利用 Neo4j 實現大型語言模型

這是關于 Neo4j 的 NaLLM 項目的一篇博客文章。這個項目是為了探索、開發和展示這些 LLM 與 Neo4j 結合的實際用途。 2023 年,ChatGPT 等大型語言模型 (LLM) 因其理解和生成類似人類的文本的能力而風靡全球。它們能夠適應不同的對話環境、回答各種主題的問題,甚至模擬創意寫…

NSSCTF中24網安培訓day1中web的題目

我flag呢 直接查看源代碼即可CtrlU [SWPUCTF 2021 新生賽]Do_you_know_http 用Burpsuite抓包&#xff0c;之后在User-agent下面添加XFF頭&#xff0c;即X-Forwarded-For:127.0.0.1 [SWPUCTF 2022 新生賽]funny_php 首先是php的弱比較&#xff0c;對于num參數&#xff0c;我們…

hot100 | 十一、二分搜索

1-leetcode35. 搜索插入位置 注意&#xff1a; 看Labuladong的書&#xff0c;知道while的判斷符號跟left right的關系 public int searchInsert(int[] nums, int target) {int left 0;int right nums.length - 1;while (left < right) {int mid left (right - left) /…

AI如何引領個人潛力的深度挖掘

AI如何引領個人潛力的深度挖掘 人工智能&#xff08;AI&#xff09;不僅是一場技術革命&#xff0c;更是對人類自身能力的一次深刻反思。本文旨在探討在AI時代下&#xff0c;個人如何挖掘并發揮自己的最大潛能&#xff0c;不僅在職場、教育領域找到新的定位&#xff0c;同時也…

PostgreSQL日志文件配置,記錄所有操作記錄

為了更詳細的記錄PostgreSQL 的運行日志&#xff0c;我們一般需要修改PostgreSQL 默認的配置文件&#xff0c;這里整理了一些常用的配置 修改配置文件 打開 PostgreSQL 配置文件 postgresql.conf。該文件通常位于 PostgreSQL 安裝目錄下的 data 文件夾中。 找到并修改以下配…

Python循環遍歷:深入理解與實戰應用

在Python編程中&#xff0c;循環遍歷是一種基本且強大的控制流結構&#xff0c;它允許我們重復執行一段代碼直到滿足某個條件為止。無論是處理數據集合&#xff08;如列表、元組、字典、集合等&#xff09;&#xff0c;還是執行重復的任務&#xff0c;循環遍歷都是不可或缺的工…

807.保持城市天際線

解題思路 首先找到四個主要方向&#xff08;東南西北&#xff09;的天際線情況。南北看是一樣的&#xff0c;東西看也是一樣的。所以統計出每行的最值&#xff0c;每列的最值&#xff0c;用一個n的數組存儲。分別存儲行和列的最值。最值的位置進行標記&#xff0c;然后對于其余…

【Qt 基礎】繪圖

畫筆 QPen pen; pen.setWidth(3); // 線條寬度 pen.setColor(Qt::red);// 畫筆顏色 pen.setStyle(Qt::DashLine);// 線條樣式 pen.setCapStyle(Qt::RoundCap);// 線端樣式 pen.setJoinStyle(Qt::BevelJoin);// 連接樣式 painter.setPen(pen);線條 線端 連接 畫刷 QBrush bru…

Spring容器詳細介紹

Spring容器 1 Spring核心容器介紹 問題導入 問題&#xff1a;按照Bean名稱獲取Bean有什么弊端&#xff0c;按照Bean類型獲取Bean有什么弊端&#xff1f; 1.1 創建容器 方式一&#xff1a;類路徑加載配置文件 ApplicationContext ctx new ClassPathXmlApplicationContext…

復合類型的字節對齊

引子 #inlcude<stdio.h> struct s{int i;char a: }; struct s sVar {5,A}; int main(void){printf("%d\n",sizeof(sVar)); }問1&#xff1a;上面這個代碼的輸出結果是多少&#xff1f; 答1&#xff1a; 思考 明明sVar這個結構體就兩個元素&#xff0c;5和…

什么是冪等?如何實現冪等?

一 定義 冪等性&#xff08;Idempotence&#xff09;是數學與計算機科學中的一個概念&#xff0c;它指的是一個操作、函數或方法被重復執行多次與僅執行一次的效果相同&#xff0c;或者說&#xff0c;其后續調用的結果不會改變之前調用的結果。 在計算機科學中&#xff0c;這個…

Spring Boot實戰:無縫對接OpenAI

Spring Boot實戰&#xff1a;無縫對接OpenAI 在當今的技術領域&#xff0c;人工智能&#xff08;AI&#xff09;已經成為一股不可忽視的力量。OpenAI作為其中的佼佼者&#xff0c;提供了強大的API供開發者使用&#xff0c;以實現各種AI功能。本文將詳細介紹如何使用Spring Boo…

開閉原則 (Open/Closed Principle, OCP)

開閉原則 (Open/Closed Principle, OCP) 開閉原則&#xff08;Open/Closed Principle, OCP&#xff09;是面向對象設計的五大原則之一。它的基本思想是&#xff1a;軟件實體&#xff08;類、模塊、函數等&#xff09;應該對擴展開放&#xff0c;對修改關閉。即在不修改現有代碼…

uniapp實現水印相機

uniapp實現水印相機-livePusher 水印相機 背景 前兩天拿到了一個需求&#xff0c;要求在內部的oaApp中增加一個衛生檢查模塊&#xff0c;這個模塊中的核心訴求就是要求拍照的照片添加水印。對于這個需求&#xff0c;我首先想到的是直接去插件市場&#xff0c;下一個水印相機…

多頭注意力機制詳解:多維度的深度學習利器

引言 多頭注意力機制是對基礎注意力機制的一種擴展&#xff0c;通過引入多個注意力頭&#xff0c;每個頭獨立計算注意力&#xff0c;然后將結果拼接在一起進行線性變換。本文將詳細介紹多頭注意力機制的原理、應用以及具體實現。 原理 多頭注意力機制的核心思想是通過多個注…

springAMQP自定義fanout交換機進行消息的廣播

rabbitmq一共有三種交換機&#xff1a; fanout--廣播direct--定向topic--話題 rabbitmq-web端 首先我們需要建立一個名叫cybg.fanout交換機與兩個自定義的隊列用于測試廣播效果 我這里就起名字叫做fanout_queue1&fanout_queue2 項目中&#xff1a; 首先對我們的Liste…

當代政治制度(練習題)

當代政治制度&#xff08;練習題&#xff09; *** Rz整理 僅供參考 *** 目前地方人大設立的專門委員會不包括&#xff08;B.法律審查委員會F.外交事務專門委員會 &#xff09;答案不確定 等待指點 A.法制委員會 B.法律審查委員會 C.財政經濟委員會 D.社會建設委員會 E.農業與…