您的位置:首页 > 编程语言 > Java开发

Java8新特性整理之新的时间和日期API(终章)

2018-02-22 10:30 816 查看

前言

Java8之前我们使用
Date
Calendar
这两个类处理时间,但有的特性只在某一个类有提供,比如用

于以语言无关的方式格式化和解析日期或时间的DateFormat方法就只在Date类里有。DateFormat方法也有它自己的问题。

比如,它不是线程安全的。这意味着两个线程如果尝试使用同一个formatter解析日期,你可能会得到无法预期的结果。

最后,Date和Calendar类都是可以变的。能把2014年3月18日修改成4月18日意味着什么呢?

这种设计会将你拖入维护的噩梦,接下来我们从最基本的用例入手,比如创建同时适合人与机器的日期和时间,逐渐转入到日期和时间API更高级的一些应用,比如

操纵、解析、打印输出日期-时间对象,使用不同的时区。

JDK1.8提供的日期处理类都是不可变对象,所以是线程安全的。

使用 LocalDate 和 LocalTime

开始使用新的日期和时间API时,你最先碰到的可能是LocalDate类。该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关

的信息。

创建一个LocalDate对象并读取其值 :

@Test
public void testLocalDateOf() {
LocalDate localDate = LocalDate.of(2017, 12, 12);
int year = localDate.getYear();
int month = localDate.getMonthValue();
int day = localDate.getDayOfWeek().getValue();
int maxLength = localDate.getMonth().maxLength();
int minLength = localDate.getMonth().minLength();
boolean isLeap = localDate.isLeapYear();
System.out.println("TimeTest.testLocalDateOf year: " + year + "\tmonth: " + month + "\tday: " + day
+ "\tmaxLength: " + maxLength + "\tminLength: " + minLength + "\tisLeap: " + isLeap);
}


获取当前的日期:

LocalDate now = LocalDate.now();


使用TemporalField读取LocalDate的值

TemporalField
是一个接口,它定义了如何访问temporal对象某个字段的值。
ChronoField
枚举类实现了这一接口,所以你可以很方便地使用get方法得到枚举元素的值:

@Test
public void testTemporalField() {
LocalDate localDate = LocalDate.of(2017, 12, 12);
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);

System.out.println("TimeTest.testTemporalField year: " + year + "\tmonth: " + month + "\tday: " + day);
}


创建LocalTime并读取其值

@Test
public void testLocalTime() {
LocalTime time = LocalTime.of(14, 22, 28);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
System.out.println("TimeTest.testLocalTime hour: " + hour + "\tminute: " + minute + "\tsecond: " + second);
}


LocalDate和LocalTime都可以通过解析代表它们的字符串创建。使用静态方法
parse


LocalDate date = LocalDate.parse("2017-12-12");
LocalTime time = LocalTime.parse("14:22:28");


合并日期和时间

这个复合类名叫
LocalDateTime
,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造:

@Test
public void testLocalDateTimeCombine() {
LocalDate date = LocalDate.of(2017, 12, 12);
LocalTime time = LocalTime.of(14, 22, 28);

LocalDateTime dt1 = LocalDateTime.of(2017, Month.MARCH, 12, 14, 22, 28);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
System.out.println("TimeTest.testLocalDateTimeCombine dt1: " + dt1 + "\td2: " + dt2 + "\tdt3: " + dt3
+ "\tdt4: " + dt4 +"\tdt5: " +dt5);
}


通过它们各自的
atTime
或者
atDate
方法,向LocalDate传递一个时间对象,或者向LocalTime传递一个日期对象,以创建一个LocalDateTime对象。你也可以使用

toLocalDate
或者
toLocalTime
方法,从LocalDateTime中提取LocalDate或者LocalTime对象:

LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();


机器的日期和时间格式

Instant类的设计初衷是为了便于机器使用,获取当前时刻的时间戳:

@Test
public void testInstant() {
Instant instant = Instant.now();
System.out.println("TimeTest.testInstant 时间戳 : " + System.currentTimeMillis());
System.out.println("TimeTest.testInstant 时间戳 : " + instant.atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli());
System.out.println("TimeTest.testInstant 时间戳 : " + instant.atZone(ZoneId.of("GMT+08:00")).toInstant().toEpochMilli());
System.out.println("TimeTest.testInstant 时间戳 : " + instant.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}


持续时间,时间间隔

两个日期的持续时间:

@Test
public void testDuration() {
LocalDate date1 = LocalDate.of(2017, 12, 12);
LocalDate date2 = LocalDate.of(2017, 12, 24);
LocalTime time1 = LocalTime.of(15, 7, 50);
LocalTime time2 = LocalTime.of(16, 8, 50);
LocalDateTime dateTime1 = LocalDateTime.of(2017, Month.MARCH, 12, 14, 22, 28);
LocalDateTime dateTime2 = LocalDateTime.of(2018, Month.MARCH, 12, 14, 22, 28);
Instant instant1 = Instant.ofEpochSecond(1);
Instant instant2 = Instant.now();
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
// 这里会抛异常java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
Duration d4 = Duration.between(date1, date2);
System.out.println("TimeTest.testDuration d1: " + d1.getSeconds() +"\td2: " + d2.getSeconds() +"\td3: " + d3.getSeconds());
}


由于LocalDateTime和Instant是为不同的目的而设计的,一个是为了便于人阅读使用,

另一个是为了便于机器处理,所以你不能将二者混用。如果你试图在这两类对象之间创建

duration,会触发一个DateTimeException异常。此外,由于**Duration类主要用于以秒和纳

秒衡量时间的长短**,你不能向between方法传递一个LocalDate对象做参数,否则会

抛异常java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds。

如果你需要以年、月或者日的方式对多个时间单位建模,可以使用
Period
类。使用该类的

工厂方法between,你可以使用得到两个LocalDate之间的时长,如下所示

@Test
public void testPeriod() {
LocalDate now = LocalDate.now();
LocalDate dates = LocalDate.of(2017, Month.JULY, 2);
Period period = Period.between(dates, now);
System.out.println("TimeTest.testPeriod  两个日期间隔 : " + period.getYears() + "年"
+ period.getMonths() +"月" + period.getDays() + "天");
}


操纵、解析和格式化日期

以比较直观的方式操纵LocalDate的属性

@Test
public void testModifyLocalDate() {
LocalDate date1 = LocalDate.of(2017, 3, 18);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
System.out.println("TimeTest.testModifyLocalDate date1: " + date1);
System.out.println("TimeTest.testModifyLocalDate date2: " + date2);
System.out.println("TimeTest.testModifyLocalDate date3: " + date3);
System.out.println("TimeTest.testModifyLocalDate date4: " + date4);
}


输出:

TimeTest.testModifyLocalDate date1: 2017-03-18
TimeTest.testModifyLocalDate date2: 2011-03-18
TimeTest.testModifyLocalDate date3: 2011-03-25
TimeTest.testModifyLocalDate date4: 2011-09-25


这些方法都声明在Temporal接口,所有的日期和时间API类都实现这两个方法,

使用
get
with
方法,我们可以将Temporal对象值的读取和修改区分开。

以相对方式修改LocalDate对象的属性

@Test
public void testModifyLocalDate2() {
LocalDate date1 = LocalDate.of(2017, 3, 18);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
System.out.println("TimeTest.testModifyLocalDate2 date1: " + date1);
System.out.println("TimeTest.testModifyLocalDate2 date2: " + date2);
System.out.println("TimeTest.testModifyLocalDate2 date3: " + date3);
System.out.println("TimeTest.testModifyLocalDate2 date4: " + date4);
}


输出:

TimeTest.testModifyLocalDate2 date1: 2017-03-18
TimeTest.testModifyLocalDate2 date2: 2017-03-25
TimeTest.testModifyLocalDate2 date3: 2014-03-25
TimeTest.testModifyLocalDate2 date4: 2014-09-25


与我们刚才介绍的get和with方法类似,代码最后一行使用的plus方法也是通用

方法,它和minus方法都声明于Temporal接口中。

一个求未来或历史的今天的栗子:

@Test
public void testChronology() {
LocalDate today = LocalDate.now();
System.out.println("TimeTest.testChronology today : " + today);
LocalDate oneToday = today.plus(1, ChronoUnit.WEEKS);
System.out.println("TimeTest.testChronology 一周后的今天 : " + oneToday);
LocalDate preYear = today.minus(1, ChronoUnit.YEARS);
System.out.println("TimeTest.testChronology 一年前的今天 : " + preYear);
LocalDate postYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("TimeTest.testChronology 一年后的今天 : " + postYear);
}


像LocalDate、LocalTime、LocalDateTime以及Instant这样表示时间点的日期-时间类提供了大量通用的方法,

下表对这些通用的方法进行了总结

方 法 名描 述
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 对象为模板,对某些状态进行修改创建该对象的副本

使用 TemporalAdjuster

截至目前,你所看到的所有日期操作都是相对比较直接的。有的时候,你需要进行一些更加

复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可

以使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,

更加灵活地处理日期。对于最常见的用例,日期和时间API已经提供了大量预定义的

TemporalAdjuster。你可以通过TemporalAdjuster类的静态工厂方法访问它们,如下所示:

@Test
public void testTemporalAdjuster() {
LocalDate date1 = LocalDate.of(2017, 3, 18);
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate date3 = date2.with(lastDayOfMonth());
System.out.println("TimeTest.testTemporalAdjuster date1: " + date1);
System.out.println("TimeTest.testTemporalAdjuster date2: " + date2);
System.out.println("TimeTest.testTemporalAdjuster date3: " + date3);
}


输出:

TimeTest.testTemporalAdjuster date1: 2017-03-18
TimeTest.testTemporalAdjuster date2: 2017-03-19
TimeTest.testTemporalAdjuster date3: 2017-03-31


下表提供了TemporalAdjuster类中的工厂方法

方 法 名描 述
dayOfWeekInMonth创建一个新的日期,它的值为同一个月中每一周的第几天
firstDayOfMonth创建一个新的日期,它的值为当月的第一天
firstDayOfNextMonth创建一个新的日期,它的值为下月的第一天
firstDayOfNextYear创建一个新的日期,它的值为明年的第一天
firstDayOfYear创建一个新的日期,它的值为当年的第一天
firstInMonth创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值
lastDayOfMonth创建一个新的日期,它的值为当月的最后一天
lastDayOfNextMonth创建一个新的日期,它的值为下月的最后一天
lastDayOfNextYear创建一个新的日期,它的值为明年的最后一天
lastDayOfYear创建一个新的日期,它的值为今年的最后一天
lastInMonth创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值
next/previous创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期
nextOrSame/previousOrSame创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象

格式化以及解析日期-时间对象

日期转字符串格式:

@Test
public void testDateToString() {
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDate = localDateTime.format(formatter);
System.out.println("TimeTest.testDateToString 格式化后的日期 : " + formatDate);
}


字符串转日期格式:

@Test
public void testDateFormat() {
String datetime = "2017-12-02T16:46:48";
LocalDateTime parseDate = LocalDateTime.parse(datetime, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println("TimeTest.testDateFormat 字符串转日期 : " + parseDate);
}


处理时区

新的
java.time.ZoneId
类是老版
java.util.TimeZone
的替代品。

地区ID都为
{区域}/{城市}
的格式:

ZoneId romeZone = ZoneId.of("Asia/Shanghai");


为时间点添加时区信息 :

@Test
public void testZoneId() {
ZoneId romeZone = ZoneId.of("Asia/Shanghai");
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);
System.out.println("TimeTest.testZoneId zdt1: " + zdt1);
System.out.println("TimeTest.testZoneId zdt2: " + zdt2);
System.out.println("TimeTest.testZoneId zdt3: " + zdt3);
}


输出

TimeTest.testZoneId zdt1: 2014-03-18T00:00+08:00[Asia/Shanghai]
TimeTest.testZoneId zdt2: 2014-03-18T13:45+08:00[Asia/Shanghai]
TimeTest.testZoneId zdt3: 2018-02-08T17:08:31.929+08:00[Asia/Shanghai]


通过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);


UTC/GMT固定偏差计算时区

GMT(格林威治时间)、CST(可视为美国、澳大利亚、古巴或中国的标准时间)、PST(太平洋时间)

GMT: UTC +0    =    GMT: GMT +0
CST: UTC +8    =    CST: GMT +8
PST: UTC -8    =    PST: GMT -8


以相对于UTC/格林尼治时间的偏差方式表示日期时间:

@Test
public void testZoneOffset() {
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
LocalDateTime dateTime = LocalDateTime.now();
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(dateTime, newYorkOffset);
System.out.println("TimeTest.testZoneOffset dateTimeInNewYork: " + dateTimeInNewYork);
}


更多用例查看此文:

https://www.cnblogs.com/comeboo/p/5378922.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: