JDK8 时间和日期类(八)

239 阅读7分钟

旧版日期时间API存在的问题

  1. 设计很差: 在java.util和java.sql的包中都有日期类, java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义.

  2. 非线程安全: java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一.

  3. 时区处理麻烦: 日期类并不提供国家化,没有时区支持,因此java引入java.util.Calendar和java.util.TimeZone类,但是他们同样存在上述所有的问题.

           /**
             * 1. 设计不合理
             */
            Date now = new Date(1985, 9, 23);
            System.out.println(now);
    
            /**
             * 线程不完全
             */
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            for (int i = 0; i < 50; i++) {
                new Thread(() -> {
                    Date date = null;
                    try {
                        date = sdf.parse("2020-09-09");
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    System.out.println(date);
                });
            }
    

输出结果

Connected to the target VM, address: '127.0.0.1:60324', transport: 'socket'
  Fri Oct 23 00:00:00 CST 3885
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Thu Sep 09 00:00:00 CST 2202
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  date=Wed Sep 09 00:00:00 CST 2020
  Exception in thread "Thread-40" java.lang.NumberFormatException: For input string: ""
  	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  	at java.lang.Long.parseLong(Long.java:601)
  	at java.lang.Long.parseLong(Long.java:631)
  	at java.text.DigitList.getLong(DigitList.java:195)
  	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
  	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
  	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
  	at java.text.DateFormat.parse(DateFormat.java:364)
  	at com.example.jdk.demo07datetime.Test01.lambda$main$0(Test01.java:23)
  	at java.lang.Thread.run(Thread.java:748)
  Disconnected from the target VM, address: '127.0.0.1:60324', transport: 'socket'
  
  Process finished with exit code 0
  

新的日期时间API介绍

JDK 8中增加了一套全新的日期时间API, 这套API设计合理,是线程安全的。 新的日期及时间API位于java.time包中,下面是一些关键类.

LocalDate: 表示日期,包含年月日, 格式为2019-10-19

LocalTime: 表示时间,包含时分秒,格式为 16:53:33.143423434

LocalDateTime:表示日期时间,包含年月日,时分秒 格式为 2018-10-12T15:12:13.234

DateTimeFormater: 日期时间格式化

Instant: 时间戳,表示一个特定的时间瞬间

Duration: 用于计算两个时间(LocalTime,时分秒)的间隔

Period: 用于计算2个日期(LocalDate,年月日)的间隔

ZoneDateTime: 包含时区的时间

JDK 8的日期和时间类

LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间. 他们提供简单的日期和时间,并不包含当前的日期信息,也不包含与时区相关的信息

 public static void testLocalDate() {
        //当前日期
        LocalDate now = LocalDate.now();
        System.out.println(now);
        //指定日期
        LocalDate date = LocalDate.of(2018, 12, 12);
        System.out.println(date);
        //获取指定日期
        System.out.println("year= " + now.getYear());
        System.out.println("month= " + now.getMonthValue());
        System.out.println("date= " + now.getDayOfMonth());

    }

    public static void testLocalTime() {
        //LocalTime 表示时间,有时分秒
        //当前时间
        LocalTime nowTime =LocalTime.now();
        System.out.println(nowTime);

        //指定时间
        LocalTime localTime =LocalTime.of(13,22,34);
        System.out.println(localTime);

        //获取时间
        System.out.println(nowTime.getHour());
        System.out.println(nowTime.getMinute());
        System.out.println(nowTime.getSecond());
        System.out.println(nowTime.getNano());
    }

    public static void testLocalDateTime() {
        //LocalDateTime LocalDate+LocalTime,有年月日 时分秒
        //当前时间
        LocalDateTime nowDateTime =LocalDateTime.now();
        System.out.println(nowDateTime);

        //指定时间
        LocalDateTime localTime =LocalDateTime.of(2018,7,23,13,22,34);
        System.out.println(localTime);

        //获取时间
        System.out.println("year= " + nowDateTime.getYear());
        System.out.println("year= " + nowDateTime.getMonthValue());
        System.out.println("date= " + nowDateTime.getDayOfMonth());
        System.out.println(nowDateTime.getHour());
        System.out.println(nowDateTime.getMinute());
        System.out.println(nowDateTime.getSecond());
        System.out.println(nowDateTime.getNano());
    }

    /**
     * 修改时间
     */
    @Test
    public void editLocalDateTime(){
        LocalDateTime nowDateTime =LocalDateTime.now();
        //修改后返回新的时间
        LocalDateTime localDateTime = nowDateTime.withYear(9102);
        System.out.println("nowDateTime= " + nowDateTime);
        System.out.println("localDateTime= " + localDateTime);

        //增年份: plus
        LocalDateTime plusYears = nowDateTime.plusYears(2);
        System.out.println("years+2 ="+plusYears);
        //减年份: minus
        LocalDateTime minusYears = nowDateTime.minusYears(2);
        System.out.println("years-2 ="+minusYears);

    }

    /**
     * 比较时间
     */
    @Test
    public void compareLocalDateTime(){
        LocalDateTime nowDateTime =LocalDateTime.now();
        LocalDateTime localTime =LocalDateTime.of(2018,7,23,13,22,34);

        System.out.println(nowDateTime.isAfter(localTime));
        System.out.println(nowDateTime.isBefore(localTime));
        System.out.println(nowDateTime.isEqual(localTime)); 
    }

日期的时间格式化与解析

通过java.time.format.DateTimeFormater类进行日期时间解析与格式化

   /**
     * 日期格式化
     */
    @Test
    public void formatLocalDateTime(){
        LocalDateTime nowDateTime =LocalDateTime.now();
       //格式化,指定时间的格式,jdk 自带的格式
        DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE_TIME;
        String format = nowDateTime.format(isoDateTime);
        System.out.println(format);
        //指定格式
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateTimeFormatter);

        //解析
        LocalDateTime parse = LocalDateTime.parse("2017-09-09 15:13:23",dateTimeFormatter);
        System.out.println(parse);
        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                LocalDateTime parse2 = LocalDateTime.parse("2017-09-09 15:13:23",dateTimeFormatter);
                System.out.println("parse2 ="+parse2);
            }).start();
        }
    }

