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
表示日期(年月日), LocalTime
表示时间(时分秒纳秒), 全都不包时区信息
创建实例
now
方法1
2
3
4
5
6
7
8
9
10
11LocalDate.now();
LocalDate.now(Clock.systemDefaultZone()); // 系统默认时区
LocalDate.now(ZoneId.systemDefault()); // 系统默认时区
LocalDate.now(ZoneId.of("UTC-10")); // 夏威夷: 2020-04-21
LocalDate.now(ZoneOffset.ofHours(8)); // +8
LocalTime.now();
LocalTime.now(Clock.systemDefaultZone());
LocalTime.now(ZoneId.systemDefault());
LocalTime.now(ZoneId.of("UTC+8"));
LocalTime.now(ZoneOffset.ofHours(8));of
方法1
2
3
4
5
6
7
8
9LocalDate.of(2021, 10, 31); // 年月日
LocalDate.ofYearDay(2021, 100); // 2021-04-10, 某一年的某一天
LocalDate.ofEpochDay(0); // 1970-01-01, 以1970-01-01为起点, 单位: 天
LocalTime.of(17, 45); // 时 分
LocalTime.of(17, 45, 59); // 时 分 秒
LocalTime.of(17, 45, 59, 999_999_999); // 时 分 秒 纳秒 0 ~ 999,999,999
LocalTime.ofSecondOfDay(43200); // 12:00 以当天的秒数
LocalTime.ofNanoOfDay(43200000000000L); // 12:00 以当天的纳秒时间戳 都需要先转成
Instant
1
2
3ZonedDateTime zone = Instant.ofEpochMilli(1625390224369L).atZone(ZoneId.systemDefault());
zone.toLocalDate();
zone.toLocalTime();Date
类1
2
3
4
5
6
7
8ZonedDateTime zone = new Date().toInstant().atZone(ZoneId.systemDefault());
zone.toLocalDate();
zone.toLocalTime();
// 也可以通过先转成时间戳再转换
ZonedDateTime zone = Instant.ofEpochMilli(new Date().getTime()).atZone(ZoneId.systemDefault());
zone.toLocalDate();
zone.toLocalTime();字符串
1
2
3
4
5LocalDate.parse("2007-12-03") // 2007-12-03
LocalDate.parse("2007/12/03", DateTimeFormatter.ofPattern("yyyy/MM/dd"))
LocalTime.parse("10:15:30");
LocalTime.parse("10/15/30", DateTimeFormatter.ofPattern("HH/mm/ss"));LocalDateTime
1
2
3LocalDateTime now = LocalDateTime.now();
now.toLocalDate();
now.toLocalTime();
读取属性
1 | LocalDate now = LocalDate.now(2021, 7, 4); |
修改属性
1 | LocalDate now = LocalDate.now(); |
转换方法
时间戳
1
2
3
4// 转秒
LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond()
// 转毫秒需要先转成Instant
LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()LocaldateTime
1
2
3
4
5
6
7
8
9LocalDate now = LocalDate.now();
LocalDateTime localDateTime = now.atStartOfDay();
now.atTime(LocalTime.now());
now.atTime(11, 45);
now.atTime(11, 45, 59);
now.atTime(11, 45, 59, 999999999);
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
OffsetDateTime offsetDateTime = localDate.atTime(OffsetTime.now());Date
1
java.util.Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
字符串
1
2
3
4
5
6
7localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 2018-10-23
// 预定义格式 在DateTimeFormatter里
localDate.format(DateTimeFormatter.ISO_DATE); // 2018-10-23
localDate.format(DateTimeFormatter.BASIC_ISO_DATE); // 20181023
// 2018年 12月 23日 星期日
localDate.format(DateTimeFormatter.ofPattern("yyyy年 MM月 dd日 E", Locale.CHINESE));
其他方法
1 | // 计算两个LocalDate之间的时间差, 返回Period |
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 | /* 创建 */ |
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); |
DateTimeFormatter
格式化
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 时区
相互转换
1.LocalDate转Date
1 | LocalDate nowLocalDate = LocalDate.now(); |
2.LocalDateTime转Date
1 | LocalDateTime localDateTime = LocalDateTime.now(); |
3.Date转LocalDateTime(LocalDate)
1 | Date date = new Date(); |
4.LocalDate转时间戳
1 | LocalDate localDate = LocalDate.now(); |
5.LocalDateTime转时间戳
1 | LocalDateTime localDateTime = LocalDateTime.now(); |
6.时间戳转LocalDateTime(LocalDate)
1 | long timestamp = System.currentTimeMillis(); |
时区
ZoneId
jdk8中使用新的时区类java.time.ZoneId
来替代原来的java.util.TimeZone
, 对应的时间类是ZonedDateTime
. 使用方式如下:
1 | // 创建 |
另一种表示时区的方式是使用ZoneOffset
, 它是以当前时间和世界标准时间(UTC)/格林威治时间(GMT)的偏差来表示, 对应的类是OffsetDateTime
.
1 | ZoneOffset zoneOffset = ZoneOffset.of("+08:00"); |
ZoneOffset
继承ZoneId
1 | ZoneOffset.ofHours(8); |
Clock
1 | Clock clock = Clock.systemDefaultZone(); |
ChronoUnit
枚举类, 表示时间单位, 天: DAYS
十年:DECADES
世纪: CENTURIES
, 在时间计算(加/减)时常用
其方法getDuration
能获取单位对应的Duration
对象
ChronoField
枚举类, 一天的秒数:SECOND_OF_DAY
一年的天数:DAY_OF_YEAR
其range()
方法返回对应的范围, 如:DAY_OF_YEAR
的范围是1 ~ 365/366
其他历法
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());