??作者简介:Java领域优质创作者🏆,博客专家认证🏆 ??技术活,该赏 ??点赞 👍 收藏 ?再看,养成习惯
大家好,我是小虚竹。之前有粉丝私聊我,问能不能把JAVA8 新的日期时间API(JSR-310)知识点梳理出来。答案是肯定的,谁让我宠粉呢。由于内容偏多(超十万字了),会拆成多篇来写。
闲话就聊到这,请看下面的正文。
常用于计算的类介绍
介绍下java8 中提供了几个常用于计算的类:
- Duration:表示秒和纳秒的时间量
- Period:表示年月日的时间量
- TemporalUnit:日期时间的基本单位
- TemporalField:日期时间的属性
- ValueRange:表示取值范围
Duration
Duration类说明
包路径:java.time.Duration
public final class Duration
implements TemporalAmount, Comparable<Duration>, Serializable {
private final long seconds;
private final int nanos;
...
}
Duration 是TemporalAmount 的实现类,类里包含两个变量seconds 和 nanos ,所以Duration 是由秒和纳秒组成的时间量。
一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。
Duration常用的用法
创建Duration对象
Duration 适合处理较短的时间,需要更高的精确性。我们能使用between()方法比较两个瞬间的差:
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
可以通过LocalDateTime 类获取获取Duration对象
LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);
Duration duration = Duration.between(first, second);
System.out.println(duration);
访问Duration的时间
Duration 对象中可以获取秒和纳秒属性。但没有毫秒属性,跟System.getCurrentTimeMillis()不同。
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("纳秒:"+duration.getNano());
可以转换整个时间成其他单位,如纳秒,毫秒,分钟,小时,天
Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("纳秒:"+duration.getNano());
System.out.println("纳秒:"+duration.toNanos());
System.out.println("毫秒:"+duration.toMillis());
System.out.println("分:"+duration.toMinutes());
System.out.println("小时:"+duration.toHours());
System.out.println("天:"+duration.toDays());
由图上可知,getNano 方法和toNanos 方法不太一样,前者是获取这段时间的小于1s的部分,后者是整个时间转化为纳秒。
Duration计算
plusNanos()
plusMillis()
plusSeconds()
plusMinutes()
plusHours()
plusDays()
minusNanos()
minusMillis()
minusSeconds()
minusMinutes()
minusHours()
minusDays()
以plusSeconds 和minusSeconds 为例:
LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);
Duration duration = Duration.between(first, second);
System.out.println(duration);
Duration duration1 = duration.plusSeconds(10);
System.out.println("plusSeconds 后:"+duration);
System.out.println("plusSeconds 后新的Duration对象:"+duration1);
Duration duration2 = duration.minusSeconds(10);
System.out.println("minusSeconds 后:"+duration);
System.out.println("minusSeconds 后新的Duration对象:"+duration2);
由上面的验证可知,这些计算方法执行后,会返回一个新的Duration对象,原先的Duration对象不变。
Period
Period类说明
包路径:java.time.Period
public final class Period
implements ChronoPeriod, Serializable {
private final int years;
private final int months;
private final int days;
...
}
Period 是ChronoPeriod 的实现类,类里包含两个变量years ,months 和 days ,所以Period 是由年,月和日组成的时间量。
Period常用的用法
创建Period对象
LocalDate first = LocalDate.of(2021, 8, 29);
LocalDate second = LocalDate.of(2022, 9, 30);
Period period = Period.between(first, second);
System.out.println(period);
访问Period的时间
LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
System.out.println("年:"+period.getYears());
System.out.println("月:"+period.getMonths());
System.out.println("日:"+period.getDays());
可以转换整个时间成其他单位,月
LocalDate first = LocalDate.of(2021, 8, 29);
LocalDate second = LocalDate.of(2022, 9, 30);
Period period = Period.between(first, second);
System.out.println(period);
System.out.println("月:"+period.toTotalMonths());
由图上可知,getMonths 方法和toTotalMonths 方法不太一样,前者是获取这段时间的月的部分,后者是整个时间转化为以月为单位长度。
toTotalMonths 源码:
public long toTotalMonths() {
return years * 12L + months;
}
Duration计算
plusDays()
plusMonths()
plusYears()
minusDays()
minusMonths()
minusYears()
以plusMonths 和minusMonths 为例:
LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
Period period1 = period.plusMonths(1);
System.out.println("plusMonths 后:"+period);
System.out.println("plusMonths 后新的Period对象:"+period1);
Period period2 = period.minusMonths(1);
System.out.println("minusMonths 后:"+period);
System.out.println("minusMonths 后新的Period对象:"+period2);
由上面的验证可知,这些计算方法执行后,会返回一个新的Period对象,原先的Period对象不变。
TemporalUnit
TemporalUnit类说明
包路径:java.time.temporal.TemporalUnit
public interface TemporalUnit {
...
}
public enum ChronoUnit implements TemporalUnit {
private final String name;
private final Duration duration;
...
}
TemporalUnit 主要实现类是枚举类型ChronoUnit
一个ChronoUnit成员会维护一个字符串名字属性name和一个Duration类型的实例。
其中ChronoUnit枚举了标准的日期时间单位集合,就是常用的年、月、日、小时、分钟、秒、毫秒、微秒、纳秒,这些时间单位的时间量到底是多少,代表多长的时间,在该枚举类中都有定义。
public enum ChronoUnit implements TemporalUnit {
NANOS("Nanos", Duration.ofNanos(1)),
MICROS("Micros", Duration.ofNanos(1000)),
MILLIS("Millis", Duration.ofNanos(1000_000)),
SECONDS("Seconds", Duration.ofSeconds(1)),
MINUTES("Minutes", Duration.ofSeconds(60)),
HOURS("Hours", Duration.ofSeconds(3600)),
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
DAYS("Days", Duration.ofSeconds(86400)),
WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
YEARS("Years", Duration.ofSeconds(31556952L)),
DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),
CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),
MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),
ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),
FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));
private final String name;
private final Duration duration;
private ChronoUnit(String name, Duration estimatedDuration) {
this.name = name;
this.duration = estimatedDuration;
}
···
}
ChronoUnit常用的用法
LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime offset = localDateTime.plus(1, ChronoUnit.DAYS);
Assert.assertNotSame(localDateTime, offset);
System.out.println(offset);
TemporalField
TemporalField类说明
包路径:java.time.temporal.TemporalField
public interface TemporalField {
...
}
public enum ChronoField implements TemporalField {
private final String name;
private final TemporalUnit baseUnit;
private final TemporalUnit rangeUnit;
private final ValueRange range;
...
}
TemporalField 主要实现类是枚举类型ChronoField
一个ChronoField成员会维护一个字符串名字属性name、一个TemporalUnit的基础单位baseUnit、一个TemporalUnit的表示范围的单位rangeUnit和一个ValueRange类型的range用于表示当前属性的范围。
public enum ChronoField implements TemporalField {
NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999))
SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second")
MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute")
CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12))
CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24))
AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod")
DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday")
DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day")
DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366))
ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5))
ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53))
ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7))
ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7))
MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month")
private final TemporalUnit baseUnit;
private final String name;
private final TemporalUnit rangeUnit;
private final ValueRange range;
private final String displayNameKey;
...
}
ChronoField常用的用法
ALIGNED_WEEK_OF_MONTH 和 ALIGNED_DAY_OF_WEEK_IN_MONTH 使用示例
int num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);
System.out.println(num);
num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_WEEK_OF_MONTH);
System.out.println(num);
ValueRange
ValueRange类说明
ValueRange 表示取值范围。
public final class ValueRange implements Serializable {
private final long minSmallest;
private final long minLargest;
private final long maxSmallest;
private final long maxLargest;
...
}
ValueRange常用的用法
ValueRange valueRange = ValueRange.of(1L, 10000L);
System.out.println(valueRange);
valueRange = ValueRange.of(1L, 5L, 10000L, 50000L);
System.out.println(valueRange);
LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
ValueRange valueRange = localDateTime.range(ChronoField.DAY_OF_MONTH);
System.out.println(valueRange.getMinimum());
System.out.println(valueRange.getMaximum());
System.out.println(valueRange.getLargestMinimum());
System.out.println(valueRange.getSmallestMaximum());
Chronology 判断是否闰年
判断是否闰年是由年表Chronology 提供的,通常情况下,我们使用ISO下的年表,是IsoChronology 。
看下代码实现
@Override
public boolean isLeapYear(long prolepticYear) {
return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
}
好精炼的代码,值得我们研究研究
闰年的基本判定方法: 1、非整百年:能被4整除的为闰年。(如2004年就是闰年,2001年不是闰年) 2、整百年:能被400整除的是闰年。(如2000年是闰年,1900年不是闰年)
((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
这段代码用了两个条件,这两个条件都符合,才是闰年。
- (prolepticYear & 3) == 0
- (prolepticYear % 100) != 0 || (prolepticYear % 400) == 0
(prolepticYear & 3) == 0 用了与运算符“&”,其使用规律如下: 两个操作数中位都为1,结果才为1,否则结果为0。
3 的二进制是011 ,prolepticYear & 3 目的是保留最后2位二进制数,然后判断是否最后两位二进制数等于0。如果等于0,证明能被4整除。闰年一定要满足是4的倍数的条件;
(prolepticYear % 100) != 0 || (prolepticYear % 400) == 0 这个就比较好理解了,看是不是100的倍数或者是不是400 倍数。
而且小虚竹发现java.time.Year#isLeap() 用的实现代码逻辑是一样的
public static boolean isLeap(long year) {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
}
即使是巨佬写的代码,也存在代码的复用性问题
上面IsoChronology 是对Chronology接口接口的isLeapYear实现,MinguoChronology等实现类的isLeapYear,互用了IsoChronology的isLeapYear方法。
public boolean isLeapYear(long prolepticYear) {
return IsoChronology.INSTANCE.isLeapYear(prolepticYear + YEARS_DIFFERENCE);
}
巨佬是有考虑复用的,在MinguoChronology等实现类已经有复用了。
java.time.Year#isLeap() 的优先级高,因为它是静态方法。isoChronology ** 可以引Year.isLeap** Year ** 不可以引Chronology.isLeapYear** 。
博主发现在IsoChronology ** 的resolveYMD** 中已经存在了对Year.isLeap 的引用。
有的工具类会为了减少外部类依赖,重新写一次底层方法,避免外部类(或是不在一个包底下)的类依赖,这个已经用了,说不过去 。所以代码是存在复用性问题的。
实战
int year = 2020;
System.out.println(Year.isLeap(year));
System.out.println(IsoChronology.INSTANCE.isLeapYear(year));
LocalDate localDate = LocalDate.of(2021,9,7);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate.isLeapYear());
System.out.println(localDateTime.toLocalDate().isLeapYear());
比较日期时间的先后
基本上都有这四个比较方法::compareTo()、isBefore()、isAfter()、和equals()
比较-LocalDate
LocalDate localDate1 = LocalDate.of(2021, 8, 14);
// 比较指定日期和参数日期,返回正数,那么指定日期时间较晚(数字较大):13
int i = localDate1.compareTo(LocalDate.of(2021, 8, 1));
System.out.println(i);
// 比较指定日期是否比参数日期早(true为早):true
System.out.println(localDate1.isBefore(LocalDate.of(2021,8,31)));
// 比较指定日期是否比参数日期晚(true为晚):false
System.out.println(localDate1.isAfter(LocalDate.of(2021,8,31)));
// 比较两个日期是否相等:true
System.out.println(localDate1.isEqual(LocalDate.of(2021, 8, 14)));
比较-LocalTime
LocalTime localTime1 = LocalTime.of(23, 26, 30);
LocalTime localTime2 = LocalTime.of(23, 26, 32);
System.out.println(localTime1.compareTo(localTime2));
System.out.println(localTime1.isBefore(localTime2));
System.out.println(localTime1.isAfter(localTime2));
System.out.println(localTime1.equals(LocalTime.of(23, 26, 30)));
比较-OffsetDateTime
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
OffsetDateTime offsetDateTime3 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
OffsetDateTime offsetDateTime2 = OffsetDateTime.of(localDateTime2, ZoneOffset.ofHours(8));
System.out.println(offsetDateTime1.compareTo(offsetDateTime2));
System.out.println(offsetDateTime1.isBefore(offsetDateTime2));
System.out.println(offsetDateTime1.isAfter(offsetDateTime2));
System.out.println(offsetDateTime1.equals(offsetDateTime3));
比较-OffsetTime
LocalTime localTime1 = LocalTime.of( 13, 14, 20);
OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));
OffsetTime offsetTime3 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));
LocalTime localTime2 = LocalTime.of(13, 14, 30);
OffsetTime offsetTime2 = OffsetTime.of(localTime2, ZoneOffset.ofHours(8));
System.out.println(offsetTime1.compareTo(offsetTime2));
System.out.println(offsetTime1.isBefore(offsetTime2));
System.out.println(offsetTime1.isAfter(offsetTime2));
System.out.println(offsetTime1.equals(offsetTime3));
比较-ZonedDateTime
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneOffset.ofHours(8));
System.out.println(zonedDateTime1.compareTo(zonedDateTime2));
System.out.println(zonedDateTime1.isBefore(zonedDateTime2));
System.out.println(zonedDateTime1.isAfter(zonedDateTime2));
System.out.println(zonedDateTime1.equals(zonedDateTime3));
计算日期时间的间隔
Duration 和**Period ** 都有 **between ** 方法
这个就不在重复说了,上面Duration 和Period 的常用用法里有介绍到。
推荐相关文章
hutool日期时间系列文章
1DateUtil(时间工具类)-当前时间和当前时间戳
2DateUtil(时间工具类)-常用的时间类型Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)转换
3DateUtil(时间工具类)-获取日期的各种内容
4DateUtil(时间工具类)-格式化时间
5DateUtil(时间工具类)-解析被格式化的时间
6DateUtil(时间工具类)-时间偏移量获取
7DateUtil(时间工具类)-日期计算
8ChineseDate(农历日期工具类)
9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具类封装)
10TemporalAccessorUtil{@link TemporalAccessor} 工具类封装
其他
要探索JDK的核心底层源码,那必须掌握native用法
万字博文教你搞懂java源码的日期和时间相关用法
java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案
源码分析:JDK获取默认时区的风险和最佳实践
高级JAVA开发必备技能:时区的规则发生变化时,如何同步JDK的时区规则
今天是持续写作的第 9 / 100 天。 可以关注我,点赞我、评论我、收藏我啦。
|