??作者簡介:Java領域優質創作者🏆,博客專家認證🏆
??技識訓,該賞
??點贊 👍 收藏 ?再看,養成習慣
大家好,我是小虛竹,之前有粉絲私聊我,問能不能把JAVA8 新的日期時間API(JSR-310)知識點梳理出來,答案是肯定的,誰讓我寵粉呢,由于內容偏多(超十萬字了),會拆成多篇來寫,
閑話就聊到這,請看下面的正文,
文章目錄
- 常用于計算的類介紹
- Duration
- Duration類說明
- Duration常用的用法
- 創建Duration物件
- 訪問Duration的時間
- Duration計算
- Period
- Period類說明
- Period常用的用法
- 創建Period物件
- 訪問Period的時間
- Duration計算
- TemporalUnit
- TemporalUnit類說明
- ChronoUnit常用的用法
- TemporalField
- TemporalField類說明
- ChronoField常用的用法
- ValueRange
- ValueRange類說明
- ValueRange常用的用法
- Chronology 判斷是否閏年
- 實戰
- 比較日期時間的先后
- 比較-LocalDate
- 比較-LocalTime
- 比較-OffsetDateTime
- 比較-OffsetTime
- 比較-ZonedDateTime
- 計算日期時間的間隔
- 推薦相關文章
- hutool日期時間系列文章
- 其他
常用于計算的類介紹
介紹下java8 中提供了幾個常用于計算的類:
- Duration:表示秒和納秒的時間量
- Period:表示年月日的時間量
- TemporalUnit:日期時間的基本單位
- TemporalField:日期時間的屬性
- ValueRange:表示取值范圍
Duration
Duration類說明
包路徑:java.time.Duration
public final class Duration
implements TemporalAmount, Comparable<Duration>, Serializable {
private final long seconds;
private final int nanos;
...
}
Duration 是TemporalAmount 的實作類,類里包含兩個變數seconds 和 nanos ,所以Duration 是由秒和納秒組成的時間量,
一個Duration實體是不可變的,當創建出物件后就不能改變它的值了,
Duration常用的用法
創建Duration物件
Duration 適合處理較短的時間,需要更高的精確性,我們能使用between()方法比較兩個瞬間的差:
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);

可以通過LocalDateTime 類獲取獲取Duration物件
LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);
Duration duration = Duration.between(first, second);
System.out.println(duration);

訪問Duration的時間
Duration 物件中可以獲取秒和納秒屬性,但沒有毫秒屬性,跟System.getCurrentTimeMillis()不同,
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("納秒:"+duration.getNano());

可以轉換整個時間成其他單位,如納秒,毫秒,分鐘,小時,天
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("納秒:"+duration.getNano());
System.out.println("納秒:"+duration.toNanos());
System.out.println("毫秒:"+duration.toMillis());
System.out.println("分:"+duration.toMinutes());
System.out.println("小時:"+duration.toHours());
System.out.println("天:"+duration.toDays());

由圖上可知,getNano 方法和toNanos 方法不太一樣,前者是獲取這段時間的小于1s的部分,后者是整個時間轉化為納秒,
Duration計算
plusNanos()
plusMillis()
plusSeconds()
plusMinutes()
plusHours()
plusDays()
minusNanos()
minusMillis()
minusSeconds()
minusMinutes()
minusHours()
minusDays()
以plusSeconds 和minusSeconds 為例:
LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);
Duration duration = Duration.between(first, second);
System.out.println(duration);
Duration duration1 = duration.plusSeconds(10);
System.out.println("plusSeconds 后:"+duration);
System.out.println("plusSeconds 后新的Duration物件:"+duration1);
Duration duration2 = duration.minusSeconds(10);
System.out.println("minusSeconds 后:"+duration);
System.out.println("minusSeconds 后新的Duration物件:"+duration2);

由上面的驗證可知,這些計算方法執行后,會回傳一個新的Duration物件,原先的Duration物件不變,
Period
Period類說明
包路徑:java.time.Period
public final class Period
implements ChronoPeriod, Serializable {
/**
* The number of years.
*/
private final int years;
/**
* The number of months.
*/
private final int months;
/**
* The number of days.
*/
private final int days;
...
}
Period 是ChronoPeriod 的實作類,類里包含兩個變數years ,months 和 days ,所以Period 是由年,月和日組成的時間量,
Period常用的用法
創建Period物件
LocalDate first = LocalDate.of(2021, 8, 29);
LocalDate second = LocalDate.of(2022, 9, 30);
Period period = Period.between(first, second);
System.out.println(period);

