Java 8:新時間和日期API

目錄

一、舊的時間和日期

二、Java 8 新時間和日期API

1、LocalDate 只獲取年月日

2、LocalTime 只會獲取時分秒

3、LocalDateTime 獲取年月日時分秒,相當於 LocalDate + LocalTime

4、Instant 獲取秒數,用於表示一個時間戳(精確到納秒)

5、Duration : 用於計算兩個“時間”間隔 ,Period : 用於計算兩個“日期”間隔

6、DateTimeFormatter : 解析和格式化日期或時間

7、ZonedDate、ZonedTime、ZonedDateTime : 帶時區的時間或日期

8、TemporalAdjuster : 時間校正器


一、舊的時間和日期

  • Java 的 java.util.Date 和 java.util.Calendar 類易用性差,不支持時區,而且都不是線程安全的。
  • Date如果不格式化,打印出的日期可讀性差。
Sat Oct 12 17:12:32 CST 2019

使用 SimpleDateFormat 對時間進行格式化,但 SimpleDateFormat 是線程不安全的,SimpleDateFormat 的 format 方法源碼如下:

private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);
    boolean useDateFormatSymbols = useDateFormatSymbols();
    for (int i = 0; i < compiledPattern.length; ) {
        int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
            count = compiledPattern[i++] << 16;
            count |= compiledPattern[i++];
        }
        switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char) count);
                break;
            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;
            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
        }
    }
    return toAppendTo;
}

其中calendar是共享變量,並且這個共享變量沒有做線程安全控制。當多個線程同時使用相同的SimpleDateFormat對象【如用static修飾的 SimpleDateFormat 】調用format方法時,多個線程會同時調用 calendar.setTime 方法,可能一個線程剛設置好 time 值另外的一個線程馬上把設置的 time 值給修改了導致返回的格式化時間可能是錯誤的。

在多併發情況下使用 SimpleDateFormat 需注意。例如如下代碼:

 public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

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

        };
        ExecutorService pool = Executors.newFixedThreadPool(10);

        List<Future<Date>> lists = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            lists.add(pool.submit(task));
        }

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

Caused by: java.lang.NumberFormatException: multiple points

解決線程安全問題的辦法:

public static void main(String[] args) throws Exception {
    Callable<Date> task = new Callable<Date>() {
			@Override
			public Date call() throws Exception {
				return DateFormatThreadLocal.convert("20191012");
			}
		};
	ExecutorService pool = Executors.newFixedThreadPool(10);
	List<Future<Date>> lists = new ArrayList<>();	
	for (int i = 0; i < 10; i++) {
		lists.add(pool.submit(task));
	}	
	for (Future<Date> future : lists) {
		System.out.println(future.get());
	}	
	pool.shutdown();
}
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
	protected DateFormat initialValue(){
		return new SimpleDateFormat("yyyyMMdd");
	}
		
};	
public static final Date convert(String source) throws ParseException{
	return df.get().parse(source);
}

二、Java 8 新時間和日期API

Java 8的日期和時間類包含 LocalDate、LocalTime、Instant、Duration 以及 Period,這些類都包含在 java.time 包中,Java 8 新的時間API的使用方式,包括創建、格式化、解析、計算、修改,下面我們看下如何去使用。

1、LocalDate 只獲取年月日

// 創建 LocalDate
// 獲取當前年月日
LocalDate ld = LocalDate.now();
System.out.println(ld);
//構造指定的年月日
LocalDate ld1 = LocalDate.of(2019, 10, 12);
System.out.println(ld1);

// 獲取年、月、日、星期幾
int year = ld.getYear();
int year1 = ld.get(ChronoField.YEAR);
System.out.println(year + "," + year1);
// 獲取月
Month month = ld.getMonth();
int month1 = ld.get(ChronoField.MONTH_OF_YEAR);
System.out.println(month + "," + month1);
// 獲取日
int day = ld.getDayOfMonth();
int day1 = ld.get(ChronoField.DAY_OF_MONTH);
System.out.println(day + "," + day1);
// 獲取星期幾
DayOfWeek dayOfWeek = ld.getDayOfWeek();
int dayOfWeek1 = ld.get(ChronoField.DAY_OF_WEEK);
System.out.println(dayOfWeek + "," + dayOfWeek1);

運行結果:

2、LocalTime 只會獲取時分秒

