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
相关文章推荐
- java8,你应该了解的新特性(新时间与日期API)
- Java 8 新特性:Java 类库的新特性之日期时间API (Date/Time API )
- Java 8新特性 全新的日期时间API
- 详解Java8特性之新的日期时间 API
- Java8新特性之:新的日期和时间API
- Java8新特性(三)——Optional类、接口方法与新时间日期API
- Java 8新特性之新的时间日期API
- Java8新特性之深入解析日期和时间_动力节点Java学院整理
- 详解Java8特性之新的日期时间 API
- Java8新特性9--新的日期和时间API
- Java 8 新特性:Java 类库的新特性之日期时间API (Date/Time API )
- W3Cschool Java 8新特性----> 日期时间 API
- Java 8新特性(四):新的时间和日期API
- Java 8 新特性-菜鸟教程 (8) -Java 8 日期时间 API
- Java8新日期时间API的20个使用示例
- java 8 新特性 时间api使用实例
- Java8新特性之一、时间日期API
- Java8 新特性 日期API
- 使用Java 8 的日期和时间Api
- 嵌入式软件开发培训笔记——java第五天(API——math/string/集合/日期时间以及java线程)