亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java日期時間類及計算詳解

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

1. Java中與日期相關的類

1.1 java.util包

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

① Date類

    @Test
    void test06(){
        Date date1 = new Date();
        // 獲取當前時間后 +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));
    }

結果:

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

② Calendar 日歷類

總體來說,Date是一個設計相當糟糕的類,因此Java官方推薦盡量少用Date的構造器和方法。

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

示例:

    @Test
    void test05(){
        Calendar calendar =  Calendar.getInstance();
        // Calendar.YEAR 表示當前年
        int year = calendar.get(Calendar.YEAR);
        // Calendar.MONTH表示月份,但是為了計算方便,是從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 在這一周 的這一天,從星期日當第一天從1開始算的,所以會是 +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);
    }

結果:

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 在這個月 的這一天,但是為了計算方便,是從0開始算,所以顯示出來是月份 -1 的
  • Calendar.DAY_OF_YEAR 在這一年 的這一天
  • Calendar.DAY_OF_WEEK 在這一周 的這一天,從星期日當第一天從1開始算的,所以會是 +1
  • Calendar.DAY_OF_WEEK_IN_MONTH 在這一個月 這一天在 第幾周
  • Calendar.HOUR 表示今天這一天的小時(0-11),分上午和下午

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

常用api:

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

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

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

add和roll的區(qū)別

add

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

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

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

  • 當被修改的字段超出它允許的范圍時,會發(fā)生進位,即上一級字段也會增大。
  • 如果下一級字段也需要改變,那么該字段會修正到變化最小的值。
    @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);
        // 因為進位后月份改為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。**因為MONTH 的下一級字段是DATE,從31到29改變最?。ㄈ舨皇情c年則變成28日)。**所以上面2003-8-31的MONTH字段增加6后,不是變成2004-3-2,而是變成2004-2-29。

結果:

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

roll

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

    @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字段“進位”后變成2,2月沒有31日
        // YEAR字段不會改變,2003年2月只有28天
        // 2003-8-31 => 2003-2-28
        cal2.roll(Calendar.MONTH, 6);
        System.out.println(cal2.getTime());
    }

結果:

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

設置Calendar的容錯性

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

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

上面程序①②兩處的代碼完全相似,但它們運行的結果不一樣:

  • ①處代碼可以正常運行,因為設置MONTH字段的值為13,將會導致YEAR字段加1;
  • ②處代碼將會導致運行時異常,因為設置的MONTH字段值超出了MONTH字段允許的范圍。

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

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

  • 當Calendar 處于lenient模式時,每個時間字段可接受超出它允許范圍的值;
  • 當Calendar 處于 non-lenient模式時,如果為某個時間字段設置的值超出了它允許的取值范圍,程序將會拋出異常。

set

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

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

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

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

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

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

結果

Fri Sep 05 16:59:50 CST 2003

如果程序將①處代碼注釋起來,因為Calendar的 set()方法具有延遲修改的特性,即調用set()方法后Calendar實際上并未計算真實的日期,它只是使用內部成員變量表記錄MONTH字段被修改為8,接著程序設置DATE字段值為5,程序內部再次記錄DATE字段為5——就是9月5日,因此最后輸出2003-9-5。

1.2 java.time包

JAVA8之后新增了java.time包,提供了一些與日期時間有關的新實現類:

具體每個類對應的含義說明梳理如下表:

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

① LocalDate 本地日期類

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

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

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

② 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()可以接受某一個“瞬間”,比如當前時間,或者是過去、將來的一個時間。 比如,通過一個“瞬間”創(chuàng)建一個LocalDateTime對象

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