JDK8的Instant类

Instant时间戳/时间线,内部保存了1970年1月1日00:00:00以来的秒和纳秒

 /**
     * 获取秒/纳秒
     */
    @Test
    public void testInstant(){
        //instant 内部保存了秒和纳秒,一般不是给用户使用的,而是方便我们程序做一些统计
        Instant now = Instant.now();
        System.out.println(now);
        //增加秒
        Instant plus = now.plusSeconds(20);
        //减少秒
        Instant minus = now.minusSeconds(120);
        //获取秒/纳秒
        System.out.println(now.getEpochSecond());
        System.out.println(now.getNano());
    }

JDK8计算日期时间差类

Duration/Period类:计算日期时间差

  1. Duration: 用于计算2个时间(LocalTime,时分秒)的间隔

  2. Period: 用于计算2个日期(LocalDate,年月日)的距离

@Test
    public void testDuration(){
        //Duration 计算时间的间隔
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime localTime =LocalDateTime.of(2018,7,23,13,22,34);
        Duration duration = Duration.between(now, localTime);
        System.out.println("相差的天数:" + duration.toDays());
        System.out.println("相差的小时数:" + duration.toHours());
        System.out.println("相差的分钟数:" + duration.toMinutes());
        System.out.println("相差的秒数:" + duration.toMillis());

        //pperiod日期日期的间隔
        LocalDate now1 = LocalDate.now();
        LocalDate localDate = LocalDate.of(2018, 12, 12);
        //between 让后面的时间减去前面的时间
        Period period = Period.between(localDate, now1);
        System.out.println("相差的年数:" + period.getYears());
        System.out.println("相差的月份:" + period.getMonths());
        System.out.println("相差的天数:" + period.getDays());
    }

jdk8 中的时间校正器

有时我们可能需要获取例如: 将日期调整到"下一个月的第一天"等操作. 可以通过时间校正器来进行

  • TemporalAdjuster: 时间校正器
  • TemporalAdjusters: 该类通过静态方法提供了大量的常用TemporalAdjuster的实现
@Test
    public void testTemporalAdjuster(){
        //将日期调整到下一个月的第一天
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime with = now.with(TemporalAdjusters.firstDayOfMonth());
        System.out.println(with);
    }

设置日期时间的时区

java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为: ZonedDate、ZonedTime、ZonedDateTime

其中每个时区都对应着ID. ID的格式为"区域/城市". 例如: Asia/ShangHai 等

ZoneId: 该类包含了所有的时区信息.

@Test
    public void testZonedDate(){
        // 获取所有的时区id
        ZoneId.getAvailableZoneIds().forEach(System.out::println);

        //不带时区
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now: "+now);
        
        //带时区 标准世界时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("zonedDateTime: "+zonedDateTime);

        //now(): 使用计算机的默认时区
        ZonedDateTime zonedDateTime2 = ZonedDateTime.now();
        System.out.println("zonedDateTime2: "+zonedDateTime2);

        //指定的时区创建时间
        ZonedDateTime zonedDateTime3 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
        System.out.println("zonedDateTime3: "+zonedDateTime3);

        //修改时区,时间
        ZonedDateTime withZoneSameInstant = zonedDateTime2.withZoneSameInstant(ZoneId.of("Asia/ShangHai"));
        System.out.println("withZoneSameInstant: "+withZoneSameInstant);

        //只改时区 不改时间
        ZonedDateTime withZoneSameLocal = zonedDateTime2.withZoneSameLocal(ZoneId.of("Asia/ShangHai"));
        System.out.println("withZoneSameLocal: "+withZoneSameLocal);
    }