目錄
3、LocalDateTime 獲取年月日時分秒,相當於 LocalDate + LocalTime
4、Instant 獲取秒數,用於表示一個時間戳(精確到納秒)
5、Duration : 用於計算兩個“時間”間隔 ,Period : 用於計算兩個“日期”間隔
6、DateTimeFormatter : 解析和格式化日期或時間
7、ZonedDate、ZonedTime、ZonedDateTime : 帶時區的時間或日期
一、舊的時間和日期
- 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 | 返回後一個/前一個給定的星期幾,如果這個值滿足條件,直接返回 |