《Java8实战》读书笔记11:Java8中新的日期时间API

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java8实战》读书笔记11:Java8中新的日期时间API相关的知识,希望对你有一定的参考价值。

《Java8实战》读书笔记11:Java8中新的日期、时间API

第 12 章 新的日期和时间API

旧时间日期 API 表

类名介绍
java.util.Date1. 这个类无法表示日期,只能以毫秒的精度表示时间。
2. 年份的起始选择是1900年,月份的起始从0开始。2014年3月18日 = Date date = new Date(114, 2, 18)
3. 不支持时区,但 toString() 结果确默认显示时区信息。
4. 可以变的。
java.util.Calendar1. 月份依旧是从 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.LocalDateTimeLocalDateLocalTime 的合体。它表示日期和时间不带时区。可以直接创建,也可通过合并日期和时间来创建。
ZonedDateTimeLocalDateLocalTime时区的合体
java.time.Instant便于机器处理,从计算机的角度,对时间建模。是一个持续时间段上某个点。以 Unix元年(UTC时区1970年1月1日午夜时分)开始所经历的秒数。
1. 可用于在应用程序中记录事件时间戳
java.time.Duration两个时间 Temporal 对象:AB之间持续的时长
1. LocalDateTimeInstant 是为不同的目的而设计的,无法将二者混用计算时长
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 开始了。
可惜的是这 MonthDayOfWeek 是不是有点霸道了。

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();

LocalDateLocalTime 都可以通过静态方法 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:003秒之后。

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 对象
ofTemporal对象的某个部分创建该对象的实例
parse由字符串创建 Temporal 对象的实例
atOffsetTemporal 对象和某个时区偏移相结合
atZoneTemporal 对象和某个时区相结合
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_IDSmap 存所有时区信息。

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 小结

  • 这一章中,你应该掌握下面这些内容。
  1. Java 8之前版的java.util.Date类以及其他用于建模日期时间的类有很多不一致及设计上的缺陷,包括易变性以及糟糕的偏移值、默认值和命名。
  2. 新版的日期和时间API中,日期-时间对象是不可变的。
  3. 新的API提供了两种不同的时间表示方式,有效地区分<

    以上是关于《Java8实战》读书笔记11:Java8中新的日期时间API的主要内容,如果未能解决你的问题,请参考以下文章

    《Java8实战》读书笔记10:组合式异步编程 CompletableFuture

    《Java8实战》读书笔记10:组合式异步编程 CompletableFuture

    《Java8实战》读书笔记13:Java8 与 Scala

    《Java8实战》读书笔记13:Java8 与 Scala

    《Java8实战》读书笔记08:接口的默认方法

    《Java8实战》读书笔记12:函数式编程