【Java系列】Java 8 日期/时间 API 简介

目录

1. 概述

2. 现有日期/时间 API 的问题

3. 使用 LocalDate/LocalTime/LocalDateTime API

3.1 LocalDate

3.2 LocalTime

3.3 LocalDateTime

4. 使用 ZonedDateTime API

5. 使用 Period and Duration API

5.1 Period

5.2 Duration

6. 与 Date 和 Calendar 的兼容性

7. 日期/时间格式化

8. 向后移植和替代选项

8.1. ThreeTen Project

8.2. Joda-Time Library

9. 小结


1. 概述

Java 8 引入了新的日期和时间 API,以解决旧版 java.util.Datejava.util.Calendar 的缺点。

在本教程中,我们从现有 Date 和 Calendar API 中的问题开始,讨论新的 Java 8 日期和时间 API 如何解决这些问题。

我们还将了解新 Java 8 项目的一些核心类,它们是 java.time 包的一部分,例如 LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Period、Duration 及其支持的 API。

2. 现有日期/时间 API 的问题

  • 线程安全 - Date Calendar 类不是线程安全的,这使得开发人员必须处理难以调试的并发问题,并编写额外的代码来处理线程安全。 相反,Java 8 中引入的新日期和时间 API 是不可变的且线程安全的,从而消除了开发人员的并发问题。
  • API 设计和易于理解 - Date Calendar API 设计不佳,没有足够的方法来执行日常操作。 新的日期/时间 API 以 ISO 为中心,并遵循 date、time、duration 和 periods 的一致领域模型。 有多种实用方法支持最常见的操作。
  • 时区 - 开发人员必须使用旧 API 编写额外的逻辑来处理时区逻辑,而使用新 API 时,可以使用 LocalZonedDate/Time API 来轻松处理时区问题。

3. 使用 LocalDate/LocalTime/LocalDateTime API

最常用的类是 LocalDate、LocalTimeLocalDateTime。 正如它们的名称所示,它们代表观察者上下文中的本地日期/时间。

当不需要在上下文中显式指定时区时,我们主要使用这些类。 作为本节的一部分,我们将介绍最常用的 API。

3.1 LocalDate

LocalDate 表示 ISO 格式 (yyyy-MM-dd) 的日期,不包含时间。 我们可以用它来存储生日和发薪日等日期。

可以从系统时钟创建当前日期的实例:

LocalDate localDate = LocalDate.now();

我们可以使用 of 方法或 parse 方法获取代表特定日、月、年的 LocalDate

例如,这些代码片段代表 2015 年 2 月 20 日的 LocalDate

LocalDate.of(2015, 02, 20);

LocalDate.parse("2015-02-20");

LocalDate 提供了各种实用方法来获取各种信息。 让我们快速浏览一下其中一些 API 方法。

// 获取当前本地日期并添加一天
LocalDate tomorrow = LocalDate.now().plusDays(1);

// 获取当前日期并减去一个月。请注意它如何接受枚举作为时间单位
LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);

// 解析日期"2016-06-12"并获取星期几。返回值是表示 DayOfWeek 的对象,对应int值范围从 1(星期一)到 7(星期日)。建议应用程序使用枚举而不是 int 值来确保代码清晰
DayOfWeek sunday = LocalDate.parse("2016-06-12").getDayOfWeek();

// 解析日期"2016-06-12"并获取月份。返回值是表示月份的int值,范围从 1 到 31
int twelve = LocalDate.parse("2016-06-12").getDayOfMonth();

// 测试某个日期是否出现在闰年
boolean leapYear = LocalDate.now().isLeapYear();

// 确定一个日期与另一个日期的关系是在另一个日期之前还是之后发生
boolean notBefore = LocalDate.parse("2016-06-12")
  .isBefore(LocalDate.parse("2016-06-11"));

boolean isAfter = LocalDate.parse("2016-06-12")
  .isAfter(LocalDate.parse("2016-06-11"));

// 请注意:isBefore isAfter 方法没有包含关系。下面的示例都返回false
LocalDate a = LocalDate.of(2012, 6, 30);
boolean isFalse = a.isAfter(a);
boolean isFalse = a.isBefore(a);


// 获取表示给定日期的当天开始 (2016-06-12T00:00) 的 LocalDateTime
LocalDateTime beginningOfDay = LocalDate.parse("2016-06-12").atStartOfDay();

