欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java日期時間類及計(jì)算詳解

 更新時間:2022年07月25日 10:29:41   作者:??我是你下藥都得不到的男人?  
這篇文章主要介紹了Java日期時間類及計(jì)算詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下,希望對你的學(xué)習(xí)有所幫助

1. Java中與日期相關(guān)的類

1.1 java.util包

類名具體描述
DateDate對象算是JAVA中歷史比較悠久的用于處理日期、時間相關(guān)的類了,但是隨著版本的迭代演進(jìn),其中的眾多方法都已經(jīng)被棄用,所以Date更多的時候僅被用來做一個數(shù)據(jù)類型使用,用于記錄對應(yīng)的日期與時間信息
Calender為了彌補(bǔ)Date對象在日期時間處理方法上的一些缺陷,JAVA提供了Calender抽象類來輔助實(shí)現(xiàn)Date相關(guān)的一些日歷日期時間的處理與計(jì)算。
TimeZoneTimezone類提供了一些有用的方法用于獲取時區(qū)的相關(guān)信息

① Date類

    @Test
    void test06(){
        Date date1 = new Date();
        // 獲取當(dāng)前時間后 +100 ms時間
        Date date2 = new Date(System.currentTimeMillis() + 100);
        System.out.println(date1);
        System.out.println(date1.compareTo(date2));
        System.out.println(date1.before(date2));
    }

結(jié)果:

Fri Jul 22 15:31:16 CST 2022
-1
true

② Calendar 日歷類

總體來說,Date是一個設(shè)計(jì)相當(dāng)糟糕的類,因此Java官方推薦盡量少用Date的構(gòu)造器和方法。

如果需要對日期、時間進(jìn)行加減運(yùn)算,或獲取指定時間的年、月、日、時、分、秒信息,可使用Calendar工具類。

示例:

    @Test
    void test05(){
        Calendar calendar =  Calendar.getInstance();
        // Calendar.YEAR 表示當(dāng)前年
        int year = calendar.get(Calendar.YEAR);
        // Calendar.MONTH表示月份,但是為了計(jì)算方便,是從0開始算,所以顯示出來是月份 -1 的
        int month = calendar.get(Calendar.MONTH);
        // Calendar.DAY_OF_MONTH 在這個月 的這一天
        int dom = calendar.get(Calendar.DAY_OF_MONTH);
        // Calendar.DAY_OF_YEAR 在這一年 的這一天
        int doy = calendar.get(Calendar.DAY_OF_YEAR);
        // Calendar.DAY_OF_WEEK 在這一周 的這一天,從星期日當(dāng)?shù)谝惶鞆?開始算的,所以會是 +1
        int dow = calendar.get(Calendar.DAY_OF_WEEK);
        // Calendar.DAY_OF_WEEK_IN_MONTH 在這一個月 這一天在 第幾周
        int dowim = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
        System.out.println(year+"年"+ month+"月");
        System.out.println(dom+"日");
        System.out.println(doy+"日");
        System.out.println(dow+"日");
        System.out.println(dowim);
    }

結(jié)果:

2022年6月20日11時8分19秒859毫秒
AM_PM: 0
HOUR: 11
DAY_OF_MONTH: 20日
DAY_OF_YEAR: 201日
DAY_OF_WEEK: 4日
DAY_OF_WEEK_IN_MONTH: 3

  • Calendar.DAY_OF_MONTH 在這個月 的這一天,但是為了計(jì)算方便,是從0開始算,所以顯示出來是月份 -1 的
  • Calendar.DAY_OF_YEAR 在這一年 的這一天
  • Calendar.DAY_OF_WEEK 在這一周 的這一天,從星期日當(dāng)?shù)谝惶鞆?開始算的,所以會是 +1
  • Calendar.DAY_OF_WEEK_IN_MONTH 在這一個月 這一天在 第幾周
  • Calendar.HOUR 表示今天這一天的小時(0-11),分上午和下午

具體可以看Calendar的靜態(tài)屬性,不需要刻意記

常用api:

Calendar類提供了大量訪問、修改日期時間的方法 ,常用方法如下:

方法描述
void add(int field, int amount)根據(jù)日歷的規(guī)則,為給定的日歷字段添加或減去指定的時間量。
int get(int field)返回指定日歷字段的值。
int getActualMaximum(int field)返回指定日歷字段可能擁有的最大值。例如月,最大值為11。
int getActualMinimum(int field)返回指定日歷字段可能擁有的最小值。例如月,最小值為0。
void roll(int field, int amount)與add()方法類似,區(qū)別在于加上 amount后超過了該字段所能表示的最大范圍時,也不會向上一個字段進(jìn)位。
void set(int field, int value)將給定的日歷字段設(shè)置為給定值。
void set(int year, int month, int date)設(shè)置Calendar對象的年、月、日三個字段的值。
void set(int year, int month, int date, int hourOfDay, int minute, int second)設(shè)置Calendar對象的年、月、日、時、分、秒6個字段的值。

上面的很多方法都需要一個int類型的field參數(shù), field是Calendar類的類變量,如 Calendar.YEAR、Calendar.MONTH等分別代表了年、月、日、小時、分鐘、秒等時間字段。**需要指出的是, Calendar.MONTH字段代表月份,月份的起始值不是1,而是O,所以要設(shè)置8月時,用7而不是8。**如上面演示的程序就示范了Calendar類的常規(guī)用法。

add和roll的區(qū)別

add

add(int field, int amount)的功能非常強(qiáng)大,add主要用于改變Calendar的特定字段的值。

  • 如果需要增加某字段的值,則讓 amount為正數(shù);
  • 如果需要減少某字段的值,則讓 amount為負(fù)數(shù)即可。

add(int field, int amount)有如下兩條規(guī)則:

  • 當(dāng)被修改的字段超出它允許的范圍時,會發(fā)生進(jìn)位,即上一級字段也會增大。
  • 如果下一級字段也需要改變,那么該字段會修正到變化最小的值。
    @Test
    void test07(){
        Calendar cal1 = Calendar.getInstance();
        // 2003-8-23
        cal1.set(2003, 7, 23, 0, 0, 0);
        // 2003-8-23 => 2004-2-23
        cal1.add(Calendar.MONTH, 6);
        System.out.println(cal1.getTime());

        Calendar cal2 = Calendar.getInstance();
        // 2003-8-31
        cal2.set(2003, 7, 31, 0, 0, 0);
        // 因?yàn)檫M(jìn)位后月份改為2月,2月沒有31日,自動變成29日,若不是閏年則變成28日
        // 2003-8-31 => 2004-2-29
        cal2.add(Calendar.MONTH, 6);
        System.out.println(cal2.getTime());
    }

對于上面的例子,8-31就會變成2-29。**因?yàn)镸ONTH 的下一級字段是DATE,從31到29改變最?。ㄈ舨皇情c年則變成28日)。**所以上面2003-8-31的MONTH字段增加6后,不是變成2004-3-2,而是變成2004-2-29。

結(jié)果:

Mon Feb 23 00:00:00 CST 2004
Sun Feb 29 00:00:00 CST 2004

roll

roll()的規(guī)則與add()的處理規(guī)則不同—— 當(dāng)被修改的字段超出它允許的范圍時,上一級字段不會增大。

    @Test
    void test08(){
        Calendar cal1 = Calendar.getInstance();
        // 2003-8-23
        cal1.set(2003, 7, 23, 0, 0, 0);
        // 2003-8-23 => 2003-2-23
        cal1.roll(Calendar.MONTH, 6);
        System.out.println(cal1.getTime());

        Calendar cal2 = Calendar.getInstance();
        cal2.set(2003, 7, 31, 0, 0, 0);
        // MONTH字段“進(jìn)位”后變成2,2月沒有31日
        // YEAR字段不會改變,2003年2月只有28天
        // 2003-8-31 => 2003-2-28
        cal2.roll(Calendar.MONTH, 6);
        System.out.println(cal2.getTime());
    }

結(jié)果:

Sun Feb 23 00:00:00 CST 2003
Fri Feb 28 00:00:00 CST 2003

設(shè)置Calendar的容錯性

調(diào)用Calendar對象的set()方法來改變指定時間字段的值時,有可能傳入一個不合法的參數(shù),例如為MONTH字段設(shè)置13,這將會導(dǎo)致怎樣的后果呢?看如下程序:

    @Test
    void test09(){
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.getTime());
        // ① 結(jié)果是Year字段+1,MONTH字段為1(2月)
        cal.set(Calendar.MONTH, 13);
        System.out.println(cal.getTime());
        // 關(guān)閉容錯性
        cal.setLenient(false);
        // ② 導(dǎo)致運(yùn)行異常
        cal.set(Calendar.MONTH, 13);
        System.out.println(cal.getTime());
    }

上面程序①②兩處的代碼完全相似,但它們運(yùn)行的結(jié)果不一樣:

  • ①處代碼可以正常運(yùn)行,因?yàn)樵O(shè)置MONTH字段的值為13,將會導(dǎo)致YEAR字段加1;
  • ②處代碼將會導(dǎo)致運(yùn)行時異常,因?yàn)樵O(shè)置的MONTH字段值超出了MONTH字段允許的范圍。

