Java8 时间 API

前言

Java8 中最为人津津乐道的新改变恐怕当属函数式 API 的加入。但实际上,Java8 所加入的新功能远不止这个。 本文将基于《Java SE8 for the Really Impatient》的第 5 章,归纳一下 Java8 加入的位于java.time包下的日期和时间 API。


时间点与时间间隔

在我们常说的四维空间体系中,时间轴往往作为除长宽高三轴以外的第四轴。时间轴由无穷多个时间点组成,而两个时间点之间的距离组成一个时间间隔。相较于我们常说的日期、时间,时间点本身所携带的信息是很少的,不会携带如时区等冗余的信息。作为时间轴上的一个点,我们可以将它称为绝对时间。

Java8 引入了 Instant 类(瞬时)来表示时间轴上的一个点。Instant 的构造方法是 private 的,我们只能通过调用它的静态工厂方法来产生一个 Instant 实例。其中最常用的是 Instant.now() 方法,返回当前的时间点。Instant 类也实现了 comparesTo 和 equals 方法来对比两个瞬时点。

通过调用 Duration.between() 方法我们便可以计算两个时间点之间的时间间隔:


1

2

3

4

5

6

7

8

Instant start = Instant.now();

runAlgorithm();

Instant end = Instant.now();

Duration timeElapsed = Duration.between(start, end);

long millis = timeElapsed.toMillis();

between 方法返回一个 Duration 实例。Duration 内部以 long 成员来存储时间间隔信息,最小单位可去到纳秒,同时提供了如 toMillistoSeconds 等方法。

Instant 和 Duration 类常用的方法包括如下:

方法 描述
plusminus 对当前 Instant 或 Duration 增加或减少一段时间
plusNanosplusMillisplusSeconds
plusMinutesplusHoursplusDays
根据指定的时间单位,对当前 Instant 或者 Duration 添加一段时间。
minusNanosminusMillisminusSeconds
minusMinutesminusHoursminusDays
根据指定的时间单位,对当前 Instant 或者 Duration 减少一段时间。
multipliedBydividedBynegated 返回当前 Duration 与指定 long 值相乘或相除得到的时间间隔
isZeroisNegative 检查 Duration 是否为0或负数

注意:Instant 类和 Duration 类都是不可变的,上述方法都会返回一个新的实例。


本地日期

在新的时间 API 中,Java 提供了两种时间格式:不带时区信息的本地时间和带时区的时间。本地日期表示一个日期,而本地时间还包含(一天中的)时间,但它们都不包含任何有关时区的信息。 例如,June 14, 1903 就是一个本地日期。由于日期不含一天中的时间,也不含时区信息,所以它无法与一个准确的瞬时点对应。相反,July 16, 1969, 09:32:00 EDT 就是一个带时区的时间, 它表示了时间轴上准确的一点。但有很多计算是不需要考虑时区的,在某些情况下考虑时区甚至可能导致错误的结果。出于此原因,API 设计者们更推荐使用不带时区的时间,除非你真的需要这个时区信息。

LocalDate就是一个不带时区的本地日期:它只带有年份、月份和当月的天数。你可以通过LocalDate的静态工厂方法nowof来创建一个实例:


1

2

LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14);

alonzosBirthday = LocalDate.of(1903, Month.JUNE, 14);

这里我们看到,静态工厂方法中指示月份的数字是以1开始的,因此6就代表着六月。如果你实在是太喜欢以0开始,无法接受这种设定,你也可以使用枚举类型Month来指定月份。

下表中列出了LocalDate对象的一些常用方法。详细的方法说明请参考LocalDate的 JavaDoc

