以前处理日期,经常在 Date、Calendar 和 SimpleDateFormat 之间来回转换。Java 8 增加了 java.time,把日期、时间和时间点分开表示,常用的加减和格式化操作也更直接。
下面的示例分别放在 main 方法中运行,使用这些导入:
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoUnit;
import java.util.Date;日期和时间分开表示
只需要日期,用 LocalDate;只需要一天中的时间,用 LocalTime;两个都需要,用 LocalDateTime:
LocalDate date = LocalDate.of(2018, 7, 15);
LocalTime time = LocalTime.of(20, 30);
LocalDateTime dateTime = LocalDateTime.of(date, time);
System.out.println(date); // 2018-07-15
System.out.println(time); // 20:30
System.out.println(dateTime); // 2018-07-15T20:30月份直接写 1 到 12,不用再像 Calendar 那样从 0 开始。
这三个类型都不带时区。2018-07-15T20:30 只是一组日期和时间,还不能确定它是北京时间的晚上八点半,还是其他时区的八点半。
加减与比较
这些对象是不可变的,修改操作会返回新对象:
LocalDate date = LocalDate.of(2018, 7, 15);
LocalDate nextWeek = date.plusWeeks(1);
System.out.println(date); // 2018-07-15
System.out.println(nextWeek); // 2018-07-22
System.out.println(date.isBefore(nextWeek)); // true所以只写 date.plusWeeks(1),不会改变原来的 date。
月份加减遇到目标月份没有对应日期时,会调整到有效日期:
LocalDate date = LocalDate.of(2018, 1, 31);
System.out.println(date.plusMonths(1)); // 2018-02-28格式化与解析
用 DateTimeFormatter 指定格式:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.of(2018, 7, 15, 20, 30, 45);
String text = dateTime.format(formatter);
System.out.println(text); // 2018-07-15 20:30:45
System.out.println(LocalDateTime.parse(text, formatter)); // 2018-07-15T20:30:45DateTimeFormatter 是不可变且线程安全的,可以复用。
如果要严格校验日期,再加上 ResolverStyle.STRICT:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd")
.withResolverStyle(ResolverStyle.STRICT);
LocalDate.parse("2018-02-30", formatter); // 抛出 DateTimeParseException这里用 uuuu 表示年份。yyyy 表示纪元中的年份,严格解析时,如果没有纪元信息,就无法确定完整日期。上面的格式没有提供纪元,所以使用 uuuu。
计算相差多久
两个日期之间相差多少天,可以用 ChronoUnit.DAYS.between:
LocalDate start = LocalDate.of(2018, 6, 9);
LocalDate end = LocalDate.of(2018, 7, 15);
System.out.println(ChronoUnit.DAYS.between(start, end)); // 36
Period period = Period.between(start, end);
System.out.println(period.getMonths()); // 1
System.out.println(period.getDays()); // 6Period 按年、月、日表示日期差,所以这里的 getDays() 是剩余的 6 天,不是总天数。
计算两个时间之间的时长,可以用 Duration:
LocalDateTime start = LocalDateTime.of(2018, 7, 15, 20, 30);
LocalDateTime end = start.plusMinutes(95);
Duration duration = Duration.between(start, end);
System.out.println(duration.toMinutes()); // 95这里计算的是不带时区的本地日期时间差。需要跨时区比较实际经过的时间时,可以先转成时间点。
时间点、时区与 Date 转换
Instant 表示时间线上的一个点。把本地日期时间转换成时间点,需要指定时区:
ZoneId zone = ZoneId.of("Asia/Shanghai");
LocalDateTime local = LocalDateTime.of(2018, 7, 15, 20, 30);
Instant instant = local.atZone(zone).toInstant();
System.out.println(instant); // 2018-07-15T12:30:00Z末尾的 Z 表示 UTC。这个例子中,上海时间比 UTC 快 8 小时。
已有代码使用 java.util.Date 时,可以通过 Instant 转换:
Instant instant = Instant.parse("2018-07-15T12:30:00Z");
Date date = Date.from(instant);
Instant restored = date.toInstant();
LocalDateTime local = LocalDateTime.ofInstant(restored, ZoneId.of("Asia/Shanghai"));
System.out.println(restored); // 2018-07-15T12:30:00Z
System.out.println(local); // 2018-07-15T20:30Date 保存的是时间点,不保存时区。转成 LocalDateTime 时指定的时区,决定了最终看到的日期和时间。Date 只有毫秒精度,Instant 中比毫秒更细的部分在转换时也不会保留。