System.out.println("當前日期:"+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("目標日期距離今天的時間差:"+p.getYears()+" 年 "+p.getMonths()+" 個月 "+p.getDays()+" 天" );

//目標日期距離今天的時間差: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對象,對時間的計算,算好之后返回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("開始時間到結束時間,持續(xù)了"+duration.toDays()+"天");
System.out.println("開始時間到結束時間,持續(xù)了"+duration.toHours()+"小時");
System.out.println("開始時間到結束時間,持續(xù)了"+duration.toMillis()/1000+"秒");

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

2. 時間間隔計算

2.1 Period與Duration類

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

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

DurationPeriod具體使用的時候還需要有一定的甄別,因為部分的方法很容易使用中被混淆,下面分別說明下。

2.1.1 Duration

Duration的最小計數單位為納秒,其內部使用secondsnanos兩個字段來進行組合計數表示duration總長度。

image-20220719150424650

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

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

關于Duration的主要API的使用,參見如下示意:

@Test
    void durationTEst(){
        LocalTime target = LocalTime.parse("00:02:35.700");
        // 獲取當前日期,此處為了保證后續(xù)結果固定,注掉自動獲取當前日期,指定固定日期
        // 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相關接口與Duration類似,其計數的最小單位是,看下Period內部時間段記錄采用了年、月、日三個field來記錄:

常用的API方法列舉如下:

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

關于Period的主要API的使用,參見如下示意:

    @Test
    void periodTest(){
        LocalDate target = LocalDate.parse("2021-07-11");
        // 獲取當前日期,此處為了保證后續(xù)結果固定,注掉自動獲取當前日期,指定固定日期
        // 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, 因為period值為11月27天,即year字段為0
        System.out.println(period.getYears());
        // 輸出:11, 因為period值為11月27天,即month字段為11
        System.out.println(period.getMonths());
        // 輸出:27, 因為period值為11月27天,即days字段為27
        System.out.println(period.getDays());
        // 輸出:P14M27D, 因為period為11月27天,加上3月,變成14月27天
        System.out.println(period.plusMonths(3L));
        // 輸出:P11M15D,因為period為11月27天,僅將days值設置為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都是用于日期之間的計算操作。

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

Duration的坑

先看個例子,計算兩個日期相差的天數,使用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 + "天");
}

運行后會報錯:

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)

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

Period的坑

同樣是計算兩個日期相差的天數,再看下使用Period的實現:

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í)行結果:

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

執(zhí)行是不報錯,但是結果明顯是錯誤的。這是因為getDays()并不會將Period值換算為天數,而是單獨計算年、月、日,此處只是返回天數這個單獨的值。

再看下面的寫法:

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()) + "天");
}

結果為:

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

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

2.3 計算日期差

2.3.1 通過LocalDate來計算

LocalDate中的toEpocDay可返回當前時間距離原點時間之間的天數,可以基于這一點,來實現計算兩個日期之間相差的天數:

代碼如下:

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 + "天");
}

結果為:

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

2.3.2 通過時間戳來計算

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

數學邏輯計算(不推薦)

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

不推薦、代碼略...

計算接口處理耗時

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

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

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

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

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

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

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

2.4 計算時間差

使用Hutool工具進行計算

一款超厲害的國產Java工具——Hutool。Hutool是一個Java工具包類庫,對文件、流、加密解密、轉碼、正則、線程、XML等JDK方法進行封裝,組成各種Util工具類。適用于很多項目以及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>

封裝時間類進行計算

制作Calendar工具類計算:

基于Calendar對時間計算進行相應的封裝處理,如下面兩個例子,可以根據需求將相關的計算封裝在一個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();
}

