1.Java时间API历史

Java在1.0引入了Date类。从事后看来,这个类的设计当时是没有经过深思熟虑的。这个类中的很多方法在1.1版本的JDK中就被废弃了。

Java在1.1引入了Calendar类。这个类实际上也没有得到好评:这个类的实例是可变的,并且这个类也没有处理闰秒的问题。

Java在1.8引入了java.timeAPI。这套API可以比较好的弥补以前的时间API中的一些缺陷。

2.时间线与Instant

Java中引入Instant来代表时间轴上的一个点。时间原点epoch,是1970年本初子午线经过格林威治皇家观测台的时间。从这远点开始,每天的时间用86400秒来度量,精度为纳秒。

可以使用Instant.now()来获取当前时间点,两个Instant之间可以使用Duration.between()来比较时间间隔。

可以通过Instant.equals()和compareTo()方法来比较两个时间点得大小和相等性。

Instant start = Instant.now();
runSomeAlorithm();
Instant end = Instant.now(); Duration timeElapsed = Duration.between(start, end);

Instant类中有很多重载的加、减方法。Duration类中除了基本的加减方法,还有一些乘除方法,以及判断是否负数(isNegative(),isZero())。详情参见API文档。

3.本地日期时间

Java中的人类方便阅读的日期时间有两类:本地日期时间和时区日期时间。本地日期时间只有日期和/或当天的时间信息,没有时区的信息,因此并不对应Instant。而时区时间则可以对应Instant。

Java 8中使用LocalDate表示日期:

LocalDate today = LocalDate.now();
LocalDate birthDay = LocalDate.of(1989, 7, 16);
LocalDate dob = LocalDate.of(1989, Month.JULY, 16);

java.util.Date不同的是,LocalDate中的月不是从0开始的。年份也不是从1900开始。LocalDate有一些常用的加、减方法。也可以获取某个LocalDate是一个月中的第几天,一年中的第几天等。具体详见API文档。

4.日期Adjusters

有时候需要计算类似于“每个月的第一个星期二”这类的需求。使用TemporalAdjusters类可以提供一堆静态方法来做一些通用的调节。

LocalDate firstTuesday = LocalDate.of(year, month, day)
    .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));

也可以通过实现TemporalAdjuster接口来实现自己的调整器:

TemporalAdjuster NEXT_WORKDAY = w -> {
    LocalDate result = (LocalDate) w; do { result = result.plusDays(1); }while (result.getDayOfWeek().getValue() >= 6); return result; }; LocalDate backToWork = today.with(NEXT_WORKDAY);

5.本地时间

LocalTime表示一天中的时间。例如:15:00:00. LocalTime类也有now()和of()方法来构建实例。

LocalTime rightNow = LocalTime.now();
LocalTime bedTime = LocalTime.of(22, 30);

LocalTime有一系列的加减操作方法,判断两个时间哪个在前哪个在后的方法,变化指定时间的方法等,具体参见API文档。

6.时区时间

时区时间使用ZonedDateTime来表示。该类中有一系列的静态方法来创建时区时间:

public static ZonedDateTime of(int year,
                               int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone) public static ZonedDateTime now(ZoneId zone) public static ZonedDateTime now()

看一个例子:

ZonedDateTime time = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of("America/New_York")); //1969-07-16T09:32-04:00[America/New_York]

时区时间的使用相对来说比较复杂,所以Java API的设计者建议开发者更多的使用本地日期时间。ZonedDateTime中的方法与前面提到的类大同小异。详见API文档。

7.格式化与解析

DateTimeFormatter提供了三种formatter来打印日期时间值,例如:
BASIC_ISO_DATE 年月日,时区偏移量 19690716-0500这几种方式其实都不适合我们常用的yyyy-MM-dd的表示方法。

如果要自定义日期时间格式,可以使用ofPattern方法:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");

其中,e表示day of week。其中e表示3,E表示Wed, EEEE表示Wednesday, EEEEE:W

具体的这些模式元素参见API文档即可。

8.与老的日期时间API互操作:

Instant<->java.util.Date Date.from(instant) date.toInstant() ZonedDateTime<->java.util.GregorianCalendar GregorianCalendar.from(zonedDateTime) cal.toZonedDateTime() Instant<->java.sql.Timestamp Timestamp.from(instant) timestamp.toInstant() LocalDateTime<->java.sql.Timestamp Timestamp.valueOf(localDateTime) timestamp.toLocalDateTime() LocalDate<->java.sql.Date Date.valueOf(localDate) date.toLocalDate(); Time.valueOf(localTime) time.toLocalTime() DateTimeFormatter<->java.text.DateFormat format.toFormat() None java.util.TimeZone<->ZoneId TimeZone.getTimeZone(id) timeZone.toZoneId() java.nio.file.attribute.FileTime<->Instant FileTime.from(instant) fileTime.toInstant()