前言

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全: java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差: Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,另外这两个类都有相同的名字,这就显得非常糟糕。
  • 时区处理麻烦: 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

为了解决以上问题,Java 8引入了新的日期时间API。新API具有以下特点:

  • 线程安全: 新的日期时间API中的类都是线程安全的。
  • 不可变性: 与传统日期时间类不同,新的日期时间API中的类是不可变(immutable)的,这使得更容易编写高质量的代码。
  • 类型安全性: 新的日期时间API中所有的类和接口都具有类型安全性,这意味着可以在编译时捕获许多常见的日期时间错误。

Java 8日期时间API中主要涉及到以下类和接口:

  • java.time.LocalDate:表示一个ISO-8601格式的日期,不包含时间信息
  • java.time.LocalTime:表示一个ISO-8601格式的时间,不包含日期信息
  • java.time.LocalDateTime:表示一个ISO-8601格式的日期时间
  • java.time.Instant:表示从‘1970-01-01T00:00:00Z’开始的秒数(时间戳)
  • java.time.Duration:表示两个时刻之间的持续时间或时间段
  • java.time.Period:表示两个日期之间的天数、月数或年数
  • java.time.ZonedDateTime:表示带时区的日期时间信息
  • java.time.temporal:用于校正时间
  • java.time.DateTimeFormatter:用于格式化和解析日期时间信息

一、LocalDate、LocalTime以及LocalDateTime

  • LocalDate主要表示日期类对象如:2024-11-06;
  • LocalTime主要表示时间类对象如:20:31:00;
  • LocalDateTime则是两个结合版,表示日期加时间如:2024-11-06T20:31:00。

这三者的使用方法其实是如出一辙的,下面我们以LocalDateTime为例,了解一下他们的作用及用法:

@Test
public void test3(){
    LocalDateTime now = LocalDateTime.now();//当前时间 yyyy-MM-ddTHH:mm:ss
    LocalDateTime localDateTime = LocalDateTime.of(2024, 11, 6, 12, 30, 30);//2024-11-06T12:30:30

    LocalDateTime plusHours = localDateTime.plusHours(1);//加上一个小时 -> 2024-11-06T13:30:30
    LocalDateTime minusMinutes = localDateTime.minusMinutes(10);//减去10分钟 -> 2024-11-06T12:20:30
    int dayOfWeek = localDateTime.getDayOfWeek().getValue();//获取当前时间是一周中的第几天 -> 3
    int year = localDateTime.getYear();//获取年 -> 2024
    int month = localDateTime.getMonthValue();//获取月份 -> 11
    int hour = localDateTime.getHour();//获取小时 -> 12
    int minute = localDateTime.getMinute();//获取分钟 -> 30
    int second = localDateTime.getSecond();//获取秒 -> 30
}

二、Instant 时间戳

  • Instant:时间戳, 存储了一个从1970-01-01 00:00:00 以来的 秒 和 纳秒 数据
  • getEpochSecond():获取的是秒
  • toEpochMilli():获取的是毫秒
  • getNano():获取的是纳秒
  • ofEpochSecond():通过秒级时间戳获取Instant
  • ofEpochMilli():通过毫秒级时间戳获取Instant
/**
 * Instant : 时间戳, 存储了一个从1970-01-01 00:00:00 以来的 秒 和 纳秒 数据
 * getEpochSecond() : 获取的是秒
 * toEpochMilli() : 获取的是毫秒
 * getNano() : 获取的是纳秒
 * ofEpochSecond() : 通过秒级时间戳获取Instant
 * ofEpochMilli() : 通过毫秒级时间戳获取Instant
 */
@Test
public void test4(){
    Instant inst1 = Instant.now();//默认获取 UTC 时区
    System.out.println("UTC 时区: " + inst1);//2024-11-06T12:44:24.803617100Z

    System.out.println("Instant 秒: " + inst1.getEpochSecond());//1730897064
    System.out.println("Instant 毫秒: " + inst1.toEpochMilli());//1730897064803
    System.out.println("当前时间戳(毫秒): " + System.currentTimeMillis());//1730897064808
    System.out.println("Instant 纳秒: " + inst1.getNano());//803617100

    //根据指定时间戳获取 Instant 对象, 默认UTC时区
    Instant ods1 = Instant.ofEpochSecond(1730897056);//2024-11-06T12:44:16Z
    System.out.println("指定时间戳获取Instant: " + ods1);

    //转换成指定时区,或者当前时区
    OffsetDateTime ods2 = ods1.atOffset(ZoneOffset.ofHours(8));//2024-11-06T20:44:16+08:00
    System.out.println("时区加8:" + ods2);

    ZonedDateTime ods3 = ods1.atZone(ZoneId.systemDefault());//2024-11-06T20:44:16+08:00[Asia/Shanghai]
    System.out.println("时区加8:" + ods3);
}

效果图如下:

在这里插入图片描述
需要注意的是Instant 不管是通过Now()获取当前时间,还是通过ofEpochSecond()和ofEpochMilli()都是UTC时区的时间,后续需要结合时区使用。


三、ZonedDateTime 带时区的时间

1、ZoneId