關(guān)鍵在于程序中粗體字代碼行,Calendar提供了一個setLenient()用于設(shè)置它的容錯性,Calendar默認(rèn)支持較好的容錯性,通過 setLenient(false)可以關(guān)閉Calendar的容錯性,讓它進(jìn)行嚴(yán)格的參數(shù)檢查。

Calendar有兩種解釋日歷字段的模式:lenient模式和non-lIenient模式:

  • 當(dāng)Calendar 處于lenient模式時,每個時間字段可接受超出它允許范圍的值;
  • 當(dāng)Calendar 處于 non-lenient模式時,如果為某個時間字段設(shè)置的值超出了它允許的取值范圍,程序?qū)伋霎惓!?/li>

set

set()方法延遲修改 :set(f, value)方法將日歷字段f更改為value,此外它還設(shè)置了一個內(nèi)部成員變量,以指示日歷字段f已經(jīng)被更改。

盡管日歷字段f是立即更改的,但該Calendar所代表的時間卻不會立即修改,直到下次調(diào)用get()、getTime()、getTimeInMillis()、add()或roll()時才會重新計(jì)算日歷的時間。

這被稱為 set()方法的延遲修改,采用延遲修改的優(yōu)勢是多次調(diào)用set()不會觸發(fā)多次不必要的計(jì)算(需要計(jì)算出一個代表實(shí)際時間的long型整數(shù))。

    @Test
    void test10(){
        Calendar cal = Calendar.getInstance();
        // 2003-8-31
        cal.set(2003, 7, 31);
        cal.set(Calendar.MONTH, 8);

        // ① 將月份設(shè)置為9月,但是9月沒有31號,如果立即修改,系統(tǒng)會把cal自動調(diào)整為10月1日
        // System.out.println(cal.getTime());

        // 設(shè)置DATE字段為5
        cal.set(Calendar.DATE, 5);
        // 輸出結(jié)果為 2003-9-5
        System.out.println(cal.getTime());
    }

結(jié)果

Fri Sep 05 16:59:50 CST 2003

如果程序?qū)ⅱ偬幋a注釋起來,因?yàn)镃alendar的 set()方法具有延遲修改的特性,即調(diào)用set()方法后Calendar實(shí)際上并未計(jì)算真實(shí)的日期,它只是使用內(nèi)部成員變量表記錄MONTH字段被修改為8,接著程序設(shè)置DATE字段值為5,程序內(nèi)部再次記錄DATE字段為5——就是9月5日,因此最后輸出2003-9-5。

1.2 java.time包

JAVA8之后新增了java.time包,提供了一些與日期時間有關(guān)的新實(shí)現(xiàn)類:

具體每個類對應(yīng)的含義說明梳理如下表:

類名含義說明
LocalDate獲取當(dāng)前的日期信息,僅有簡單的日期信息,不包含具體時間、不包含時區(qū)信息。
LocalTime獲取當(dāng)前的時間信息,僅有簡單的時間信息,不含具體的日期、時區(qū)信息。
LocalDateTime可以看做是LocalDate和LocalTime的組合體,其同時含有日期信息與時間信息,但是依舊不包含任何時區(qū)信息。
OffsetDateTime在LocalDateTime基礎(chǔ)上增加了時區(qū)偏移量信息。
ZonedDateTime在OffsetDateTime基礎(chǔ)上,增加了時區(qū)信息
ZoneOffset時區(qū)偏移量信息, 比如+8:00或者-5:00等
ZoneId具體的時區(qū)信息,比如Asia/Shanghai或者America/Chicago

① LocalDate 本地日期類

LocalDate localDate = LocalDate.now();
// 也可以通過 LocalDate.of(年,月,日)去構(gòu)造
System.out.println("當(dāng)前日期:"+localDate.getYear()+" 年 "+localDate.getMonthValue()+" 月 "+localDate.getDayOfMonth()+"日" );

// 計(jì)算
LocalDate pluslocalDate = localDate.plusDays(1);//增加一天
LocalDate pluslocalDate = localDate.plusYears(1);//增加一年

// 對兩個日期的判斷,是在前、在后、或者相等。
LocalDate.isBefore(LocalDate);
LocalDate.isAfter();
LocalDate.isEqual();
//結(jié)果

② LocalTime 本地時間類

LocalDate pluslocalDate = localDate.plusDays(1);//增加一天
LocalDate pluslocalDate = localDate.plusYears(1);//增加一年

LocalDate和LocalTime 都有類似作用的api

LocalDate.plusDays(1) 增加一天

LocalTime.plusHours(1) 增加一小時 等等~

LocalTime.isBefore(LocalTime);

LocalTime.isAfter();

③ LocalDateTime 本地日期時間類

public final class LocalDateTime ...{
    private final LocalDate date;
    private final LocalTime time;
}

LocalDateTime = LocalDate + LocalTime

④ Instant 類

Instant 是瞬間,某一時刻的意思

Instant.ofEpochMilli(System.currentTimeMillis())
Instant.now()

通過Instant可以創(chuàng)建一個 “瞬間” 對象,ofEpochMilli()可以接受某一個“瞬間”,比如當(dāng)前時間,或者是過去、將來的一個時間。 比如,通過一個“瞬間”創(chuàng)建一個LocalDateTime對象

LocalDateTime now = LocalDateTime.ofInstant(
    Instant.ofEpochMilli(System.currentTimeMillis()),ZoneId.systemDefault());

System.out.println("當(dāng)前日期:"+now.getYear()+" 年 "+now.getMonthValue()+" 月 "+now.getDayOfMonth()+"日" );

⑤ Period 類

Period 是 時期,一段時間 的意思

Period有個between方法專門比較兩個日期的

LocalDate startDate = LocalDateTime.ofInstant(
    Instant.ofEpochMilli(1601175465000L), ZoneId.systemDefault())
						.toLocalDate();//1601175465000是2020-9-27 10:57:45
Period p  =  Period.between(startDate,  LocalDate.now());

System.out.println("目標(biāo)日期距離今天的時間差:"+p.getYears()+" 年 "+p.getMonths()+" 個月 "+p.getDays()+" 天" );

//目標(biāo)日期距離今天的時間差:1 年 1 個月 1 天

查看between源碼:

public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
    return startDateInclusive.until(endDateExclusive);
}
public Period until(ChronoLocalDate endDateExclusive) {
    LocalDate end = LocalDate.from(endDateExclusive);
    long totalMonths = end.getProlepticMonth() - this.getProlepticMonth();  // safe
    int days = end.day - this.day;
    if (totalMonths > 0 && days < 0) {
        totalMonths--;
        LocalDate calcDate = this.plusMonths(totalMonths);
        days = (int) (end.toEpochDay() - calcDate.toEpochDay());  // safe
    } else if (totalMonths < 0 && days > 0) {
        totalMonths++;
        days -= end.lengthOfMonth();
    }
    long years = totalMonths / 12;  // safe
    int months = (int) (totalMonths % 12);  // safe
    return Period.of(Math.toIntExact(years), months, days);
}

只接受兩個LocalDate對象,對時間的計(jì)算,算好之后返回Period對象

⑥ Duration 類

Duration 是期間持續(xù)時間的意思

示例代碼:

LocalDateTime end = LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.systemDefault());
LocalDateTime start = LocalDateTime.ofInstant(Instant.ofEpochMilli(1601175465000L), ZoneId.systemDefault());
Duration duration = Duration.between(start, end);

System.out.println("開始時間到結(jié)束時間,持續(xù)了"+duration.toDays()+"天");
System.out.println("開始時間到結(jié)束時間,持續(xù)了"+duration.toHours()+"小時");
System.out.println("開始時間到結(jié)束時間,持續(xù)了"+duration.toMillis()/1000+"秒");

可以看到between也接受兩個參數(shù),LocalDateTime對象,源碼是對兩個時間的計(jì)算,并返回對象。

2. 時間間隔計(jì)算

2.1 Period與Duration類

JAVA8開始新增的java.time包中有提供DurationPeriod兩個類,用于處理日期時間間隔相關(guān)的場景,兩個類的區(qū)別點(diǎn)如下:

描述
Duration時間間隔,用于秒級的時間間隔計(jì)算
Period日期間隔,用于天級別的時間間隔計(jì)算,比如年月日維度的

DurationPeriod具體使用的時候還需要有一定的甄別,因?yàn)椴糠值姆椒ê苋菀资褂弥斜换煜?,下面分別說明下。

2.1.1 Duration

Duration的最小計(jì)數(shù)單位為納秒,其內(nèi)部使用secondsnanos兩個字段來進(jìn)行組合計(jì)數(shù)表示duration總長度。

image-20220719150424650

Duration的常用API方法梳理如下:

