介绍 Java 8 Date/Time API
1.概述
java8 引入新的日期时间API,为了解决原来
java.util.Date , java.util.Calendar
的一些缺陷。本文首先介绍原Date,Calendar API的问题,然后来说明java8 Date , Time API是如何解决的。
同时,我们也了解java8中java.time包中一些常用类及其API,如
LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Period, Duration
.
2.原Date/Time APIs问题
线程安全 —— Date和Calendar类不是线程安全的,在调试并发程序时让人头痛,开发者必须自己写额外的代码处理线程安全。相反,java8中引入新的Date和Time API是不可变且线程安全的,因此开发者无需担心并发问题。
简单易懂 —— Date和Calendar类
时区和时间 —— 使用原来的API开发者不得不写额外逻辑处理时区逻辑,因此新的API可以使用Local、ZoneDate/TimeAPI处理时区问题。
3.使用LocalDate,LocalTime,LocalDateTime类API
最常用的类是
LocalDate, LocalTime, LocalDateTime
,见名思意,它们分别表示当前上下文的本地日期/时间。
这些类主要用在时区无需显示指定的场景。本节让介绍其最常用的API.
3.1.使用LocalDate
LocalDate 表示ISO格式日期 (yyyy-MM-dd),没有时间。
可以用来存储日期,如生日,付款日期等。当前日期可以用系统时钟创建:
LocalDate localDate = LocalDate.now();
LocalDate表示特定年,月,日,可以使用of方法或parse方法创建。举例:
LocalDate.of(2015, 02, 20);
LocalDate.parse("2015-02-20");
LocalDate提供多个工具方法可以获得不同信息。让我们快速了解下。
下面代码片段获得本地日期,然后加一天:
LocalDate tomorrow = LocalDate.now().plusDays(1);LocalDate tomorrow = LocalDate.now().plusDays(1);
下面代码获得当前日期,然后减去一个月。注意其枚举时间单位参数:
LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);
下面两行代码解析“2016-06-12”,然后获取星期几和月中那一天。注意其返回值,第一个为DayOfWeek,第二个是int。
DayOfWeek sunday = LocalDate.parse("2016-06-12").getDayOfWeek();
int twelve = LocalDate.parse("2016-06-12").getDayOfMonth();
也可以轻松测试闰年,示例代码如下:
boolean leapYear = LocalDate.now().isLeapYear();
两个日期比较,before和after很便捷:
boolean notBefore = LocalDate.parse("2016-06-12").isBefore(LocalDate.parse("2016-06-11"));
boolean isAfter = LocalDate.parse("2016-06-12").isAfter(LocalDate.parse("2016-06-11"));
可以从日期中获取边界。下面示例分别获取当天的开始及当月的第一天。
LocalDateTime beginningOfDay = LocalDate.parse("2016-06-12").atStartOfDay();
LocalDate firstDayOfMonth = LocalDate.parse("2016-06-12").with(TemporalAdjusters.firstDayOfMonth());
下面我们看看LocalTime类。
3.2 使用LocalTime
LocalTime表示时间,没有日期。与LocalDate类似,LocalTime能通过of和parse方法从系统时钟创建。
下面通过示例了解下常用API.
LocalTime now = LocalTime.now();
下面代码示例,我们通过解析字符串创建LocalTime表示06:30AM。
LocalTime sixThirty = LocalTime.parse("06:30");
也可以使用工厂方法创建LocalTime。示例如下:
LocalTime sixThirty = LocalTime.of(6, 30);
通过plus方法增加一小时。servenThirth为07:30AM.
LocalTime sevenThirty = LocalTime.parse("06:30").plus(1, ChronoUnit.HOURS);
提供便捷的方法获取时间单元。
int six = LocalTime.parse("06:30").getHour();
比较日期,是否晚于或早于特定时间。示例代码:
boolean isbefore = LocalTime.parse("06:30").isBefore(LocalTime.parse("07:30"));
LocalTime类中通过常量可以获得max,min,noon时间。在执行数据库时间范围查询时非常有用。下面代码表示23:59:59.99.
LocalTime maxTime = LocalTime.MAX
下面看看LocalDateTime类。
3.3使用LocalDateTime
LocalDateTime表示日期和时间组合。这时最常用的类,提供了丰富的API.
通过从系统时钟创建:
LocalDateTime.now();
通过工厂方法of或parse创建:
LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
LocalDateTime.parse("2015-02-20T06:30:00");
提供了工具API实现增加或减少单元日期或时间。如年,月,日,分钟等。
localDateTime.plusDays(1);
localDateTime.minusHours(2);
通过get方法获取时间单元。
localDateTime.getMonth();
4.使用ZonedDateTime
java8提供了ZonedDateTime类,用于处理带时区的日期和时间。ZoneId表示不同的时区。大约有40不同的时区。
下面代码创建一个时区:
ZoneId zoneId = ZoneId.of("Europe/Paris");
获取所有时区集合:
Set allZoneIds = ZoneId.getAvailableZoneIds();
把LocalDateTime转换成特定的时区:
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
ZonedDateTime提供parse方法获得特定时区的dateTime:
ZonedDateTime.parse("2015-05-03T10:15:30+01:00[Europe/Paris]");
另外和时区一起使用的类是OffsetDateTime类,OffsetDateTime是不变的,表示date-time偏移,存储所有日期和时间字段,精确至纳秒,从UTC/Greenwich计算偏移。
可以通过OffSetDateTime
实例结合ZoneOffset
创建LocalDateTime。
LocalDateTime localDateTime = LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
我们创建ZoneOffset,增加两个小时:
ZoneOffset offset = ZoneOffset.of("+02:00");
OffsetDateTime offSetByTwo = OffsetDateTime.of(localDateTime, offset);
先localDateTime的值为 2015-02-20 06:30 +02:00.
下面我们看如何通过Period和Duration类修改日期和时间值。
5.使用Period 和 Duration
Period类代表年,月,日的时间量,Duration类代表秒和纳秒的数量。
5.1使用Period
Period类主要用来修改给定日期值,或获取两日期之间的差值:
LocalDate initialDate = LocalDate.parse("2007-05-10");
通过Period可以操作日期:
LocalDate finalDate = initialDate.plus(Period.ofDays(5));
Period类有不同的get方法,如getYears,getMonths,getDays可以获得不同周期值。下面代码返回5,反应日期间隔:
int five = Period.between(finalDate, initialDate).getDays();
也可以通过ChronoUnit实现通用功能:
int five = ChronoUnit.DAYS.between(initialDate , initialDate);
使用Duration
与Period类似, Duration类用于处理时间, 下面代码创建LocalTime然后增加30秒。
LocalTime initialTime = LocalTime.of(6, 30, 0);
LocalTime finalTime = initialTime.plus(Duration.ofSeconds(30));
两个时间之间间隔通过Duration类获得时间单元。
int thirty = Duration.between(finalTime, initialTime).getSeconds();
通用可以通过ChronoUnit 类获得。
int thirty = ChronoUnit.SECONDS.between(finalTime, initialTime);
兼容原Date and Calendar
java8已经提供了toInstant方法,可以转换Date和Calendar实例至新的DateTime。
下面是原Date类中新增的方法:
public static Date from(Instant instant) {
try {
return new Date(instant.toEpochMilli());
} catch (ArithmeticException ex) {
throw new IllegalArgumentException(ex);
}
}
public Instant toInstant() {
return Instant.ofEpochMilli(getTime());
}
示例转换:
LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());
也可以通过Epoch时间构建LocalDateTime,Epoch指的是一个特定的时间:1970-01-01 00:00:00 UTC。
结果为2016-06-13T11:34:50:
LocalDateTime.ofEpochSecond(1465817690, 0, ZoneOffset.UTC);
7.Date and Time 格式化
java8提供了非常简单的方式格式化日期和时间:
LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JANUARY, 25, 6, 30);
下面代码传入一个ISO日期格式去格式化一个本地时间,结果为2015-01-25:
LocalDate localDate = localDateTime.format(DateTimeFormatter.ISO_DATE);
DateTimeFormatter 类提供了多个标准格式选项。也支持自定义的格式。下面示例代码结果为2015/01/25:
localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
也可以通过格式样式格式化,如SHORT, LONG or MEDIUM 。下面代码输出为25-Jan-2015 06:30:00:
localDateTime
.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.withLocale(Locale.UK);
总结
java8提供了丰富的API操作日期时间,相比较原来的API,使用更容易更便捷。如果使用jdk较低版本,可以使用joda库实现,本来java8API就是由joda提供。joda可以通过maven引入:
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.4</version>
</dependency>