方法 描述
nowof 静态工厂方法,可以根据当前时间或指定的年月日来创建一个LocalDate对象
plusDaysplusWeeks
plusMonthsplusYears
返回在当前LocalDate的基础上加上几天、几周、几个月或者几年后的新的LocalDate对象,原有的LocalDate对象保持不变
minusDaysminusWeeks
minusMonthsminusYears
返回在当前LocalDate的基础上减去几天、几周、几个月或者几年后的新的LocalDate对象,原有的LocalDate对象保持不变
plusminus 返回在当前LocalDate的基础上加上或减去一个Duration或者Period的新的LocalDate对象,原有的LocalDate对象保持不变
withDayOfMonthwithDayOfYear
withMonthwithYear
返回一个月份天数、年份天数、月份、年份修改为指定的值的新的LocalDate对象,原有的LocalDate对象保持不变
getDayOfMonth 获取月份天数(在 [1,31][1,31] 之间)
getDayOfYear 获取年份天数(在 [1,366][1,366] 之间)
getDayOfWeek 获取星期几(返回一个DayOfWeek枚举值)
getMonthgetMonthValue 获取月份,返回一个Month枚举的值,或者是 [1,12][1,12] 之间的一个数字
getYear 获取年份,在 [−999999999,999999999][−999999999,999999999] 之间
until 获取两个日期之间的Period对象,或者以指定ChronoUnits为单位的数值
isBeforeisAfter 比较两个LocalDate
isLeapYear 是否为闰年

注意:LocalDate 类是不可变的,上述方法都会返回一个新的实例。

在上一节中我们提到,两个瞬时点Instant之间的是一个持续时间Duration。对于本地时间,对应的对象就是时段Period, 它表示一段逝去的年月日。


本地时间

LocalTime代表一天中的某个时间,例如下午3点30分。 同样,你可以通过LocalTime的静态工厂方法nowof来创建一个实例。


1

2

LocalTime rightNow = LocalTime.now();

LocalTime bedtime= LocalTime.of(22, 30)

下表中列出了LocalTime对象的一些常用方法。详细的方法说明请参考LocalTime的 JavaDoc

方法 描述
nowof 静态工厂方法,可以根据当前时间或指定的时分秒来创建一个LocalTime对象
plusHoursplusMinutes
plusSecondsplusNanos
返回在当前LocalTime的基础上加上几小时、几分钟、几秒或者几纳秒后的新的LocalTime对象,原有的LocalTime对象保持不变
minusHoursminusMinutes
minusSecondsminusNanos
返回在当前LocalTime的基础上减去几小时、几分钟、几秒或者几纳秒后的新的LocalTime对象,原有的LocalTime对象保持不变
plusminus 返回在当前LocalTime的基础上加上或减去一个Duration的新的LocalTime对象,原有的LocalTime对象保持不变
withHourwithMinute
withSecondwithNano
返回一个小时数、分钟数、秒数、纳秒数修改为指定的值的新的LocalTime对象,原有的LocalTime对象保持不变
getHourgetMinute
getSecondgetNano
返回该LocalTime的小时、分钟、秒钟及纳秒值
isBeforeisAfter 比较两个LocalTime

注意:LocalTime 类是不可变的,上述方法都会返回一个新的实例。

LocalDateTime类则可看作是LocalDateLocalTime的结合。它用于存储本地时区中的某个时间点,包含当前的年月日等日期信息, 同时也包含了时钟、分钟、秒钟等时间信息。同样,LocalDateTime也是不可变的。

详细的方法说明请参考LocalDateTime的 JavaDoc


带时区的时间

Java8 的时间 API 当然也加入了对时区的支持。分别对应着LocalDateLocalTimeLocalDateTime,带时区的时间类为ZonedDate、 ZonedTimeZonedDateTime

Java 中的时区信息来自于 IANA(Internet Assigned Numbers Authority)的数据库,其中每个时区都有着对应的 ID,例如America/New_York或者Europe/Berlin。 调用ZoneId.getAvailableIds方法即可获取所有可用的时区信息。

你还可以使用ZoneId.of(id)方法,用指定的时区 ID 来获取对应的ZoneId对象。通过调用local.atZone(zoneId)方法, 你可以将一个LocalDateTime转换成一个ZonedDateTime对象,或者通过调用静态方法ZonedDateTime.of来创建一个对象。

ZonedDateTime的许多方法都与LocalDateTime一致。下表中列出了ZonedDateTime特有的常用方法,详细的方法说明请参考ZonedDateTime的 JavaDoc

方法 描述
nowofofInstant 根据当前时间或指定的年月日时分秒、纳秒和ZoneId,或者一个Instant和一个ZoneId来创建一个ZonedDateTime对象
withZoneSameInstantwithZoneSameLocal 返回时区失去中的一个新的ZonedDateTime对象,它表示相同的瞬时点或本地时间
getOffset 获得与 UTC 之间的时差,返回一个ZoneOffset对象
toLocalDatetoLocalTimetoInstant 返回对应的本地日期、本地时间或瞬时点

