天天看點

Java 8 新的時間日期 API

1. 概述

1.1 簡介

Java 8 引入了一套全新的時間日期API,操作起來更簡便。簡單介紹下,

LocalDate

LocalTime

LocalDateTime

的使用;

java.util.Date

月份從0開始,

java.time.LocalDate

月份從1開始并且提供了枚舉。

java.util.Date

SimpleDateFormatter

都不是線程安全的,而

LocalDate

LocalTime

和最基本的

String

一樣,是不變類型,不但線程安全,而且不能修改,它們分别使用 ISO-8601 月曆系統的日期和時間,它們提供了簡單的日期或時間,并不包含目前的時間資訊,也不包含與時區相關的資訊。

注 : ISO-8601 月曆系統是國際标準化組織制定的現代公民的日期和時間的表示法

1.3 環境

2. LocalDate、LocalTime、LocalDateTime

LocalDate

LocalTime

LocalDateTime

三者的使用方式基本一緻,是以我們這裡就以

LocalDateTime

為例進行索命

2.1 執行個體

@Test
public void t1() {
    // 擷取目前時間
    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1);

    // 擷取指定的時間
    LocalDateTime ldt2 = LocalDateTime.of(2018, 12, 10, 10, 10, 10);
    System.out.println(ldt2);

    // 加兩年
    LocalDateTime ldt3 = ldt1.plusYears(2);
    System.out.println(ldt3);

    // 減兩個月
    LocalDateTime ldt4 = ldt1.minusYears(2);
    System.out.println(ldt4);

    // 擷取年月日時分秒
    System.out.println(ldt1.getYear());
    System.out.println(ldt1.getMonthValue());
    System.out.println(ldt1.getDayOfMonth());
    System.out.println(ldt1.getHour());
    System.out.println(ldt1.getMinute());
    System.out.println(ldt1.getSecond());
}
           

2.2 Instant : 時間戳

使用 Unix 元年 1970年1月1日 00:00:00 所經曆的毫秒值

@Test
public void t2() {
    Instant ins = Instant.now();  //預設使用 UTC 時區
    System.out.println(ins);

    // 時間偏移量
    OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
    System.out.println(odt);

    // 擷取毫秒值
    System.out.println(ins.getNano());
    
    // 對于元年開始進行運算
    Instant ins2 = Instant.ofEpochSecond(60);
    System.out.println(ins2);
}
           

2.3 Duration : 用于計算兩個“時間”間隔

@Test
public void t3() throws InterruptedException {
    // 時間戳
    Instant ins1 = Instant.now();

    Thread.sleep(1000);

    Instant ins2 = Instant.now();
    Duration duration = Duration.between(ins1, ins2);
    System.out.println("所耗費時間為:" + duration.toMillis());

    System.out.println("--------------------------------");

    // LocalTime
    LocalTime lt1 = LocalTime.now();

    Thread.sleep(1000);

    LocalTime lt2 = LocalTime.now();

    System.out.println("所耗費時間為:" + Duration.between(lt1, lt2).toMillis());
}
           

2.4 Period : 用于計算兩個“日期”間隔

@Test
public void t5() {
    LocalDate ld1 = LocalDate.of(2017, 1, 1);
    LocalDate ld2 = LocalDate.now();
    Period period = Period.between(ld1, ld2);
    System.out.println("相差" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");
}
           

2.5 日期的操作

TemporalAdjuster

時間校正器,有時我們可能需要擷取例如:将日期調整到下個周日等操作。

TemporalAdjusters

該類通過靜态方法提供了大量的常用

TemporalAdjuster

的實作。

@Test
public void t6() {
    LocalDateTime ldt = LocalDateTime.now();

    // 指定這個月的一個固定日期
    LocalDateTime ldt2 = ldt.withDayOfMonth(10);
    System.out.println(ldt2);

    // 擷取下個周日
    LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println("擷取下個周日 : " + ldt3);

    // 自定義:擷取下個工作日
    LocalDateTime ldt5 = ldt.with((l) -> {
        LocalDateTime ldt6 = (LocalDateTime) l;
        DayOfWeek dayOfWeek = ldt6.getDayOfWeek();

        if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
            return ldt6.plusDays(3);
        } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
            return ldt6.plusDays(2);
        } else {
            return ldt6.plusDays(1);
        }
    });
    System.out.println("擷取下個工作日 : " + ldt5);
}
           

3. DateTimeFormatter

3.1 簡介

在 JDK 1.8 中可以使用

DateTimeFormatter

來代替

SimpleDateFormat

進行日期的格式化,而且

DateTimeFormatter

是線程安全的

3.2 執行個體

@Test
public void t8() {
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime ldt = LocalDateTime.now();

    // 将日期格式化為字元串,兩種方式否可以
    String dtfDate = dtf.format(ldt);
    String ldtDate = ldt.format(dtf);
    System.out.println("dtfDate : " + dtfDate);
    System.out.println("ldtDate : " + ldtDate);

    // 将字元串格式化為 LocalDateTime
    LocalDateTime ldt2 = LocalDateTime.parse(dtfDate, dtf);
    System.out.println("時間為 : " + ldt2);
}
           

3.3 線程安全

在多線程使用 SimpleDateFormat 進行格式化日期的時候會有線程安全問題。

@Test
public void t1() throws Exception {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    Callable<Date> task = new Callable<Date>() {
        @Override
        public Date call() throws Exception {
            return sdf.parse("2018-12-10");
        }
    };

    ExecutorService executor = Executors.newFixedThreadPool(10);
    List<Future<Date>> results = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        results.add(executor.submit(task));
    }

    for (Future<Date> future : results) {
        System.out.println(future.get());
    }
    executor.shutdown();
}
           

可以使用 ThreadLocal 的鎖解決 SimpleDateFormat 的線程安全問題

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatThreadLocal {
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static final Date convert(String source) throws ParseException {
        return df.get().parse(source);
    }
}
           

測試方法

@Test
public void t2() throws Exception {
    Callable<Date> task = new Callable<Date>() {
        @Override
        public Date call() throws Exception {
            return DateFormatThreadLocal.convert("2018-12-10");
        }
    };

    ExecutorService executor = Executors.newFixedThreadPool(10);
    List<Future<Date>> results = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        results.add(executor.submit(task));
    }

    for (Future<Date> future : results) {
        System.out.println(future.get());
    }
    executor.shutdown();
}
           

使用 DateTimeFormatter 進行格式化,DateTimeFormatter 是線程安全的

@Test
public void t3() throws Exception {
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    Callable<LocalDate> task = new Callable<LocalDate>() {
        @Override
        public LocalDate call() throws Exception {
            return LocalDate.parse("2018-12-10",dtf);
        }
    };

    ExecutorService executor = Executors.newFixedThreadPool(10);
    List<Future<LocalDate>> results = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        results.add(executor.submit(task));
    }

    for (Future<LocalDate> future : results) {
        System.out.println(future.get());
    }
    executor.shutdown();
}
           

4. 時區設定

@Test
public void t2() {
    LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(ldt);

    // 帶時區的時間
    ZonedDateTime zdt = ldt.atZone(ZoneId.of("Asia/Shanghai"));
    System.out.println(zdt);
}
           
本文首發于淩風部落格: Java 8 新的時間日期 API 作者: 淩風