close
前言

整理了 Java 日期處理的十個坑,希望對大家有幫助。

1. 用 Calendar 設置時間的坑

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

運行結果:
Thu Mar 26 22:28:05 GMT+08:00 2020

解析:

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

正例:

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

2. Java 日期格式化 YYYY 的坑

反例:

Calendar calendar = Calendar.getInstance();calendar.set(2019, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");System.out.println("2019-12-31 轉 YYYY-MM-dd 格式後 " + dtf.format(testDate));

運行結果:

2019-12-31 轉 YYYY-MM-dd 格式後 2020-12-31

解析:

為什麼明明是 2019 年 12 月 31 號,就轉了一下格式,就變成了 2020 年 12 月 31 號了?因為 YYYY 是基於周來計算年的,它指向當天所在周屬於的年份,一周從周日開始算起,周六結束,只要本周跨年,那麼這一周就算下一年的了。正確姿勢是使用 yyyy 格式。

正例:

Calendar calendar = Calendar.getInstance();calendar.set(2019,Calendar.DECEMBER,31);DatetestDate=calendar.getTime();SimpleDateFormatdtf=newSimpleDateFormat("yyyy-MM-dd");System.out.println("2019-12-31 轉 yyyy-MM-dd 格式後 " + dtf.format(testDate));

3. Java日期格式化hh的坑。

反例:

String str = "2020-03-18 12:00";SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd hh:mm");Date newDate = dtf.parse(str);System.out.println(newDate);
運行結果:

Wed Mar 18 00:00:00 GMT+08:00 2020

解析:

設置的時間是 12 點,為什麼運行結果是 0 點呢?因為 hh 是 12 制的日期格式,當時間為 12 點,會處理為 0 點。正確姿勢是使用 HH,它才是 24 小時制。

正例:

Stringstr="2020-03-1812:00";SimpleDateFormatdtf=newSimpleDateFormat("yyyy-MM-ddHH:mm");DatenewDate=dtf.parse(str);System.out.println(newDate);

4. Calendar 獲取的月份比實際數字少 1 即 (0-11)

反例:

//獲取當前月,當前是3月Calendar calendar = Calendar.getInstance();System.out.println("當前"+calendar.get(Calendar.MONTH)+"月份");

運行結果:

當前2月份

解析:

The first month of the year in the Gregorian and Julian calendarsis JANUARY which is 0;

也就是1月對應的是下標 0,依次類推。因此獲取正確月份需要加 1。

正例:

//獲取當前月,當前是3月Calendar calendar = Calendar.getInstance();System.out.println("當前"+(calendar.get(Calendar.MONTH)+1)+"月份");

5. Java 日期格式化 DD 的坑

反例:

Calendarcalendar=Calendar.getInstance();calendar.set(2019, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-DD");System.out.println("2019-12-31 轉 yyyy-MM-DD 格式後 " + dtf.format(testDate));

運行結果:

2019-12-31 轉 yyyy-MM-DD 格式後 2019-12-365
解析:

DD 和 dd 表示的不一樣,DD 表示的是一年中的第幾天,而 dd 表示的是一月中的第幾天,所以應該用的是 dd。

正例:

Calendar calendar = Calendar.getInstance();calendar.set(2019, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");System.out.println("2019-12-31 轉 yyyy-MM-dd 格式後 " + dtf.format(testDate));

6. SimleDateFormat的format 初始化問題

反例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");System.out.println(sdf.format(20200323));

運行結果:

1970-01-01

解析:

用 format 格式化日期是,要輸入的是一個 Date 類型的日期,而不是一個整型或者字符串。

正例:

Calendar calendar = Calendar.getInstance();calendar.set(2020,Calendar.MARCH,23);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");System.out.println(sdf.format(calendar.getTime()));

7. 日期本地化問題

反例:

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);

運行結果:

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

解析:

DateTimeFormatter 這個類默認進行本地化設置,如果默認是中文,解析英文字符串就會報異常。可以傳入一個本地化參數(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);

8. SimpleDateFormat 解析的時間精度問題

反例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Stringtime="2020-03";System.out.println(sdf.parse(time));

運行結果:

Exceptioninthread"main"java.text.ParseException:Unparseabledate:"2020-03"atjava.text.DateFormat.parse(DateFormat.java:366)atcom.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)

解析:

SimpleDateFormat 可以解析長於/等於它定義的時間精度,但是不能解析小於它定義的時間精度。

正例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");String time = "2020-03";System.out.println(sdf.parse(time));

9. SimpleDateFormat 的線性安全問題

反例:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches { public static void main(String args[]) { // String to be scanned to find the pattern. String line = "This order was placed for QT3000! OK?"; String pattern = "(.*)(\\d+)(.*)"; // Create a Pattern object Pattern r = Pattern.compile(pattern); // Now create matcher object. Matcher m = r.matcher(line); if (m.find()) { System.out.println("Found value: " + m.group(0)); System.out.println("Found value: " + m.group(1)); System.out.println("Found value: " + m.group(2)); } else { System.out.println("NO MATCH"); } }}

運行結果:

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)

解析:

全局變量的 SimpleDateFormat,在並發情況下,存在安全性問題。

SimpleDateFormat 繼承了 DateFormat;

DateFormat 類中維護了一個全局的 Calendar 變量;

sdf.parse(dateStr) 和 sdf.format(date),都是由 Calendar 引用來儲存的;

如果 SimpleDateFormat 是 static 全局共享的,Calendar 引用也會被共享;

又因為 Calendar 內部並沒有線程安全機制,所以全局共享的 SimpleDateFormat 不是線性安全的。

解決 SimpleDateFormat 線性不安全問題,有三種方式:

將 SimpleDateFormat 定義為局部變量;

使用 ThreadLocal;

方法加同步鎖 synchronized。

正例:

public class SimpleDateFormatTest { private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static ThreadLocal < DateFormat > threadLocal = new ThreadLocal < DateFormat > (); public static DateFormat getDateFormat() { DateFormat df = threadLocal.get(); if (df == null) { df = new SimpleDateFormat(DATE_FORMAT); threadLocal.set(df); } return df; } public static String formatDate(Date date) throws ParseException { return getDateFormat().format(date); } public static Date parse(String strDate) throws ParseException { return getDateFormat().parse(strDate); } public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue < > (1000)); while (true) { threadPoolExecutor.execute(() - > { try { String dateString = formatDate(new Date()); Date parseDate = parse(dateString); String dateString2 = formatDate(parseDate); System.out.println(dateString.equals(dateString2)); } catch (ParseException e) { e.printStackTrace(); } }); } }}

