java 8 新的日期和时间API

LocalDate、LocalTime、Instant、Duration和Period

使用LocalDate和LocalTime

LocalDate是一个不可变对象,提供了简单的日期,不包含时间,也没有附带任何时区相关的信息。你可以通过静态方法of创建一个LocalDate实例。LocalDate提供了很多实用的方法来读取常用的值,比如年份、月份、星期几等。

1
2
3
4
5
6
7
8
9
10
LocalDate date = LocalDate.of(2014,3,18);
int year = date.getYear(); // 2014
Month month = date.getMonth(); // MARCH
int day = month.dayOfMonth(); // 18
DayOfWeek dow = date.getDayOfWeek(); // TUESDAY
int len = date.lengthOfMonth(); // 31(days in March)
boolean leap = date.isLeapYear(); // false

// 也可以通过工厂方法从系统时钟获取当前日期
LocalDate today = LocalDate.now();

你还可以传递一个TemporalField参数拿到同样的信息。TemporalField是一个接口,定义了如何访问tempoal对象某个字段的值。ChronoFIeld枚举实现了这个接口,所以你可以用get方法得到枚举元素的值。

1
2
3
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);

可以使用LocalTime来表示时间,可以使用of重载的两个工厂方法创建它的实例。第一个重载函数接收小时和分钟,第2个重载函数同时还接受秒。同LocalDate一样,LocalTime也提供了一些getter方法来访问这些变量的值。

1
2
3
4
5
6
7
8
LocalTime time = LocalTime.of(16,55,32);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();

int _hour = time.get(ChronoField.HOUR_OF_DAY);

LocalTime _time = LocalTime.now();

合并日期和时间

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

1
2
3
4
5
6
7
8
LocalDateTime dt1 = LocalDateTime.of(2018,9,15,16,50,30); // 直接创建
LocalDateTime dt2 = LocalDateTime.of(date,time); // 复合LocalDate和LocalTime
LocalDateTime dt3 = date.atTime(13,59,39); // 通过日期指定时间
LocalDateTime dt4 = time.atDate(date); // 通过时间指定日期
LocalDateTime dt5 = date.atTime(time);

LocalDate date = dt1.toLocalDate(); // 得到LocalDate
LocalTime time = dt1.toLocalTime(); // 得到LocalTime

机器的日期和时间格式

java.time.Instant是给机器使用的,包含秒和纳秒构成的数字,它是以Unix元年时间(UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算。你可以向静态方法ofEpochSecond传递一个代表秒数的值创建一个实例。它还有一个增强版本的方法,它接收第二个以纳秒为单位的参数,对传入的描述进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在0到999 999 999之间。
下面的这些调用会返回几乎相同的Instant对象。

1
2
3
4
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3,0);
Instant.ofEpochSecond(2,1_000_000_000); // 2秒之后再加上100万纳秒(1秒)
Instant.ofEpochSecond(4,-1_000_000_000); // 4秒之前的100万纳秒(1秒)

日期/时间间隔

表示2个日期/时间的间隔,可以使用Duration或Period。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LocalTime time1 = LocalTime.of(15,23);
LocalTime time2 = LocalTime.of(5,29);
Duration d1 = Duration.between(time1,time2);

LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.of(2019,1,1);
Duration d2 = Duration.between(date1,date2);

LocalDateTime dateTime1 = LocalDateTime.of(date1,time1);
LocalDateTime dateTime2 = LocalDateTime.of(date2,time2);
Duration d3 = Duration.between(dateTime1,dateTime2);

Instant instant1 = Instant.ofEpochSecond(3);
Instant instant2 = Instant.ofEpochSecond(5,2_000_000_000);
Duration d4 = Duration.between(instant1,instant2);

Duration threeMiniutes = Duration.ofMinutes(3);
Duration _threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

Period tenDyas = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2,6,1);

日期-时间内中表示时间间隔的通用方法

方法名 是否静态方法 方法描述
between 创建两个时间点之间的interval
from 由一个临时节点创建interval
of 由它的组成部分创建interval
parse 由字符串创建interval
addTo 创建该interval的副本,并将其叠加到某个指定的temporal对象
get 读取该interval的状态
isNegative 检查该interval是否是负值,不包含0
iszero 检查该interval的时长是否为0
minus 通过减去一定的时间创建该interval的副本
multipliedBy 将interval的值乘以某个标量创建该interval的副本
negated 以忽略某个时长的方式创建该interval的副本
plus 以增加某个指定时长的方式创建该interval的副本
subtrctFrom 从指定的temporal对象中减去该interval

上面介绍的这些日期-时间对象都是不可变的,是为了更好的支持函数式编程,确保线程安全,保持领域模式一致性。

操作、解析和格式化日期

如果已经有了一个LocalDate对象,如果想要修改日期,可以使用它的withAttribute和with方法,会创建一个副本,并按照需要修改的属性。

