返回介绍

Java SE 8 新的时间和日期 API

发布于 2025-02-26 22:54:42 字数 7876 浏览 0 评论 0 收藏 0

为什么我们需要一个新的时间日期 API

Java 开发中一直存在一个问题,JDK 提供的时间日期 API 一直对开发者没有提供良好的支持。

比如,已有的的类(如 java.util.DateSimpleDateFormatter )不是线程安全的,会在并发情况下留下一些隐患,这不是开发者在编写处理日期的代码块时想要的效果。

某些日期时间处理类也表现出了相当不合理的设计,比如在 java.util.Date 类中,年从 1900 开始,月从 1 开始,日从 0 开始——就表现的不是很直观。

这些问题导致我们会选择一些第三方的日期时间处理库,比如: Joda-Time

为了在 JDK 中解决这些问题并提供更好的方式,Java SE8 中设计了新的时间日期处理 API。

这个项目为 JSR-310 并由 Joda-Time(Stephen Colebourne) 和 Oracle 共同设计,并会放在 Java SE 8 的 java.time 包下。

核心思路

这个新的 API 由三个核心思路组成:

  • 不可改变值的类 。一个严重的问题是,对于 Java 中已经存在的格式化处理类(Formatter)不是线程安全的。这使开发人员在日常开发中需要编写线程安全的日期处理代码变得很麻烦。新的 API 保证所有的核心类中的值是不可变的,避免了并发情况下带来的不必要的问题。
  • 领域驱动设计 。新的 API 模型可以精确的表示出 DateTime 的差异性。在以前的 Java 库中这一点就表现的非常差。比如, java.util.Date 他表示一个时间点,从 Unix 时代开始就是以毫秒数的形式保存,但是你调用它的 toString 方法时,结果却显示它是有时区概念的,这就容易让开发者产生歧义。领域驱动设计的重点是从长远好处出发且简单易懂,当你需要把你以前的处理时间的模块代码移植到 Java8 上时你就需要考虑一下领域模型的设计了。
  • 区域化时间体系 。新的 API 允许人们在时区不同的时间体系下使用。比如日本或者泰国,他们不必要遵循 ISO-8601。新 API 为大多数开发者减少了很多额外的负担,我们只需要使用标准的时间日期 API。

LocalDate 类和 LocalTime 类

LocalDate 类和 LocalTime 类很有可能是你使用新 API 时第一个遇见的类。他们本地化的原因是他们能够根据系统环境来表示日期和时间,就像放在桌上的日历或者挂在墙上的时钟。还有一个混合类是 LocalDateLocalTime 组合而成的,叫 LocalDateTime

当你不知道你所运行环境的时区时,你应该使用这些本地化的类。比如桌面 JavaFX 程序就是其中之一。甚至可以在处于不同时区的分布式系统中使用这些类。

现有的时间日期 API 中的类都不是线程安全的,开发者需要处理潜在的并发问题——这不是大部分的开发者想要的。

创建对象

在新的 API 中所有的核心类都可以由工厂方法很方便的构建。当我通过某些类自身的字段来构建它时,可以使用 of 方法;当我通过从另外一个类型的转换来构建它时,可以使用 from 方法。同样也可以通过 parse 方法来由一个 String 参数构建它。参见代码 1. 代码 1

LocalDateTime timePoint = LocalDateTime.now(
    );     // The current date and time
LocalDate.of(2012, Month.DECEMBER, 12); // from values
LocalDate.ofEpochDay(150);  // middle of 1970
LocalTime.of(17, 18); // the train I took home today
LocalTime.parse("10:15:30"); // From a String

在 Java SE 8 中我们可以使用 Java 标准的 getter 方法来获取想要的值,参见代码 2

代码 2

LocalDate theDate = timePoint.toLocalDate();
Month month = timePoint.getMonth();
int day = timePoint.getDayOfMonth();
timePoint.getSecond();

你也可以对对象的值进行运算操作。因为新的 API 中所有的类型都是不可变的,他们都是调用了 with 方法并返回一个新对象,相当于使用了 setter 赋值。对于每一个字段都有提供了基本的运算方法。参见代码 3

代码 3

// Set the value, returning a new object
LocalDateTime thePast = timePoint.withDayOfMonth(
    10).withYear(2010);

/* You can use direct manipulation methods, 
    or pass a value and field pair */
LocalDateTime yetAnother = thePast.plusWeeks(
    3).plus(3, ChronoUnit.WEEKS);

新的 API 提供的一个调节器的概念–用来封装通用的处理逻辑的一段代码。你对任意时间使用 WithAdjuster 来设置一个或者多个字段,或者可以使用 PlusAdjuster 来对字段进行增量或者减法操作. 值类型也可以被当做调节器使用,用来更新字段的值. 新的 API 定义了一些内置的调节器,但是如果你希望实现某些特定的业务逻辑,你也可以自己实现一个调节器. 参见代码 4。

代码 4

import static java.time.temporal.TemporalAdjusters.*;

