Java8 新特性 —— LocalDateTime,Instant(时间戳),DateTimeFormatter(格式化),Duration和Period(时间对比),时区详解

前言

在旧版的 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 只是相同字段的数值相比较

    作者:重生之绝世牛码

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java8 新特性 —— LocalDateTime,Instant(时间戳),DateTimeFormatter(格式化),Duration和Period(时间对比),时区详解

    发表回复