訪問Period的時間
LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
System.out.println("年:"+period.getYears());
System.out.println("月:"+period.getMonths());
System.out.println("日:"+period.getDays());

可以轉換整個時間成其他單位,月
LocalDate first = LocalDate.of(2021, 8, 29);
LocalDate second = LocalDate.of(2022, 9, 30);
Period period = Period.between(first, second);
System.out.println(period);
System.out.println("月:"+period.toTotalMonths());

由圖上可知,getMonths 方法和toTotalMonths 方法不太一樣,前者是獲取這段時間的月的部分,后者是整個時間轉化為以月為單位長度,
toTotalMonths 原始碼:
public long toTotalMonths() {
return years * 12L + months; // no overflow
}
Duration計算
plusDays()
plusMonths()
plusYears()
minusDays()
minusMonths()
minusYears()
以plusMonths 和minusMonths 為例:
LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
Period period1 = period.plusMonths(1);
System.out.println("plusMonths 后:"+period);
System.out.println("plusMonths 后新的Period物件:"+period1);
Period period2 = period.minusMonths(1);
System.out.println("minusMonths 后:"+period);
System.out.println("minusMonths 后新的Period物件:"+period2);

由上面的驗證可知,這些計算方法執行后,會回傳一個新的Period物件,原先的Period物件不變,
TemporalUnit
TemporalUnit類說明
包路徑:java.time.temporal.TemporalUnit
public interface TemporalUnit {
...
}
public enum ChronoUnit implements TemporalUnit {
private final String name;
private final Duration duration;
...
}
TemporalUnit 主要實作類是列舉型別ChronoUnit
一個ChronoUnit成員會維護一個字串名字屬性name和一個Duration型別的實體,
其中ChronoUnit列舉了標準的日期時間單位集合,就是常用的年、月、日、小時、分鐘、秒、毫秒、微秒、納秒,這些時間單位的時間量到底是多少,代表多長的時間,在該列舉類中都有定義,
public enum ChronoUnit implements TemporalUnit {
NANOS("Nanos", Duration.ofNanos(1)),
MICROS("Micros", Duration.ofNanos(1000)),
MILLIS("Millis", Duration.ofNanos(1000_000)),
SECONDS("Seconds", Duration.ofSeconds(1)),
MINUTES("Minutes", Duration.ofSeconds(60)),
HOURS("Hours", Duration.ofSeconds(3600)),
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
DAYS("Days", Duration.ofSeconds(86400)),
WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
YEARS("Years", Duration.ofSeconds(31556952L)),
DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),
CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),
MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),
ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),
FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));
private final String name;
private final Duration duration;
private ChronoUnit(String name, Duration estimatedDuration) {
this.name = name;
this.duration = estimatedDuration;
}
···
}
ChronoUnit常用的用法
LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime offset = localDateTime.plus(1, ChronoUnit.DAYS);
// 非同一物件
Assert.assertNotSame(localDateTime, offset);
System.out.println(offset);

TemporalField
TemporalField類說明
包路徑:java.time.temporal.TemporalField
public interface TemporalField {
...
}
public enum ChronoField implements TemporalField {
private final String name;
private final TemporalUnit baseUnit;
private final TemporalUnit rangeUnit;
private final ValueRange range;
...
}
TemporalField 主要實作類是列舉型別ChronoField
一個ChronoField成員會維護一個字串名字屬性name、一個TemporalUnit的基礎單位baseUnit、一個TemporalUnit的表示范圍的單位rangeUnit和一個ValueRange型別的range用于表示當前屬性的范圍,
public enum ChronoField implements TemporalField {
//一秒鐘的納秒數
NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999))
//一分鐘的秒數
SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second")
//一個小時的分鐘數
MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute")
//一上午或者一下午有多少個小時
CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12))
//一天的小時數
CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24))
//上午還是下午
AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod")
//一周的第幾天
DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday")
//當前月的天數
DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day")
//當前年的天數
DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366))
//當前月的周數
ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5))
//當前年的周數
ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53))
//以每月的第一天為星期一,然后計算當天是一周的第幾天
ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7))
//以每月的第一天為星期一,然后計算當天是一周的第幾天
ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7))
//當前年的月數
MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month")
private final TemporalUnit baseUnit;
private final String name;
private final TemporalUnit rangeUnit;
private final ValueRange range;
private final String displayNameKey;
...
}
ChronoField常用的用法
ALIGNED_WEEK_OF_MONTH 和 ALIGNED_DAY_OF_WEEK_IN_MONTH 使用示例
//每七天一周,2021-08-31 是周二,對應的值是3
int num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);
System.out.println(num);
//這個月的第5周 2021-08-31
num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_WEEK_OF_MONTH);
System.out.println(num);