// 获取表示月份开始 (2016-06-01) 的 LocalDate
LocalDate firstDayOfMonth = LocalDate.parse("2016-06-12")
  .with(TemporalAdjusters.firstDayOfMonth());

现在让我们看看如何使用 LocalTime。 

3.2 LocalTime

LocalTime 表示不带日期的时间。

LocalDate 类似,我们可以从系统时钟或使用 parse 和 of 方法创建 LocalTime 的实例。

现在我们将快速浏览一些常用的 API。

// 从系统时钟创建当前 LocalTime 的实例
LocalTime now = LocalTime.now();

// 通过解析字符串来创建表示上午 6:30 的 LocalTime
LocalTime sixThirty = LocalTime.parse("06:30");

// 使用工厂方法创建代表上午 6:30 的 LocalTime
LocalTime sixThirty = LocalTime.of(6, 30);

// 通过解析字符串并使用"plus" API 添加一个小时来创建一个 LocalTime。结果将是代表上午 7:30 的 LocalTime
LocalTime sevenThirty = LocalTime.parse("06:30").plus(1, ChronoUnit.HOURS);

// 有多种 get 方法可用于获取特定的时间单位,如小时、分钟和秒
int six = LocalTime.parse("06:30").getHour();

// 检查一个特定时间是否在另一个特定时间之前或之后,下面示例将返回true
boolean isbefore = LocalTime.parse("06:30").isBefore(LocalTime.parse("07:30"));

// 一天中的最大、最小和中午时间可以通过 LocalTime 类中的常量获得。当执行数据库查询以查找给定时间范围内的记录时,这非常有用。
LocalTime maxTime = LocalTime.MAX;
LocalTime min = LocalTime.MIN;
LocalTime noon = LocalTime.NOON;

现在让我们深入了解 LocalDateTime

3.3 LocalDateTime

该类提供了各种 API。 我们将看看一些最常用的。

// LocalDateTime 的实例可以从系统时钟获取,类似于 LocalDate 和 LocalTime
LocalDateTime.now();

// 下面的代码示例解释了如何使用工厂"of"和"parse"方法创建实例。 结果将是一个代表 2015 年 2 月 20 日上午 6:30 的 LocalDateTime 实例
LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
LocalDateTime.parse("2015-02-20T06:30:00");

// 有一些实用程序 API 可以支持特定时间单位(例如天、月、年和分钟)的加法和减法。
// 下面的代码演示了"加"和"减"方法。 这些 API 的行为与 LocalDate 和 LocalTime 中的对应 API 完全相同
localDateTime.plusDays(1);
localDateTime.minusHours(2);

// Getter 方法也可用于提取类似于日期和时间类的特定单位。 鉴于上面的 LocalDateTime 实例,此代码示例将返回二月
localDateTime.getMonth();

4. 使用 ZonedDateTime API

当我们需要处理特定于时区的日期和时间时,Java 8 提供了 ZonedDateTime。 ZoneId是用于表示不同区域的标识符。 大约有 40 个不同的时区,ZoneId 可以按照如下方式表示它们。

我们为上海创建一个时区:

ZoneId zoneId = ZoneId.of("Asia/Shanghai");

我们可以获得所有时区 ID 的集合:

Set allZoneIds = ZoneId.getAvailableZoneIds();

LocalDateTime 可以转换为特定区域:

ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);

ZonedDateTime 提供了解析方法来获取特定于时区的日期时间:

ZonedDateTime.parse("2015-05-03T10:15:30+01:00[Asia/Shanghai]");

使用时区的另一种方法是使用 OffsetDateTimeOffsetDateTime 是带有偏移量的日期时间的不可变表示形式。 此类存储所有日期和时间字段(精确到纳秒)以及与 UTC/Greenwich 的偏移量。

可以使用 ZoneOffset 创建 OffSetDateTime 实例。 在这里,我们创建一个代表 2015 年 2 月 20 日上午 6:30 的 LocalDateTime

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);

然后我们通过创建 ZoneOffset 并设置 localDateTime 实例来添加两个小时的时间:

ZoneOffset offset = ZoneOffset.of("+02:00");

OffsetDateTime offSetByTwo = OffsetDateTime
  .of(localDateTime, offset);

我们现在的 localDateTime 为 2015-02-20 06:30 +02:00。

现在让我们继续讨论如何使用 Period Duration 类修改日期和时间值。

