java.time
是Java 8提供的一套新的线程安全的日期时间工具包,用于替代旧有的java.util.Date
和java.util.Calendar
等日期时间操作工具。java.time
基于JSR 310
规范,脱胎于joda-time
,在Java 8以前,但凡用过joda-time
,都不会再想回去使用java.util.Calendar
来操作日期时间。
JSR 310
引入两个时间概念,一是计算机时间Instant
,精确到纳秒,它以1970 年 1 月 1 日
为纪元,返回纪元到当前的时间偏移量,提供秒级和毫秒级的时间戳方法;二是人类可直观感受的DateTime
系列,包含时区、日期、时间等,并提供更为精确的年、月、星期、日、上午/下午、小时、分钟、秒、毫秒、纳秒的计算。java.time
中提供的工具方法很多,
在这套
Date & Time API
中,每个Instant
或DateTIme
实例都代表着创建实例时那一瞬间的时间状态,每一次调整时间都将返回一个新的实例,因此它是线程安全的。
java.time
下提供的常用工具类类全限定名 | 说明 |
---|---|
Clock | 抽象时钟类,提供对当前时刻的访问。其子类有FixedClock 、OffsetClock 、SystemClock 、TickClock ,但都需要通过Clock 提供的工厂方法来访问 |
Duration | 基于时间的时段类,可用于描述如视频、音频、持续时间等时长 |
Instant | 瞬时时间类,某个瞬间的时间点 |
LocalDate | 与时区无关的日期类,通常可以用它来获取本机日期,如2019-05-19 |
LocalDateTime | 与时区无关的日期时间类,通常可以用它来获取本机日期时间,如2019-05-19T14:50:11.926 |
LocalTime | 与时区无关的时间类,通常可以用它来获取本机时间,如14:50:11.926 |
MonthDay | 提供月-日的访问类,可使用该类存储生日,如--05-19 |
OffsetDateTime | 指定了偏移量的日期时间类,如2019-05-19T14:50:11.926+08:00 ,描述UTC+8 |
OffsetTime | 指定了偏移量的时间类,如14:50:11.926+08:00 ,描述UTC+8 |
Period | 基于日期的时段类,可用于描述在某地的停留天数、某物的使用天数等 |
Year | 提供年的访问类,如2019 |
YearMonth | 提供年-月的访问类,如2019-05 |
ZonedDateTime | 指定了时区的日期时间类,如2019-05-19T14:50:11.926+08:00[Asia/Shanghai] ,含偏移量,描述本机时间的时区 |
ZoneId | 抽象时区ID类,如Asia/Shanghai |
ZoneOffset | 时区偏移类,是时区ID类子类,描述某个时区的时间偏移量,如+8:00 |
"时钟,计时器"
Clock clock = Clock.systemUTC();
long startTime = clock.millis();
Thread.sleep(888L);
long endTime = clock.millis();
long time = end - start; --> 888
"当前系统13位毫秒级时间戳"
Clock.systemUTC().millis(); --> System.currentTimeMillis();
"当前系统默认时区"
Clock.systemDefaultZone().getZone(); --> TimeZone.getDefault();
"时间差"
LocalTime startTime = LocalTime.now();
LocalTime endTime = LocalTime.now().plusHours(8).plusSeconds(80L);
Duration.between(startTime, endTime).toString(); --> PT8H1M20S
"日期差"
LocalDate startDate = LocalDate.now();
LocalDate endDate = LocalDate.now().plusDays(124L);
Period.between(startDate, endDate).toString(); --> P4M1D
"不含时区不含偏移的本机日期时间、日期、时间"
LocalDateTime.now(); --> 2019-05-20T00:23:17.560
LocalDate.now(); --> 2019-05-20
LocalTime.now(); --> 00:23:17.560
"三小时前、三分钟前、三秒前。同LocalDateTime"
LocalTime.now().minus(Duration.ofHours(3)); --> 21:23:17.560
LocalTime.now().minus(3L, ChronoUnit.HOURS); --> 21:23:17.560
LocalTime.now().minus(Duration.ofMinutes(3)); --> 00:20:17.560
LocalTime.now().minus(3L, ChronoUnit.MINUTES); -->00:20:17.560
LocalTime.now().minus(Duration.ofSeconds(3)); --> 00:23:14.560
LocalTime.now().minus(3L, ChronoUnit.SECONDS); --> 00:23:14.560
"三小时后、三分钟后、三秒后。同LocalDateTime"
LocalTime.now().plus(Duration.ofHours(3)); --> 03:23:17.560
LocalTime.now().plus(3L, ChronoUnit.HOURS); --> 03:23:17.560
LocalTime.now().plus(Duration.ofMinutes(3)); --> 00:26:17.560
LocalTime.now().plus(3L, ChronoUnit.MINUTES); -->00:26:17.560
LocalTime.now().plus(Duration.ofSeconds(3)); --> 00:23:20.560
LocalTime.now().plus(3L, ChronoUnit.SECONDS); --> 00:23:20.560
"三天前、三周前、三月前、三年前。同LocalDateTime"
LocalDate.now().minus(Period.ofDays(3)); --> 2019-05-17
LocalDate.now().minus(3L, ChronoUnit.DAYS); --> 2019-05-17
LocalDate.now().minus(Period.ofWeeks(3)); --> 2019-04-29
LocalDate.now().minus(3L, ChronoUnit.WEEKS); --> 2019-04-29
LocalDate.now().minus(Period.ofYears(3)); --> 2016-05-20
LocalDate.now().minus(3L, ChronoUnit.YEARS); --> 2016-05-20
"三天后、三周后、三月后、三年后。同LocalDateTime"
LocalDate.now().plus(Period.ofDays(3)); -->2019-05-23
LocalDate.now().plus(3L, ChronoUnit.DAYS); --> 2019-05-23
LocalDate.now().plus(Period.ofWeeks(3)); -->2019-06-10
LocalDate.now().plus(3L, ChronoUnit.WEEKS); --> 2019-06-10
LocalDate.now().plus(Period.ofMonths(3)); -->2019-08-20
LocalDate.now().plus(3L, ChronoUnit.MONTHS); --> 2019-08-20
LocalDate.now().plus(Period.ofYears(3)); -->2022-05-20
LocalDate.now().plus(3L, ChronoUnit.YEARS); --> 2022-05-20
"是否在日期之前、之后、相等"
LocalDateTime.now().isBefore(LocalDateTime.now().plusDays(1L)); --> true
LocalDateTime.now().isAfter(LocalDateTime.now().plusDays(1L)); --> false
LocalDateTime.now().isEqual(LocalDateTime.now().plusDays(1L)); --> false
"当前年、月、日、当前星期第几天、小时、分、秒"
LocalDateTime.now().get(ChronoField.YEAR); --> 2019
LocalDateTime.now().get(ChronoField.MONTH_OF_YEAR); --> 5
LocalDateTime.now().get(ChronoField.DAY_OF_MONTH); --> 20
LocalDateTime.now().get(ChronoField.DAY_OF_WEEK); --> 1
LocalDateTime.now().get(ChronoField.HOUR_OF_DAY); --> 0
LocalDateTime.now().get(ChronoField.MINUTE_OF_HOUR); --> 23
LocalDateTime.now().get(ChronoField.SECOND_OF_MINUTE); --> 17
"快速格式化"
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); --> 2019-05-20 00:23:17
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()); --> 2019-05-20 00:23:17
LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); --> 2019-05-20
DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now()); --> 2019-05-20
LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); --> 00:23:17
DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalTime.now()); --> 00:23:17
"年、年月、月日、月、星期"
Year.now(); --> 2019
YearMonth.now(); --> 2019-05
MonthDay.now(); --> --05-20
Month.from(MonthDay.now()); --> MAY
Month.from(LocalDateTime.now()); --> MAY
DayOfWeek.from(LocalDate.now()); --> MONDAY
如官方文档所说,Clock
为使用者提供一个时钟,这个时钟的时间是随时变化的。同时它提供了时区的存储和获取、当前毫秒级时间戳的方法,用于替代TimeZone.getDefault()
和System.currentTimeMillis()
两个方法。
Clock
是一个抽象父类,它有四个私有子类,SystemClock
、FixedClock
、OffsetClock
和TickClock
,分别提供不同业务场景的Clock
实例。这四个子类不能直接使用,而是需要通过Clock
提供的工厂方法来创建。
常量或方法 | 说明 |
---|---|
static Clock systemUTC() |
返回一个基于UTC 时间的系统时钟SystemClock ,时间偏移量为0 。方法实现为new SystemClock(ZoneOffset.UTC) |
static Clock systemDefaultZone() |
返回一个基于默认时区的系统时钟,时间偏移量为默认时区的偏移量,可通过修改系统属性user.timezone 来调整Java的默认时区。方法实现为new SystemClock(ZoneId.systemDefault()) |
static Clock system(ZoneId zone) |
通过指定时区来获取系统时钟。方法实现为new SystemClock(zone) |
static Clock tickSeconds(ZoneId zone) |
返回一个截断至整秒的时钟TickClock ,它始终将秒之后的纳秒设置为0 |
static Clock tickMinutes(ZoneId zone) |
返回一个截断至分钟的时钟TickClock ,它始终将分钟之后的秒设置为0 |
static Clock tick(Clock baseClock, Duration tickDuration) |
返回一个根据tickDuration 进行截断的时钟TickClock 实例 |
static Clock fixed(Instant fixedInstant, ZoneId zone) |
根据传入的Instant 和ZoneId ,创建一个固定的时钟FixedClock 实例 |
static Clock offset(Clock baseClock, Duration offsetDuration) |
根据传入的baseClock 和offsetDuration ,创建一个可偏移时间的时钟OffsetClock 实例 |
ZoneId getZone() | 获取当前Clock 实例的时区 |
Clock withZone(ZoneId zone) | 为Clock 设置时区,并返回一个新的Clock 类,新Clock 类的具体类型与实现该方法的子类相关,原有Clock 实例不变。 |
long millis() | 获取当前毫秒级时间戳,用于替代System.currentTimeMillis() 。方法实现为instant().toEpochMilli() |
Instant instant() | 获取当前Clock 实例的Instant 对象,该对象只与计算机时间相关,与时区便宜没有任何关系。根据Clock 类的具体实现类型,其Instant 对象的转换方式也不同 |
static Clock systemUTC()
工厂方法,创建一个基于UTC
时间偏移量为0
的系统时钟SystemClock
public static Clock systemUTC() {
return new SystemClock(ZoneOffset.UTC);
}
System.out.println(Clock.systemUTC().instant()); --> 2019-05-19T12:16:33.473Z 本机时间为 2019-05-19T20:16:33.483
System.out.println(Clock.systemUTC().getZone()); --> Z
static Clock systemDefaultZone()
工厂方法,创建一个基于默认时区的系统时钟SystemClock
,默认时区可通过系统变量user.timezone
修改。
测试本机默认时区为Asia/Shanghai
public static Clock systemDefaultZone() {
ZoneOffset.UTC实际上就是偏移量为0的时区,ZoneOffset.ofTotalSeconds(0)
return new SystemClock(ZoneId.systemDefault());
}
System.out.println(Clock.systemDefaultZone().getZone()); --> Asia/Shanghai
static Clock system(ZoneId zone)
返回一个指定时区的系统时钟SystemClock
,时区可通过ZoneId
获取或创建,如ZoneId.of("Asia/Shanghai")
。所有支持的时区可在ZoneId.getAvailableZoneIds()
方法返回的Set
集合中查找
public static Clock system(ZoneId zone) {
Objects.requireNonNull(zone, "zone");
return new SystemClock(zone);
}
System.out.println(Clock.system(ZoneId.systemDefault()).getZone()); --> Asia/Shanghai
System.out.println(Clock.system(ZoneId.of("Europe/London")).getZone()); --> Europe/London
static Clock tickSeconds(ZoneId zone)
返回一个截断至整秒的时钟TickClock
,它始终将秒之后的纳秒设置为0
,通过测试代码可看到该TickClock
已经将毫秒清零。TickClock
实例在方法中是基于SystemClock
创建的。
public static Clock tickSeconds(ZoneId zone) {
NANOS_PER_SECOND = 1000_000_000L
return new TickClock(system(zone), NANOS_PER_SECOND);
}
System.out.println(Clock.systemUTC().millis()); --> 1558268804056
System.out.println(Clock.tickSeconds(ZoneId.systemDefault()).millis()); --> 1558268804000
System.out.println(Clock.systemUTC().instant()); --> 2019-05-19T12:26:44.056Z
System.out.println(Clock.tickSeconds(ZoneId.of("Europe/London")).instant()); --> 2019-05-19T12:26:44Z
static Clock tickMinutes(ZoneId zone)
返回一个截断至分钟的时钟TickClock
,它始终将分钟之后的秒设置为0
,通过测试代码可看到该TickClock
已经将秒清零。TickClock
实例在方法中是基于SystemClock
创建的。
public static Clock tickMinutes(ZoneId zone) {
NANOS_PER_MINUTE = 60_000_000_000L
return new TickClock(system(zone), NANOS_PER_MINUTE);
}
System.out.println(Clock.systemUTC().millis()); --> 1558268973207
System.out.println(Clock.tickMinutes(ZoneId.systemDefault()).millis()); --> 1558268940000
System.out.println(Clock.systemUTC().instant()); --> 2019-05-19T12:29:33.207Z
System.out.println(Clock.tickMinutes(ZoneId.of("Europe/London")).instant()); --> 2019-05-19T12:29:00Z
static Clock tick(Clock baseClock, Duration tickDuration)
返回一个根据tickDuration
进行截断的时钟TickClock
实例,被截掉的纳米时间段由tickDuration.toNanos()
控制。
public static Clock tick(Clock baseClock, Duration tickDuration) {
Objects.requireNonNull(baseClock, "baseClock");
Objects.requireNonNull(tickDuration, "tickDuration");
截断的时长不能未负数
if (tickDuration.isNegative()) {
throw new IllegalArgumentException("Tick duration must not be negative");
}
获取截断时长的纳秒
long tickNanos = tickDuration.toNanos();
if (tickNanos % 1000_000 == 0) {
// ok, no fraction of millisecond
} else if (1000_000_000 % tickNanos == 0) {
// ok, divides into one second without remainder
} else {
throw new IllegalArgumentException("Invalid tick duration");
}
// 小于等于1时直接使用baseClock
if (tickNanos <= 1) {
return baseClock;
}
return new TickClock(baseClock, tickNanos);
}
System.out.println(Clock.tickSeconds(ZoneId.systemDefault()).millis()); --> 1558269350000
System.out.println(Clock.tick(clock, Duration.ofSeconds(1L)).millis()); --> 1558269350000
System.out.println(Clock.tick(clock, Duration.ofMillis(1000L)).millis()); --> 1558269350000
System.out.println(Clock.tickMinutes(ZoneId.systemDefault()).millis()); --> 1558269300000
System.out.println(Clock.tick(clock, Duration.ofMinutes(1L)).millis()); --> 1558269300000
System.out.println(Clock.tick(clock, Duration.ofSeconds(60L)).millis()); --> 1558269300000
根据示例得知Clock tickSeconds(ZoneId zone)
等同于Clock.tick(clock, Duration.ofSeconds(1L))
和Clock.tick(clock, Duration.ofMillis(1000L))
Clock tickMinutes(ZoneId zone)
则等同于Clock.tick(clock, Duration.ofMinutes(1L))
和Clock.tick(clock, Duration.ofSeconds(60L))
static Clock fixed(Instant fixedInstant, ZoneId zone)
根据传入的Instant
和ZoneId
,创建一个固定的时钟FixedClock
实例,之所以为固定时钟是因为FixedClock
的Instant
不可变,而其它几个Clock
子类的Instant
则是有可能变化的。
若
OffsetClock
和TickClock
的baseClock
是FixedClock
类型,那么它们的Instant也同样是不可变的。
public static Clock fixed(Instant fixedInstant, ZoneId zone) {
Objects.requireNonNull(fixedInstant, "fixedInstant");
Objects.requireNonNull(zone, "zone");
return new FixedClock(fixedInstant, zone);
}
Clock clock = Clock.systemUTC(); --> SystemClock
Clock fixedClock = Clock.fixed(clock.instant(), ZoneId.systemDefault()); --> FixedClock
System.out.println(clock.millis()); --> 1558270080378
System.out.println(fixedClock.millis()); --> 1558270080378
Thread.sleep(1000L); --> 休眠一秒
System.out.println(clock.millis()); --> 1558270081380,clock已变化
System.out.println(fixedClock.millis()); --> 1558270080378
System.out.println(Clock.fixed(fixedClock.instant(), ZoneId.systemDefault()).millis()); --> 1558270080378
static Clock offset(Clock baseClock, Duration offsetDuration)
根据传入的baseClock
和offsetDuration
,创建一个可偏移时间的时钟OffsetClock
实例。
public static Clock offset(Clock baseClock, Duration offsetDuration) {
Objects.requireNonNull(baseClock, "baseClock");
Objects.requireNonNull(offsetDuration, "offsetDuration");
偏移量为0时,直接使用baseClock
if (offsetDuration.equals(Duration.ZERO)) {
return baseClock;
}
return new OffsetClock(baseClock, offsetDuration);
}
Clock clock = Clock.systemUTC();
System.out.println(clock.instant()); UTC 0 --> 2019-05-19T12:58:06.150Z
System.out.println(Clock.offset(clock, Duration.ofHours(13L)).instant()); +13:00 --> 2019-05-20T01:58:06.170Z
System.out.println(Clock.offset(clock, Duration.ofHours(-13L)).instant()); -13:00 --> 2019-05-18T23:58:06.170Z
# 根据当前本机时间偏移量创建`OffsetClock`,本机偏移量为+8:00
int totalSeconds = OffsetDateTime.now().getOffset().getTotalSeconds();
System.out.println(Clock.offset(clock, Duration.ofSeconds(totalSeconds )).instant()); +8:00 --> 2019-05-19T20:58:06.170Z
ZoneId getZone()
// SystemClock FixedClock
public ZoneId getZone() {
return zone;
}
// OffsetClock TickClock
public ZoneId getZone() {
return baseClock.getZone();
}
除了OffsetClock
和TickClock
,SystemClock
和FixedClock
都是要求在创建新实例时传入时区并存储下来,而OffsetClock
和TickClock
存储的则是传入的baseClock
的时区。毕竟任何代码也不能通过时间偏移量来确定时区,因为这并不可控。但在实际开发中,开发人员有可能通过时间偏移量来推导然后确定时区,比如+8:00
推导为Asia/Chongqing
或Asia/Shanghai
,开发人员可以任选其一,但API工具不能这样做。
Clock withZone(ZoneId zone)
由于OffsetClock
和TickClock
不会存储时区,所以这两个子类的时区要通过baseClock
来存储。因此在实现Clock withZone(ZoneId zone)
方法时,SystemClock
和FixedClock
会直接通过设置新时区来创建新的实例,而OffsetClock
和TickClock
则是要通过设置baseClock
的新时区来创建新的实例。
// SystemClock
public Clock withZone(ZoneId zone) {
if (zone.equals(this.zone)) { // intentional NPE
return this;
}
return new SystemClock(zone);
}
// FixedClock
public Clock withZone(ZoneId zone) {
if (zone.equals(this.zone)) { // intentional NPE
return this;
}
return new FixedClock(instant, zone);
}
// OffsetClock
public Clock withZone(ZoneId zone) {
if (zone.equals(baseClock.getZone())) { // intentional NPE
return this;
}
return new OffsetClock(baseClock.withZone(zone), offset);
}
// TickClock
public Clock withZone(ZoneId zone) {
if (zone.equals(baseClock.getZone())) { // intentional NPE
return this;
}
return new TickClock(baseClock.withZone(zone), tickNanos);
}
long millis()
SystemClock
通过System.currentTimeMillis()
获取时间戳,每次调用都会产生新值;FixedClock
通过传入的Instant
来获取时间戳,所以时间戳永远不会发生变化;OffsetClock
通过baseClock
和offset
偏移量相加来获取时间戳,如北京时间戳将会是UTC
时间戳再加上8
小时的毫秒的时间戳;TickClock
则是baseClock
减去多余的毫秒后再返回时间戳,减去多余的毫秒根据其截断的时长得到若
OffsetClock
和TickClock
的baseClock
是FixedClock
类型,那么它们的时间戳也同样是永远不会发生变化。
// SystemClock
public long millis() {
return System.currentTimeMillis();
}
// FixedClock
public long millis() {
return instant.toEpochMilli();
}
// OffsetClock
public long millis() {
return Math.addExact(baseClock.millis(), offset.toMillis());
}
// TickClock
public long millis() {
long millis = baseClock.millis();
1毫秒等于1000000纳秒,先将tickNanos转换为毫秒,然后在通过求余的方式获得要截断的毫秒数
return millis - Math.floorMod(millis, tickNanos / 1000_000L);
}
Instant instant()
SystemClock
通过System.currentTimeMillis()
获取最新的Instant
对象,每次调用都会产生新值;FixedClock
通过传入的Instant
来获取,Instant
对象永远不会发生变化;OffsetClock
通过baseClock
和offset
偏移量相加来获取Instant
;TickClock
则是baseClock
的毫秒值减去多余的毫秒后再获取最新的Instant
对象,减去多余的毫秒根据其截断的时长得到// SystemClock
public Instant instant() {
return Instant.ofEpochMilli(millis());
}
// FixedClock
public Instant instant() {
return instant;
}
// OffsetClock
public Instant instant() {
return baseClock.instant().plus(offset);
}
// TickClock
public Instant instant() {
if ((tickNanos % 1000_000) == 0) {
long millis = baseClock.millis();
1毫秒等于1000000纳秒,先将tickNanos转换为毫秒,然后在通过求余的方式获得要截断的毫秒数
return Instant.ofEpochMilli(millis - Math.floorMod(millis, tickNanos / 1000_000L));
}
Instant instant = baseClock.instant();
long nanos = instant.getNano();
long adjust = Math.floorMod(nanos, tickNanos);
return instant.minusNanos(adjust);
}
待续