除此之外,Java8 还提供了一个OffsetDateTime类,用来表示带有(与 UTC 相比的)偏移量的时间。这个类专门用于一些不需要时区规则的业务场景, 比如某些网络协议。对于人类可读的时间,ZonedDateTime是更好的选择。

详情请查阅OffsetDateTime的 JavaDoc


日期校正器

有些时候,我们可以能需要得到类似“每月的第一个星期二”这样的日期。Java8 提供了TemporalAdjuster接口,用以实现自定义的日期校正逻辑。 通过将创建好的TemporalAdjuster传递给日期时间类的with方法便可在原有日期时间对象的基础上产生出一个符合要求的日期时间。 例如,你可以通过如下代码来计算下一个星期二:


1

2

3

4

5

6

7

8

9

10

11

TemporalAdjuster NEXT_TUESDAY = (Temporal temporal) -> {

    int dowValue = DayOfWeek.TUESDAY.getValue();

    int calDow = temporal.get(ChronoField.DAY_OF_WEEK);

    if (calDow == dowValue) {

        return temporal;

    }

    int daysDiff = calDow - dowValue;

    return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS);

};

LocalDate nextTuesDay = today.with(NEXT_TUESDAY);

此处利用 Lambda 表达式快速实现了一个匿名的TemporalAdjuster对象。注意 Lambda 表达式的参数类型为 Temporal, 某些LocalDate或者LocalDateTime之类的类特有的方法将不可用,在使用前必须进行强制转换。 你可以通过ofDateAdjuster方法和一个UnaryOperator<LocalDate>来避免强制转换:


1

2

3

4

5

6

7

8

9

10

11

12

TermporalAdjuster NEXT_WORKDAY = TemporalAdjusters.ofDateAdjuster((LocalDate w) -> {

    LocalDate result;

    DayOfWeek dow = w.getDayOfWeek();

    if (dow == DayOfWeek.FRIDAY)

        result = w.plusDays(3);

    else if (dow == DayOfWeek.SATURDAY)

        result = w.plusDays(2);

    else

        result = w.plusDays(1);

    return result;

});

上述代码中使用的ofDateAdjuster方法来自类TemporalAdjusters。实际上这个类通过静态方法提供了大量的常用TemporalAdjuster实现。 比如,我们也可以通过如下代码来计算下一个星期二:


1

2

3

LocalDate nextTuesDay = LocalDate.now().with(

  TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)

);

下表中列出了TemporalAdjusters的一些常用方法。详细的方法说明请参考TemporalAdjustersJavaDoc

方法 描述
previous(dayOfWeek)next(dayOfWeek) 返回被校正日期之后或之前最近的指定星期几
previoursOrSame(dayOfWeek)nextOrSame(dayOfWeek) 返回从被校正日期开始,之前或之后的指定星期几。如果被校正日期已吻合条件,被校正的日期实例将被直接返回
dayOfWeekInMonth(n, dayOfWeek) 返回该月中指定的第几个星期几
firstInMonth(dayOfWeek)lastInMonth(dayOfWeek) 返回该月第一个或最后一个星期几
firstDayOfMonth()firstDayOfNextMonth()
firstDayOfNextYear()lastDayOfMonth()
lastDayOfPreviousMonth()lastDayOfYear()
返回方法名所描述的日期

格式化和解析

除了日期校正,日期与字符串之间的相互转换也是十分常见的操作。对于原有的java.util.Date等类,我们使用java.text.DateFormat来对日期进行格式化和解析。 对于 Java8 新引入的日期时间类,我们使用java.time.format.DateTimeFormatter类。

DateTimeFormatter类提供了三种格式化方法来打印日期时间:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式

下表中列出了所有预定义的DateTimeFormatter。详细说明可参考DateTimeFormatter的 JavaDoc

