Java8 JSR310规范-时间日期API使用总结

参考资料

  1. 【小家java】java8新特性之—全新的日期、时间API(JSR 310规范),附SpringMVC、Mybatis中使用JSR310的正确姿势
  2. Java 8新特性(四):新的时间和日期API
  3. Java 8的时间类使用大全
  4. Java8中计算时间的四种方式及区别Period、Duration、ChronoUnit、Until & 时间区间Duration的简单使用
  5. Java8之新日期时间篇


零. 前言

在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,主要有:

  • ⏹Java的java.util.Date和java.util.Calendar类易用性差,不支持时区,而且他们都不是线程安全的;
    用于格式化日期的类DateFormat被放在java.text包中,它是一个抽象类,所以我们需要实例化一个SimpleDateFormat对象来处理日期格式化,并且DateFormat也是非线程安全,你得把它用ThreadLocal包起来才能在多线程中使用。
  • ⏹对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,从Calendar中获取的月份需要加一才能表示当前月份。

由于以上这些问题,出现了一些三方的日期处理框架,例如Joda-Time,date4j等开源项目。但是,Java需要一套标准的用于处理时间和日期的框架,于是Java 8中引入了新的日期API。新的日期API是JSR-310规范的实现,Joda-Time框架的作者正是JSR-310的规范的倡导者,所以能从Java 8的日期API中看到很多Joda-Time的特性。


一. 构建对象

1.1 LocalDate

⏹只有年月日的时间

// 构造2017年1月1日的时间对象
LocalDate localDate1 = LocalDate.of(2017, 1, 1);
// 获取当前时间
LocalDate localDate2 = LocalDate.now();
// 通过年月对象来构造
LocalDate localDate3 = YearMonth.of(2022, 12).atDay(1);
// 通过LocalDateTime获取LocalDate
LocalDate localDate4 = LocalDateTime.now().toLocalDate();
LocalDate localDate5 = LocalDate.from(LocalDateTime.now());

1.2 LocalTime

⏹只有时间,没有年月日

// 构造时分秒对象
LocalTime localTime1 = LocalTime.of(10, 15, 12);
// 构造时分对象
LocalTime localTime2 = LocalTime.of(10, 15);
// 构造当前时间对象
LocalTime localTime3 = LocalTime.now();
// 通过LocalDateTime获取LocalTime 
LocalTime localTime4 = LocalDateTime.now().toLocalTime();
LocalTime localTime5 = LocalTime.from(LocalDateTime.now());

1.3 LocalDateTime

⏹有年月日和时间

// 构造年月日时分秒对象
LocalDateTime localDateTime1 = LocalDateTime.of(2023, 5, 15, 10, 15, 10);
// 构造年月日时分对象
LocalDateTime localDateTime2 = LocalDateTime.of(2023, 5, 15, 10, 15);
// 构造当前时间对象
LocalDateTime localDateTime3 = LocalDateTime.now();
// 通过LocalDate + LocalTime来构造
LocalDateTime localDateTime4 = LocalDate.of(2023, 5, 15).atTime(10, 15, 10);
LocalDateTime localDateTime5 = LocalDate.of(2023, 5, 15).atTime(LocalTime.of(10, 15, 10));
// 一天的开头和结尾LocalDateTime 
LocalDateTime localDateTime6 = LocalDate.of(2023, 5, 15).atStartOfDay();
LocalDateTime localDateTime7 = LocalDate.of(2023, 5, 15).atTime(LocalTime.MIN);
LocalDateTime localDateTime8 = LocalDate.of(2023, 5, 15).atTime(LocalTime.MAX);
// 通过Instant来构建(需要指定时区)
LocalDateTime localDateTime9 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());

1.4 Instant

⏹Instant用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()有些类似,不过

  • Instant可以精确到纳秒(Nano-Second)
  • System.currentTimeMillis()方法只精确到毫秒(Milli-Second)。
// 获取当前时间的时间戳(UTC时间,非北京/东京时间)
Instant instant1 = Instant.now();
// 获取北京时间的时间戳
Instant instant2 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));
// 通过LocalDateTime构造Instant
Instant instant3 = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
// 通过Date构造Instant
Instant instant4 = new Date().toInstant();

1.5 YearMonth

⏹年月时间对象

