《Java8实战》读书笔记11:Java8中新的日期时间API
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java8实战》读书笔记11:Java8中新的日期时间API相关的知识,希望对你有一定的参考价值。
《Java8实战》读书笔记11:Java8中新的日期、时间API
- 第 12 章 新的日期和时间API
- 12.1 LocalDate、LocalTime、Instant、Duration 以及 Period
- 12.2 操纵、解析和格式化日期
- 12.3 处理不同的时区和历法
- 12.4 小结
- 学习总结
- 参考资料
第 12 章 新的日期和时间API
旧时间日期 API 表
类名 | 介绍 |
---|---|
java.util.Date | 1. 这个类无法表示日期,只能以毫秒的精度表示时间。 2. 年份的起始选择是1900年,月份的起始从0开始。2014年3月18日 = Date date = new Date(114, 2, 18) 。 3. 不支持时区,但 toString() 结果确默认显示时区信息。4. 可以变的。 |
java.util.Calendar | 1. 月份依旧是从 0 开始计算。2. 可以变的。 |
java.text.DateFormat | 日期格式化工具。不是 线程安全的。 |
Java8 时间日期 API 表
这些新类都位于 java.time 包下。提供了统一风格的 API。
下表所有类都实现了 Temporal 接口。
如:获取年、月、日、周、时、分、秒等方法。
接口 | 介绍 |
---|---|
java.time.temporal | 接口 。定义了如何读取和操纵为时间建模的对象的值。 |
java.time.temporal.TemporalField | 接口 。它定义了如何访问temporal对象某个字段的值。 |
java.time.chrono.ChronoLocalDate | 用于高级全球化用例的任意时间表中没有时间或时区的日期。API的设计鼓励使用LocalDate 而不是此接口,即使在应用程序需要处理多个日历系统的情况下也是如此。 |
类 | 介绍 |
---|---|
java.time.LocalDate | 日期对象 |
java.time.LocalTime | 时间对象 |
java.time.LocalDateTime | LocalDate 和 LocalTime 的合体。它表示日期和时间 但 不带时区 。可以直接创建,也可通过合并日期和时间来创建。 |
ZonedDateTime | LocalDate 、LocalTime 、时区 的合体 |
java.time.Instant | 便于机器处理,从计算机的角度,对时间建模。是一个持续时间段上某个点。以 Unix元年 (UTC时区1970年1月1日午夜时分)开始所经历的秒数。1. 可用于在应用程序中记录事件时间戳。 |
java.time.Duration | 两个时间 Temporal 对象:A 和B 之间持续的时长 。1. LocalDateTime 和 Instant 是为不同的目的而设计的,无法将二者混用计算时长 。2. 本类主要用于以 秒 和纳秒 衡量时间的长短,between 方法不支持LocalDate 做参数,因为它不带时间 。 |
java.time.Period | 此类用于计算年、月、日 之间持续的时长 。 |
java.time.temporal.ChronoField | 此枚举实现了 TemporalField 接口,所以你可以很方便地使用 get 方法得到枚举元素的值。 |
java.time.format.DateTimeFormatter | 用于打印和解析日期时间对象的格式化程序。 |
ZoneOffset | 与格林威治/ UTC的时区偏移,例如+08:00 |
ZoneId | 时区id |
12.1 LocalDate、LocalTime、Instant、Duration 以及 Period
12.1.1 使用 LocalDate 和 LocalTime
代码清单12-1 创建一个LocalDate对象并读取其值
可喜可贺☺,月
份再也不用从 0
开始了。
可惜的是这 Month
、DayOfWeek
是不是有点霸道了。
LocalDate date = LocalDate.of(2022, 7, 5); // 2022-07-08
int year = date.getYear(); // 2022
Month month = date.getMonth(); // JULY
int day = date.getDayOfMonth(); // 5
DayOfWeek dow = date.getDayOfWeek(); // TUESDAY
int len = date.lengthOfMonth(); // 31
boolean leap = date.isLeapYear(); // false
使用工厂方法从系统时钟中获取当前的日期
LocalDate today = LocalDate.now();
代码清单12-2 使用TemporalField读取LocalDate的值
ChronoField
枚举实现了 TemporalField
接口。
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
代码清单12-3 创建LocalTime并读取其值
时间:13:45:20
LocalTime time = LocalTime.of(12, 30, 59);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
LocalDate
和 LocalTime
都可以通过静态方法 parse
解析代表它们的字符串创建。
LocalDate date = LocalDate.parse("2022-07-04"); // 2022-07-04
LocalTime time = LocalTime.parse("12:30:59"); // 12:30:59
12.1.2 合并日期和时间
代码清单12-4 直接创建LocalDateTime对象,或者通过合并日期和时间的方式创建
// 2022-07-04T20:53:12
LocalDateTime dt1 = LocalDateTime.of(2022, Month.JULY, 5, 12, 30, 59); // 2022-07-05T12:30:59
LocalDateTime dt2 = LocalDateTime.of(date, time); // 2022-07-05T12:30:59
LocalDateTime dt3 = date.atTime(12, 30, 59); // 2022-07-05T12:30:59
LocalDateTime dt4 = date.atTime(time); // 2022-07-05T12:30:59
LocalDateTime dt5 = time.atDate(date); // 2022-07-05T12:30:59
从 LocalDateTime中
提取 LocalDate
或者 LocalTime
组件:
LocalDate date1 = dt1.toLocalDate(); // 2022-07-05
LocalTime time1 = dt1.toLocalTime(); // 12:30:59
12.1.3 机器的日期和时间格式(机器喜欢的日期格式)
使用 秒
和 纳秒
创建实例。以下都是:1970-01-01T00:00:00
的 3秒
之后。
Instant.ofEpochSecond(3); // 3秒之后
Instant.ofEpochSecond(3, 0); // 3秒之后 + 0纳秒
Instant.ofEpochSecond(2, 1_000_000_000); // 2秒之后 + 10亿纳秒(1秒)
Instant.ofEpochSecond(4, -1_000_000_000); // 4秒之后 - 10亿纳秒(1秒)
12.1.4 定义 Duration 或 Period
Duration
用于时间
由于Duration类主要用于以秒和纳秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数
LocalTime time1 = LocalTime.now();
LocalTime time2 = time1.plusMinutes(180); // 加 180 秒
Duration betweenTime = Duration.between(time1, time2); // 求持续时长
LocalDateTime dateTime1 = LocalDateTime.now();
LocalDateTime dateTime2 = dateTime1.plusHours(24); // 加 24 小时
Duration betweenDateTime= Duration.between(dateTime1, dateTime2); // 求持续时长
Instant instant1 = Instant.now();
Instant instant2 = instant1.plusMillis(1000); // 加 1000 毫秒
Duration betweenInstant = Duration.between(instant1, instant2); // 求持续时长
Period
用于日期
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(3);
Period betweenDate = Period.between(date1, date2);
代码清单12-5 创建Duration和Period对象
分别用于表示时间
或日期
之间的时长
。
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
表12-1 日期-时间类中表示时间间隔的通用方法
Duration类和Period类共享了很多相似的方法,参见表12-1所示。
方法名 | 是否是静态方法 | 方法描述 |
---|---|---|
between | 是 | 创建两个时间点之间的 时间间隔 。 |
from | 是 | 由一个临时时间点创建 时间间隔 。 |
of | 是 | 由它的组成部分创建 时间间隔 的实例。 |
parse | 是 | 由字符串创建 时间间隔 的实例。 |
addTo | 否 | 创建该 时间间隔 的副本,并将其叠加到某个指定的 temporal 对象。 |
get | 否 | 读取该 时间间隔 的状态。 |
isNegative | 否 | 检查该 时间间隔 是否为负值,不包含零。 |
isZero | 否 | 检查该 时间间隔 的时长是否为零。 |
minus | 否 | 通过减去一定的时间创建该 时间间隔 的副本。 |
multipliedBy | 否 | 将 时间间隔 的值乘以某个标量创建该 时间间隔 的副本。 |
negated | 否 | 以忽略某个时长的方式创建该 时间间隔 的副本。 |
plus | 否 | 以增加某个指定的时长的方式创建该 时间间隔 的副本。 |
subtractFrom | 否 | 从指定的 temporal 对象中减去该 时间间隔 。 |
12.2 操纵、解析和格式化日期
代码清单12-6 以比较直观的方式操纵LocalDate的属性
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18
LocalDate date2 = date1.withYear(2011); // 2011-03-18
LocalDate date3 = date2.withDayOfMonth(25); // 2011-03-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); // 2011-09-25
代码清单12-7 以相对方式修改LocalDate对象的属性
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18
LocalDate date2 = date1.plusWeeks(1); // 2014-03-25
LocalDate date3 = date2.minusYears(3); // 2011-03-25
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS); // 2011-09-25
表12-2 表示时间点的日期-时间类的通用方法
大概你已经猜到,像LocalDate、LocalTime、LocalDateTime以及Instant这样表示时
间点的日期时间类提供了大量通用的方法,表12-2对这些通用的方法进行了总结。
方法名 | 是否是静态方法 | 描述 |
---|---|---|
from | 是 | 依据传入的 Temporal 对象创建对象实例 |
now | 是 | 依据系统时钟创建 Temporal 对象 |
of | 是 | 由Temporal 对象的某个部分创建该对象的实例 |
parse | 是 | 由字符串创建 Temporal 对象的实例 |
atOffset | 否 | 将 Temporal 对象和某个时区偏移相结合 |
atZone | 否 | 将 Temporal 对象和某个时区相结合 |
format | 否 | 使用某个指定的格式器将 Temporal 对象转换为字符串(Instant类不提供该方法) |
get | 否 | 读取 Temporal 对象的某一部分的值 |
minus | 否 | 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值减去一定的时长创建该副本 |
plus | 否 | 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值加上一定的时长创建该副本 |
with | 否 | 以该 Temporal 对象为模板,对某些状态进行修改创建该对象的副本 |
12.2.1 使用 TemporalAdjuster
代码清单12-8 使用预定义的TemporalAdjuster
import static java.time.temporal.TemporalAdjusters.*;
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); // 2014-03-23
LocalDate date3 = date2.with(lastDayOfMonth()); // 2014-03-31
表12-3 TemporalAdjuster类中的工厂方法
表12-3提供了TemporalAdjuster中包含的工厂方法列表。
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 创建一个新的日期,它的值为同一个月中每一周的第几天 |
firstDayOfMonth | 创建一个新的日期,它的值为当月的第一天 |
firstDayOfNextMonth | 创建一个新的日期,它的值为下月的第一天 |
firstDayOfNextYear | 创建一个新的日期,它的值为明年的第一天 |
firstDayOfYear | 创建一个新的日期,它的值为当年的第一天 |
firstInMonth | 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值 |
lastDayOfMonth | 创建一个新的日期,它的值为当月的最后一天 |
lastDayOfNextMonth | 创建一个新的日期,它的值为下月的最后一天 |
lastDayOfNextYear | 创建一个新的日期,它的值为明年的最后一天 |
lastDayOfYear | 创建一个新的日期,它的值为今年的最后一天 |
lastInMonth | 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值 |
next/previous | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期 |
nextOrSame/previousOrSame | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象 |
代码清单12-9 TemporalAdjuster接口
TemporalAdjuster
是一个 函数式接口
。
@FunctionalInterface
public interface TemporalAdjuster
Temporal adjustInto(Temporal temporal);
实现个 18年后的方法。
如果是高频使用的逻辑可以建个类
来方便维护,我这里走一波就直接上Lambda
了
TemporalAdjuster after18Years = TemporalAdjusters.ofDateAdjuster(
temporal ->
return temporal.plus(18, ChronoUnit.YEARS);
);
LocalDate now = LocalDate.now(); // 2022-07-05
LocalDate date = now.with(after18Years);
System.out.println(date.toString()); // 2040-07-05
12.2.2 打印输出及解析日期时间对象
java.time.format
这个包下都是时间格式化相关的内容 。最重要的比如:DateTimeFormatter
- 使用了两个不同的格式器生成了字符串 (感觉平时也就这两个用的上)
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2014-03-18
- 使用工厂方法
parse
按指定格式,解析出日期对象
和老的java.util.DateFormat
相比较,所有的DateTimeFormatter
实例都是线程安全的。
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
代码清单12-10 按照某个模式创建DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
代码清单12-11 创建一个本地化的DateTimeFormatter
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("yyyy年 MMMM d日 ", Locale.CHINA);
LocalDate date1 = LocalDate.of(2022, 07, 05);
String formattedDate = date1.format(italianFormatter); // 2022年 七月 5日
LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter); // 2022-07-05
代码清单12-12 构造一个DateTimeFormatter
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
12.3 处理不同的时区和历法
ZoneId
中有一个叫 SHORT_IDS
的 map
存所有时区信息。
ZoneId romeZone = ZoneId.of("Asia/Shanghai");
将老时区对象 TimeZone
转为 ZoneId
:
ZoneId zoneId = TimeZone.getDefault().toZoneId();
代码清单12-13 为时间点添加时区信息
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);
通过ZoneId,你还可以将LocalDateTime转换为Instant:
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
Instant instantFromDateTime = dateTime.toInstant(romeZone);
你也可以通过反向的方式得到LocalDateTime对象:
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
12.3.1 利用和 UTC/格林尼治时间的固定偏差计算时区
“纽约落后于伦敦5小时”。
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
获取 ZoneOffset
ZoneOffset offset1 = OffsetDateTime.now().getOffset(); // 获取系统默认时区
ZoneOffset offset2 = ZoneOffset.of("+8"); // 指定获取东八区
System.out.println(offset1.equals(offset2)); // true
12.3.2 使用别的日历系统
略。。。
12.4 小结
- 这一章中,你应该掌握下面这些内容。
- Java 8之前
老
版的java.util.Date
类以及其他用于建模日期时间的类有很多不一致及设计
上的缺陷
,包括易变性以及糟糕的偏移值、默认值和命名。 新版
的日期和时间API
中,日期-时间对象是不可变
的。- 新的API提供了
两种
不同的时间表示方式
,有效地区分<
以上是关于《Java8实战》读书笔记11:Java8中新的日期时间API的主要内容,如果未能解决你的问题,请参考以下文章
《Java8实战》读书笔记10:组合式异步编程 CompletableFuture