格式 示例
BASIC_ISO_DATE 20111203
ISO_LOCAL_DATE
ISO_LOCAL_TIME
ISO_LOCAL_DATE_TIME
2011-12-03
10:15:30
2011-12-03T10:15:30
ISO_OFFSET_DATE
ISO_OFFSET_TIME
ISO_OFFSET_DATE_TIME
2011-12-03+01:00
10:15:30+01:00
2011-12-03T10:15:30+01:00
ISO_ZONED_DATE_TIME 2011-12-03T10:15:30+01:00[Europe/Paris]
ISO_INSTANT 2011-12-03T10:15:30Z
ISO_ORDINAL_DATE 2012-337
ISO_WEEK_DATE 2012-W48-6
ISO_DATE
ISO_TIME
ISO_DATE_TIME
2011-12-03+01:002011-12-03
10:15:30+01:0010:15:30
2011-12-03T10:15:30+01:00[Europe/Paris]
RFC_1123_DATE_TIME Tue, 3 Jun 2008 11:05:30 GMT

通过调用DateTimeFormatter类的format方法即可对日期进行格式化:


1

2

String formatted = DateTimeFormatter.ISO_DATE_TIME.format(apollolllaunch);

  // 1969-07-16T09:32:00-0500[America/New_York]

标准格式主要用于机器可读的时间戳。为了产生人类可读的日期和时间,你需要使用语言环境相关的格式。 下表中列出了 Java8 提供的 4 种风格:

风格 日期 时间
SHORT 7/16/69 9:32 AM
MEDIUM Jul 16, 1969 9:32:00 AM
LONG July 16, 1969 9:32:00 AM EDT
FULL Wednesday, July 16, 1969 9:32:00 AM EDT

你可以通过静态方法ofLocalizedDateofLocalizedTimeofLocalizedDateTime来创建这些格式:


1

2

3

4

5

DateTimeFormatter formatter =

    DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);

String formatted = formatter.format(apollolllaunch);

    // July 16, 1969 9:32:00 AM EDT

这些方法使用的都是默认的语言环境。通过使用withLocale方法可以更改为其他语言环境:


1

2

String formatted = formatter.withLocale(Locale.FRENCH).format(apollolllaunch);

    // 16 juillet 1969 09:32:00 EDT

你可以通过调用formatter.toFormat()方法来获取一个等效的java.util.DateFormat对象。

最后,你可以通过指定的模式来自定义日期的格式。例如:


1

formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");

其中不同的符号对应着不同的含义。下表中列出了不同符号的具体含义和实例,详情可查阅DateTimeFormatter的 JavaDoc

含义 符号 示例
纪元 G
GGGG
GGGGG
AD
Anno Domini
A
年份 yy
yyyy
69
1969
月份 M
MM
MMM
MMMM
MMMMM
7
07
Jul
July
J
日份 d
dd
6
06
星期几 e
E
EEEE
EEEEE
3
Wed
Wednesday
W
24小时制时钟([0,23][0,23]) H
HH
9
09
12小时制时钟([0,11][0,11]) K
KK
9
09
AM/PM a AM
分钟 mm 02
秒钟 ss 00
时区 ID VV America/New_York
时区名称 z
zzzz
EDT
Eastern Daylight Time
时差 x
xx
xxx
XXX
-04
-0400
-04:00
-Z4:ZZ
本地化的时差 O
OOOO
GMT-4
GMT-04:00

要从一个字符串中解析出日期时间,可以使用静态方法parse的各个重载方法。例如:


1

2

3

4

LocalDate churchsBirthday = LocalDate.parse("1903-06-14");