根據日期和天數進行計算

    /**
         * 獲取當前時間的月幾號0點時間或第二天0時間戳(即幾號的24點)
     * @param calendar 當前時間對象
     * @param day 幾號, 值范圍 是1 到 當前時間月天數 + 1 整數, 
         *  傳入(day+1)為day號的第二天0點時間(day號的24點時間),
         *  如果值為當前時間月天數+1則結果為當前月的下個月1號0點(即當月最后一天的24點),
         *  如果當前月的天數為31天, 傳入32時則為當前月的下個月1號0點(即當月最后一天的24點)
     * @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);

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

制作Date工具類計算

Java項目開發(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日期類型相關操作類;該類負責對日期格式化轉換、日期比較、日期加減、潤年判斷、獲 
 * 取相關的日期信息等。
 *  * @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;
    /**
     * 一天所對應的毫秒數
     */
    public static final long ONE_DAY_MILLISECOND = 24 * ONE_HOUR_MILLISECOND;
    /**
     * 一周所對應的毫秒數
     */
    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;
    /**
     * 從配置文件中返回配置項"date.format",默認的日期格式符 (yyyy-MM-dd),
     *
     * @return a string representing the date pattern on the UI
     */
    public static synchronized String getDatePattern() {
        defaultDatePattern = "yyyy-MM-dd";
        return defaultDatePattern;
    }
    /**
     * 校驗日期入參是否正確,如防止sql注入
     * @author linjx 2018-12-24
     * @param desc 入參的描述
     * @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);
    }
    /**
     * 將日期字符串按指定的格式轉為Date類型
     *
     * @param strDate 待解析的日期字符串
     * @param format  日期格式
     * @return 字符串對應的日期對象
     */
    public static final Date parseDate(String strDate, String format) {
        return DateUtil2.parse(strDate, format, strDate);
    }
    /**
     * 將日期字符串按系統配置中指定默認格式(getDatePattern()返回的格式)轉為Date類型
     *
     * @param strDate 待解析的日期字符串
     * @return 字符串對應的日期對象
     */
    public static Date parseDate(String strDate) {
        return parseDate(strDate, getDatePattern());
    }
    /**
     * <p>檢查所給的年份是否是閏年</p>
     *
     * @param year 年
     * @return 檢查結果: 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整除
        }
    }
    /**
     * 按照默認格式化樣式格式化當前系統時間
     *
     * @return 日期字符串
     */
    public static String getCurrentTime() {
        return formatDate(new Date());
    }

    /**
     * 按照默認格式化樣式格式化當前系統時間
     *
     * @param format String 日期格式化標準
     * @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);
    }
    /**
     * 按照默認格式化樣式格式化指定的日期
     *
     * @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將時間部分清零后對應日期
     */
    public static Date getCleanDay(Date day) {
        return getCleanDay(getCalendar(day));
    }

    /**
     * 設置當天最后時間
     *
     * @param day
     * @return 返回當天最后時間
     */
    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對應的Calendar對象
     *
     * @param day
     * @return 返回date對應的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();
    }
    /**
     * 根據year,month,day構造日期對象
     *
     * @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對應日期對象
     *
     * @param date
     * @return 日期所在星期的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfWeek(Date date) {
        return getFirstCleanDay(Calendar.DAY_OF_WEEK, date);
    }
    /**
     * 當前日期所在星期的第一天00:00:00對應日期對象
     *
     * @return 當前日期所在星期的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfWeek() {
        return getFirstDayOfWeek(null);
    }
    /**
     * 日期date所在月份的第一天00:00:00對應日期對象
     *
     * @param date
     * @return 日期所在月份的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfMonth(Date date) {
        return getFirstCleanDay(Calendar.DAY_OF_MONTH, date);
    }
    /**
     * 當前日期所在月份的第一天00:00:00對應日期對象
     *
     * @return 當前日期所在月份的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfMonth() {
        return getFirstDayOfMonth(null);
    }
    /**
     * 日期date所在月份的最后一天23, 59, 59對應日期對象
     *
     * @param date
     * @return 日期date所在月份的最后一天23, 59, 59對應日期對象
     */
    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對應日期對象
     *
     * @param date
     * @return 日期date所在季度的第一天00:00:00對應日期對象
     */
    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;
    }
    /**
     * 當前日期所在季度的第一天00:00:00對應日期對象
     *
     * @return 當前日期所在季度的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfSeason() {
        return getFirstDayOfMonth(null);
    }
    /**
     * 日期date所在年份的第一天00:00:00對應日期對象
     *
     * @param date
     * @return 日期date所在年份的第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfYear(Date date) {
        return makeDate(getYear(date), 1, 1);
    }
    /**
     * 獲取某年的第一天
     * @param year
     * @return 日期date所在年份的第一天00:00:00對應日期對象
     */
    public static Date getFirstDaylOfYear(int year){
        return makeDate(year, 1, 1);
    }
    /**
     * 當前日期年度的第一天00:00:00對應日期對象
     *
     * @return 當前日期年度第一天00:00:00對應日期對象
     */
    public static Date getFirstDayOfYear() {
        return getFirstDayOfYear(new Date());
    }

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

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

    /**
     * 獲取當前日期是星期幾<br>
     *
     * @param dt
     * @return 當前日期是星期幾
     */
    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);
    }
    /**
     * 將指定定的日期轉換成LocalDateTime
     *
     * @param date
     * @return
     */
    public static LocalDateTime asLocalDateTime(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

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

    /**
     * 按照“年-月”生成當月的工作日
     * @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;
    }
    /**
     * 按照年份生成一年內的工作日
     * @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();//獲取當前日期 
        /*
         * 2018-03-31時通過測試:
         * 不設置cal.set(Calendar.DAY_OF_MONTH, 1)時,得到的日期是2018-02-28而不是3月
         */
        cal.add(Calendar.MONTH, -1);
        cal.set(Calendar.DAY_OF_MONTH, 1);//設置為1號,當前日期既為本月第一天 
        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();//獲取當前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        c.set(Calendar.DAY_OF_MONTH, 1);// 設置為1號
        return getCleanDay(c);
    }
    /**
     * 獲取上月月末
     * @param date
     * @return
     */
    public static Date getLastMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當前日期 
        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();//獲取當前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        c.set(Calendar.DAY_OF_MONTH, day);// 設置為1號
        return getCleanDay(c);
    }
    /**
     * 本月月末
     * @param date
     * @return
     */
    public static Date getMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當前日期 
        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();//獲取當前日期 
        c.setTime(date);
        c.add(Calendar.MONTH, 1);
        c.set(Calendar.DAY_OF_MONTH, 1);// 設置為1號
        return getCleanDay(c);
    }
    /**
     * 獲取下月 月末
     * @param date
     * @return
     */
    public static Date getNextMonthEnd(Date date) {
        Calendar c = Calendar.getInstance();//獲取當前日期 
        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;
    }
    /**
     * 傳入多少分鐘
     * 獲取兩個時間的差值,有多少個小時,用于計算請了多少小時假
     * 尾數不足0.5小時按0.5小時計,超過0.5小時按1小時計
     * * ((int) diff / 30) 有n個半小時要轉int
     * 最后 如果余數不為0 就要補上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");
    }
       /**
     * 根據傳進來的Time和Date,獲取到那天對應完成日期
     * @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();
    }
    /**
     * 根據傳入日期獲取目標日期
     * @param date nullable 原始日期
     * @param monthDiff nullable 月份偏移量
     * @param dayOfMonth nullable 當月幾號
     * @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;
    }
    /**
     * 獲取某個月的實際最大天數, 如2016-02, 最大天數為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天數
        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. 時間格式轉換

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

3.1 SimpleDataFormat實現

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

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

輸出結果如下:

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

補充說明:

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

3.2 DataTimeFormatter實現

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

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("當前時間:" + format);
    // 字符串轉Date
    LocalDateTime parse = LocalDateTime.parse("2022-07-08 06:19:27", dateTimeFormatter);
    Date date = Date.from(parse.atZone(ZoneId.systemDefault()).toInstant());
    System.out.println("轉換后Date對象: " + date);
}

輸出結果:

當前時間:2022-07-19 17:19:27
轉換后Date對象: Fri Jul 08 06:19:27 CST 2022

3.3 日期時間格式模板

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

2022-07-08 12:02:34
2022/07/08 12:02:34.238
2022年07月08日 12點03分48秒

在JAVA中,為了方便各種格式轉換,提供了基于時間模板進行轉換的實現能力:

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

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

4. 消失的八小時問題

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

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

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

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

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

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

spring.jackson.time-zone=GMT+8

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

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

相關文章

  • Java基于ArrayList實現群主發(fā)紅包功能

    Java基于ArrayList實現群主發(fā)紅包功能

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

    實例分析java對象中淺克隆和深克隆

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

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

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

    Java如何實現雙向鏈表功能

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

    spring boot @ResponseBody轉換JSON 時 Date 類型處理方法【兩種方法】

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

    SpringBoot手動開啟事務:DataSourceTransactionManager問題

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

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

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

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

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

    IDEA Spring Boot 自動化構建+部署的實現

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

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

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

最新評論