YearMonth yearMonth1 = YearMonth.of(2022, 12);
YearMonth yearMonth2 = YearMonth.now();
// 通过LocalDate和LocalDateTime来构造
YearMonth yearMonth3 = YearMonth.from(LocalDate.now());
YearMonth yearMonth4 = YearMonth.from(LocalDateTime.now());

1.6 MonthDay

⏹月日时间对象,多用来表示周期性事件,例如生日和纪念日

MonthDay monthDay1 = MonthDay.of(2022, 12);
MonthDay monthDay2 = MonthDay.from(LocalDateTime.now());
MonthDay monthDay3 = MonthDay.now();

1.7 Period

⏹Period是以年月日来衡量一个时间段的对象。
⏹可以通过.between()来创建一个段时间,由于以年月日来衡量,因此接收的参数只能是LocalDate类型的,不能是LocalDateTime类型的。

// 2年3个月6天的时间段
Period period1 = Period.of(2, 3, 6);
// 2017-01-05 到 2017-02-05 这段时间
Period period2 = Period.between(
        LocalDate.of(2017, 1, 5),
        LocalDate.of(2017, 2, 5)
);
// 当前日期和2023年11月11日之间的这段时间
Period untilPeriod = LocalDate.now().until(LocalDate.of(2023, 11, 11));

// 1整年,1个月,1个周,1整天的时间段
Period periodYears = Period.ofYears(1);
Period periodMonths = Period.ofMonths(1);
Period periodWeeks = Period.ofWeeks(1);
Period periodDays = Period.ofDays(1);
// 3年3月3天的时间段
Period year3Month3Day3 = Period.ofYears(3)
                .plus(Period.ofMonths(3))
                .plus(Period.ofDays(3));

1.8 Duration

⏹Duration是以年月日时分秒来衡量一个时间段的对象。

// ChronoUnit.YEARS ChronoUnit.MONTHS ChronoUnit.WEEKS 无法使用,会报错.

// 5天
Duration duration4 = Duration.of(5, ChronoUnit.DAYS);
// 5个小时
Duration duration5 = Duration.of(5, ChronoUnit.HOURS);
// 5分钟
Duration duration6 = Duration.of(5, ChronoUnit.MINUTES);
// 5秒
Duration duration7 = Duration.of(5, ChronoUnit.SECONDS);
// 1000毫秒
Duration duration8 = Duration.of(1000, ChronoUnit.MILLIS);

// 2017-01-05 10:07:00 到 2017-02-05 10:07:00 这个时间段
Duration duration9 = Duration.between(
    // 2017-01-05 10:07:00
    LocalDateTime.of(2017, Month.JANUARY, 5, 10, 7, 0),
    // 2017-02-05 10:07:00
    LocalDateTime.of(2017, Month.FEBRUARY, 5, 10, 7, 0)
);

// 3天3小时的时间段对象
Duration duration10 = Duration.ofDays(3).plusHours(3);

二. 获取

2.1 时间戳

⏹获取时间戳的四种方式

System.currentTimeMillis();
Clock.systemDefaultZone().millis();
Calendar.getInstance().getTimeInMillis();
Instant.now().toEpochMilli()

2.2 当前年

int year = LocalDate.now().getYear();

2.3 月

2.3.1 当前月

int monthValue1 = LocalDate.now().getMonthValue();
Month month = LocalDate.now().getMonth();
int monthValue2 = month .getValue();

2.3.2 当前月底对象

LocalDate localDate = YearMonth.of(2022, 12).atEndOfMonth();

2.3.3 月总天数

int dayCount = LocalDate.of(2016, 2, 1).lengthOfMonth();
System.out.println(dayCount);  // 29

ValueRange range = LocalDate.of(2023, 9, 13).range(ChronoField.DAY_OF_MONTH);
range.getMaximum();  // 30

YearMonth yearMonth = YearMonth.of(2023, Month.JANUARY);
System.out.println(yearMonth.lengthOfMonth());  // 31

2.4 日

2.4.1 当前日

⏹日期是月份的第几号

int dayOfMonth = LocalDate.now().getDayOfMonth();

2.4.2 年份的第几天

⏹当前日期在本年度处于第几天

int dayOfYear1 = LocalDate.of(2023, 9, 13).getDayOfYear();  // 256
int dayOfYear2 = LocalDate.of(2023, 9, 13).get(ChronoField.DAY_OF_YEAR);  // 256