ZonedDateTime apollolllaunch =

    ZonedDateTime.parse("1969-07-16 03:32:00-0400",

                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"));


与遗留代码互操作

尽管使用全新的 API 可以获得更好的开发体验,但兼容遗留代码总是不可避免的。因此,熟知新的日期时间类和旧的日期时间类之间的转换方法也是我们必须学习的。

总体来讲,转换规则可以归纳为下表:

To 遗留类 From 遗留类
java.time.Instant
java.util.Date
Date.from(instant) date.toInstant()
java.time.Instant
java.sql.Timestamp
Timestamp.from(instant) timestamp.toInstant()
java.time.Instant
java.nio.file.attribute.FileTime
FileTime.from(instant) fileTime.toInstant()
java.time.ZonedDateTime
java.util.GregorianCalendar
GregorianCalendar.from(zonedDateTime) cal.toZonedDateTime()
java.time.LocalDate
java.sql.Time
Date.valueOf(localDate) date.toLocalDate()
java.time.LocalTime
java.sql.Time
Date.valueOf(localDate) date.toLocalTime()
java.time.LocalDateTime
java.sql.Timestamp
Timestamp.valueOf(localDateTime) timestamp.toLocalDateTime()
java.time.ZoneId
java.util.TimeZone
Timezone.getTimeZone(id) timeZone.toZoneId()
java.time.format.DateTimeFormatter
java.text.DateFormat
formatter.toFormat()
时间: 2024-10-27 12:29:46

Java8 时间 API的相关文章

java8 时间使用

为什么需要新的时间API 文章来源:https://www.cnblogs.com/guozp/p/10342775.html 在Java 8之前的日期/时间API之前,现有的与日期和时间相关的类存在诸多问题,其中主要有: Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义 java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理.另外

Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析

目录 0.前言 1.TemporalAccessor源码 2.Temporal源码 3.TemporalAdjuster源码 4.ChronoLocalDate源码 5.LocalDate源码 6.总结 0.前言 通过前面Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类中主要的类关系简图如下: 可以看出主要的LocalDate, LocalTime, LocalDateTime, Instant都是实现相同的接口,这里以LocalDate为例分析jav

Java8 日期时间API

使用Java8,新的日期时间API引入覆盖旧的日期时间API的以下缺点. 非线程安全 - java.util.Date不是线程安全的,因此开发者必须在使用日期处理并发性问题.新的日期时间API是不可变的,并且没有setter方法. 设计不佳 - 默认的开始日期从1900年,开始每月从1天从0开始,所以没有统一.不直接使用方法操作日期.新的API提供了这样操作实用方法. 困难的时区处理 - 开发人员必须编写大量的代码来处理时区的问题.新的API设计开发保持特定领域设计. JAVA8引入了java.

20191227 Java8 日期时间API

背景 Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有: 非线程安全 ? java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一. 设计很差 ? Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义.java.util.Date同时包含日期

Java日期时间API系列11-----Jdk8中java.time包中的新的日期时间API类,使用java8日期时间API重写农历LunarDate

通过Java日期时间API系列7-----Jdk8中java.time包中的新的日期时间API类的优点,java8具有很多优点,现在网上查到的农历转换工具类都是基于jdk7及以前的类写的,下面使用java新的日期时间API重写农历LunarDate. package com.xkzhangsan.time; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import ja

Java 8的日期和时间API

本节继续探讨Java 8的新特性,主要是介绍Java 8对日期和时间API的增强,关于日期和时间,我们在之前已经介绍过两节了,32节介绍了Java 1.8以前的日期和时间API,主要的类是Date和Calendar,由于它的设计有一些不足,业界广泛使用的是一个第三方的类库Joda-Time,关于Joda-time,我们在33节进行了介绍.Java 1.8学习了Joda-time,引入了一套新的API,位于包java.time下,本节,我们就来简要介绍这套新的API. 我们先从日期和时间的表示开始

计算机程序的思维逻辑 (95) - Java 8的日期和时间API

?本节继续探讨Java 8的新特性,主要是介绍Java 8对日期和时间API的增强,关于日期和时间,我们在之前已经介绍过两节了,32节介绍了Java 1.8以前的日期和时间API,主要的类是Date和Calendar,由于它的设计有一些不足,业界广泛使用的是一个第三方的类库Joda-Time,关于Joda-time,我们在33节进行了介绍.Java 1.8学习了Joda-time,引入了一套新的API,位于包java.time下,本节,我们就来简要介绍这套新的API. 我们先从日期和时间的表示开

Java 8 新特性:Java 类库的新特性之日期时间API (Date/Time API ) ——诺诺&quot;涂鸦&quot;记忆

----------   诺诺学习技术交流博客.期待与您交流!    ---------- 详情请查看:http://blog.csdn.net/sun_promise  日期时间API (Date/Time API ) 1.Java8之前java.util.Date和Calendar类的弊端 1)最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不遵守单一职责). 后来从JDK 1.1 开始,这三项职责分开了: 使用Calendar类实现日期和

Java 8 日期时间API使用介绍

如何正确处理时间 现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离.所以我们要表示时间, 就需要人为定义一个原点. 原点被规定为,格林威治时间(GMT)1970年1月1日的午夜 为起点,之于为啥是GMT时间,大概是因为本初子午线在那的原因吧. Java中的时间 如果你跟你朋友说:"我们 1484301456 一起去吃饭,别迟到!",而你朋友能马上理解你说的时间,表示时间