Java 8 为Date和Time引入了新的 API,以解决旧 java.util.Date 和 java.util.Calendar 的缺点。
现有日期/时间API的问题
- 线程安全 – Date和Calendar类不是线程安全的,这让开发人员不得不处理难以调试的并发问题,并编写额外的代码来处理线程安全。相反,Java 8 中引入的新日期和时间API 是不可变的和线程安全的,从而使开发人员摆脱了并发问题。
- API 设计和易于理解 – 日期和日历API 设计不佳,执行日常操作的方法不足。新的日期/时间API 以 ISO 为中心,并遵循日期、时间、持续时间和期间的一致性模型。有各种各样的实用程序方法支持最常见的操作。
- ZonedDate和Time – 使用旧 API,开发人员必须编写额外的逻辑来处理时区,而使用新 API,可以使用Local和ZonedDate / Time API 来处理时区。
本地日期/时间
JAVA8 中最常用的日期类是 LocalDate、LocalTime和LocalDateTime。正如它们的名字所表明的,它们代表了观察者上下文中的本地日期/时间。
LocalDate
LocalDate表示不带时间的 ISO 格式 (yyyy-MM-dd) 的日期。我们可以用它来存储生日和入职日期等。
可以从系统时钟创建当前日期的实例:
LocalDate localDate = LocalDate.now();
我们可以通过 of()
方法或parse()
方法获取表示特定日、月和年的 LocalDate。
LocalDate.of(2022, 04, 20);
LocalDate.parse("2022-04-20");
还可以通过一些 API 来进行时间计算:
明天
LocalDate tomorrow = LocalDate.now().plusDays(1);
上个月
LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);
字符串时间星期几
DayOfWeek sunday = LocalDate.parse("2022-06-12").getDayOfWeek();
字符串时间几号
int twelve = LocalDate.parse("2022-06-12").getDayOfMonth();
是否闰年
boolean leapYear = LocalDate.now().isLeapYear();
LocalTime
LocalTime表示没有日期的时间。
可以从系统时钟创建当前时间的实例:
LocalTime now = LocalTime.now();
我们可以通过 of()
方法或parse()
方法获取表示特定时间的 LocalTime。
LocalTime.of(10, 28);
LocalTime.parse("06:30");
还可以通过一些 API 来进行时间计算:
一个小时后
LocalTime nextHour = LocalTime.now().plus(1, ChronoUnit.HOURS);
几点
int six = LocalTime.parse("06:30").getHour();
一天的最大、最小和中午时间
LocalTime maxTime = LocalTime.MAX; // 23:59:59.99
LocalDateTime
LocalDateTime用于表示日期和时间的组合。当我们需要日期和时间的组合时,这是最常用的类。
可以从系统时钟创建当前时间的实例:
LocalDateTime now = LocalDateTime.now();
我们可以通过 of()
方法或parse()
方法获取表示特定时间的 LocalDateTime。
LocalDateTime.of(2022, Month.FEBRUARY, 22, 06, 30);
LocalDateTime.parse("2022-02-22T06:30:00");
LocalDateTime 的 API 与 LocalDate 和 LocalTime 中的 API 重合度非常高。
ZonedDateTime
ZonedDateTime 主要处理与时区有关的时间,其中 ZoneId 指代了近 40 个不同的时区。
对于法国巴黎的时区:
ZoneId zoneId = ZoneId.of("Europe/Paris");
中国的时区:
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
获取所有可用的时区:
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
将 LocalDateTime 转换为特定区域:
private static void printZoneDate(){
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(dateTime, ZoneId.of("Europe/Paris"));
System.out.println("原始 " + dateTime);
System.out.println("时区 " + zonedDateTime);
}
时区 2022-04-04T21:50:59.063+02:00[Europe/Paris]
ZonedDateTime提供parse方法来获取特定于时区的日期时间:
ZonedDateTime.parse("2022-04-04T21:50:59.063+02:00[Europe/Paris]");
使用时区的另一种方法是使用 OffsetDateTime。OffsetDateTime 是具有偏移量的日期时间的不可变类。此类存储所有日期和时间字段,精度为纳秒,以及与 UTC/格林威治的偏移量。
使用 ZoneOffset 给 LocalDateTime 添加 8个小时的偏移量:
ZoneOffset offset = ZoneOffset.of("+08:00");
LocalDateTime dateTime = LocalDateTime.now();
OffsetDateTime offSetByTwo = OffsetDateTime.of(dateTime, offset);
期间与持续时间
Period 类表示以年、月和日表示的时间量,而 Duration 类表示以秒和纳秒为单位的时间量。
期间 Period
使用 Period 来操作日期 Date:
LocalDate finalDate = localDate.plus(Period.ofDays(5));
Period类有各种 getter 方法,例如getYears、getMonths和getDays来从Period对象中获取值。
例如,当我们尝试获取天数的差异时,这将返回一个int值:
int days = Period.between(initialDate, finalDate).getDays();
我们可以使用ChronoUnit.between以特定单位(例如天、月或年)获取两个日期之间的 Period:
long days = ChronoUnit.DAYS.between(initialDate, finalDate);
持续时间 Duration
Duration 类用于处理时间 Time。
给一个上午 9:20 的时间,添加 30 秒:
LocalTime time = LocalTime.of(9, 20, 0);
LocalTime finalTime = time.plus(Duration.ofSeconds(30));
使用Duration类的 between()
方法计算两个时间的时间差:
long thirty = Duration.between(time, finalTime).getSeconds();
上面的场景,也可以改写为:
long thirty = ChronoUnit.SECONDS.between(time, finalTime);
旧时间类转为新时间类
Java 8 添加了toInstant()方法,该方法有助于将现有的Date和Calendar实例转换为新的 Date 和 Time API:
LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());
日期和时间格式化
Java 8 提供了用于轻松格式化Date和Time的 API。
把时间格式化为 ISO 日期格式:
String localDateString = localDateTime.format(DateTimeFormatter.ISO_DATE);
格式化的结果为 2022-04-05
。
也可以自定义返回格式:
localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
格式化的结果为 2022/04/05
。
临时方案
Java 8日期/时间 API 由 Joda-Time 库的作者(Stephen Colebourne)和 Oracle 共同领导,Joda-Time 库几乎支持 Java 8 中新加 API 的所有能力。如果你的项目还不能迁移到 Java8 以上的版本,那么才用 Joda-Time 库作为临时方案会是一个不错的选择。
要想在项目中使用 Joda-Time,只需要添加依赖到 pom.xml 中即可:
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.4</version>
</dependency>
当然,如果你不想为了使用新的时间 API 而学习 Joda-Time 的 API,还有一个库可以尝试,即 ThreeTen。该项目采用了与 Java8 相同的 API,如果在低版本的 JDK 中使用它,当项目迁移到 JDK8 以上后,只需要修改包路径,即可迁移完成。
要想在项目中使用 ThreeTen,只需要添加依赖到 pom.xml 中即可:
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threetenbp</artifactId>
<version>1.3.1</version>
</dependency>