LocalDateTime timePoint = ...
foo = timePoint.with(lastDayOfMonth());
bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY));

// Using value classes as adjusters
timePoint.with(LocalTime.now());

截取

新的 API 提供了表示不同精度的时间点类型来表示日期、时刻、日期+时刻。

API 提供的 truncatedTo 方法适用于这种场景:他允许你从一个字段中截取出一个值。参见代码 5。

代码 5

LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);

时区

我们参考了以前的对于时区的很复杂的抽象方式。时区就是一个规则的集合,同样的时区遵守同样的时间标准规定,时区大约有 40 条规则。时区是由世界统一时间(UTC) 来定义的。 他们基本上是同步的但是也有细小的差别。时区有两种命名定义: 简写型,比如, “PLT”,完整型,“Asia/Karachi.”。当你设计程序的时候,你应该考虑是在什么时区下运行。

  • ZoneId 是时区的标示符. 每一个 ZoneId 都表示这些时区遵循同样的规则。 当你编码时你可以考虑使用例如“PLT”,“Asia/Karachi,”这种字符串来创建 ZoneId。下面是一段完整的使用 ZoneId 的代码,参见代码 6。

代码 6

// You can specify the zone id when creating a zoned date time
ZoneId id = ZoneId.of("Europe/Paris");
ZonedDateTime zoned = ZonedDateTime.of(dateTime, id);
assertEquals(id, ZoneId.from(zoned));
  • ZoneOffset 是一段时间内代表格林尼治时间/世界同一时间与时区之间的差异。他能表示一个特殊的差异时区偏移量。参见代码 7。

代码 7

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

时区类

  • ZonedDateTime 是一个带有时区的日期时间类。他能表示出与任意时区的某个时间点的时差. 如果你希望代表一个日期和时间不依赖特定服务器环境,你就应该使用 ZonedDateTime.

代码 8

ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
  • OffsetDateTime 是一个带有偏移量的时间日期类。如果你的服务器处在不同的时区,他可以存入数据库中也可以用来记录某个准确的时间点。
  • OffsetTime 是一个带有偏移量的时间类。参见代码 9 代码 9
OffsetTime time = OffsetTime.now();
// changes offset, while keeping the same point on the timeline
OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(offset);
// changes the offset, and updates the point on the timeline
OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(offset);
// Can also create new object with altered fields as before
changeTimeWithNewOffset.withHour(3).plusSeconds(2);

Java 中已经存在了表示时区的类— java.util.TimeZone —但是他不能用在 Java SE 8 中,因为在 JSR-310 中所有的时间日期类是不可变的,时区类确是可变的。

Period 类

Periods 类用来表示例如“三个月零一天”这种描述一段时间的值。这是目前看来与其他类不同的表示一段时间而不是时间点的类。 参见代码 10。

代码 10

// 3 years, 2 months, 1 day
Period period = Period.of(3, 2, 1);

// You can modify the values of dates using periods
LocalDate newDate = oldDate.plus(period);
ZonedDateTime newDateTime = oldDateTime.minus(period);
// Components of a Period are represented by ChronoUnit values
assertEquals(1, period.get(ChronoUnit.DAYS));

Duration 类

Duration 类也是用来描述一段时间的,他和 Period 类似,但是不同于 Period 的是,它表示的精度更细。参见代码 11。

// A duration of 3 seconds and 5 nanoseconds
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);

我们可以对其进行加减或者 with 函数操作,也可以使用它来修改一个时间日期对象的值。

Java SE 8 的 java.time 包中新的时间日期 API 的功能可用性和安全性都大大提升了。 新的 API 很好用,可以适应大部分的场景。

Chronology 系列类

由于我们需要支持无 ISO 日期年表表示的环境, Java SE 8 首次引入了 Chronology 类,在这种环境下使用。 他们也实现了核心的时间日期接口。 Chronology:

  • ChronoLocalDate
  • ChronoLocalDateTime
  • ChronoZonedDateTime

这些类应该使用在具有高度国际化的应用中需要使用本地化的时间体系时,没有这种需求的话最好不要使用它们。有一些时间体系中甚至没有一个月,一个星期这样的概念,我们只能通过更加通用抽象的字段进行运算。

其余的 API 改动

Java SE 8 还提供了一些其他场景下使用的类。 MonthDay 类表示一个月中的某几天,表示节日的时候可以使用。 YearMonth 类可以用来表示在比如信用卡的生效和失效时间(译者注:信用卡失效时间以月为单位)。

Java SE 8 中也提供了对于 JDBC 的新的类型支持,但是是一个非公开的修改。

这些类也可以和某些数据库类型进行映射。如下表,描述了这些类对于 ANSI SQL 的对应关系

ANSI SQLJava SE 8
DATELocalDate
TIMELocalTime
TIMESTAMPTIMESTAMP
TIMESTAMP WITH TIMEZONEOffsetDateTime

总结

Java SE 8 提供的 java.time 中新的日期和时间 API。很大的提升了安全性,功能也更为强大。新的 API 架构模型,会让开发人员在各种各样场景下很方面的使用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文