2.5 周

2.5.1 本周周几

⏹当前日期是本周的周几

int dayOfWeek = LocalDate.now().getDayOfWeek().getValue();

2.5.2 月份第几周

int num = LocalDate.of(2021, 8, 28).get(ChronoField.ALIGNED_WEEK_OF_MONTH);  // 5

2.5.3 年度第几周

int num = LocalDate.of(2021, 8, 28).get(ChronoField.ALIGNED_WEEK_OF_YEAR);  // 35

2.5.4 本月对齐周

⏹对齐周的取值范围为1~7
⏹对齐周是从日历上看,当前日所在月份按周向上对齐。
⏹2023年8月28号所在的对齐周是7,因为31是7月所在的日期,不是8月的。

int num1 = LocalDate.of(2023, 8, 15).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);  // 1
int num2 = LocalDate.of(2023, 8, 28).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);  // 7

在这里插入图片描述

2.6 两个日期之间的所有日期

⏹方式1

// 开始日
LocalDate startLocalDate = LocalDate.of(2023, 5, 10);
// 结束日
LocalDate endLocalDate = LocalDate.of(2023, 6, 8);
// 开始日和结束日之间的天数间隔
long distance = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);
// 指定格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

List<String> localDateList = 
		// 使用生成流生成日期
		Stream.iterate(startLocalDate, d -> d.plusDays(1))
        .limit(distance + 1)
        // 将日期全部格式化
        .map(localDate_ -> localDate_.format(formatter))
        .collect(toList());
System.out.println(localDateList);
/*
	2023-05-10
	, 2023-05-11
	, 2023-05-12
	, 2023-05-13
	, 2023-05-14
	, 2023-05-15
	, 2023-05-16
	, ...... 
*/

⏹方式2:参考该文章
Java 获取两个日期之间的所有日期


三. 计算

3.1 两个日期的间隔年

long betweenYears = ChronoUnit.YEARS.between(LocalDate.of(2017, 1, 1), LocalDate.of(2022, 10, 16));
System.out.println(betweenYears);  // 5

long untilYears = LocalDate.of(2017, 1, 1).until(LocalDate.of(2022, 10, 16), ChronoUnit.YEARS);
System.out.println(untilYears);  // 5

3.2 两个日期的间隔月

long betweenMonths = ChronoUnit.MONTHS.between(LocalDate.of(2017, 1, 1), LocalDate.of(2018, 5, 16));
System.out.println(betweenMonths);  // 16

long untilMonths = LocalDate.of(2017, 1, 1).until(LocalDate.of(2018, 5, 16), ChronoUnit.MONTHS);
System.out.println(untilMonths);  // 16

3.3 两个日期的间隔日

long betweenDays = ChronoUnit.DAYS.between(LocalDate.of(2017, 2, 13), LocalDate.of(2017, 5, 18));
System.out.println(betweenDays);  // 94

long untilDays = LocalDate.of(2017, 2, 13).until(LocalDate.of(2017, 5, 18), ChronoUnit.DAYS);
System.out.println(untilDays);  // 94

LocalDateTime fromLocalDateTime = LocalDate.of(2017, 2, 13).atTime(8, 0, 0);
LocalDateTime toLocalDateTime = LocalDate.of(2017, 5, 18).atTime(8, 30, 0);
long days = Duration.between(fromLocalDateTime, toLocalDateTime).toDays();
System.out.println(days);  // 94

3.4 两个日期的间隔时,分等

⏹间隔时

long untilHours = LocalTime.of(10, 15).until(LocalTime.of(12, 45), ChronoUnit.HOURS);
System.out.println(untilHours);  // 2

⏹间隔分

long untilMinutes = LocalTime.of(10, 15).until(LocalTime.of(12, 45), ChronoUnit.MINUTES);
System.out.println(untilMinutes);  // 150

3.5 时间矫正器

方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfNextMonth 返回下月的最后一天
lastDayOfNextYear 返回下一年的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next/previous 返回后一个/前一个给定的星期几
nextOrSame/previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回

3.5.1 本月(第一天/最后一天)

