關於Java中的日期你應該知道的事

1. 簡介

Java中的時間相關庫真是一言難盡,以至於被逼出了一個joda庫,Java時間處理庫一直被吐槽當然也是有原因的。

java.util.Date、java.util.Calendar不好用,但是也勉強夠用了。

Java8的時候,也許是KPI要求,他們終於決定對時間庫做點重構,哦,不,已經不叫重構了,叫重寫。

功能是完善了,但是對像我這樣的API調用愛好者來說卻太過複雜,不信你看,下面的類和接口你知道幾個:

  1. LocalTime
  2. LocalDate
  3. LocalDateTime
  4. OffsetDateTime
  5. ZonedDateTime
  6. TemporalAccessor
  7. Temporal
  8. ChronoField
  9. TemporalAdjusters
  10. TemporalAdjuster
  11. TemporalUnit
  12. ChronoUnit
  13. Year
  14. YearMonth
  15. ZoneOffset
  16. ZoneId
  17. Instant
  18. Period
  19. ChronoPeriod
  20. Clock
  21. TemporalAmount
  22. DateTimeFormatter

當然,不瞭解上面這些類和接口也並不影響我們調用API,但是多瞭解一點總是沒錯的。

當然,這裏我們也不會介紹全部的類和接口,我們會簡單的說一下它們的邏輯,重點介紹一點常用且使用的類,如下面這些類。

  1. LocalDate
  2. LocalDateTime
  3. DateTimeFormatter
  4. ZoneId
  5. Instant
  6. Duration
  7. Period
  8. TemporalAdjusters

2. LocalDate

2.1 創建

LocalDate的構造方法是私有的,創建只能通過靜態工程方法,主要是of和parse。

@Test
public void createLocalDate(){
    LocalDate.now();//今天
    LocalDate.of(2021, 1, 1);
    LocalDate.of(2021, Month.JANUARY, 1);
    LocalDate.parse("2021-01-01");
}

2.2 計算

新的日期庫最大的特點是日期計算真的非常方便,基本可以執行任何你需要的。

 @Test
public void calcLocalDate(){
    LocalDate now = LocalDate.now();
    now.plusDays(7);//7天之後
    now.plusYears(1);//1年之後
    now.minusMonths(1);//1月之前
    now.minusWeeks(1);//1周之前
}

2.3 比較

@Test
public void compareLocalDate(){
    LocalDate now = LocalDate.now();
    LocalDate first = LocalDate.of(2021, 1, 1);
    System.out.println(now.isBefore(first));//now日期是不是早於first
    System.out.println(now.isAfter(first));//now日期是不是晚於first
    System.out.println(now.isEqual(first));//now和first是不是同一天
}

2.4 其他

@Test
public void otherLocalDate(){
    LocalDate now = LocalDate.now();
    now.toEpochDay();//當前日期時間戳對應的天
    now.withYear(2021);//指定年份
    now.until(LocalDate.now().plusDays(36));//計算2個日期的時間區間
    now.atStartOfDay();//一天的開始時間
    now.format(DateTimeFormatter.ISO_DATE_TIME);//格式化時間
}

3. LocalDateTime

LocalDateTime與LocalDate的使用方法基本一致,只是多了比LocalDate多了時間部分,他們之前的方法基本可以相互套用。

@Test
public void chronoFieldTest(){
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println(localDateTime.getLong(ChronoField.MINUTE_OF_HOUR));//當前的分鐘
    System.out.println(localDateTime.getLong(ChronoField.HOUR_OF_DAY));//當前是一天的第多少個小時
    System.out.println(localDateTime.getLong(ChronoField.DAY_OF_MONTH));//當前是一個月中的第多少天
    System.out.println(localDateTime.getLong(ChronoField.DAY_OF_YEAR));//當前是一年中的第多少天
    System.out.println(localDateTime.getLong(ChronoField.ALIGNED_WEEK_OF_MONTH));//當前是這個月的第多少周,不是按完整的周計算
    System.out.println(localDateTime.getLong(ChronoField.EPOCH_DAY));//時間戳對應的天
}
@Test
public void chronoUnitTest(){
    LocalDateTime localDateTime = LocalDateTime.now();
    localDateTime.plus(1,ChronoUnit.YEARS);//一年之後
    localDateTime.plus(1,ChronoUnit.HOURS);//一小時之後
    localDateTime.plus(1,ChronoUnit.HALF_DAYS);//半天之後
    localDateTime.plus(1,ChronoUnit.MONTHS);//一月之後
}

4. DateTimeFormatter

DateTimeFormatter很簡單,也很複雜,基本就是使用ofPattern方法指定格式化時間的格式就可以了。

@Test
public void datetimeFormatter(){
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDateTime date = LocalDateTime.now();
    System.out.println(formatter.format(date));
    System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(date));
    System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(date));
    System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date));
    System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).format(date));
    System.out.println(DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(date));
}

5. ZoneId與ZoneOffset

ZoneId是一個抽象類,主要是爲了處理時區問題的,ZoneOffset是ZoneId的實現類。

其實主要就涉及本地時間和時間戳的一個轉換問題。

@Test
public void zoneId(){
    long now = System.currentTimeMillis();
    Instant instant = Instant.ofEpochMilli(now);
    ZoneId zoneId = ZoneId.of("Asia/Shanghai");//東八區北京時間
    LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId);

    System.out.println(now);
    System.out.println(localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
    System.out.println(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
    System.out.println(localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli());
}

對於上面的代碼,如果你能解釋爲什麼使用ZoneOffset.UTC,比正確的時區換算出來的毫秒要大,你就理解了Java時區這個問題。

如果對於時區還不太瞭解,可以看一下後面GTM、UTC與時間戳的內容。

6. Instant

@Test
public void instant() throws InterruptedException {
    //1毫秒
    Instant instant = Instant.ofEpochMilli(1);
    System.out.println(instant.toEpochMilli());

    //1秒再過100萬納秒
    instant = Instant.ofEpochSecond(1,1_000_000_000);
    System.out.println(instant.toEpochMilli());

    Instant start = Instant.now();
    TimeUnit.SECONDS.sleep(2);
    Instant end = Instant.now();
    Duration duration = Duration.between(start,end);
    System.out.println(duration.toMillis());
}

7. Duration與Period

Duration和Period都有時間區間、時間段的意味,但是Period更強調週期,例如一年、一個月、一週等。

思考這樣一個問題:女朋友把每一年的第99天定爲愛情紀念日,2019年的第99天是2019-04-09,那麼明年的愛情紀念日應該用下面哪一種方式計算:

  1. Duration.ofDays(365)
  2. Period.ofYears(1)

如果,女朋友今天心情不好,想要問一下距離下一次愛情紀念日還有多少天?又應該用哪一種方式計算?

用下面的代碼測試一下吧,我只能幫你到這裏了。

@Test
public void test(){
    LocalDate localDate = LocalDate.parse("2019-04-09");
    System.out.println(localDate.getLong(ChronoField.DAY_OF_YEAR));

    LocalDate durationLocalDate = localDate.plusDays(365);
    System.out.println(durationLocalDate.format(DateTimeFormatter.ISO_DATE));
    System.out.println(durationLocalDate.getLong(ChronoField.DAY_OF_YEAR));
    
    LocalDate periodLocalDate = localDate.plus(Period.ofYears(1));
    System.out.println(periodLocalDate.format(DateTimeFormatter.ISO_DATE));
    System.out.println(periodLocalDate.getLong(ChronoField.DAY_OF_YEAR));
}

另外LocalDateTime使用Duration,LocalDate使用Period。

@Test
public void duration(){
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime localDateTime = now.plusDays(1);
    Duration duration = Duration.between(now, localDateTime);
    System.out.println(duration.toDays());

    Period period = Period.between(now.toLocalDate(), localDateTime.toLocalDate());
    System.out.println(period.getDays());
}
@Test
public void duration(){
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime localDateTime = now.plusYears(1);
    Duration duration = Duration.between(now, localDateTime);
    System.out.println(duration.toMillis());
    System.out.println(Duration.ofDays(1).toHours());
}

8. TemporalAdjuster與TemporalAdjusters

如果你要日程安排類的相關功能,TemporalAdjuster絕對是一個好的選擇。

一看TemporalAdjusters,我們就能猜到應該是TemporalAdjuster的工具類,它提供了下面這些方法:

方法 說明
lastDayOfYear 今年的最後一天
firstDayOfYear 當年的第一天
1astDayOLMonth 下月的最後一天
next(DayOfWeek) 下一個星期X
firstDayOfMonth 當月的第1天
lastDayOfNextYear 明年的最後一天
lastDayOfNextMonth 下月的最後一天
firstDayOfNextYear 明年的第一天
firstDayOfNextMonth 下個月的第1天
previous(DayOfWeek) 前一個星期X
nextOrSame(DayOfWeek) 下一個星期X,如果當前就是星期X,那就是當前日期
lastInMonth(DayOfWeek) 當月最後一個星期X
firstInMonth(DayOfWeek) 當月第一個星期X
previousOrSame(DayOfWeek) 前一個星期X,如果當前就是星期X,那就是當前日期
dayOfWeekInMonth(int,DayOfWeek) 當月第Y個星期X,例如,母親節,5月的第2個星期天

到底怎麼使用呢?

看一些小例子就清楚了:

@Test
public void temporalAdjusters(){
    LocalDate now = LocalDate.now();
    LocalDate tmp;
    //下一個星期五
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.FRIDAY);
    tmp = now.with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    //下一個星期五,如果當前日期是星期五,那麼就是當前日期
    temporalAdjuster = TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY);
    tmp = now.with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    temporalAdjuster = TemporalAdjusters.lastDayOfMonth();
    tmp = now.with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    //當月月的最後一個週五
    temporalAdjuster = TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY);
    tmp = now.with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    //當月月第一個週六
    temporalAdjuster = TemporalAdjusters.firstInMonth(DayOfWeek.SATURDAY);
    tmp = now.with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    //母親節:5月第2個星期天
    temporalAdjuster = TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.SUNDAY);
    tmp = now.withMonth(5).with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

    //父親節:6月第3個星期天
    temporalAdjuster = TemporalAdjusters.dayOfWeekInMonth(3,DayOfWeek.SUNDAY);
    tmp = now.withMonth(6).with(temporalAdjuster);
    System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}

9. GTM、UTC與時間戳

9.1 GMT

GMT(Greenwish Mean Time): 格林威治標準時間,指位於倫敦郊區的皇家格林尼治天文臺的標準時間。

爲啥使用這個時間做標準時間呢?

因爲本初子午線被定義在通過那裏的經線。

爲啥本初子午線被定義在通過那裏的經線呢?

你的問題太多了,電影中知道太多的角色都沒有什麼好結局。

格林威治標準時間的正午是指當太陽橫穿格林尼治子午線時,也就是在格林尼治上空最高點的時間。由於地球在它的橢圓軌道里的運動速度不均勻,這個時刻可能和實際的太陽時相差16分鐘。地球每天的自轉是有些不規則的,而且正在緩慢減速。

所以,格林尼治時間已經不再被作爲標準時間使用。現在的標準時間是協調世界時(UTC),它由原子鐘提供。

9.2 UTC

UTC(Coordinated Universal Time):世界統一時間,世界標準時間、國際協調時間

雖然GMT不夠精確,但是我們通常讓我GMT與UTC是等價的。

9.3 時間戳

時間戳是指格林威治時間(GMT)自:1970-01-01 00:00:00至當前所經過的秒(10位)或者毫秒(13位)數。

所以,從時間戳的定義我們就知道了時間戳和時區沒有任何關係,不存在本地時間轉化問題,因此,在存儲時間戳的時候最好不要做加減換算,使用本地時間的時候才需要

時間戳雖然不需要加減換算,但是時間戳到本地時間卻需要,因爲從時間戳定義我們知道,它關聯了本地時間GMT。

所以,時間戳轉本地時間是需要時區的,大致的流程是:

  1. 時間戳 -> GMT時間
  2. GMT時間 + 時區偏移 -> 本地時間,例如,我們在東八區,所以我們的時間就是GMT+8

當然我們計算的時候通常是先執行時間戳加減,然後再轉換爲時間,結果上是沒有問題的,邏輯上容易讓人誤解。

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