JDK8之前的日期api有很多不便的地方, 如:
java.util.Date
和java.util.Calendar
类易用性差, 不支持时区, 而且他们都不是线程安全的- 用于格式化日期的类DateFormat被放在java.text包中, 它是一个抽象类, 在处理日期格式化时我们会实例化一个
SimpleDateFormat
对象, 但DateFormat也是非线程安全 - 对日期的计算方式繁琐, 而且容易出错, 因为月份是从0开始的, 从Calendar中获取的月份需要加一才能表示当前月份
由于以上这些问题, 出现了一些三方的日期处理框架, 例如Joda-Time, date4j等开源项目. 其中Joda-Time框架的作者正是JSR-310的规范的倡导者, 而Java 8中引入了新的日期API是JSR-310规范的实现, 所以能从Java 8的日期API中看到很多Joda-Time的特性.java.time
包下有5个包组成, 大部分情况下只用基础包和format就够了
java.time
: 包含值对象的基础包java.time.chrono
: 提供对不同的日历系统的访问java.time.format
: 格式化和解析时间和日期java.time.temporal
: 包括底层框架和扩展特性java.time.zone
: 包含时区支持的类
所有类都是不可变的, 线程安全的
日期/时间类
这些类的方法具有统一的前缀, 其含义如下:
前缀 | 含义 | 示例 |
---|---|---|
now | 静态工厂方法, 用当前时间创建实例 | LocalDate.now(); |
of | 静态工厂方法 | LocalDate.of(2018, 12, 20); |
parse | 静态工厂方法, 关注于解析 | LocalDate.parse("2018-12-20"); |
get | 获取某个字段的值 | localDate.getYear(); |
is | 比对判断 | localDate.isAfter(LocalDate.now()); |
with | 基于当前实例创建新的实例, 但部分字段被更新 | localDate.withMonth(3); |
plus | 在当前实例基础上增加(值可负), 返回新实例 | localDate.plusDays(1); |
minus | 在当前实例基础上减小(值可负), 返回新实例 | localDate.minusDays(1); |
to | 基于当前实例转换出另一个类型的实例 | localDateTime.toLocalDate(); |
at | 把当前对象和另一个对象结合, 生成新的类型的实例 | localDate.atTime(21, 30, 50) |
format | 格式化 | localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); |
LocalDate和LocalTime
LocalDate
表示日期(年月日), 但不包含时间(时分秒), 也不包时区信息, 内部使用3个属性(int: year, short: month, short: day)
记录时间信息.
1 | /* 创建 */ |
LocalTime
与LocalDate
类似, 大部分接口名都相同, 包含纳秒信息, 不包时区信息, 内部使用(byte: hour, byte: minute, byte: second, int: nano)
记录时间信息
1 | LocalTime localTime = LocalTime.now(); |
LocalDateTime
LocalDateTime
内部用两个属性: LocalDate
和LocalTime
记录时间信息, 不包含时区信息
1 | /* 创建 */ |
Instant
Instant
表示一个UTC时间戳(从1970-01-01T00:00:00Z开始),不包含时区信息 , 相比System.currentTimeMillis()
, 它可以精确到纳秒. 其内部有两个常量, seconds
表示从1970-01-01 00:00:00开始到现在的秒数, nanos
表示纳秒部分(不超过999,999,999). 可以替换Date
使用
1 | /* 创建 */ |
Duration
Duration
用来表示一小段时间(时分秒), 其内部和Instant
类似也有seconds
和nanos
1 | /* 创建 */ |
Period
用来表示较长的时间(年月日)
1 | /* 创建 */ |
日期调整与格式化
加减调整
jdk8中时间日期对象都是不可变的, 因此在调整时, 总是会返回新的实例. 调整方法主要有plus
, minus
和 with
1 | LocalDate date = LocalDate.of(2018, 12, 5); // 2018-12-05 |
在进行复杂的操作时, 比如下一个工作日, 下个月的第一天时, 可以使用with
的重载方法, 接受一个TemporalAdjuster
参数.
1 | // 返回下一个距离当前时间最近的星期日 |
java.time.temporal.ChronoField枚举类
此枚举类是作为get方法的参数获取时间的某个字段(年/月/日/时/分/秒…)值, 它里面的属性含义有的跟Calendar的成员变量含义差不多
1 | localDate.with(ChronoField.DAY_OF_WEEK, 4); |
java.time.temporal.ChronoUnit枚举类
此枚举表示时间单位
1 | localDate.minus(1, ChronoUnit.DAYS); |
java.time.temporal.TemporalAdjusters类
TemporalAdjusters
主要配合时间日期的with
方法使用, 用来对时间日期进行调整, 中有很多静态方法可以使用
方法名 | 描述 |
---|---|
dayOfWeekInMonth |
返回同一个月中每周的第几天 |
firstDayOfMonth |
返回当月的第一天 |
firstDayOfNextMonth |
返回下月的第一天 |
firstDayOfNextYear |
返回下一年的第一天 |
firstDayOfYear |
返回本年的第一天 |
firstInMonth |
返回同一个月中第一个星期几 |
lastDayOfMonth |
返回当月的最后一天 |
lastDayOfNextMonth |
返回下月的最后一天 |
lastDayOfNextYear |
返回下一年的最后一天 |
lastDayOfYear |
返回本年的最后一天 |
lastInMonth |
返回同一个月中最后一个星期几 |
next / previous |
返回后一个/前一个给定的星期几 |
nextOrSame / previousOrSame |
返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |
还可以通过创建自定义的TemporalAdjuster
实现来实现更复杂的逻辑, TemporalAdjuster
是函数式接口, 所有可以使用Lambda表达式. 比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):
1 | LocalDate date = LocalDate.of(2017, 1, 5); |
格式化
JDK8中提供了一个新的类java.time.format.DateTimeFormatter
来处理格式化操作. 日期类中有一个format
方法, 该方法接收一个DateTimeFormatter
类型的参数.
1 | LocalDateTime dateTime = LocalDateTime.now(); |
其中DateTimeFormatter
中预定义了许多格式
变量 | 输出示例 |
---|---|
BASIC_ISO_DATE | 20111203 |
ISO_LOCAL_DATE | 2011-12-03 |
ISO_OFFSET_DATE | 2011-12-03+08:00 |
ISO_DATE | 2011-12-03 或 2011-12-03+08:00 |
ISO_LOCAL_TIME | 10:15 或 10:15:30 |
ISO_OFFSET_TIME | 10:15+01:00 或 10:15:30+08:00 |
ISO_TIME | 10:15, 10:15:30 或 10:15:30+08:00 |
ISO_LOCAL_DATE_TIME | 2011-12-03T10:15:30 |
ISO_OFFSET_DATE_TIME | 2011-12-03T10:15:30+08:00 |
ISO_ZONED_DATE_TIME | 2011-12-03T10:15:30+08:00[Asia/Shanghai] |
ISO_DATE_TIME | 2011-12-03T10:15:30, 2011-12-03T10:15:30+08:00 或 2011-12-03T10:15:30+08:00[Asia/Shanghai] |
ISO_ORDINAL_DATE | 2012-337 |
ISO_WEEK_DATE | 2012-W48-6 |
ISO_INSTANT | 2011-12-03T10:15:30Z |
RFC_1123_DATE_TIME | Tue, 3 Jun 2008 11:05:30 GMT |
还可以自定义格式
1 | DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("HH.mm.ss") |
模板字段含义如下
G 年代标志符
y 年
M 月
d 日
h 时 (12小时制)
H 时 (24小时制)
m 分
s 秒
S 毫秒
E 星期几
D 一年中的第几天
F 一月中第几个星期(以每个月1号为第一周,8号为第二周为标准计算)
w 一年中第几个星期
W 一月中第几个星期(不同于F的计算标准,是以星期为标准计算星期数,例如1号是星期三,是当月的第一周,那么5号为星期日就已经是当月的第二周了)
a 上午 / 下午 标记符
k 时 (24小时制,其值与H的不同点在于,当数值小于10时,前面不会有0)
K 时 (12小时值,其值与h的不同点在于,当数值小于10时,前面不会有0)
z 时区
时区
jdk8中使用新的时区类java.time.ZoneId
来替代原来的java.util.TimeZone
, 对应的时间类是ZonedDateTime
. 使用方式如下:
1 | // 创建 |
另一种表示时区的方式是使用ZoneOffset
, 它是以当前时间和世界标准时间(UTC)/格林威治时间(GMT)的偏差来表示, 对应的类是OffsetDateTime
.
1 | ZoneOffset zoneOffset = ZoneOffset.of("+08:00"); |
其他历法
Java中使用的历法是ISO 8601日历系统, 它是世界民用历法, 也就是我们所说的公历. 平年有365天, 闰年是366天. 闰年的定义是: 非世纪年, 能被4整除; 世纪年能被400整除. 为了计算的一致性, 公元1年的前一年被当做公元0年, 以此类推.
此外Java 8还提供了4套其他历法(但是没有农历), 每套历法都包含一个日期类, 分别是:
ThaiBuddhistDate
:泰国佛教历MinguoDate
:中华民国历JapaneseDate
:日本历HijrahDate
:伊斯兰历
它们都继承了ChronoLocalDate
类, 但开发中要避免直接使用ChronoLocalDate
类.1
2MinguoDate minguoDate = MinguoDate.from(LocalDate.now());
LocalDate localDate = LocalDate.from(MinguoDate.now());