LocalDate of1 = LocalDate.of(2022, 10, 16);
// 本月第一天
LocalDate result1 = of1.with(TemporalAdjusters.firstDayOfMonth());  // 2022-10-01
// 本月最后一天
LocalDate result2 = of1.with(TemporalAdjusters.lastDayOfMonth());  // 2022-10-31

3.5.2 本月第一个周?

// 本月第一个周五
LocalDate of1 = LocalDate.of(2022, 10, 16);
LocalDate result3 = of1.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY));  // 2022-10-07

3.5.3 (上/下)一个周?

// 上一个周六
LocalDate result4 = LocalDate.of(2023, 9, 16).with(TemporalAdjusters.previous(DayOfWeek.SATURDAY));
System.out.println(result4);  // 2023-09-09

// 下一个周五
LocalDate result5 = LocalDate.of(2023, 9, 16).with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(result5);  // 2023-09-22

3.5.4 自定义时间矫正器_获取指定月份工资日

LocalDate salaryDate = this.getSalaryDate(2023, 6);
System.out.println(salaryDate);  // 2023-06-23
 
// 下一个工资日,工资是每个月的25号发,如果25号是周六或者周日的话,就改为该周的周五发.
private LocalDate getSalaryDate(Integer year, Integer month) {
    
    

    // 自定义一个时间矫正器,用来计算工资日
    TemporalAdjuster salaryTemporalAdjuster = temporal -> {
    
    

        LocalDate date = LocalDate.class.cast(temporal);
        // 获取当前月的25号的LocalDate对象
        LocalDate localDate25 = date.plusDays(24);
        // 获取指定月份25号的DayOfWeek对象
        DayOfWeek salaryWeek = localDate25.getDayOfWeek();

        // 如果指定月份的25号既不是周六也不是周日的话,直接返回当月的25号的LocalDate
        if (DayOfWeek.SATURDAY.compareTo(salaryWeek) != 0 && DayOfWeek.SUNDAY.compareTo(salaryWeek) != 0) {
    
    
            return localDate25;
        }

        // 如果指定月的25号是周六或者周日的话,那么将日期提前到上一个周五当做工资日
        return localDate25.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
    };

    return LocalDate.of(year, month, 1).with(salaryTemporalAdjuster);
}

四. 修改

4.1 年

LocalDate localDate1 = LocalDate.of(2023, 8, 14).withYear(1997);
// 1997-08-14

4.2 月

LocalDate localDate2 = LocalDate.of(2023, 8, 14).withMonth(5);
// 2023-05-14

4.3 日

4.3.1 指定月的第?天

LocalDate localDate3 = LocalDate.of(2023, 8, 14).withDayOfMonth(29);
// 2023-08-29

4.3.2 指定年的第?天

LocalDate localDate4 = LocalDate.of(2023, 8, 14).withDayOfYear(6);
// 2023-01-06

4.4 ?天前/后

// 3天前
LocalDate.now().minusDays(3);
// 3天后
LocalDate.now().plusDays(3);

// 构造3天的时间段对象
Period period3Day = Period.ofDays(3);
// 3天前
LocalDate.now().plus(period3Day);
// 3天后
LocalDate.now().minus(period3Day);

// 3天前
LocalDate.now().plus(3, ChronoUnit.DAYS);
// 3天后
LocalDate.now().minus(3, ChronoUnit.DAYS);

4.5 ?周前/后

// 1周前
LocalDate.now().minusWeeks(1);
// 1周后
LocalDate.now().plusWeeks(1);

4.6 ?月前/后

// 1月前
LocalDate.now().minusMonths(1);
// 1月后
LocalDate.now().plusMonths(1);

// 1月前
LocalDate.now().minus(1, ChronoUnit.MONTHS);
// 1月后
LocalDate.now().plus(1, ChronoUnit.MONTHS);

4.7 ?年前/后

// 1年前
LocalDate.now().minusYears(1);
// 1年后
LocalDate.now().plusYears(1);

五. 判断

5.1 日期A早于日期B

LocalDate.of(2023, 8, 14).isBefore(LocalDate.of(2023, 8, 15));  // true
LocalDate.of(2023, 8, 14).compareTo(LocalDate.of(2023, 8, 15));  // -1

5.2 日期A晚于日期B

LocalDate.of(2023, 8, 14).isAfter(LocalDate.of(2023, 8, 15));  // false
LocalDate.of(2023, 8, 14).compareTo(LocalDate.of(2023, 8, 15));  // -1

