Java SE 8 的Date和Time
原文作者:Ben Evans and Richard Warburton
我们为什么需要新的Date和Time库?
Java开发人员长期存在的困难一直是对普通开发人员的Date和Time用例的支持不充分。
例如,现有的类(如java.util.Date和SimpleDateFormatter)不是线程安全的,导致用户可能出现并发问题 - 这不是一般开发人员在编写日期处理代码时期望处理的问题。
一些Date和Time 类也表现出相当差的API设计。 例如,java.util.Date中的年份从1900开始,月份从1开始,几天从0开始 - 不是很直观。
这些问题以及其他一些问题导致了第三方Date和Time 库的普及,例如Joda-Time。
为了解决这些问题并在JDK核心中提供更好的支持,为Java SE 8设计了一个没有这些问题的新的Date和Time API。
该项目由Joda-Time(Stephen Colebourne)的作者和Oracle在JSR 310下共同领导,并将出现在新的Java SE 8软件包java.time中。
核心概念
新API由三个核心思想驱动:
-
不可变值(immutable-value)类。 Java中现有格式化程序的一个严重缺点是它们不是线程安全的。这增加了开发者的负担以线程安全的方式使用它们,并在他们日期处理代码的日常开发中思考并发问题。新API通过确保其所有核心类都是不可变的且表示定义良好的值,来避免此问题。
-
领域驱动的设计(domain-driven design)。新API非常精确地为其领域建模,其中的类代表了Date和Time的不同用例。这与之前的Java库不同,它们在这方面相当差。例如,java.util.Date表示时间轴上的一个瞬间 - 一个自UNIX纪元以来毫秒数的包装器 - 但是如果你调用toString(),结果表明它有一个时区,导致了在开发人员中的混淆。这种对域驱动设计的强调提供了清晰度和可理解性方面的长期优势,但在从先前的API移植到Java SE 8时,您可能需要考虑您的应用程序的日期的域模型。
-
年表(chronologies)的分离。新API允许人们使用不同的日历系统,以支持世界某些地区(如日本或泰国)用户的需求,他们不一定遵循ISO-8601标准。这样做不会给大多数开发人员带来额外的负担,他们只需要使用标准的年表。
LocalDate 和 LocalTime
当使用新的API时,你可能遇到的第一个类是LocalDate和LocalTime。它们代表观察者的上下文中的日期和时间,这个意义上来说,它们是本地的,例如桌面上的日历或墙上的时钟。 还有一个名为LocalDateTime的复合类,它是LocalDate和LocalTime的配对。
消除不同观察者上下文歧义的时区,这里被放到了一边;当你不需要那些上下文时,你应该使用这些本地类。桌面JavaFX应用程序可能是其中之一。这些类甚至可以被用于代表分布式系统上时间,其有一致的时区。
创建对象
新API中的所有核心类都是由流畅的factory方法构建的。 当通过其组成领域构建一个值时,factory被称为; 从另一种类型转换时,factory从中调用。 还有一些将字符串作为参数的解析方法。 参见清单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
Standard Java getter conventions are used in order to obtain values from Java SE 8 classes, as shown in Listing 2.
标准Java 的getter常规被用于为了从Java SE 8类获取值,如清单2所示。
LocalDate theDate = timePoint.toLocalDate();
Month month = timePoint.getMonth();
int day = timePoint.getDayOfMonth();
timePoint.getSecond();
您还可以更改对象值以执行计算。 因为在新API中所有核心类都是不可变的,所以这些方法被称作with,并返回新对象,而不是使用setter(参见清单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还具有调整器(adjuster)的概念 - 一个可用于包装通用处理逻辑的代码块。 您可以编写用于设置一个或多个字段的WithAdjuster,也可以编写用于添加或减去某些字段的PlusAdjuster。 值类也可以充当调整器,在这种情况下,它们更新它们所代表的字段的值。 内置调整器由新API定义,但如果您具有希望重用的特定业务逻辑,则可以编写自己的调整器。 参见清单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通过提供各种类型来支持不同的精确时间点,这些类型分别表示日期、时间,以及日期与时间组合;但显然有一些精度的概念,其比这些精确时间点更精细。
truncatedTo方法就存在于支持这样的用例,而且其允许您将一个值截断到一个字段上,如清单5所示。
LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);
时区
我们之前看过的本地类,以抽象的方式去掉了时区引入的复杂性。 时区是一组规则,对应于标准时间相同的区域。 大约有40个。 时区由它们与协调世界时(UTC)的偏移量定义。 它们大致同步移动,但是按照指定的差异。
时区可以通过两个标识符来引用:简短的,例如“PLT”,以及更长的,例如“亚洲/卡拉奇”。在设计应用程序时,您应该考虑,1) 哪些场景适合使用时区,2) 何时偏移量是合适的。
- ZoneId是区域的标识符(参见清单6)。 每个ZoneId对应于一些定义该位置时区的规则。 在设计软件时,如果考虑使用诸如“PLT”或“Asia / Karachi”之类的字符串,则应使用此域类。 一个示例用例是存储用户对其时区的偏好。
// 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是表示格林威治/ UTC与时区之间差异的时间段。 这可以在特定时刻针对特定ZoneId来解决,如清单7所示。
ZoneOffset offset = ZoneOffset.of("+2:00");
时区的类
- ZonedDateTime是具有完全合规时区的日期和时间(参见清单8)。 这可以在任何时间点解决偏移。 经验法则是,如果要在不依赖于特定服务器的上下文的情况下表示日期和时间,则应使用ZonedDateTime。
ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
- OffsetDateTime是具有已解析偏移的日期和时间。 这对于将数据序列化到数据库中非常有用,如果服务器位于不同的时区,也应该用作记录时间戳的序列化格式。
- OffsetTime是具有已解析偏移的时间,如清单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类都是不可变的,时区是可变的。
Periods
Period表示诸如“3个月和1天”之类的值,其是时间线上的距离。 这与我们到目前为止所看到的其他类相反,后者是时间轴上的点。 参见清单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));
Durations
Duration是时间线上按时间测量的距离,它实现与Period类似的目的,但具有不同的精度,如清单11所示。
// A duration of 3 seconds and 5 nanoseconds
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);
可以对Duration实例执行正常的加,减和“with”操作,还可以使用这个Duration修改日期或时间的值。
Chronologies
为了支持使用非ISO日历系统的开发人员的需求,Java SE 8引入了年表概念,该概念表示日历系统并充当日历系统中时间点的factory。 还有一些接口与核心时间点类对应,但由以下量参数化:
Chronology:
ChronoLocalDate
ChronoLocalDateTime
ChronoZonedDateTime
这些类纯粹适用于那些正在开发需要考虑本地日历系统的高度国际化应用程序的开发人员,并且没有这些要求的开发人员不应该使用它们。 一些日历系统甚至没有一个月或一周的概念,并且需要通过非常通用的字段API来执行计算。
API的其余部分
Java SE 8还有一些其他通用用例的类。 有MonthDay类,其包含一对Month和Day,表示生日非常有用。 YearMonth类涵盖信用卡开始日期和到期日期用例和情景,其中人们没有指定那一天的日期。
Java SE 8中的JDBC将支持这些新类型,但不会对公共JDBC API进行更改。 现有的泛型setObject和getObject方法就足够了。
These types can be mapped to vendor-specific database types or ANSI SQL types; for example, the ANSI mapping looks like Table 1.
这些类型可以映射到供应商特有的数据库类型或ANSI SQL类型; 例如,ANSI映射看起来像表1。
ANSI SQL | Java SE 8 |
DATE | LocalDate |
TIME | LocalTime |
TIMESTAMP | LocalDateTime |
TIME WITH TIMEZONE | OffsetTime |
TIMESTAMP WITH TIMEZONE | OffsetDateTime |
结论
Java SE 8将在java.time中附带一个新的日期和时间API,为开发人员提供极大改进的安全性和功能。 新的API很好地为域建模,并且有精心选择的类用于为各种开发人员用例建模。
上一篇: Java 学习总结(十六)
下一篇: Java SE IO流
推荐阅读
-
Java8利用stream的distinct()方法对list集合中的对象去重和抽取属性去重
-
Java日期时间API系列12-----Jdk8中java.time包中的新的日期时间API类,日期格式化,常用日期格式大全
-
Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析
-
Java日期时间API系列11-----Jdk8中java.time包中的新的日期时间API类,使用java8日期时间API重写农历LunarDate
-
Java日期时间API系列9-----Jdk8中java.time包中的新的日期时间API类的Period和Duration的区别
-
为什么不建议使用Date,而是使用Java8新的时间和日期API?
-
Java日期时间API系列30-----Jdk8中java.time包中的新的日期时间API类,减少时间精度方法性能比较和使用。
-
解决Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题
-
Java13-day04【Integer、int和String的相转、自动装箱和拆箱、Date、SimpleDateFormat、Calendar、异常、try...catch、throws】
-
面试又挂了,你理解了 Java 8 的 Consumer、Supplier、Predicate和Function吗?