方法描述
between計(jì)算兩個時間的間隔,默認(rèn)是
ofXxxof開頭的一系列方法,表示基于給定的值創(chuàng)建一個Duration實(shí)例。比如ofHours(2L),則表示創(chuàng)建一個Duration對象,其值為間隔2小時
plusXxxplus開頭的一系列方法,用于在現(xiàn)有的Duration值基礎(chǔ)上增加對應(yīng)的時間長度,比如plusDays()表示追加多少天,或者plusMinutes()表示追加多少分鐘
minusXxxminus開頭的一系列方法,用于在現(xiàn)有的Duration值基礎(chǔ)上扣減對應(yīng)的時間長度,與plusXxx相反
toXxxxto開頭的一系列方法,用于將當(dāng)前Duration對象轉(zhuǎn)換為對應(yīng)單位的long型數(shù)據(jù),比如toDays()表示將當(dāng)前的時間間隔的值,轉(zhuǎn)換為相差多少天,而toHours()則標(biāo)識轉(zhuǎn)換為相差多少小時。
getSeconds獲取當(dāng)前Duration對象對應(yīng)的秒數(shù), 與toXxx方法類似,只是因?yàn)镈uration使用秒作為計(jì)數(shù)單位,所以直接通過get方法即可獲取到值,而toDays()是需要通過將秒數(shù)轉(zhuǎn)為天數(shù)換算之后返回結(jié)果,所以提供的方法命名上會有些許差異。
getNano獲取當(dāng)前Duration對應(yīng)的納秒數(shù)“零頭”。注意這里與toNanos()不一樣,toNanos是Duration值的納秒單位總長度,getNano()只是獲取不滿1s剩余的那個零頭,以納秒表示。
isNegative檢查Duration實(shí)例是否小于0,若小于0返回true, 若大于等于0返回false
isZero用于判斷當(dāng)前的時間間隔值是否為0 ,比如比較兩個時間是否一致,可以通過between計(jì)算出Duration值,然后通過isZero判斷是否沒有差值。
withSeconds對現(xiàn)有的Duration對象的nanos零頭值不變的情況下,變更seconds部分的值,然后返回一個新的Duration對象
withNanos對現(xiàn)有的Duration對象的seconds值不變的情況下,變更nanos部分的值,然后返回一個新的Duration對象

關(guān)于Duration的主要API的使用,參見如下示意:

@Test
    void durationTEst(){
        LocalTime target = LocalTime.parse("00:02:35.700");
        // 獲取當(dāng)前日期,此處為了保證后續(xù)結(jié)果固定,注掉自動獲取當(dāng)前日期,指定固定日期
        // LocalDate today = LocalDate.now();
        LocalTime today = LocalTime.parse("12:12:25.600");

        // 輸出:12:12:25.600
        System.out.println(today);
        // 輸出:00:02:35.700
        System.out.println(target);

        Duration duration = Duration.between(target, today);

        // 輸出:PT12H9M49.9S
        System.out.println(duration);
        // 輸出:43789
        System.out.println(duration.getSeconds());
        // 輸出:900000000
        System.out.println(duration.getNano());
        // 輸出:729
        System.out.println(duration.toMinutes());
        // 輸出:PT42H9M49.9S
        System.out.println(duration.plusHours(30L));
        // 輸出:PT15.9S
        System.out.println(duration.withSeconds(15L));

    }

2.1.2 Period

Period相關(guān)接口與Duration類似,其計(jì)數(shù)的最小單位是,看下Period內(nèi)部時間段記錄采用了年、月、日三個field來記錄:

常用的API方法列舉如下:

方法描述
between計(jì)算兩個日期之間的時間間隔。注意,這里只能計(jì)算出相差幾年幾個月幾天
ofXxxof()或者以of開頭的一系列static方法,用于基于傳入的參數(shù)構(gòu)造出一個新的Period對象
withXxxwith開頭的方法,比如withYears、withMonthswithDays等方法,用于對現(xiàn)有的Period對象中對應(yīng)的年、月、日等字段值進(jìn)行修改(只修改對應(yīng)的字段,比如withYears方法,只修改year,保留month和day不變),并生成一個新的Period對象
getXxx讀取Period中對應(yīng)的year、month、day字段的值。注意下,這里是僅get其中的一個字段值,而非整改Period的不同單位維度的總值。
plusXxx對指定的字段進(jìn)行追加數(shù)值操作
minusXxx對指定的字段進(jìn)行扣減數(shù)值操作
isNegative檢查Period實(shí)例是否小于0,若小于0返回true, 若大于等于0返回false
isZero用于判斷當(dāng)前的時間間隔值是否為0 ,比如比較兩個時間是否一致,可以通過between計(jì)算出Period值,然后通過isZero判斷是否沒有差值。

關(guān)于Period的主要API的使用,參見如下示意:

    @Test
    void periodTest(){
        LocalDate target = LocalDate.parse("2021-07-11");
        // 獲取當(dāng)前日期,此處為了保證后續(xù)結(jié)果固定,注掉自動獲取當(dāng)前日期,指定固定日期
        // LocalDate today = LocalDate.now();
        LocalDate today = LocalDate.parse("2022-07-08");

        // 輸出:2022-07-08
        System.out.println(today);
        // 輸出:2021-07-11
        System.out.println(target);

        Period period = Period.between(target, today);

        // 輸出:P11M27D, 表示11個月27天
        System.out.println(period);
        // 輸出:0, 因?yàn)閜eriod值為11月27天,即year字段為0
        System.out.println(period.getYears());
        // 輸出:11, 因?yàn)閜eriod值為11月27天,即month字段為11
        System.out.println(period.getMonths());
        // 輸出:27, 因?yàn)閜eriod值為11月27天,即days字段為27
        System.out.println(period.getDays());
        // 輸出:P14M27D, 因?yàn)閜eriod為11月27天,加上3月,變成14月27天
        System.out.println(period.plusMonths(3L));
        // 輸出:P11M15D,因?yàn)閜eriod為11月27天,僅將days值設(shè)置為15,則變?yōu)?1月15天
        System.out.println(period.withDays(15));
        // 輸出:P2Y3M44D
        System.out.println(Period.of(2, 3, 44));

    }

2.2 Duration與Period的坑

Duration與Period都是用于日期之間的計(jì)算操作。

  • Duration主要用于秒、納秒等維度的數(shù)據(jù)處理與計(jì)算。
  • Period主要用于計(jì)算年、月、日等維度的數(shù)據(jù)處理與計(jì)算

Duration的坑

先看個例子,計(jì)算兩個日期相差的天數(shù),使用Duration的時候:

public void calculateDurationDays(String targetDate) {
    LocalDate target = LocalDate.parse(targetDate);
    LocalDate today = LocalDate.now();
    System.out.println("today : " + today);
    System.out.println("target: " + target);
    long days = Duration.between(target, today).abs().toDays();
    System.out.println("相差:"  + days + "天");
}

運(yùn)行后會報錯:

today : 2022-07-07
target: 2022-07-11
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
    at java.time.LocalDate.until(LocalDate.java:1614)
    at java.time.Duration.between(Duration.java:475)
    at com.veezean.demo5.DateService.calculateDurationDays(DateService.java:24)

點(diǎn)擊看下Duration.between源碼,可以看到注釋上明確有標(biāo)注著,這個方法是用于秒級的時間段間隔計(jì)算,而我們這里傳入的是兩個級別的數(shù)據(jù),所以就不支持此類型運(yùn)算,然后拋異常了。

Period的坑

同樣是計(jì)算兩個日期相差的天數(shù),再看下使用Period的實(shí)現(xiàn):

public void calculateDurationDays(String targetDate) {
    LocalDate target = LocalDate.parse(targetDate);
    LocalDate today = LocalDate.now();
    System.out.println("today : " + today);
    System.out.println("target: " + target);
    // 注意,此處寫法錯誤!這里容易踩坑:
    long days = Math.abs(Period.between(target, today).getDays());
    System.out.println("相差:"  + days + "天");
}

執(zhí)行結(jié)果:

today : 2022-07-07
target: 2021-07-07
相差:0天

執(zhí)行是不報錯,但是結(jié)果明顯是錯誤的。這是因?yàn)間etDays()并不會將Period值換算為天數(shù),而是單獨(dú)計(jì)算年、月、日,此處只是返回天數(shù)這個單獨(dú)的值。

再看下面的寫法:

public void calculateDurationDays(String targetDate) {
    LocalDate target = LocalDate.parse(targetDate);
    LocalDate today = LocalDate.now();
    System.out.println("today : " + today);
    System.out.println("target: " + target);
    Period between = Period.between(target, today);
    System.out.println("相差:"
            + Math.abs(between.getYears()) + "年"
            + Math.abs(between.getMonths()) + "月"
            + Math.abs(between.getDays()) + "天");
}

結(jié)果為:

today : 2022-07-07
target: 2021-07-11
相差:0年11月26天

所以說,如果想要計(jì)算兩個日期之間相差的絕對天數(shù),用Period不是一個好的思路。

2.3 計(jì)算日期差

2.3.1 通過LocalDate來計(jì)算

LocalDate中的toEpocDay可返回當(dāng)前時間距離原點(diǎn)時間之間的天數(shù),可以基于這一點(diǎn),來實(shí)現(xiàn)計(jì)算兩個日期之間相差的天數(shù):

代碼如下:

public void calculateDurationDays(String targetDate) {
    LocalDate target = LocalDate.parse(targetDate);
    LocalDate today = LocalDate.now();
    System.out.println("today : " + today);
    System.out.println("target: " + target);
    long days = Math.abs(target.toEpochDay() - today.toEpochDay());
    System.out.println("相差:" + days + "天");
}

結(jié)果為:

today : 2022-07-07
target: 2021-07-11
相差:361天

2.3.2 通過時間戳來計(jì)算

如果是使用的Date對象,則可以通過將Date日期轉(zhuǎn)換為毫秒時間戳的方式相減然后將毫秒數(shù)轉(zhuǎn)為天數(shù)的方式來得到結(jié)果。需要注意的是通過毫秒數(shù)計(jì)算日期天數(shù)的差值時,需要屏蔽掉時分秒帶來的誤差影響。

