以前处理日期,经常在 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:45

DateTimeFormatter 是不可变且线程安全的,可以复用。

如果要严格校验日期,再加上 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());   // 6

Period 按年、月、日表示日期差,所以这里的 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:30

Date 保存的是时间点,不保存时区。转成 LocalDateTime 时指定的时区,决定了最终看到的日期和时间。Date 只有毫秒精度,Instant 中比毫秒更细的部分在转换时也不会保留。

最后修改:2018 年 07 月 15 日
如果觉得我的文章对你有用,请随意赞赏