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
成员来存储时间间隔信息,最小单位可去到纳秒,同时提供了如 toMillis
、toSeconds
等方法。
Instant
和 Duration
类常用的方法包括如下:
方法 | 描述 |
---|---|
plus 、minus |
对当前 Instant 或 Duration 增加或减少一段时间 |
plusNanos 、plusMillis 、plusSeconds 、plusMinutes 、plusHours 、plusDays |
根据指定的时间单位,对当前 Instant 或者 Duration 添加一段时间。 |
minusNanos 、minusMillis 、minusSeconds 、minusMinutes 、minusHours 、minusDays |
根据指定的时间单位,对当前 Instant 或者 Duration 减少一段时间。 |
multipliedBy 、dividedBy 、negated |
返回当前 Duration 与指定 long 值相乘或相除得到的时间间隔 |
isZero 、isNegative |
检查 Duration 是否为0或负数 |
注意:Instant
类和 Duration
类都是不可变的,上述方法都会返回一个新的实例。
本地日期
在新的时间 API 中,Java 提供了两种时间格式:不带时区信息的本地时间和带时区的时间。本地日期表示一个日期,而本地时间还包含(一天中的)时间,但它们都不包含任何有关时区的信息。 例如,June 14, 1903 就是一个本地日期。由于日期不含一天中的时间,也不含时区信息,所以它无法与一个准确的瞬时点对应。相反,July 16, 1969, 09:32:00 EDT 就是一个带时区的时间, 它表示了时间轴上准确的一点。但有很多计算是不需要考虑时区的,在某些情况下考虑时区甚至可能导致错误的结果。出于此原因,API 设计者们更推荐使用不带时区的时间,除非你真的需要这个时区信息。
LocalDate
就是一个不带时区的本地日期:它只带有年份、月份和当月的天数。你可以通过LocalDate
的静态工厂方法now
或of
来创建一个实例:
1
2
|
LocalDate alonzosBirthday = LocalDate.of( 1903 , 6 , 14 ); alonzosBirthday = LocalDate.of( 1903 , Month.JUNE, 14 ); |
这里我们看到,静态工厂方法中指示月份的数字是以1开始的,因此6就代表着六月。如果你实在是太喜欢以0开始,无法接受这种设定,你也可以使用枚举类型Month
来指定月份。
下表中列出了LocalDate
对象的一些常用方法。详细的方法说明请参考LocalDate
的 JavaDoc。
方法 | 描述 |
---|---|
now 、of |
静态工厂方法,可以根据当前时间或指定的年月日来创建一个LocalDate 对象 |
plusDays 、plusWeeks 、plusMonths 、plusYears |
返回在当前LocalDate 的基础上加上几天、几周、几个月或者几年后的新的LocalDate 对象,原有的LocalDate 对象保持不变 |
minusDays 、minusWeeks 、minusMonths 、minusYears |
返回在当前LocalDate 的基础上减去几天、几周、几个月或者几年后的新的LocalDate 对象,原有的LocalDate 对象保持不变 |
plus 、minus |
返回在当前LocalDate 的基础上加上或减去一个Duration 或者Period 的新的LocalDate 对象,原有的LocalDate 对象保持不变 |
withDayOfMonth 、withDayOfYear 、withMonth 、withYear |
返回一个月份天数、年份天数、月份、年份修改为指定的值的新的LocalDate 对象,原有的LocalDate 对象保持不变 |
getDayOfMonth |
获取月份天数(在 [1,31][1,31] 之间) |
getDayOfYear |
获取年份天数(在 [1,366][1,366] 之间) |
getDayOfWeek |
获取星期几(返回一个DayOfWeek 枚举值) |
getMonth 、getMonthValue |
获取月份,返回一个Month 枚举的值,或者是 [1,12][1,12] 之间的一个数字 |
getYear |
获取年份,在 [−999999999,999999999][−999999999,999999999] 之间 |
until |
获取两个日期之间的Period 对象,或者以指定ChronoUnits 为单位的数值 |
isBefore 、isAfter |
比较两个LocalDate |
isLeapYear |
是否为闰年 |
注意:LocalDate
类是不可变的,上述方法都会返回一个新的实例。
在上一节中我们提到,两个瞬时点Instant
之间的是一个持续时间Duration
。对于本地时间,对应的对象就是时段Period
, 它表示一段逝去的年月日。
本地时间
LocalTime
代表一天中的某个时间,例如下午3点30分。 同样,你可以通过LocalTime
的静态工厂方法now
和of
来创建一个实例。
1
2
|
LocalTime rightNow = LocalTime.now(); LocalTime bedtime= LocalTime.of( 22 , 30 ) |
下表中列出了LocalTime
对象的一些常用方法。详细的方法说明请参考LocalTime
的 JavaDoc。
方法 | 描述 |
---|---|
now 、of |
静态工厂方法,可以根据当前时间或指定的时分秒来创建一个LocalTime 对象 |
plusHours 、plusMinutes 、plusSeconds 、plusNanos |
返回在当前LocalTime 的基础上加上几小时、几分钟、几秒或者几纳秒后的新的LocalTime 对象,原有的LocalTime 对象保持不变 |
minusHours 、minusMinutes 、minusSeconds 、minusNanos |
返回在当前LocalTime 的基础上减去几小时、几分钟、几秒或者几纳秒后的新的LocalTime 对象,原有的LocalTime 对象保持不变 |
plus 、minus |
返回在当前LocalTime 的基础上加上或减去一个Duration 的新的LocalTime 对象,原有的LocalTime 对象保持不变 |
withHour 、withMinute 、withSecond 、withNano |
返回一个小时数、分钟数、秒数、纳秒数修改为指定的值的新的LocalTime 对象,原有的LocalTime 对象保持不变 |
getHour 、getMinute 、getSecond 、getNano |
返回该LocalTime 的小时、分钟、秒钟及纳秒值 |
isBefore 、isAfter |
比较两个LocalTime |
注意:LocalTime
类是不可变的,上述方法都会返回一个新的实例。
LocalDateTime
类则可看作是LocalDate
和LocalTime
的结合。它用于存储本地时区中的某个时间点,包含当前的年月日等日期信息, 同时也包含了时钟、分钟、秒钟等时间信息。同样,LocalDateTime
也是不可变的。
详细的方法说明请参考LocalDateTime
的 JavaDoc。
带时区的时间
Java8 的时间 API 当然也加入了对时区的支持。分别对应着LocalDate
、LocalTime
和LocalDateTime
,带时区的时间类为ZonedDate
、 ZonedTime
、ZonedDateTime
。
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。
方法 | 描述 |
---|---|
now 、of 、ofInstant |
根据当前时间或指定的年月日时分秒、纳秒和ZoneId ,或者一个Instant 和一个ZoneId 来创建一个ZonedDateTime 对象 |
withZoneSameInstant 、withZoneSameLocal |
返回时区失去中的一个新的ZonedDateTime 对象,它表示相同的瞬时点或本地时间 |
getOffset |
获得与 UTC 之间的时差,返回一个ZoneOffset 对象 |
toLocalDate 、toLocalTime 、toInstant |
返回对应的本地日期、本地时间或瞬时点 |
除此之外,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
的一些常用方法。详细的方法说明请参考TemporalAdjusters
的JavaDoc。
方法 | 描述 |
---|---|
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:00 ; 2011-12-03 10:15:30+01:00 ; 10: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 |
你可以通过静态方法ofLocalizedDate
、ofLocalizedTime
和ofLocalizedDateTime
来创建这些格式:
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() |
无 |
Java8 时间 API的更多相关文章
- Java8新特性--日期和时间API
如何正确处理时间 现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离.所以我们要表示 ...
- java8 时间使用
为什么需要新的时间API 文章来源:https://www.cnblogs.com/guozp/p/10342775.html 在Java 8之前的日期/时间API之前,现有的与日期和时间相关的类存在 ...
- Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析
目录 0.前言 1.TemporalAccessor源码 2.Temporal源码 3.TemporalAdjuster源码 4.ChronoLocalDate源码 5.LocalDate源码 6.总 ...
- Java8系列 (六) 新的日期和时间API
概述 在Java8之前, 我们一般都是使用 SimpleDateFormat 来解析和格式化日期时间, 但它是线程不安全的. @Test public void test() { SimpleDate ...
- 都9012了,Java8中的日期时间API你还没有掌握?
一,Java8日期时间API产生的前因后果 1.1 为什么要重新定义一套日期时间API 操作不方便:java中最初的Date不能直接对指定字段进行加减操作也不支持国际化,后来新增了Calendar,但 ...
- 详解Java8的日期和时间API
详解Java8的日期和时间API 在JDK1.0的时候,Java引入了java.util.Date来处理日期和时间:在JDK1.1的时候又引入了功能更强大的java.util.Calendar,但是C ...
- Java日期时间API系列11-----Jdk8中java.time包中的新的日期时间API类,使用java8日期时间API重写农历LunarDate
通过Java日期时间API系列7-----Jdk8中java.time包中的新的日期时间API类的优点,java8具有很多优点,现在网上查到的农历转换工具类都是基于jdk7及以前的类写的,下面使用ja ...
- 【Java8新特性】关于Java8中的日期时间API,你需要掌握这些!!
写在前面 Java8之前的日期和时间API,存在一些问题,比如:线程安全的问题,跨年的问题等等.这些问题都在Hava8中的日期和时间API中得到了解决,而且Java8中的日期和时间API更加强大.立志 ...
- 详解Java8特性之新的日期时间 API
详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...
随机推荐
- AndroidStudio SDK版本下载
错误描述: pkg: /data/local/tmp/com.example.myapplication Failure [INSTALL_FAILED_OLDER_SDK] 出现这个错误,研究了半天 ...
- 13、SQL Server 自定义函数
SQL Server 自定义函数 在SQL Server中不仅可以使用系统函数(如:聚合函数,字符串函数,时间日期函数等)还可以根据需要自定义函数. 自定义函数分为标量值函数和表值函数. 其中,标量值 ...
- 简易google地图api调用
代码如下: <!DOCTYPE html> <html> <head> <meta name="viewport" content=&qu ...
- 使用WMI控制Windows进程 和服务
1.使用WMI控制Windows进程 本文主要介绍两种WMI的进行操作:检查进程是否存在.创建新进行 代码如下: using System; using System.Collections.Gene ...
- Android之使用json进行网络数据交换
JSON作为一种轻量级的数据交换格式,凭借其易于阅读和编写.易于解析.传输速度快等优点流行了起来.最近正好在学习Android端从服务端端取数据,Json便派上了用场.好,下面开始切入主题. 1.准备 ...
- centos5.2 x86 安装 oracle 11g2r 日志
一.安装centos 二.安装ora所需的库 三.修改centos内核 四.建用户组和目录结构等 五.安装ora11g2r 六.安装sqlplus的翻页程序和help补丁 七.自启动脚本 八.常用命令 ...
- Spring mvc 中有关 Shiro 1.2.3 配置问题
Spring 版本:3.2.x, 4.0.x [问题说明] 首先介绍下配置出错情况: (1)项目中,Spring3 and Spring4 的 applicationContext.xml aop ...
- (转载)用css来实现十字的布局
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- canvas 渐变
那么第一种渐变方式就是LinearGradient,具体实施就是以下代码: var colorStyle=context.createLinearGradient(0,0,0,HEIGHT); col ...
- 【leetcode】1. Two Sums
题目 https://leetcode.com/problems/two-sum/ Given an array of integers, find two numbers such that t ...