//創建 LocalTime
LocalTime localTime = LocalTime.of(12, 14, 14);
LocalTime localTime1 = LocalTime.now();
System.out.println(localTime + "," + localTime1);
//獲取小時
int hour = localTime.getHour();
int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
System.out.println(hour + "," + hour1);
//獲取分
int minute = localTime.getMinute();
int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
System.out.println(minute + "," + minute1);
//獲取秒
int second = localTime.getMinute();
int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
System.out.println(second + "," + second1);

 運行結果:

3、LocalDateTime 獲取年月日時分秒,相當於 LocalDate + LocalTime

// 創建 LocalDateTime
LocalDateTime ldt = LocalDateTime.now();
LocalDate ld = LocalDate.now();
LocalTime lt = LocalTime.now();
LocalDateTime ldt1 = LocalDateTime.of(2019, Month.OCTOBER, 10, 14, 46, 56);
LocalDateTime ldt2 = LocalDateTime.of(ld, lt);
LocalDateTime ldt3 = ld.atTime(lt);
LocalDateTime ldt4 = lt.atDate(ld);
// 獲取LocalDate
LocalDate localDate2 = ldt.toLocalDate();
// 獲取LocalTime
LocalTime localTime2 = ldt.toLocalTime();
System.out.println(ldt);
System.out.println(ldt1);
System.out.println(ldt2);
System.out.println(ldt3);
System.out.println(ldt4);
System.out.println(localDate2);
System.out.println(localTime2);

 運行結果:

4、Instant 獲取秒數,用於表示一個時間戳(精確到納秒)

     如果只是爲了獲取秒數或者毫秒數,可以使用System.currentTimeMillis()。

// 創建Instant對象。默認使用UTC時區
Instant instant = Instant.now();
System.out.println(instant);
// 獲取秒數
long currentSecond = instant.getEpochSecond();
System.out.println(currentSecond);
// 獲取毫秒數
long currentMilli = instant.toEpochMilli();
System.out.println(currentMilli);

 運行結果:

5、Duration : 用於計算兩個“時間”間隔 ,Period : 用於計算兩個“日期”間隔

Instant ins1 = Instant.now();
System.out.println("--------------------");
try {
	Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
System.out.println("所耗費時間爲:" + Duration.between(ins1, ins2).toMillis());
System.out.println("----------------------------------");
LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.of(2011, 1, 1);
Period pe = Period.between(ld2, ld1);
System.out.println(pe.getYears());
System.out.println(pe.getMonths());
System.out.println(pe.getDays());

 運行結果:

修改 LocalDate、LocalTime、LocalDateTime、Instant。

LocalDate、LocalTime、LocalDateTime、Instant 爲不可變對象,修改這些對象對象會返回一個副本。

增加、減少年數、月數、天數等,以LocalDateTime爲例:

LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 12, 14, 32, 0);
// 增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
// 減少一個月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);  
// 通過with修改某些值
// 修改年爲2020
localDateTime = localDateTime.withYear(2020);
localDateTime = localDateTime.with(ChronoField.YEAR, 2020);
// 時間計算
// 獲取該年的第一天
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.with(firstDayOfYear());

6、DateTimeFormatter : 解析和格式化日期或時間

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);

 運行結果:

7、ZonedDate、ZonedTime、ZonedDateTime : 帶時區的時間或日期

LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(ldt);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
System.out.println(zdt);

運行結果:

8、TemporalAdjuster : 時間校正器

public void test() {
    LocalDateTime ldt = LocalDateTime.now();
    System.out.println(ldt);
    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 ldt4 = (LocalDateTime) l;
        DayOfWeek dow = ldt4.getDayOfWeek();
        if (dow.equals(DayOfWeek.FRIDAY)) {
            return ldt4.plusDays(3);
        } else if (dow.equals(DayOfWeek.SATURDAY)) {
            return ldt4.plusDays(2);
        } else {
            return ldt4.plusDays(1);
        }
    });
    System.out.println(ldt5);
}

運行結果:

TemporalAdjusters 包含許多靜態方法,可以直接調用,以下列舉一些:

方法名 描述
dayOfWeekInMonth 返回同一個月中每週的第幾天
firstDayOfMonth 返回當月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一個月中第一個星期幾
lastDayOfMonth 返回當月的最後一天
lastDayOfNextMonth 返回下月的最後一天
lastDayOfNextYear 返回下一年的最後一天
lastDayOfYear 返回本年的最後一天
lastInMonth 返回同一個月中最後一個星期幾
next / previous 返回後一個/前一個給定的星期幾
nextOrSame / previousOrSame 返回後一個/前一個給定的星期幾,如果這個值滿足條件,直接返回
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章