<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          又踩坑了,java日期閏年處理,算少一天!

          共 10746字,需瀏覽 22分鐘

           ·

          2024-04-12 01:15

          前言

          今年是2024年,剛好是閏年。大家都知道,閏年是有366天的,其中二月份有29天。最近作者有個項目組出了一個生產(chǎn)問題,跟閏年相關(guān)的。所以寫篇文章跟大家講講這個bug,順便講講Java日期處理的一些坑,讓大家避坑~

          1. 閏年處理的坑

          這個bug是如何產(chǎn)生的呢?我給大家舉個例子:

          比如產(chǎn)品要求支持查詢一年之間內(nèi)的交易流水,并且對這個時間區(qū)間的檢驗。用戶選了開始時間和結(jié)束時間后,前端先檢驗,檢驗通過后,傳到后端。后端也檢驗,然后開始查詢。

          • 假設(shè)當天時間2024.02.29日,客戶選的開始時間是2023.02.28,結(jié)束時間是2024.02.29.
          • 前端檢驗的時候,是用終止時間2024.02.29減12個月,得到開始時間2023.02.28,因此認為區(qū)間是合法的
          • 后端檢驗的時候,用開始時間2023.02.28 去加上12個月,得到2024.02.28,即得到2023.02.28 ~ 2024.02.28,因此認為這個時間范圍是不合法的。這就前后端不一致了,產(chǎn)生bug了。

          后端偽代碼是這樣實現(xiàn)的:

          public class Test {

              public static void main(String[] args) {
                  // 從前端獲取的日期字符串
                  String startDateString = "20230228"; // 替換為從前端獲取的startDate
                  String endDateString = "20240229";   // 替換為從前端獲取的endDate

                  // 將字符串轉(zhuǎn)換為LocalDate對象
                  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                  LocalDate startDate = LocalDate.parse(startDateString, formatter);
                  LocalDate endDate = LocalDate.parse(endDateString, formatter);

                  // 將startDate增加12個月
                  LocalDate startDatePlus12Months = startDate.plusMonths(12);

                  // 檢查是否在時間區(qū)間范圍內(nèi)
                  if (startDatePlus12Months.isBefore(endDate)) {
                      System.out.println("時間區(qū)間超過一年");
                      throw new RuntimeException();
                  } else {
                      System.out.println("時間區(qū)間小于一年");
                  }

              }
          }

          運行結(jié)果拋出了一場,輸出時間區(qū)間超過一年

          其實這個例子,就是我們是否把閏年的02.29日這一天算進去,怎么理解都算合理,但是就是要避免前后端不一致的坑。

          關(guān)于閏年處理的坑,我總結(jié)為主要是這幾個方面:

          • 在一個日期值上加或減時間的代碼,比如像加減1年或1個月的代碼
          • 數(shù)據(jù)庫查詢,報表這些,月度和年度統(tǒng)計可能會少算 1 天
          • 證書/密碼/密鑰/緩存 等的過期時間,可能會比預(yù)期的早了一天 固定長度數(shù)組,長度365時不夠的
          • 判斷是否是閏年的方法, 4年一潤,百年不潤,四百年又潤

          小伙伴們在開發(fā)代碼的時候,注意就好啦~

          2. 日期格式y(tǒng)yyy-MM-dd HH:mm:ss 大小寫含義區(qū)別

          YYYY 和 yyyy 區(qū)別

          • 年份大寫YYYY: 當天所在的周屬于的年份,一周從周日開始,周六結(jié)束,只要本周跨年,那么這周就算進入下一年。
          • yyyy: 表示四位年份。

          比如看這個例子:

          從輸出結(jié)果,大家可以發(fā)現(xiàn),同一個日期,只是轉(zhuǎn)化了一下格式,日期就變了。就是因為年份YYYY大寫的問題。

          HH 和 hh 區(qū)別

          • 小時大寫HH:代表24小時制的小時。
          • hh:是12制的日期格式,當時間為12點,會處理為0點。

          3.SimleDateFormat的format初始化問題

          4. DateTimeFormatter 日期本地化問題

          反例

          String dateStr = "Wed Mar 18 10:00:00 2020";
          DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy");
          LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
          System.out.println(dateTime);

          運行結(jié)果

          Exception in thread "main" java.time.format.DateTimeParseException: Text 'Wed Mar 18 10:00:00 2020' could not be parsed at index 0
           at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
           at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
           at java.time.LocalDateTime.parse(LocalDateTime.java:492)
           at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)

          解析:

          DateTimeFormatter 這個類默認進行本地化設(shè)置,如果默認是中文,解析英文字符串就會報異常??梢詡魅胍粋€本地化參數(shù)(Locale.US)解決這個問題

          正例:

          String dateStr = "Wed Mar 18 10:00:00 2020";
          DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy",Locale.US);
          LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
          System.out.println(dateTime);

          5. Calendar設(shè)置時間的坑

          反例:

          Calendar c = Calendar.getInstance();
          c.set(Calendar.HOUR, 10);
          System.out.println(c.getTime());

          運行結(jié)果:

          Thu Mar 26 22:28:05 GMT+08:00 2020

          解析:

          我們設(shè)置了10小時,但運行結(jié)果是22點,而不是10點。因為Calendar.HOUR默認是按12小時制處理的,需要使用Calendar.HOUR_OF_DAY,因為它才是按24小時處理的。

          Calendar c = Calendar.getInstance();
          c.set(Calendar.HOUR_OF_DAY, 10);

          6.日期計算的坑

          日期計算,比如new Date()加一個30天,有些小伙伴可能會這么寫:

          得到的日期居然比當前日期還要早,根本不是晚 30 天的時間。這是因為發(fā)生了int發(fā)生了溢出。

          可以把30修改為 30L,或者直接用Java 8的加減API,如下:

          7. SimpleDateFormat 線性安全問題

          比如這個例子

          public class SimpleDateFormatTest {

              private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

              public static void main(String[] args) {
                  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));

                  while (true) {
                      threadPoolExecutor.execute(() -> {
                          String dateString = sdf.format(new Date());
                          try {
                              Date parseDate = sdf.parse(dateString);
                              String dateString2 = sdf.format(parseDate);
                              System.out.println(dateString.equals(dateString2));
                          } catch (ParseException e) {
                              e.printStackTrace();
                          }
                      });
                  }
              }

          SimpleDateFormat 是個共享變量,多線程跑的時候,就會有問題:

          Exception in thread "pool-1-thread-49" java.lang.NumberFormatException: For input string: "5151."
           at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
           at java.lang.Long.parseLong(Long.java:589)
           at java.lang.Long.parseLong(Long.java:631)
           at java.text.DigitList.getLong(DigitList.java:195)
           at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
           at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
           at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
           at java.text.DateFormat.parse(DateFormat.java:364)
           at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
           at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
           at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
           at java.lang.Thread.run(Thread.java:748)
          Exception in thread "pool-1-thread-47" java.lang.NumberFormatException: For input string: "5151."
           at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
           at java.lang.Long.parseLong(Long.java:589)
           at java.lang.Long.parseLong(Long.java:631)
           at java.text.DigitList.getLong(DigitList.java:195)
           at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
           at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
           at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
           at java.text.DateFormat.parse(DateFormat.java:364)
           at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
           at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
           at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
           at java.lang.Thread.run(Thread.java:748)

          或者直接使用DateTimeFromatter

          8. Java日期的夏令時問題

          反例:

          TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          System.out.println(sdf.parse("1986-05-04 00:30:00"));
          運行結(jié)果:
          Sun May 04 01:30:00 CDT 1986

          解析:

          先了解一下夏令時

          • 夏令時,表示為了節(jié)約能源,人為規(guī)定時間的意思。
          • 一般在天亮早的夏季人為將時間調(diào)快一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節(jié)約照明用電。
          • 各個采納夏時制的國家具體規(guī)定不同。目前全世界有近110個國家每年要實行夏令時。
          • 1986年4月,中國中央有關(guān)部門發(fā)出“在全國范圍內(nèi)實行夏時制的通知”,具體作法是:每年從四月中旬第一個星期日的凌晨2時整(北京時間),將時鐘撥快一小時。(1992年起,夏令時暫停實行。)
          • 夏時令這幾個時間可以注意一下哈,1986-05-04, 1987-04-12, 1988-04-10, 1989-04-16, 1990-04-15, 1991-04-14。

          結(jié)合demo代碼,中國在1986-05-04當天還在使用夏令時,時間被撥快了1個小時。所以0點30分打印成了1點30分。如果要打印正確的時間,可以考慮修改時區(qū)為東8區(qū)。

          正例:

          TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));

          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          System.out.println(sdf.parse("1986-05-04 00:30:00"));



          ???? 點擊下方閱讀原文,獲取魚皮往期編程干貨。

          往期推薦

          我的編程學習小圈子

          幾個有點冷門的 vscode 插件,但絕對好用!

          Redis 有幾種緩存讀寫策略?

          我們公司的春招來啦!

          我被刷幾萬元的血淚經(jīng)驗。。。

          唉,新項目內(nèi)測延期了。。我的一點思考

          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三级黄视频| 日本欧美国产 | 【乱子伦】国产精品www | 激情理论| 日日躁夜夜躁 |