5.3 日期相等

⏹普通日期判断相等

// 使用.equals()判断
LocalDate.of(2023, 9, 12).equals(LocalDate.now());
// 使用.compareTo()判断
LocalDate.of(2023, 8, 14).compareTo(LocalDate.of(2023, 8, 14));  // 0

⏹生日判断相等

LocalDate birthdayLocalDate = LocalDate.of(1994, 12, 16);
// 构造月日对象
MonthDay birthMonthDay = MonthDay.of(
	birthdayLocalDate.getMonth()
	, birthdayLocalDate.getDayOfMonth()
);
MonthDay.now().equals(birthMonthDay);

5.4 闰年

LocalDate.now().isLeapYear();
Year.isLeap(2020);

六. 格式化

6.1 字符串转日期

  • 非严格模式下的字符串转日期
// 构造一个日期格式化对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
// 字符串 --> 日期
LocalDate parseLocalDate = LocalDate.parse("2016年10月25日", formatter);

6.2 日期转字符串

LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月"));

DateTimeFormatter.ofPattern("yyyy年MM月").format(LocalDate.now());

七. 转换

⏹从包含的信息量而言,LocalDateTime > LocalDate > LocalTime,因此可以自上而下转换,不能自下而上转换。

⏹LocalDate和Date类、时间戳之间转换

  • Date对象表示特定的日期和时间,而LocalDate(Java8)对象只包含没有任何时间信息的日期。
  • 因此,如果我们只关心日期而不是时间信息,则可以在Date和LocalDate之间进行转换。
  • 转换的中间桥梁都是时间戳Instant对象,但是转换的时候如果没有考虑时区,就会报错的。

7.1 LocalDateTime --> LocalDate

LocalDate localDate = LocalDate.from(LocalDateTime.now());

7.2 LocalDateTime --> LocalTime

LocalTime localTime = LocalTime.from(LocalDateTime.now());

7.3 LocalDateTime 互转 Instant

// 构造一个LocalDateTime对象
LocalDateTime localDateTime1 = LocalDate.of(2023, 5, 15).atTime(LocalTime.MAX);

// LocalDateTime --> Instant
Instant instant = localDateTime1.atZone(ZoneId.systemDefault()).toInstant();
// Instant --> LocalDateTime
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

7.3 LocalDate --> YearMonth

YearMonth yearMonth = YearMonth.from(LocalDate.now());

7.4 LocalDate --> MonthDay

MonthDay monthDay = MonthDay.from(LocalDate.now());

7.5 Date 互转 LocalDateTime

⏹Date --> LocalDateTime

Date date = new Date();
// Date 转换 Instant 
Instant instantOfDate = date.toInstant();
// Instant 转换 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(instantOfDate, ZoneId.systemDefault());

⏹LocalDateTime --> Date

Instant instantLocalDateTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
Date dateLocalDateTime = Date.from(instantLocalDateTime);

7.6 Date 互转 LocalDate

⏹Date --> LocalDate

public LocalDate date2LocalDate(Date date) {
    
    
    ZonedDateTime zdt = date.toInstant().atZone(ZoneId.systemDefault());
    return zdt.toLocalDate();
}

⏹LocalDate --> Date

public Date localDate2Date(LocalDate localDate){
    
    
    ZonedDateTime zdt = localDate.atStartOfDay(ZoneId.systemDefault());
    return Date.from(zdt.toInstant());
}

八. 小案例

8.1 统计程序运行所消耗的时间

// 开始时间
Instant startInstant = Instant.now();

// 模拟程序运行耗时
try {
    
    
    Thread.sleep(2000);
} catch (InterruptedException e) {
    
    
    e.printStackTrace();
}

// 终了时间
Instant endInstant = Instant.now();

// 获取开始和终了时间的时间段对象
Duration taskTime = Duration.between(startInstant, endInstant);
long second = taskTime.toSeconds();

8.2 高并发下Data格式化正确使用方式

// 高并发环境下Data的正确使用方式,要和ThreadLocal进行绑定
private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

// 和线程绑定 保证安全
public static String format(Date date) {
    
    
    return threadLocal.get().format(date);
}

猜你喜欢

转载自blog.csdn.net/feyehong/article/details/132845562