1
2
3
4
5
6
LocalDate _date1 = LocalDate.of(2019,5,15);
LocalDate _date2 = _date1.withYear(2018); // 2018-05-15
// 使用withAttribute方法
LocalDate _date3 = _date1.withDayOfMonth(20); // 2019-05-20
// 使用with方法
LocalDate _date4 = _date1.with(ChronoField.MONTH_OF_YEAR,2); // 2019-02-15

上面是比较直接的修改方式,也可以以相对方式修改日期,比如:

1
2
LocalDate _date5 = _date1.plusYears(1); // 2020-05-15
LocalDate _date6 = _date1.minusWeeks(1); // 2019-05-08

表示时间点的日期-时间类的通用方法

方法名 是否静态方法 方法描述
from 根据传入的Temporal对象创建实例
now 根据系统时钟创建Temporal实例
of 由Temporal对象的某个部分创建该对象的实例
parse 由字符串创建Temporal对象的实例
atOffset 将Temporal对象和某个时区偏移相结合
atZone 将Temporal对象和某个时区相结合
format 使用某个指定的格式器将Temporal对象转换为字符串
get 读取Temporal对象某一部分的值
minus 通过将当前Temporal对象减去一定的时长创建副本
plus 通过将当前Temporal对象增加一定的时长创建副本
with 以该Temporal对象为模板,将某些状态进行修改创建该对象的副本。

使用TemporalAdjuster
前面的日期操作都是相对比较直接的,你还可使用TemporalAdjuster来进行一些更复杂的操作。
比如:

1
LocalDate _date7 = _date1.with(nextOrSame(DayOfWeek.SUNDAY)); // 2019-05-19

TemporalAdjuster的工厂方法

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

如果上面的方法还不能满足你的要求,实现自己的TemporalAdjuster也很简单。实现TemporalAdjuster的adjustInto接口即可。
下面实现一个自定义的TemporalAdjuster,能够计算明天的日期,并过滤掉周六周日。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class NextWorkingDayAdjuster implements TemporalAdjuster {

@Override
public Temporal adjustInto(Temporal temporal) {
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;

if (dayOfWeek == DayOfWeek.FRIDAY) {
dayToAdd = 3;
}
else if (dayOfWeek == DayOfWeek.SATURDAY) {
dayToAdd = 2;
}
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
}
}

NextWorkingDayAdjuster adjuster = new NextWorkingDayAdjuster();
LocalDate _date8 = _date1.with(adjuster);

日期解析和格式化

java8日期格式化的类在java.time.format包中,其中最重要的类是DateTimeFormatter。DateTimeFormatter提供了大量的预定义格式的DateTimeFormatter。
比如:
格式化:

1
2
String s1 = _date1.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = _date1.format(DateTimeFormatter.ISO_LOCAL_DATE);

解析:

1
LocalDate dd1 = LocalDate.parse("20190112",DateTimeFormatter.BASIC_ISO_DATE);

这些DateTimeFormmater的实例都是线程安全的,所以你能够以单例的形式创建Formatter实例,并在多个线程之间共享。

你还可以通过DateTimeFormatter的ofPattern方法创建。比如:

1
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

另外,你还可以通过DateTimeFormatterBuilder来创建。
比如

1
2
3
4
5
6
7
8
BASIC_ISO_DATE = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue(YEAR, 4)
.appendValue(MONTH_OF_YEAR, 2)
.appendValue(DAY_OF_MONTH, 2)
.optionalStart()
.appendOffset("+HHMMss", "Z")
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);

处理不同的时区

新的java.time.ZoneId是对老的java.util.TimeZone的替代,大大简化了时区处理的操作。
你可以按照ZoneId.of(地区ID标识)来得到指定时区。

1
ZoneId romeZone = ZoneId.of("Europe/Rome");

或者通过toZoneId()来将老的时区对象转换为ZoneId。

1
ZoneId zoneId = TimeZone.getDefault().toZoneId();

有了ZoneId,你可以将它和LocalDate、LocalDateTime或Instant整合起来,构造为一个ZoneDateTime实例,它代表了相对于指定时区的时间点。

1
2
3
ZonedDateTime zonedDateTime = date.atStartOfDay(zoneId);
zonedDateTime = dateTime1.atZone(romeZone);
zonedDateTime = instant.atZone(romeZone);

ZoneOffset是ZoneId的一个子类,表示的是当前时间与伦敦格林尼治子午线时间的差异。比如纽约落后于伦敦5小时,可以使用下面的方式表示:

1
2
3
ZoneOffset zoneOffset = ZoneOffset.of("-05:00"); // 美国东部时间偏移量
LocalDateTime localDateTime = LocalDateTime.of(2018,9,19,15,30,29);
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime,zoneOffset);

-05:00的偏差实际上对应的是美国东部标准时间。注意:这种方式定义的ZoneOffset没有考虑夏令时的影响,所以大多数情况下,不推荐使用。

参考《java8实战》

Donny wechat
欢迎关注我的个人公众号
打赏,是超越赞的一种表达。
Show comments from Gitment