ValueRange
ValueRange類說明
ValueRange 表示取值范圍,
public final class ValueRange implements Serializable {
/**
* The smallest minimum value.最小值
*/
private final long minSmallest;
/**
* The largest minimum value.最大可能最小值
*/
private final long minLargest;
/**
* The smallest maximum value.最小可能最大值
*/
private final long maxSmallest;
/**
* The largest maximum value.最大值
*/
private final long maxLargest;
...
}
ValueRange常用的用法
ValueRange valueRange = ValueRange.of(1L, 10000L);
System.out.println(valueRange);
valueRange = ValueRange.of(1L, 5L, 10000L, 50000L);
System.out.println(valueRange);

LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
ValueRange valueRange = localDateTime.range(ChronoField.DAY_OF_MONTH);
System.out.println(valueRange.getMinimum());
System.out.println(valueRange.getMaximum());
System.out.println(valueRange.getLargestMinimum());
System.out.println(valueRange.getSmallestMaximum());

Chronology 判斷是否閏年
判斷是否閏年是由年表Chronology 提供的,通常情況下,我們使用ISO下的年表,是IsoChronology ,
看下代碼實作
@Override
public boolean isLeapYear(long prolepticYear) {
return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
}
好精煉的代碼,值得我們研究研究
閏年的基本判定方法:
1、非整百年:能被4整除的為閏年,(如2004年就是閏年,2001年不是閏年)
2、整百年:能被400整除的是閏年,(如2000年是閏年,1900年不是閏年)
((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
這段代碼用了兩個條件,這兩個條件都符合,才是閏年,
- (prolepticYear & 3) == 0
- (prolepticYear % 100) != 0 || (prolepticYear % 400) == 0
(prolepticYear & 3) == 0 用了與運算子“&”,其使用規律如下:
兩個運算元中位都為1,結果才為1,否則結果為0,
3 的二進制是011 ,prolepticYear & 3 目的是保留最后2位二進制數,然后判斷是否最后兩位二進制數等于0,如果等于0,證明能被4整除,閏年一定要滿足是4的倍數的條件;
(prolepticYear % 100) != 0 || (prolepticYear % 400) == 0 這個就比較好理解了,看是不是100的倍數或者是不是400 倍數,
而且小虛竹發現java.time.Year#isLeap() 用的實作代碼邏輯是一樣的
public static boolean isLeap(long year) {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
}
即使是巨佬寫的代碼,也存在代碼的復用性問題
上面IsoChronology 是對Chronology介面介面的isLeapYear實作,MinguoChronology等實作類的isLeapYear,互用了IsoChronology的isLeapYear方法,
//MinguoChronology
public boolean isLeapYear(long prolepticYear) {
return IsoChronology.INSTANCE.isLeapYear(prolepticYear + YEARS_DIFFERENCE);
}
巨佬是有考慮復用的,在MinguoChronology等實作類已經有復用了,
java.time.Year#isLeap() 的優先級高,因為它是靜態方法,isoChronology ** 可以引Year.isLeap**
Year ** 不可以引Chronology.isLeapYear** ,
博主發現在IsoChronology ** 的resolveYMD** 中已經存在了對Year.isLeap 的參考,

有的工具類會為了減少外部類依賴,重新寫一次底層方法,避免外部類(或是不在一個包底下)的類依賴,這個已經用了,說不過去 ,所以代碼是存在復用性問題的,
實戰
int year = 2020;
System.out.println(Year.isLeap(year));
System.out.println(IsoChronology.INSTANCE.isLeapYear(year));
LocalDate localDate = LocalDate.of(2021,9,7);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate.isLeapYear());
System.out.println(localDateTime.toLocalDate().isLeapYear());

比較日期時間的先后
基本上都有這四個比較方法::compareTo()、isBefore()、isAfter()、和equals()
比較-LocalDate
LocalDate localDate1 = LocalDate.of(2021, 8, 14);
// 比較指定日期和引數日期,回傳正數,那么指定日期時間較晚(數字較大):13
int i = localDate1.compareTo(LocalDate.of(2021, 8, 1));
System.out.println(i);
// 比較指定日期是否比引數日期早(true為早):true
System.out.println(localDate1.isBefore(LocalDate.of(2021,8,31)));
// 比較指定日期是否比引數日期晚(true為晚):false
System.out.println(localDate1.isAfter(LocalDate.of(2021,8,31)));
// 比較兩個日期是否相等:true
System.out.println(localDate1.isEqual(LocalDate.of(2021, 8, 14)));

比較-LocalTime
LocalTime localTime1 = LocalTime.of(23, 26, 30);
LocalTime localTime2 = LocalTime.of(23, 26, 32);
// 兩個時間進行比較 大回傳1,小就回傳-1,一樣就回傳0:-1
System.out.println(localTime1.compareTo(localTime2));
// 比較指定時間是否比引數時間早(true為早):true
System.out.println(localTime1.isBefore(localTime2));
// 比較指定時間是否比引數時間晚(true為晚):false
System.out.println(localTime1.isAfter(localTime2));
// 比較兩個時間是否相等:true
System.out.println(localTime1.equals(LocalTime.of(23, 26, 30)));

比較-OffsetDateTime
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
OffsetDateTime offsetDateTime3 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
OffsetDateTime offsetDateTime2 = OffsetDateTime.of(localDateTime2, ZoneOffset.ofHours(8));
// 兩個時間進行比較 大回傳1,小就回傳-1,一樣就回傳0:-1
System.out.println(offsetDateTime1.compareTo(offsetDateTime2));
// 比較指定時間是否比引數時間早(true為早):true
System.out.println(offsetDateTime1.isBefore(offsetDateTime2));
// 比較指定時間是否比引數時間晚(true為晚):false
System.out.println(offsetDateTime1.isAfter(offsetDateTime2));
// 比較兩個時間是否相等:true
System.out.println(offsetDateTime1.equals(offsetDateTime3));

比較-OffsetTime
LocalTime localTime1 = LocalTime.of( 13, 14, 20);
OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));
OffsetTime offsetTime3 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));
LocalTime localTime2 = LocalTime.of(13, 14, 30);
OffsetTime offsetTime2 = OffsetTime.of(localTime2, ZoneOffset.ofHours(8));
// 兩個時間進行比較 大回傳1,小就回傳-1,一樣就回傳0:-1
System.out.println(offsetTime1.compareTo(offsetTime2));
// 比較指定時間是否比引數時間早(true為早):true
System.out.println(offsetTime1.isBefore(offsetTime2));
// 比較指定時間是否比引數時間晚(true為晚):false
System.out.println(offsetTime1.isAfter(offsetTime2));
// 比較兩個時間是否相等:true
System.out.println(offsetTime1.equals(offsetTime3));

