源码学习系列,java8新特性,Date & Time API —— java.time

java.time是Java 8提供的一套新的线程安全的日期时间工具包,用于替代旧有的java.util.Datejava.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中,每个InstantDateTIme实例都代表着创建实例时那一瞬间的时间状态,每一次调整时间都将返回一个新的实例,因此它是线程安全的。

1. java.time下提供的常用工具类

类全限定名说明
Clock抽象时钟类,提供对当前时刻的访问。其子类有FixedClockOffsetClockSystemClockTickClock,但都需要通过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

1.1. Clock

如官方文档所说,Clock为使用者提供一个时钟,这个时钟的时间是随时变化的。同时它提供了时区的存储和获取、当前毫秒级时间戳的方法,用于替代TimeZone.getDefault()System.currentTimeMillis()两个方法。

Clock是一个抽象父类,它有四个私有子类,SystemClockFixedClockOffsetClockTickClock,分别提供不同业务场景的Clock实例。这四个子类不能直接使用,而是需要通过Clock提供的工厂方法来创建。

1.1.1. 方法介绍

常量或方法说明
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)根据传入的InstantZoneId,创建一个固定的时钟FixedClock实例
static Clock offset(Clock baseClock, Duration offsetDuration)根据传入的baseClockoffsetDuration,创建一个可偏移时间的时钟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)

根据传入的InstantZoneId,创建一个固定的时钟FixedClock实例,之所以为固定时钟是因为FixedClockInstant不可变,而其它几个Clock子类的Instant则是有可能变化的。

OffsetClockTickClockbaseClockFixedClock类型,那么它们的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)

根据传入的baseClockoffsetDuration,创建一个可偏移时间的时钟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();
}

除了OffsetClockTickClockSystemClockFixedClock都是要求在创建新实例时传入时区并存储下来,而OffsetClockTickClock存储的则是传入的baseClock的时区。毕竟任何代码也不能通过时间偏移量来确定时区,因为这并不可控。但在实际开发中,开发人员有可能通过时间偏移量来推导然后确定时区,比如+8:00推导为Asia/ChongqingAsia/Shanghai,开发人员可以任选其一,但API工具不能这样做。

Clock withZone(ZoneId zone)

由于OffsetClockTickClock不会存储时区,所以这两个子类的时区要通过baseClock来存储。因此在实现Clock withZone(ZoneId zone)方法时,SystemClockFixedClock会直接通过设置新时区来创建新的实例,而OffsetClockTickClock则是要通过设置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通过baseClockoffset偏移量相加来获取时间戳,如北京时间戳将会是UTC时间戳再加上8小时的毫秒的时间戳;
  • TickClock则是baseClock减去多余的毫秒后再返回时间戳,减去多余的毫秒根据其截断的时长得到

OffsetClockTickClockbaseClockFixedClock类型,那么它们的时间戳也同样是永远不会发生变化。

// 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通过baseClockoffset偏移量相加来获取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);
}

1.2. Duration

待续

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值