共计 8798 个字符,预计需要花费 22 分钟才能阅读完成。
一、LocalDate、LocalTime、Instant、Duration 以及 Period
1. 使用 LocalDate 和 LocalTime
创建一个 LocalDate 对象并读取其值
LocalDate date = LocalDate.of(2014, 3, 18); //2014-03-18
int year = date.getYear(); //2014
Month month = date.getMonth();//MARCH
int day = date.getDayOfMonth();//18
DayOfWeek dow = date.getDayOfWeek();//TUESDAY
int len = date.lengthOfMonth();//31
boolean leap = date.isLeapYear();//false
你还可以通过传递一个 TemporalField 参数给 get 方法拿到同样的信息。TemporalField 是一个接口,它定义了如何访问 temporal 对象某个字段的值。ChronoField 枚举实现了这一接口,所以你可以很方便地使用 get 方法得到枚举元素的值,如下所示。
使用 TemporalField 读取 LocalDate 的值
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
类似地,一天中的时间,比如 13:45:20,可以使用 LocalTime 类表示。类似地,一天中的时间,比如 13:45:20,可以使用 LocalTime 类表示。
LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
LocalDate 和 LocalTime 都可以通过解析代表它们的字符串创建。使用静态方法 parse,你可以实现这一目的:
LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13:45:20");
你可以向 parse 方法传递一个 DateTimeFormatter。它是替换老版 java.util.DateFormat 的推荐替代品。
2. 合并日期和时间
这个复合类名叫 LocalDateTime,是 LocalDate 和 LocalTime 的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造,如下所示。
直接创建 LocalDateTime 对象,或者通过合并日期和时间的方式创建
// 2014-03-18T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
注意,通过它们各自的 atTime 或者 atDate 方法,向 LocalDate 传递一个时间对象,或者向 LocalTime 传递一个日期对象的方式,你可以创建一个 LocalDateTime 对象。你也可以使用 toLocalDate 或者 toLocalTime 方法,从 LocalDateTime 中提取 LocalDate 或者 LocalTime 组件:
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
3. 机器的日期和时间格式
新的 java.time.Instant 类对时间建模的方式,基本上它是以 Unix 元年时间(传统的设定为 UTC 时区 1970 年 1 月 1 日午夜时分)开始所经历的
秒数进行计算。你可以通过向静态工厂方法 ofEpochSecond 传递一个代表秒数的值创建一个该类的实例。静态工厂方法 ofEpochSecond 还有一个增强的重载版本,它接收第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在 0 到 999 999 999 之间。这意味着下面这些对 ofEpochSecond 工厂方法的调用会返回几乎同样的 Instant 对象:
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000);
Instant.ofEpochSecond(4, -1_000_000_000);
从 Java7 开始,你就可以在你的 Java 代码里把长整型数字比如 10000000000 写成一个更具可读性 10_000_000_000。
Instant 类也支持静态工厂方法 now,它能够帮你获取当前时刻的时间戳。它包含的是由秒及纳秒所构成的数字。所以,它无法处理那些我们非常容易理解的时间单位。比如下面这段语句:
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它会抛出下面这样的异常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:
DayOfMonth
4. 定义 Duration 或 Period
你可以创建两个 LocalTimes 对象、两个 LocalDateTimes 对象,或者两个 Instant 对象之间的duration(持续期间),如下所示:
Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);
如果你试图在这两类对象之间创建 duration,会触发一个 DateTimeException 异常。此外,由于 Duration 类主要用于以秒和纳秒衡量时间的长短,你不能仅向 between 方法传递一个 LocalDate 对象做参数。
如果你需要以 年、月或者日 的方式对多个时间单位建模,可以使用 Period 类。使用该类的工厂方法 between,你可以使用得到两个 LocalDate 之间的时长,如下所示:
Period tenDays = Period.between(LocalDate.of(2014, 3, 8),
LocalDate.of(2014, 3, 18));
Duration 和 Period 类都提供了很多非常方便的工厂类,直接创建对应的实例;不再是只能以两个 temporal 对象的差值的方式来定义它们的对象。
创建 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);
日期 / 时间类中表示时间间隔的通用方法
Temporal接口的实现类:
HijrahDate, Instant, JapaneseDate, LocalDate, LocalDateTime, LocalTime, MinguoDate, OffsetDateTime, OffsetTime, ThaiBuddhistDate, Year, YearMonth, ZonedDateTime
二、操纵、解析和格式化日期
如果你已经有一个 LocalDate 对象,想要创建它的一个修改版,最直接也最简单的方法是使用 withAttribute 方法。withAttribute 方法会创建对象的一个副本,并按照需要修改它的属性。注意,下面的这段代码中所有的方法都返回一个修改了属性的对象。它们都不会修改原来的对象!
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
采用更通用的 with 方法能达到同样的目的,它接受的第一个参数是一个 TemporalField 对象,格式类似上面代码最后一行。更确切地说,使用 get 和 with 方法,我们可以将 Temporal 对象值的读取和修改区分开。如果 Temporal 对象不支持请求访问的字段,它会抛出一个 UnsupportedTemporalTypeException 异常。
以相对方式修改 LocalDate 对象的属性:
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
像 LocalDate、LocalTime、LocalDateTime 以及 Instant 这样表示时间点的日期时间类提供了大量通用的方法
1. 使用 TemporalAdjuster
有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的 with 方法,向其传递一个提供了更多定制化选择的 TemporalAdjuster 对象,更加灵活地处理日期。对于最常见的用例,日期和时间 API 已经提供了大量预定义的 TemporalAdjuster。你可以通过 TemporalAdjuster 类的静态工厂方法访问它们。
使用预定义的 TemporalAdjuster
import static java.time.temporal.TemporalAdjusters.*;
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); //2014-03-23
LocalDate date3 = date2.with(lastDayOfMonth());//2014-03-31
TemporalAdjuster 类中的工厂方法
TemporalAdjuster 接口
@FunctionalInterface
public interface TemporalAdjuster {Temporal adjustInto(Temporal temporal);
}
如果你想要使用 Lambda 表达式定义 TemporalAdjuster 对象,推荐使用 TemporalAdjusters 类的静态工厂方法 ofDateAdjuster,它接受一个 UnaryOperator<LocalDate>
3. 打印输出及解析日期时间对象
新的 java.time.format 包就是特别为这个目的而设计的。这个包中,最重要的类是 DateTimeFormatter。创建格式器最简单的方法是通过它的静态工厂方法以及常量。像 BASIC_ISO_DATE 和 ISO_LOCAL_DATE 这样的常量是 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 达到重创该日期对象的目的:
LocalDate date1 = LocalDate.parse("20140318",
DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",
DateTimeFormatter.ISO_LOCAL_DATE);
和老的 java.util.DateFormat 相比较,所有的 DateTimeFormatter 实例都是线程安全的。
按照某个模式创建 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);
创建一个本地化的 DateTimeFormatter
DateTimeFormatter italianFormatter =
DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date.format(italianFormatter); // 18. marzo 2014
LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);
DateTimeFormatterBuilder 类还提供了更复杂的格式器, 你可以通过 DateTimeFormatterBuilder 自己编程实现我们在上面代码使用的 italianFormatter,代码清单如下。
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(".")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
三、处理不同的时区和历法
新的 java.time.ZoneId 类是老版 java.util.TimeZone 的替代品。跟其他日期和时间类一样,ZoneId 类也是无法修改的。时区是按照一定的规则将区域划分成的标准时间相同的区间。在 ZoneRules 这个类中包含了 40 个这样的实例。你可以简单地通过调用 ZoneId 的 getRules()得到指定时区的规则。每个特定的 ZoneId 对象都由一个地区 ID 标识,比如:
ZoneId romeZone = ZoneId.of("Europe/Rome");
地区 ID 都为“{区域}/{城市}”的格式, 你可以通过 Java 8 的新方法 toZoneId 将一个老的时区对象转换为 ZoneId:
ZoneId zoneId = TimeZone.getDefault().toZoneId();
为时间点添加时区信息
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);
1. 利用和 UTC/ 格林尼治时间的固定偏差计算时区
ZoneOffset 类,它是 ZoneId 的一个子类,表示的是当前时间和伦敦格林尼治子午线时间的差异:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
注意,使用这种方式定义的 ZoneOffset 并未考虑任何日光时的影响,所以在大多数情况下,不推荐使用。ZoneOffset 也是 ZoneId。
你甚至还可以创建这样的 OffsetDateTime,它使用 ISO-8601 的历法系统,以相对于 UTC/ 格林尼治时间的偏差方式表示日期时间。
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset)
2. 使用别的日历系统
Java 8 中另外还提供了 4 种其他的日历系统。这些日历系统中的每一个都有一个对应的日志类,分别是 ThaiBuddhistDate、MinguoDate、JapaneseDate 以及 HijrahDate。所有这些类以及 LocalDate 都实现了 ChronoLocalDate 接口。
如下所示:
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
JapaneseDate japaneseDate = JapaneseDate.from(date);
或者,你还可以为某个 Locale 显式地创建日历系统,接着创建该 Locale 对应的日期的实例。新的日期和时间 API 中,Chronology 接口建模了一个日历系统,使用它的静态工厂方法 ofLocale,可以得到它的一个实例,代码如下:
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow();
日期及时间 API 的设计者建议我们使用 LocalDate,尽量避免使用 ChronoLocalDate,原因是开发者在他们的代码中可能会做一些假设,而这些假设在不同的日历系统中,有可能不成立。比如,有人可能会做这样的假设,即一个月天数不会超过 31 天,一年包括 12 个月,或者一年中包
含的月份数目是固定的。由于这些原因,我们建议你尽量在你的应用中使用 LocalDate,包括存储、操作、业务规则的解读;不过如果你需要将程序的输入或者输出本地化,这时你应该使用 ChronoLocalDate 类。
伊斯兰教日历
在 Java 8 新添加的几种日历类型中,HijrahDate(伊斯兰教日历)是最复杂一个,因为它会发生各种变化。Hijrah 日历系统构建于农历月份继承之上。Java 8 提供了多种方法判断一个月份,比如新月,在世界的哪些地方可见,或者说它只能首先可见于沙特阿拉伯。withVariant 方法可以用于选择期望的变化。为了支持 HijrahDate 这一标准,Java 8 中还包括了乌姆库拉(Umm Al-Qura)变量。
下面这段代码作为一个例子说明了如何在 ISO 日历中计算当前伊斯兰年中斋月的起始和终止日期:
HijrahDate ramadanDate =
HijrahDate.now().with(ChronoField.DAY_OF_MONTH, 1)
.with(ChronoField.MONTH_OF_YEAR, 9);
System.out.println("Ramadan starts on" +
IsoChronology.INSTANCE.date(ramadanDate) +
"and ends on" +
IsoChronology.INSTANCE.date(
ramadanDate.with(TemporalAdjusters.lastDayOfMonth())));