程序員必備:Java日期處理的十個坑

前言

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

一、用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.HOUROFDAY,因爲它纔是按24小時處理的。

正例:

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

二、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);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2019-12-31 轉 yyyy-MM-dd 格式後 " + dtf.format(testDate));

三、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小時制。

正例:

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

四、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 calendars
is <code>JANUARY</code> which is 0; 
也就是1月對應的是下標 0,依次類推。因此獲取正確月份需要加 1.

正例:

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

五、Java日期格式化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));

運行結果:

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

六、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()));

七、日期本地化問題

反例:

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

八、SimpleDateFormat 解析的時間精度問題

反例:

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

運行結果:

Exception in thread "main" java.text.ParseException: Unparseable date: "2020-03"
    at java.text.DateFormat.parse(DateFormat.java:366)
    at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)

解析:

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

正例:

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

九、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();
                }
            });
        }
    }

運行結果:

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"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH: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-04 00:30:00"));

個人公衆號

  • 覺得寫得好的小夥伴給個點贊+關注啦,謝謝~

  • 如果有寫得不正確的地方,麻煩指出,感激不盡。

  • 同時非常期待小夥伴們能夠關注我公衆號,後面慢慢推出更好的乾貨~嘻嘻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章