數(shù)學(xué)邏輯計(jì)算(不推薦)

分別算出年、月、日差值,然后根據(jù)是否閏年、每月是30還是31天等計(jì)數(shù)邏輯,純數(shù)學(xué)硬懟方式計(jì)算。

不推薦、代碼略...

計(jì)算接口處理耗時

在一些性能優(yōu)化的場景中,我們需要獲取到方法處理的執(zhí)行耗時,很多人都是這么寫的:

public void doSomething() {
    // 記錄開始時間戳
    long startMillis = System.currentTimeMillis();
    // do something ...

    // 計(jì)算結(jié)束時間戳
    long endMillis = System.currentTimeMillis();
    // 計(jì)算相差的毫秒數(shù)
    System.out.println(endMillis - startMillis);
}

當(dāng)然啦,如果你使用的是JDK8+的版本,你還可以這么寫:

public void doSomething() {
    // 記錄開始時間戳
    Instant start = Instant.now();
    // do something ...

    // 計(jì)算結(jié)束時間戳
    Instant end = Instant.now();

    // 計(jì)算相差的毫秒數(shù)
    System.out.println(Duration.between(start, end).toMillis());
}

2.4 計(jì)算時間差

使用Hutool工具進(jìn)行計(jì)算

一款超厲害的國產(chǎn)Java工具——Hutool。Hutool是一個Java工具包類庫,對文件、流、加密解密、轉(zhuǎn)碼、正則、線程、XML等JDK方法進(jìn)行封裝,組成各種Util工具類。適用于很多項(xiàng)目以及Web開發(fā),并且與其他框架沒有耦合性。

引入依賴:

        <!-- https://mvnrepository.com/artifact/com.xiaoleilu/hutool-all -->
        <dependency>
            <groupId>com.xiaoleilu</groupId>
            <artifactId>hutool-all</artifactId>
            <version>3.3.2</version>
        </dependency>

封裝時間類進(jìn)行計(jì)算

制作Calendar工具類計(jì)算:

基于Calendar對時間計(jì)算進(jìn)行相應(yīng)的封裝處理,如下面兩個例子,可以根據(jù)需求將相關(guān)的計(jì)算封裝在一個Util工具類中

獲取本周開始時間戳

/**
 * start
     * 本周開始時間戳
 */
public static Date getWeekStartTime() {
    Calendar calendar = Calendar.getInstance();
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
    if (dayOfWeek == 0){
        dayOfWeek = 7;
    }
    calendar.add(Calendar.DATE, - dayOfWeek + 1);

    calendar.set(Calendar.HOUR_OF_DAY, 0);
    //將分鐘至0
    calendar.set(Calendar.MINUTE, 0);
    //將秒至0
    calendar.set(Calendar.SECOND, 0);
    //將毫秒至0
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}

根據(jù)日期和天數(shù)進(jìn)行計(jì)算

    /**
         * 獲取當(dāng)前時間的月幾號0點(diǎn)時間或第二天0時間戳(即幾號的24點(diǎn))
     * @param calendar 當(dāng)前時間對象
     * @param day 幾號, 值范圍 是1 到 當(dāng)前時間月天數(shù) + 1 整數(shù), 
         *  傳入(day+1)為day號的第二天0點(diǎn)時間(day號的24點(diǎn)時間),
         *  如果值為當(dāng)前時間月天數(shù)+1則結(jié)果為當(dāng)前月的下個月1號0點(diǎn)(即當(dāng)月最后一天的24點(diǎn)),
         *  如果當(dāng)前月的天數(shù)為31天, 傳入32時則為當(dāng)前月的下個月1號0點(diǎn)(即當(dāng)月最后一天的24點(diǎn))
     * @return
     */
	public static Date getDayOfMonthStartOrEndTime(Calendar calendar, int day) {
	    Calendar calendarTemp = Calendar.getInstance();
	    calendarTemp.setTime(calendar.getTime());
	    int days = getDaysOfMonth(calendarTemp);
	    int limitDays = days + 1;
	    if (day > limitDays) {
	    	calendarTemp.set(Calendar.DAY_OF_MONTH, limitDays);
		} else {
			if (day >= 1) {
				calendarTemp.set(Calendar.DAY_OF_MONTH, day);
			} else {
				calendarTemp.set(Calendar.DAY_OF_MONTH, 1);
			}
		}
	    //將小時至0
	    calendarTemp.set(Calendar.HOUR_OF_DAY, 0);
	    //將分鐘至0
	    calendarTemp.set(Calendar.MINUTE, 0);
	    //將秒至0
	    calendarTemp.set(Calendar.SECOND, 0);
	    //將毫秒至0
	    calendarTemp.set(Calendar.MILLISECOND, 0);

	    //獲得當(dāng)前月幾號0點(diǎn)或幾號的第二天0點(diǎn)(即幾號的24點(diǎn))
	    Date startTime = calendarTemp.getTime();
	   return startTime;
	}

制作Date工具類計(jì)算

Java項(xiàng)目開發(fā)中常見的日期操作工具類封裝:代碼如下(示例):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Time;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.*;
/**
 * @author hhlm
 * @description: Java日期類型相關(guān)操作類;該類負(fù)責(zé)對日期格式化轉(zhuǎn)換、日期比較、日期加減、潤年判斷、獲 
 * 取相關(guān)的日期信息等。
 *  * @date 2022年03月02日 16:24 
 */