5. 使用 Period and Duration API

Period 类表示以年、月和日为单位的时间量,而 Duration 类表示以秒和纳秒为单位的时间量。

5.1 Period

Period 类广泛用于修改给定日期的值或获取两个日期之间的差异:

LocalDate initialDate = LocalDate.parse("2007-05-10");

我们可以使用 Period 来操作日期:

LocalDate finalDate = initialDate.plus(Period.ofDays(5));

Period 类具有各种 getter 方法,例如 getYearsgetMonths getDays,用于从 Period 对象获取对应的值。

例如,当我们尝试获取天数差异时,这将返回 int 值 5:

int five = Period.between(initialDate, finalDate).getDays();

 我们可以使用 ChronoUnit.Between 获取特定单位(例如天、月或年)中两个日期之间的 Period

long five = ChronoUnit.DAYS.between(initialDate, finalDate);

此代码示例返回五天。

让我们继续看一下 Duration 类。

5.2 Duration

Period 类似,Duration 类用于处理时间。

让我们创建一个 6:30 a.m. 的 LocalTime,然后添加 30 秒的 duration 时间以使 LocalTime 变为 6:30:30 a.m.:

LocalTime initialTime = LocalTime.of(6, 30, 0);

LocalTime finalTime = initialTime.plus(Duration.ofSeconds(30));

我们可以将两个时间作为参数获取 Duration 的间隔时间。

首先,我们使用 Duration 类的 Between() 方法来查找 finalTime initialTime 之间的时间差,并返回以秒为单位的差值:

long thirty = Duration.between(initialTime, finalTime).getSeconds();

在第二个示例中,我们使用 ChronoUnit 类的 Between() 方法来执行相同的操作:

long thirty = ChronoUnit.SECONDS.between(initialTime, finalTime);

现在我们将了解如何将现有的 Date 和 Calendar 转换为新的日期/时间。

6. 与 Date 和 Calendar 的兼容性

Java 8 添加了 toInstant() 方法,该方法有助于将现有的 Date 和 Calendar 实例转换为新的日期和时间 API:

LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());

LocalDateTime 可以根据纪元秒构建。 以下代码的结果将是表示 2016-06-13T11:34:50 的 LocalDateTime:

LocalDateTime.ofEpochSecond(1465817690, 0, ZoneOffset.UTC);

现在让我们继续讨论日期和时间格式。

7. 日期/时间格式化

Java 8 提供了用于轻松格式化日期和时间的 API:

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JANUARY, 25, 6, 30);

此代码传递 ISO 日期格式来格式化本地日期,结果为 2015-01-25:

String localDateString = localDateTime.format(DateTimeFormatter.ISO_DATE);

DateTimeFormatter 提供各种标准格式选项。

自定义模式也可以提供给 format 方法,这里返回一个 LocalDate 为 2015/01/25:

localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

我们可以传入 SHORTLONG MEDIUM 格式样式作为格式选项的一部分。

例如,这将给出表示 25-Jan-2015, 06:30:00 的 LocalDateTime 的输出:

localDateTime
  .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
  .withLocale(Locale.UK));

让我们看一下 Java 8 核心日期/时间 API 的可用替代方案。

8. 向后移植和替代选项

8.1. ThreeTen Project

于正在从 Java 7 或 Java 6 迁移到 Java 8 并且想要使用日期和时间 API 的组织,ThreeTen 项目提供了向后移植功能。

开发人员可以使用此项目中提供的类来实现与新的 Java 8 日期和时间 API 相同的功能。 一旦他们迁移到 Java 8,就可以切换包。

ThreeTen 项目的工件可以在 Maven 中央存储库中找到:


    org.threeten
    threetenbp
    1.3.1

8.2. Joda-Time Library

Java 8 日期和时间库的另一个替代方案是 Joda-Time 库。 事实上,Java 8 Date/Time API 是由 Joda-Time 库的作者 (Stephen Colebourne) 和 Oracle 共同领导的。 该库提供了 Java 8 日期/时间项目中支持的几乎所有功能。

通过在项目中包含以下 pom 依赖项来使用,可以在 Maven Central 中找到该工件:


    joda-time
    joda-time
    2.9.4

9. 小结

Java 8 提供了丰富的日期/时间 API,具有一致的API设计,使开发更容易。

你可能感兴趣的:(Java,java,开发语言)