比較-ZonedDateTime
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneOffset.ofHours(8));
// 兩個時間進行比較 大回傳1,小就回傳-1,一樣就回傳0:-1
System.out.println(zonedDateTime1.compareTo(zonedDateTime2));
// 比較指定時間是否比引數時間早(true為早):true
System.out.println(zonedDateTime1.isBefore(zonedDateTime2));
// 比較指定時間是否比引數時間晚(true為晚):false
System.out.println(zonedDateTime1.isAfter(zonedDateTime2));
// 比較兩個時間是否相等:true
System.out.println(zonedDateTime1.equals(zonedDateTime3));

計算日期時間的間隔
Duration 和**Period ** 都有 **between ** 方法
這個就不在重復說了,上面Duration 和Period 的常用用法里有介紹到,
推薦相關文章
hutool日期時間系列文章
1DateUtil(時間工具類)-當前時間和當前時間戳
2DateUtil(時間工具類)-常用的時間型別Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)轉換
3DateUtil(時間工具類)-獲取日期的各種內容
4DateUtil(時間工具類)-格式化時間
5DateUtil(時間工具類)-決議被格式化的時間
6DateUtil(時間工具類)-時間偏移量獲取
7DateUtil(時間工具類)-日期計算
8ChineseDate(農歷日期工具類)
9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具類封裝)
10TemporalAccessorUtil{@link TemporalAccessor} 工具類封裝
其他
要探索JDK的核心底層原始碼,那必須掌握native用法
萬字博文教你搞懂java原始碼的日期和時間相關用法
java的SimpleDateFormat執行緒不安全出問題了,虛竹教你多種解決方案
原始碼分析:JDK獲取默認時區的風險和最佳實踐
高級JAVA開發必備技能:時區的規則發生變化時,如何同步JDK的時區規則
今天是持續寫作的第 9 / 100 天,
可以關注我,點贊我、評論我、收藏我啦,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299658.html
標籤:其他
上一篇:Charles抓包工具實戰教程