10. Java日期的夏令時問題

反例:

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");System.out.println(sdf.parse("1986-05-04 00:30:00"));

運行結果:

Sun May 04 01:30:00 CDT 1986

解析:

先了解一下夏令時:

夏令時,表示為了節約能源,人為規定時間的意思。一般在天亮早的夏季人為將時間調快一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。各個採納夏時制的國家具體規定不同。目前全世界有近110個國家每年要實行夏令時。

1986年4月,中國中央有關部門發出「在全國範圍內實行夏時制的通知」,具體作法是:每年從四月中旬第一個星期日的凌晨2時整(北京時間),將時鐘撥快一小時。(1992年起,夏令時暫停實行。)

夏時令這幾個時間可以注意一下哈,1986-05-04, 1987-04-12, 1988-04-10, 1989-04-16, 1990-04-15, 1991-04-14.

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

正例:

TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.parse("1986-05-0400:30:00"));

- EOF -

推薦閱讀點擊標題可跳轉

1、死磕18個Java8日期處理,工作必用!

2、數據庫日期類型字段設計,應該如何選擇?

3、計算機時間到底是怎麼來的?程序員必看的時間知識!

看完本文有收穫?請轉發分享給更多人

關注「ImportNew」,提升Java技能

點讚和在看就是最大的支持❤️

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()