public class DateUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(DateUtil.class);
    public static final long ONE_MINUTE_MILLISECOND = 60 * 1000L;
    public static final long ONE_HOUR_MILLISECOND = 60 * ONE_MINUTE_MILLISECOND;
    /**
     * 一天所對應(yīng)的毫秒數(shù)
     */
    public static final long ONE_DAY_MILLISECOND = 24 * ONE_HOUR_MILLISECOND;
    /**
     * 一周所對應(yīng)的毫秒數(shù)
     */
    public static final long ONE_WEEK_MILLISECOND = 7 * ONE_DAY_MILLISECOND;
    public static final long ONE_MONTH_MILLISECOND = 30 * ONE_DAY_MILLISECOND;
    public static final long ONE_YEAR_MILLISECOND = 365 * ONE_DAY_MILLISECOND;
    private static String defaultDatePattern = null;
    /**
     * 從配置文件中返回配置項(xiàng)"date.format",默認(rèn)的日期格式符 (yyyy-MM-dd),
     *
     * @return a string representing the date pattern on the UI
     */
    public static synchronized String getDatePattern() {
        defaultDatePattern = "yyyy-MM-dd";
        return defaultDatePattern;
    }
    /**
     * 校驗(yàn)日期入?yún)⑹欠裾_,如防止sql注入
     * @author linjx 2018-12-24
     * @param desc 入?yún)⒌拿枋?
     * @param format
     * @param dateStr notEmptyString
     * @return
     */
    public static Date validDate(String desc, String format, String dateStr) {
        LOGGER.debug("validDate.desc={}, format={}, dateStr={}", desc, format, dateStr);
        Date parse = DateUtil2.parse(dateStr, format, desc);
        /**
            format='yyyy-MM-dd',dateStr='2019-12-15' or '1'='1'是不會有ParseException的
            所以需要將parse重新格式化成字符串,和dateStr比較
         */
        AssertApp.isTrue(dateStr.equals(DateUtil2.format(parse, format)), desc + "異常");
        return parse;
    }
    /**
     * 獲取日期的年份
     *
     * @return 日期的年份
     */
    public static int getYear(Date date) {
        return getCalendar(date).get(Calendar.YEAR);
    }
    /**
     * 獲取日期的月份(0-11)
     *
     * @param date
     * @return 日期的月份(0-11)
     */
    public static int getMonth(Date date) {
        return getCalendar(date).get(Calendar.MONTH);
    }
    /**
     * 獲取日期的一個月中的某天
     *
     * @param date
     * @return 日期的一個月中的某天(1-31)
     */
    public static int getDay(Date date) {
        return getCalendar(date).get(Calendar.DATE);
    }
    /**
     * 獲取日期的一個星期中的某天
     *
     * @param date
     * @return 日期的星期中日期(1:sunday-7:SATURDAY)
     */
    public static int getWeek(Date date) {
        return getCalendar(date).get(Calendar.DAY_OF_WEEK);
    }
    /**
     * 將日期字符串按指定的格式轉(zhuǎn)為Date類型
     *
     * @param strDate 待解析的日期字符串
     * @param format  日期格式
     * @return 字符串對應(yīng)的日期對象
     */
    public static final Date parseDate(String strDate, String format) {
        return DateUtil2.parse(strDate, format, strDate);
    }
    /**
     * 將日期字符串按系統(tǒng)配置中指定默認(rèn)格式(getDatePattern()返回的格式)轉(zhuǎn)為Date類型
     *
     * @param strDate 待解析的日期字符串
     * @return 字符串對應(yīng)的日期對象
     */
    public static Date parseDate(String strDate) {
        return parseDate(strDate, getDatePattern());
    }
    /**
     * <p>檢查所給的年份是否是閏年</p>
     *
     * @param year 年
     * @return 檢查結(jié)果: true - 是閏年; false - 是平年
     */
    public static boolean isLeapYear(int year) {
        if (year / 4 * 4 != year) {
            return false; //不能被4整除
        } else if (year / 100 * 100 != year) {
            return true; //能被4整除,不能被100整除
        } else if (year / 400 * 400 != year) {
            return false; //能被100整除,不能被400整除
        } else {
            return true; //能被400整除
        }
    }
    /**
     * 按照默認(rèn)格式化樣式格式化當(dāng)前系統(tǒng)時間
     *
     * @return 日期字符串
     */
    public static String getCurrentTime() {
        return formatDate(new Date());
    }

    /**
     * 按照默認(rèn)格式化樣式格式化當(dāng)前系統(tǒng)時間
     *
     * @param format String 日期格式化標(biāo)準(zhǔn)
     * @return String 日期字符串。
     */
    public static String getCurrentTime(String format) {
        return formatDate(new Date(), format);
    }
    /**
     * 按照指定格式化樣式格式化指定的日期
     *
     * @param date   待格式化的日期
     * @param format 日期格式
     * @return 日期字符串
     */
    public static String formatDate(Date date, String format) {
        if (date == null) return "";
        if (format == null) format = getDatePattern();
        SimpleDateFormat formatter = new SimpleDateFormat(format);
        return formatter.format(date);
    }
    /**
     * 按照默認(rèn)格式化樣式格式化指定的日期
     *
     * @param date 待格式化的日期
     * @return 日期字符串
     */
    public static String formatDate(Date date) {
        long offset = System.currentTimeMillis() - date.getTime();
        String pos = "前";
        if (offset < 0) {
            pos = "后";
            offset = -offset;
        }
        if (offset >= ONE_YEAR_MILLISECOND)
            return formatDate(date, getDatePattern());

        StringBuilder sb = new StringBuilder();
        if (offset >= 2 * ONE_MONTH_MILLISECOND) {
            return sb.append((offset + ONE_MONTH_MILLISECOND / 2) / ONE_MONTH_MILLISECOND).append("個月").append(pos).toString();
        }
        if (offset > ONE_WEEK_MILLISECOND) {
            return sb.append((offset + ONE_WEEK_MILLISECOND / 2) / ONE_WEEK_MILLISECOND).append("周").append(pos).toString();
        }
        if (offset > ONE_DAY_MILLISECOND) {
            return sb.append((offset + ONE_DAY_MILLISECOND / 2) / ONE_DAY_MILLISECOND).append("天").append(pos).toString();
        }
        if (offset > ONE_HOUR_MILLISECOND) {
            return sb.append((offset + ONE_HOUR_MILLISECOND / 2) / ONE_HOUR_MILLISECOND).append("小時").append(pos).toString();
        }
        if (offset > ONE_MINUTE_MILLISECOND) {
            return sb.append((offset + ONE_MINUTE_MILLISECOND / 2) / ONE_MINUTE_MILLISECOND).append("分鐘").append(pos).toString();
        }
        return sb.append(offset / 1000).append("秒").append(pos).toString();
    }
    /**
     * 將date的時間部分清零
     *
     * @param day
     * @return 返回Day將時間部分清零后對應(yīng)日期
     */
    public static Date getCleanDay(Date day) {
        return getCleanDay(getCalendar(day));
    }

    /**
     * 設(shè)置當(dāng)天最后時間
     *
     * @param day
     * @return 返回當(dāng)天最后時間
     */
    public static Date getEndDay(Date day) {
        Calendar c = Calendar.getInstance();
        c.setTime(day);
        c.set(Calendar.HOUR_OF_DAY, 23);
        c.set(Calendar.MINUTE, 59);
        c.set(Calendar.SECOND, 59);
        c.set(Calendar.MILLISECOND, 999);
        return c.getTime();
    }
    /**
     * 獲取day對應(yīng)的Calendar對象
     *
     * @param day
     * @return 返回date對應(yīng)的Calendar對象
     */
    public static Calendar getCalendar(Date day) {
        Calendar c = Calendar.getInstance();
        if (day != null)
            c.setTime(day);
        return c;
    }
    public static Date getCleanDay(Calendar c) {
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.clear(Calendar.MINUTE);
        c.clear(Calendar.SECOND);
        c.clear(Calendar.MILLISECOND);
        return c.getTime();
    }
    /**
     * 根據(jù)year,month,day構(gòu)造日期對象
     *
     * @param year  年份(4位長格式)
     * @param month 月份(1-12)
     * @param day   天(1-31)
     * @return 日期對象
     */
    public static Date makeDate(int year, int month, int day) {
        Calendar c = Calendar.getInstance();
        getCleanDay(c);
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, month - 1);
        c.set(Calendar.DAY_OF_MONTH, day);
        return c.getTime();
    }
    private static Date getFirstCleanDay(int datePart, Date date) {
        Calendar c = Calendar.getInstance();
        if (date != null)
            c.setTime(date);
        c.set(datePart, 1);
        return getCleanDay(c);
    }
    /**
     * Calendar.YEAR :1則代表的是對年份操作,
     * Calendar.MONTH :2是對月份操作;
     * Calendar.DATE : 5是對日期操作;
     * @param datePart
     * @param detal
     * @param date
     * @return
     */
    public static Date add(int datePart, int detal, Date date) {
        Calendar c = Calendar.getInstance();
        if (date != null)
            c.setTime(date);
        c.add(datePart, detal);
        return c.getTime();
    }
    /**
     * 日期date所在星期的第一天00:00:00對應(yīng)日期對象
     *
     * @param date
     * @return 日期所在星期的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfWeek(Date date) {
        return getFirstCleanDay(Calendar.DAY_OF_WEEK, date);
    }
    /**
     * 當(dāng)前日期所在星期的第一天00:00:00對應(yīng)日期對象
     *
     * @return 當(dāng)前日期所在星期的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfWeek() {
        return getFirstDayOfWeek(null);
    }
    /**
     * 日期date所在月份的第一天00:00:00對應(yīng)日期對象
     *
     * @param date
     * @return 日期所在月份的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfMonth(Date date) {
        return getFirstCleanDay(Calendar.DAY_OF_MONTH, date);
    }
    /**
     * 當(dāng)前日期所在月份的第一天00:00:00對應(yīng)日期對象
     *
     * @return 當(dāng)前日期所在月份的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfMonth() {
        return getFirstDayOfMonth(null);
    }
    /**
     * 日期date所在月份的最后一天23, 59, 59對應(yīng)日期對象
     *
     * @param date
     * @return 日期date所在月份的最后一天23, 59, 59對應(yīng)日期對象
     */
    public static Date getLastDayOfMonth(Date date) {
        Calendar cal = Calendar.getInstance();  
        cal.setTime(date);
        int MaxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), MaxDay, 23, 59, 59);
        return cal.getTime();
    }

    /**
     * 日期date所在季度的第一天00:00:00對應(yīng)日期對象
     *
     * @param date
     * @return 日期date所在季度的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfSeason(Date date) {
        Date d = getFirstDayOfMonth(date);
        int delta = DateUtil.getMonth(d) % 3;
        if (delta > 0)
            d = DateUtil.getDateAfterMonths(d, -delta);
        return d;
    }
    /**
     * 當(dāng)前日期所在季度的第一天00:00:00對應(yīng)日期對象
     *
     * @return 當(dāng)前日期所在季度的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfSeason() {
        return getFirstDayOfMonth(null);
    }
    /**
     * 日期date所在年份的第一天00:00:00對應(yīng)日期對象
     *
     * @param date
     * @return 日期date所在年份的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfYear(Date date) {
        return makeDate(getYear(date), 1, 1);
    }
    /**
     * 獲取某年的第一天
     * @param year
     * @return 日期date所在年份的第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDaylOfYear(int year){
        return makeDate(year, 1, 1);
    }
    /**
     * 當(dāng)前日期年度的第一天00:00:00對應(yīng)日期對象
     *
     * @return 當(dāng)前日期年度第一天00:00:00對應(yīng)日期對象
     */
    public static Date getFirstDayOfYear() {
        return getFirstDayOfYear(new Date());
    }

    /**
     * 當(dāng)前日期年度的最后一天23:59:59對應(yīng)日期對象
     *
     * @return 當(dāng)前日期年度的最后一天23:59:59對應(yīng)日期對象
     */
    public static Date getLastDayOfYear() {
        return parseDate(getYear(new Date()) + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss");
    }
    /**
     * 獲取某年最后一天
     * @return  傳入年份的最后一天23:59:59對應(yīng)日期對象
     */
    public static Date getLastDayOfYear(int year){
        return DateUtil2.parse(year + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss", "");
    }
    /**
     * 計(jì)算N周后的日期
     *
     * @param start 開始日期
     * @param weeks 可以為負(fù),表示前N周
     * @return 新的日期
     */
    public static Date getDateAfterWeeks(Date start, int weeks) {
        return getDateAfterMs(start, weeks * ONE_WEEK_MILLISECOND);
    }

    /**
     * 計(jì)算N月后的日期, 特殊情況:如果是'2016-1-31'一個月后是  '2017-2-28'
     *
     * @param start  開始日期
     * @param months 可以為負(fù),表示前N月
     * @return 新的日期
     */
    public static Date getDateAfterMonths(Date start, int months) {
        return add(Calendar.MONTH, months, start);
    }
    /**
     * 計(jì)算N年后的日期, 特殊情況:如果是'2016-2-29'一年后是'2017-2-28'
     *
     * @param start 開始日期
     * @param years 可以為負(fù),表示前N年
     * @return 新的日期
     */
    public static Date getDateAfterYears(Date start, int years) {
        return add(Calendar.YEAR, years, start);
    }
    /**
     * 計(jì)算N天后的日期
     *
     * @param start 開始日期
     * @param days  可以為負(fù),表示前N天
     * @return 新的日期
     */
    public static Date getDateAfterDays(Date start, int days) {
        return getDateAfterMs(start, days * ONE_DAY_MILLISECOND);
    }
    /**
     * 計(jì)算N毫秒后的日期
     *
     * @param start 開始日期
     * @param ms    可以為負(fù),表示前N毫秒
     * @return 新的日期
     */
    public static Date getDateAfterMs(Date start, long ms) {
        return new Date(start.getTime() + ms);
    }
    /**
     * 計(jì)算2個日期之間的間隔的周期數(shù)
     *
     * @param start    開始日期
     * @param end      結(jié)束日期
     * @param msPeriod 單位周期的毫秒數(shù)
     * @return 周期數(shù)
     */
    public static long getPeriodNum(Date start, Date end, long msPeriod) {
        return getIntervalMs(start, end) / msPeriod;
    }
    /**
     * 計(jì)算2個日期之間的毫秒數(shù)
     *
     * @param start 開始日期
     * @param end   結(jié)束日期
     * @return 毫秒數(shù)
     */
    public static long getIntervalMs(Date start, Date end) {
        return end.getTime() - start.getTime();
    }
    /**
     * 計(jì)算2個日期之間的天數(shù)
     *
     * @param start 開始日期
     * @param end   結(jié)束日期
     * @return 天數(shù)
     */
    public static int getIntervalDays(Date start, Date end) {
        return (int) getPeriodNum(start, end, ONE_DAY_MILLISECOND);
    }
    /**
     * 計(jì)算2個日期之間的周數(shù)
     *
     * @param start 開始日期
     * @param end   結(jié)束日期
     * @return 周數(shù)
     */
    public static int getIntervalWeeks(Date start, Date end) {
        return (int) getPeriodNum(start, end, ONE_WEEK_MILLISECOND);
    }
    /**
     * 比較日期前后關(guān)系
     *
     * @param base 基準(zhǔn)日期
     * @param date 待比較的日期
     * @return 如果date在base之前或相等返回true,否則返回false
     */
    public static boolean before(Date base, Date date) {
        return date.before(base) || date.equals(base);
    }
    /**
     * 比較日期前后關(guān)系
     *
     * @param base 基準(zhǔn)日期
     * @param date 待比較的日期
     * @return 如果date在base之后或相等返回true,否則返回false
     */
    public static boolean after(Date base, Date date) {
        return date.after(base) || date.equals(base);
    }
    /**
     * 返回對應(yīng)毫秒數(shù)大的日期
     *
     * @param date1
     * @param date2
     * @return 返回對應(yīng)毫秒數(shù)大的日期
     */
    public static Date max(Date date1, Date date2) {
        if (date1.getTime() > date2.getTime())
            return date1;
        else
            return date2;
    }
    /**
     * 返回對應(yīng)毫秒數(shù)小的日期
     *
     * @param date1
     * @param date2
     * @return 返回對應(yīng)毫秒數(shù)小的日期
     */
    public static Date min(Date date1, Date date2) {
        if (date1.getTime() < date2.getTime())
            return date1;
        else
            return date2;
    }
    /**
     * 判斷date是否在指定的時期范圍(start~end)內(nèi)
     *
     * @param start 時期開始日期
     * @param end   時期結(jié)束日期
     * @param date  待比較的日期
     * @return 如果date在指定的時期范圍內(nèi),返回true,否則返回false
     */
    public static boolean inPeriod(Date start, Date end, Date date) {
        return (end.after(date) || end.equals(date)) && (start.before(date) || start.equals(date));
    }

    /**
     * 獲取當(dāng)前日期是星期幾<br>
     *
     * @param dt
     * @return 當(dāng)前日期是星期幾
     */
    public static String getWeekOfDate(Date dt) {
        String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
        Calendar cal = Calendar.getInstance();
        cal.setTime(dt);
        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (w < 0)
            w = 0;
        return weekDays[w];
    }
    private static final DateTimeFormatter SHORT_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter LONG_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final TemporalField CHINA_DAY_OF_WEEK = WeekFields.of(Locale.CHINA).dayOfWeek();
    /**
     * 獲取指定日期所在周的第一天
     *
     * @param date
     * @return
     */
    public static LocalDate firstDayOfWeek(TemporalAccessor date) {
        return LocalDate.from(date).with(CHINA_DAY_OF_WEEK, 1);
    }
    /**
     * 獲取指定日期所在周的最后一天
     *
     * @param date
     * @return
     */
    public static LocalDate lastDayOfWeek(TemporalAccessor date) {
        return LocalDate.from(date).with(CHINA_DAY_OF_WEEK, 7);
    }
    /**
     * 獲取指定日期所在月的第一天
     *
     * @param date
     * @return
     */
    public static LocalDate firstDayOfMonth(TemporalAccessor date) {
        return LocalDate.from(date).withDayOfMonth(1);
    }
    /**
     * 獲取指定日期所在周的最后一天
     *
     * @param date
     * @return
     */
    public static LocalDate lastDayOfMonth(TemporalAccessor date) {
        return LocalDate.from(date).plusMonths(1).withDayOfMonth(1).plusDays(-1);
    }

    /**
     * 短日期格式
     *
     * @param date
     * @return
     */
    public static String shortString(TemporalAccessor date) {
        return SHORT_DATETIME_FORMATTER.format(date);
    }
    /**
     * 長日期格式
     *
     * @param date
     * @return
     */
    public static String longString(TemporalAccessor date) {
        return LONG_DATETIME_FORMATTER.format(date);
    }
    /**
     * 將指定定的日期轉(zhuǎn)換成LocalDateTime
     *
     * @param date
     * @return
     */
    public static LocalDateTime asLocalDateTime(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * 將指定定的日期轉(zhuǎn)換成LocalDate
     *
     * @param date
     * @return
     */
    public static LocalDate asLocalDate(Date date) {
        return asLocalDateTime(date).toLocalDate();
    }
    /**
     * LocalDate 轉(zhuǎn)為 Date
     *
     * @param localDate
     * @return
     */
    public static Date asDate(LocalDate localDate) {
        return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }
    /**
     * LocalDateTime 轉(zhuǎn)為 Date
     *
     * @param localDateTime
     * @return
     */
    public static Date asDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 按照“年-月”生成當(dāng)月的工作日
     * @param yearMonth [yyyy-MM]
     * @return
     */
    public static List<Date> getWorkdayOfMonth(String yearMonth){
    	int year;
    	int month ; 
    	List<Date> results = new ArrayList<>();
    	Calendar cal = Calendar.getInstance();
    	String[] splitStr = yearMonth.split("-");
    	year = Integer.parseInt(splitStr[0]);
		month = Integer.parseInt(splitStr[1]);
		cal.set(Calendar.YEAR, year);
    	cal.set(Calendar.MONTH, month - 1);
    	cal.set(Calendar.DATE, 1);
    	while(cal.get(Calendar.YEAR) == year && 
    			cal.get(Calendar.MONTH) < month){
    		int day = cal.get(Calendar.DAY_OF_WEEK);
    		if(!(day == Calendar.SUNDAY || day == Calendar.SATURDAY)){
    			results.add((Date) cal.getTime().clone());
    		}
    		cal.add(Calendar.DATE, 1);
    	}
    	return results;
    }
    /**
     * 按照年份生成一年內(nèi)的工作日
     * @param year
     * @return
     */
    public static List<Date> getWorkdayOfYear(int year){
    	List<Date> results = new ArrayList<>();
    	Calendar cal = Calendar.getInstance();
    	cal.set(Calendar.YEAR, year);
    	cal.set(Calendar.MONTH, 0);
    	cal.set(Calendar.DATE, 1);
    	while(cal.get(Calendar.YEAR) == year){
    		int day = cal.get(Calendar.DAY_OF_WEEK);
    		if(!(day == Calendar.SUNDAY || day == Calendar.SATURDAY)){
    			results.add((Date) cal.getTime().clone());
    		}
    		cal.add(Calendar.DATE, 1);
    	}
		return results;
    }
    /**
     * 獲取上一個月的第一天時間
     */
    public static Date getFirstDayOfLastMonth(){
        Calendar cal = Calendar.getInstance();//獲取當(dāng)前日期 
        /*
         * 2018-03-31時通過測試:
         * 不設(shè)置cal.set(Calendar.DAY_OF_MONTH, 1)時,得到的日期是2018-02-28而不是3月
         */
        cal.add(Calendar.MONTH, -1);
        cal.set(Calendar.DAY_OF_MONTH, 1);//設(shè)置為1號,當(dāng)前日期既為本月第一天 
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.clear(Calendar.MINUTE);
        cal.clear(Calendar.SECOND);
        cal.clear(Calendar.MILLISECOND);
        return cal.getTime();
    }
    /**
     * 獲取指定日期的上月一號
     * @param date
     * @return
     */
    public static Date getLastMonthFirst(Date date) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        c.set(Calendar.DAY_OF_MONTH, 1);// 設(shè)置為1號
        return getCleanDay(c);
    }
    /**
     * 獲取上月月末
     * @param date
     * @return
     */
    public static Date getLastMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59);
        return getCleanDay(c);
    }
    public static Date getLastMonthDay(Date date, int day) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        c.set(Calendar.DAY_OF_MONTH, day);// 設(shè)置為1號
        return getCleanDay(c);
    }
    /**
     * 本月月末
     * @param date
     * @return
     */
    public static Date getMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59);
        return getCleanDay(c);
    }
    /**
     * 獲取下月1號
     * @param date
     * @return
     */
    public static Date getNextMonthFirst(Date date) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, 1);
        c.set(Calendar.DAY_OF_MONTH, 1);// 設(shè)置為1號
        return getCleanDay(c);
    }
    /**
     * 獲取下月 月末
     * @param date
     * @return
     */
    public static Date getNextMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當(dāng)前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, 1);
        int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59);
        return getCleanDay(c);
    }
    /**
     * 判斷兩個日期之間是否為一整年
     * @param start
     * @param end
     * @return
     */
    public static boolean isOneYear(Date start, Date end) {
        Calendar startday = Calendar.getInstance();
        Calendar endday = Calendar.getInstance();
        startday.setTime(start);
        endday.setTime(end);
        if (startday.after(endday)) {
            return false;
        }
        long sl = startday.getTimeInMillis();
        long el = endday.getTimeInMillis();
        long days = ((el - sl) / (1000 * 60 * 60 * 24));
        if (days == 365 || days == 366) {
            if (startday.get(Calendar.MONTH) <= 1) {
                startday.set(Calendar.MONTH, 1);
                int lastDay = startday.getActualMaximum(Calendar.DAY_OF_MONTH);
                return (lastDay == 28 && days == 365) || (lastDay == 29 && days == 366);
            } else {
                endday.set(Calendar.MONTH, 1);
                int lastDay = endday.getActualMaximum(Calendar.DAY_OF_MONTH);
                return (lastDay == 28 && days == 365) || (lastDay == 29 && days == 366);
            }
        } else {
            return false;
        }
    }
    /**
     * @return 上一天Date
     */
    public static Date getPreviousDate(Date date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        int day = c.get(Calendar.DATE);
        c.set(Calendar.DATE, day - 1);
        return c.getTime();
    }
    
    /**
     * @return 后一天Date
     */
    public static Date getNextDate(Date date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        int day = c.get(Calendar.DATE);
        c.set(Calendar.DATE, day + 1);
        return c.getTime();
    }
    /**
     * 判斷兩個時間是否在同一天
     * @param date1
     * @param Date2
     * @return
     */
    public static boolean inSameDay(Date date1, Date Date2) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date1);
        int year1 = calendar.get(Calendar.YEAR);
        int day1 = calendar.get(Calendar.DAY_OF_YEAR);

        calendar.setTime(Date2);
        int year2 = calendar.get(Calendar.YEAR);
        int day2 = calendar.get(Calendar.DAY_OF_YEAR);
        if ((year1 == year2) && (day1 == day2)) {
            return true;
        }
        return false;
    }
    /**
     * 傳入多少分鐘
     * 獲取兩個時間的差值,有多少個小時,用于計(jì)算請了多少小時假
     * 尾數(shù)不足0.5小時按0.5小時計(jì),超過0.5小時按1小時計(jì)
     * * ((int) diff / 30) 有n個半小時要轉(zhuǎn)int
     * 最后 如果余數(shù)不為0 就要補(bǔ)上0.5
     * return 單位:小時
     */
    public static double getLeaveValue(double differenceValue){
        double needLeaveTs = ((int)(differenceValue / 30))*0.5;
        if(differenceValue % 30 != 0) needLeaveTs+= 0.5;
        return needLeaveTs;
    }
    /**
     * 傳入一個日期
     * 打卡開始時間在下午,開始時間與14:00上班時間請多少個小時
     */
    public static double getLeaveValue(Date startTime){
        double hour = startTime.getHours();
        double minute = startTime.getMinutes();
        double needLeaveTs = ((int)(minute / 30))*0.5;
        if(minute % 30 != 0) needLeaveTs += 0.5;
        needLeaveTs += hour - 14;
        return needLeaveTs;
    }
    /**
     * 獲取指定日期下午上班時間
     * @param date
     * @return
     */
    public static Date getGotoWorkAfternoon(Date date){
        return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 14:00:00", "yyyy-MM-dd HH:mm:ss");
    }
       /**
     * 根據(jù)傳進(jìn)來的Time和Date,獲取到那天對應(yīng)完成日期
     * @param date
     * @param time
     * @return
     */
    public static Date getDateByTime(Date date, Time time){
        int hour = time.getHours();
        int minutes = time.getMinutes();
        int seconds = time.getSeconds();
        String timeStr = " " + hour + ":" + minutes + ":" + seconds;

        return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date) + timeStr, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 獲取XX:XX:XX的時間字符串
     * @param t 秒
     * @return [XX:XX:XX],[XX:XX]
     */
    public static String getTimeSpanStr(int t) {
        StringBuilder sb = new StringBuilder();
        if(t >= 3600) {
            int h = t / 3600;
            if(h < 10) sb.append("0");
            sb.append(h + ":");
        }
        if(t >= 60) {
            int m = t%3600/60;
            if(m < 10) sb.append("0");
            sb.append(m + ":");
        }
        int s = t%60;
        if(s < 10) sb.append("0");
        sb.append(s);
        return sb.toString();
    }
    /**
     * 根據(jù)傳入日期獲取目標(biāo)日期
     * @param date nullable 原始日期
     * @param monthDiff nullable 月份偏移量
     * @param dayOfMonth nullable 當(dāng)月幾號
     * @return null if date is null
     */
    public static Date getDiffDate(Date date, Integer monthDiff, Integer dayOfMonth) {
        if(date == null)
            return null;
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        if(monthDiff == null)
            monthDiff = 0;
        int month = c.get(Calendar.MONTH) + monthDiff;
        c.set(Calendar.MONTH, month);
        if(dayOfMonth != null)
            c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
        return c.getTime();
    }
    /**
     * 獲取n天[前/后]的日期
     * @return
     */
    public static Date getDiffDay(Date date, Integer diffDay) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.add(Calendar.DAY_OF_YEAR, diffDay);
        return c.getTime();
    }

    /**
     * @param d1 notNull
     * @param d2 notNull
     * @return 是否是同一個月
     */
    public static boolean isSameMonth(Date d1, Date d2) {
        Calendar c = Calendar.getInstance();
        c.setTime(d1);
        int y1 = c.get(Calendar.YEAR);
        int m1 = c.get(Calendar.MONTH);
        c.setTime(d2);
        int y2 = c.get(Calendar.YEAR);
        int m2 = c.get(Calendar.MONTH);
        return y1 == y2 && m1 == m2;
    }
    /**
     * @param d1 notNull
     * @param d2 notNull
     * @return 是否是同一天
     */
    public static boolean isSameDay(Date d1, Date d2) {
        Calendar c = Calendar.getInstance();
        c.setTime(d1);
        int y1 = c.get(Calendar.YEAR);
        int m1 = c.get(Calendar.MONTH);
        int day1 = c.get(Calendar.DAY_OF_MONTH);
        c.setTime(d2);
        int y2 = c.get(Calendar.YEAR);
        int m2 = c.get(Calendar.MONTH);
        int day2 = c.get(Calendar.DAY_OF_MONTH);
        return y1 == y2 && m1 == m2 && day1 == day2;
    }
    /**
     * @return d1>=d2
     */
    public static boolean greaterThanOrEqualTo(Date d1, Date d2) {
        return d1.compareTo(d2) >= 0;
    }
    /**
     * @return d1<=d2
     */
    public static boolean lessThanOrEqualTo(Date d1, Date d2) {
        return d1.compareTo(d2) <= 0;
    }
    /**
     * @return d1>d2
     */
    public static boolean greaterThan(Date d1, Date d2) {
        return d1.compareTo(d2) > 0;
    }
    /**
     * 獲取某個月的實(shí)際最大天數(shù), 如2016-02, 最大天數(shù)為29
     */
    public static int getMaximum(Date date) {
    	Calendar c = Calendar.getInstance();
    	c.setTime(date);
    	return c.getActualMaximum(Calendar.DAY_OF_MONTH);
    }
    public static Date getFirstDayOfWeek(int year, int week) {
        Calendar c = Calendar.getInstance();
        c.set(year, Calendar.JANUARY, 1, 0, 0, 0);//定到第一天
        c.add(Calendar.DATE, (week - 1) * 7);//直接add天數(shù)
        c.setFirstDayOfWeek(Calendar.SUNDAY);
        c.setTime(c.getTime());//必須先set一次time,否則是錯誤的!
        c.set(Calendar.DAY_OF_WEEK, 1);
        return c.getTime();
    }
    public static Date getHalfPastNineDateTime(Date date){
        return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 9:30:00", "yyyy-MM-dd HH:mm:ss");
    }
    public static Date getTenOClockDateTime(Date date){
        return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 9:30:00", "yyyy-MM-dd HH:mm:ss")

3. 時間格式轉(zhuǎn)換

項(xiàng)目中,時間格式轉(zhuǎn)換是一個非常典型的日期處理操作,可能會涉及到將一個字符串日期轉(zhuǎn)換為JAVA對象,或者是將一個JAVA日期對象轉(zhuǎn)換為指定格式的字符串日期時間。

3.1 SimpleDataFormat實(shí)現(xiàn)

在JAVA8之前,通常會使用SimpleDateFormat類來處理日期與字符串之間的相互轉(zhuǎn)換:

public void testDateFormatter() {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    // 日期轉(zhuǎn)字符串
    String format = simpleDateFormat.format(new Date());
    System.out.println("當(dāng)前時間:" + format);
    try {
        // 字符串轉(zhuǎn)日期
        Date parseDate = simpleDateFormat.parse("2022-07-08 06:19:27");
        System.out.println("轉(zhuǎn)換后Date對象: " + parseDate);
        // 按照指定的時區(qū)進(jìn)行轉(zhuǎn)換,可以對比下前面轉(zhuǎn)換后的結(jié)果,會發(fā)現(xiàn)不一樣
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+5:00"));
        parseDate = simpleDateFormat.parse("2022-07-08 06:19:27");
        System.out.println("指定時區(qū)轉(zhuǎn)換后Date對象: " + parseDate);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

輸出結(jié)果如下:

當(dāng)前時間:2022-07-08 06:25:31
轉(zhuǎn)換后Date對象: Fri Jul 08 06:19:27 CST 2022
指定時區(qū)轉(zhuǎn)換后Date對象: Fri Jul 08 09:19:27 CST 2022
  •  G 年代標(biāo)志符
  •  y 年
  •  M 月
  •  d 日
  •  h 時 在上午或下午 (1~12)
  •  H 時 在一天中 (0~23)
  •  m 分
  •  s 秒
  •  S 毫秒
  •  E 星期
  •  D 一年中的第幾天
  •  F 一月中第幾個星期幾
  •  w 一年中第幾個星期
  •  W 一月中第幾個星期
  •  a 上午 / 下午 標(biāo)記符
  •  k 時 在一天中 (1~24)
  •  K 時 在上午或下午 (0~11)
  •  z 時區(qū)

補(bǔ)充說明:

SimpleDateFormat對象是非線程安全的,所以項(xiàng)目中在封裝為工具方法使用的時候需要特別留意,最好結(jié)合ThreadLocal來適應(yīng)在多線程場景的正確使用。 JAVA8之后,推薦使用DateTimeFormat替代SimpleDateFormat。

3.2 DataTimeFormatter實(shí)現(xiàn)

JAVA8開始提供DataTimeFormatter作為新的用于日期與字符串之間轉(zhuǎn)換的類,它很好的解決了SimpleDateFormat多線程的弊端,也可以更方便的與java.time中心的日期時間相關(guān)類的集成調(diào)用。

public void testDateFormatter() {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime localDateTime = LocalDateTime.now();
    // 格式化為字符串
    String format = localDateTime.format(dateTimeFormatter);
    System.out.println("當(dāng)前時間:" + format);
    // 字符串轉(zhuǎn)Date
    LocalDateTime parse = LocalDateTime.parse("2022-07-08 06:19:27", dateTimeFormatter);
    Date date = Date.from(parse.atZone(ZoneId.systemDefault()).toInstant());
    System.out.println("轉(zhuǎn)換后Date對象: " + date);
}

輸出結(jié)果:

當(dāng)前時間:2022-07-19 17:19:27
轉(zhuǎn)換后Date對象: Fri Jul 08 06:19:27 CST 2022

3.3 日期時間格式模板

對于計(jì)算機(jī)而言,時間處理的時候按照基于時間原點(diǎn)的數(shù)字進(jìn)行處理即可,但是轉(zhuǎn)為人類方便識別的場景顯示時,經(jīng)常會需要轉(zhuǎn)換為不同的日期時間顯示格式,比如:

2022-07-08 12:02:34
2022/07/08 12:02:34.238
2022年07月08日 12點(diǎn)03分48秒

在JAVA中,為了方便各種格式轉(zhuǎn)換,提供了基于時間模板進(jìn)行轉(zhuǎn)換的實(shí)現(xiàn)能力:

時間格式模板中的字幕含義說明如下:

字母使用說明
yyyy4位數(shù)的年份
yy顯示2位數(shù)的年份,比如2022年,則顯示為22年
MM顯示2位數(shù)的月份,不滿2位數(shù)的,前面補(bǔ)0,比如7月份顯示07月
M月份,不滿2位的月份不會補(bǔ)0
dd天, 如果1位數(shù)的天數(shù),則補(bǔ)0
d天,不滿2位數(shù)字的,不補(bǔ)0
HH24小時制的時間顯示,小時數(shù),兩位數(shù),不滿2位數(shù)字的前面補(bǔ)0
H24小時制的時間顯示,小時數(shù),不滿2位數(shù)字的不補(bǔ)0
hh12小時制的時間顯示,小時數(shù),兩位數(shù),不滿2位數(shù)字的前面補(bǔ)0
ss秒數(shù),不滿2位的前面補(bǔ)0
s秒數(shù),不滿2位的不補(bǔ)0
SSS毫秒數(shù)
z時區(qū)名稱,比如北京時間東八區(qū),則顯示CST
Z時區(qū)偏移信息,比如北京時間東八區(qū),則顯示+0800

4. 消失的八小時問題

4.1 日期字符串存入DB后差8小時

后端與數(shù)據(jù)庫交互的時候,可能會遇到一個問題,就是往DB中存儲了一個時間字段之后,后面再查詢的時候,就會發(fā)現(xiàn)時間數(shù)值差了8個小時這個需要在DB的連接信息中指定下時區(qū)信息:

spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai

4.2 界面時間與后臺時間差8小時

在有**一些前后端交互的項(xiàng)目中,**可能會遇到一個問題,就是前端選擇并保存了一個時間信息,再查詢的時候就會發(fā)現(xiàn)與設(shè)置的時間差了8個小時,這個其實(shí)就是后端時區(qū)轉(zhuǎn)換設(shè)置的問題。

SpringBoot的配置文件中,需要指定時間字符串轉(zhuǎn)換的時區(qū)信息:

spring.jackson.time-zone=GMT+8

這樣從接口json中傳遞過來的時間信息,jackson框架可以根據(jù)對應(yīng)時區(qū)轉(zhuǎn)換為正確的Date數(shù)據(jù)進(jìn)行處理。

到此這篇關(guān)于Java日期時間類及計(jì)算詳解的文章就介紹到這了,更多相關(guān)Java日期時間類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java基于ArrayList實(shí)現(xiàn)群主發(fā)紅包功能

    Java基于ArrayList實(shí)現(xiàn)群主發(fā)紅包功能

    這篇文章主要介紹了Java基于ArrayList實(shí)現(xiàn)群主發(fā)紅包功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • 實(shí)例分析java對象中淺克隆和深克隆

    實(shí)例分析java對象中淺克隆和深克隆

    在本篇文章中我們給大家分享了關(guān)于java對象中淺克隆和深克隆的相關(guān)知識點(diǎn)和相關(guān)代碼內(nèi)容,有興趣的朋友們學(xué)習(xí)下。
    2018-10-10
  • SpringBatch從入門到精通之StepScope作用域和用法詳解

    SpringBatch從入門到精通之StepScope作用域和用法詳解

    這篇文章主要介紹了SpringBatch從入門到精通之StepScope作用域和用法詳解,主要包括IOC容器中幾種bean的作用范圍以及可能遇到的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • Java如何實(shí)現(xiàn)雙向鏈表功能

    Java如何實(shí)現(xiàn)雙向鏈表功能

    雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點(diǎn)中都有兩個指針,分別指向直接后繼和直接前驅(qū)。所以,從雙向鏈表中的任意一個結(jié)點(diǎn)開始,都可以很方便地訪問它的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)。一般我們都構(gòu)造雙向循環(huán)鏈表
    2021-11-11
  • spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法【兩種方法】

    spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法【兩種方法】

    這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細(xì),需要的朋友可以參考下
    2018-08-08
  • SpringBoot手動開啟事務(wù):DataSourceTransactionManager問題

    SpringBoot手動開啟事務(wù):DataSourceTransactionManager問題

    這篇文章主要介紹了SpringBoot手動開啟事務(wù):DataSourceTransactionManager問題,具有很好的價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • java 后臺開發(fā)中model與entity(實(shí)體類)的區(qū)別說明

    java 后臺開發(fā)中model與entity(實(shí)體類)的區(qū)別說明

    這篇文章主要介紹了java 后臺開發(fā)中model與entity(實(shí)體類)的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • 解決SpringSecurity 一直登錄失敗的問題

    解決SpringSecurity 一直登錄失敗的問題

    這篇文章主要介紹了解決SpringSecurity 一直登錄失敗的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • IDEA Spring Boot 自動化構(gòu)建+部署的實(shí)現(xiàn)

    IDEA Spring Boot 自動化構(gòu)建+部署的實(shí)現(xiàn)

    這篇文章主要介紹了IDEA Spring Boot 自動化構(gòu)建+部署的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java利用for循環(huán)輸出空心菱形的實(shí)例代碼

    Java利用for循環(huán)輸出空心菱形的實(shí)例代碼

    這篇文章主要介紹了Java利用for循環(huán)輸出空心菱形的實(shí)例代碼,需要的朋友可以參考下
    2014-02-02

最新評論