返回带时区的时间,如:2024-11-06T21:12:53.794258+08:00[Asia/Shanghai]

  • getAvailableZoneIds()获取所有时区
  • systemDefault()获取系统时区
  • of()指定时区如:ZoneId.of(“Asia/Shanghai”)

2、ZoneOffset

指定时间偏移量,如:2024-11-06T19:12:53.794258+06:00

@Test
public void test8(){
    System.out.println(ZonedDateTime.now());//返回系统时区的带时区的时间
    System.out.println(ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()));//返回系统时区的带时区的时间
    System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));//返回Asia/Shanghai 时区当前时间
    System.out.println(LocalDateTime.now(ZoneId.of("Africa/Nairobi")));//返回Africa/Nairobi 时区当前时间
    System.out.println(LocalDateTime.now(ZoneId.systemDefault()));//返回系统时区的时间

    LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));//返回Asia/Shanghai 时区当前时间
    ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("Asia/Shanghai"));//返回Asia/Shanghai 时区当前带时区时间(与UTC相比)
    System.out.println(now);
    System.out.println(zonedDateTime);

    LocalDateTime now1 = LocalDateTime.now(ZoneOffset.ofHours(6));//返回偏移后的时间
    ZonedDateTime zonedDateTime1 = now1.atZone(ZoneOffset.ofHours(6));//返回带偏移量的时间(与UTC相比)
    System.out.println(now1);
    System.out.println(zonedDateTime1);
}

在这里插入图片描述


四、DateTimeFormatter 格式化

同样在Java8中也提供了专业的时间转换类,其中也内置了许多特定的格式,也可以自定义格式

在这里插入图片描述

- ofPattern():指定格式

//DateTimeFormatter: 格式化
@Test
public void test7(){
    LocalDateTime localDateTime = LocalDateTime.of(2024, 12, 24,20, 40, 40);//2024-12-24T20:40:40
    System.out.println(localDateTime);

    // 官方API
    DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
    String sdt1 = localDateTime.format(dateTimeFormatter1);
    System.out.println(sdt1);//2024-12-24

    // 自定义
    DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
    String sdt2 = localDateTime.format(dateTimeFormatter2);
    System.out.println(sdt2);//2024年12月24日 20:40:40

    //字符串转时间
    LocalDateTime dt = LocalDateTime.parse(sdt2, dateTimeFormatter2);
    System.out.println(dt);//2024-12-24T20:40:40
}

在这里插入图片描述


五、TemporalAdjuster 时间校正器

相对于上文时区的大范围校正时间,以及LocalDateTime中加减时间实现时间校正,TemporalAdjuster 就显得较为专业,可以返回指定日子的时间,当然也可以指定模式返回校正后的时间:

//时间校正器 TemporalAdjuster
@Test
public void test6(){
    LocalDateTime dt1 = LocalDateTime.of(2024, 12, 24,20, 40, 40);//2024-12-24T20:40:40
    System.out.println("当前时间: " + dt1);

    //将时间调整到下个周日
    LocalDateTime dt2 = dt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println("获取下个周日: " + dt2);

    //自定义获取下一个工作日
    LocalDateTime dt3 = dt1.with(temporal -> {
        LocalDateTime localDateTime = (LocalDateTime) temporal;
        DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
        if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
            return localDateTime.plusDays(3);
        } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
            return localDateTime.plusDays(2);
        } else {
            return localDateTime.plusDays(1);
        }
    });
    System.out.println("获取下一个工作日: " + dt3);
}

在这里插入图片描述


六、Duration和Period 计算时间差

Duration:时间层间计算时间差
Period:日期层面计算时间差

//计算时间间隔或者日期间隔
// Duration 计算时间间隔
// Period 计算日期间隔
@Test
public void test5() throws InterruptedException {
    LocalDateTime dt1 = LocalDateTime.of(2024, 11, 1,12, 30, 30);
    LocalDateTime dt2 = LocalDateTime.of(2025, 12, 24,20, 40, 40);

    Duration duration = Duration.between(dt1, dt2);
    System.out.println("Duration 展示毫秒秒间隔:" + duration.toMillis());//36144610000
    System.out.println("Duration 展示秒间隔:" + duration.toSeconds());//36144610
    System.out.println("Duration 展示分钟间隔:" + duration.toMinutes());//602410
    System.out.println("Duration 展示小时间隔:" + duration.toHours());//10040
    System.out.println("Duration 展示天数间隔:" + duration.toDays());//418

    LocalDate localDate1 = LocalDate.of(2024, 11, 1);
    LocalDate localDate2 = LocalDate.of(2025, 12, 24);

    Period period = Period.between(localDate1, localDate2);
    System.out.println("Period 展示天数间隔: " + period.getDays());//23
    System.out.println("Period 展示月份间隔: " + period.getMonths());//1
    System.out.println("Period 展示年间隔: " + period.getYears());//1
}

在这里插入图片描述

需要注意的是:
Duration 是精确的时间段的比较,而Period 只是相同字段的数值相比较

Logo

电影级数字人,免显卡端渲染SDK,十行代码即可调用,工业级demo免费开源下载!

更多推荐