Jdk8新特性之时间API

旧版日期时间 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类,但他们同样存在上述所有的问题。

public static void main(String[] args) {
        // 旧版日期时间 API 存在的问题
        // 1.设计部合理
        Date now = new Date(1985, 9, 23);
        System.out.println(now);

        // 2.时间格式化和解析是线程不安全的
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                try {
                    Date date = sdf.parse("2019-09-09");
                    System.out.println("date = " + date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

/**
     * 线程安全的 DateTimeFormatter
     */
    @Test public void testDateFormatter() throws InterruptedException, ExecutionException {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");

        // 定义线程池, 任务, 提交100个解析任务并输出任务的执行情况
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Callable<LocalDate> task = () -> LocalDate.parse("20190305",dtf);
        List<Future<LocalDate>> results = new ArrayList<>();
        IntStream.rangeClosed(0, 100).forEach(i -> results.add(pool.submit(task)));
        for (Future<LocalDate> future : results) {
            println(future.get());
        }
    }

新日期时间 API介绍

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

  • LocalDate :表示日期,包含年月日,格式为 2019-10-16
  • LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
  • LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
  • DateTimeFormatter :日期时间格式化类。
  • Instant:时间戳,表示一个特定的时间瞬间。
  • Duration:用于计算2个时间(LocalTime,时分秒)的距离
  • Period:用于计算2个日期(LocalDate,年月日)的距离
  • ZonedDateTime :包含时区的时间

Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:

  • ThaiBuddhistDate :泰国佛教历
  • MinguoDate :中华民国历
  • JapaneseDate :日本历
  • HijrahDate :伊斯兰历

JDK 8 的日期和时间类

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

 @Test
    public void testLocalDate() {
        // LocalDate: 表示日期,有年月日
        LocalDate date = LocalDate.of(2020, 4, 1);
        System.out.println("date = " + date);

        LocalDate now = LocalDate.now();
        System.out.println("now = " + now);

        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getDayOfMonth());
    }
 @Test
    public void testLocalTime() {
        // LocalTime: 表示时间,有时分秒
        LocalTime time = LocalTime.of(22, 00, 39);
        System.out.println("time = " + time);

        LocalTime now = LocalTime.now();
        System.out.println("now = " + now);

        System.out.println(now.getHour());
        System.out.println(now.getMinute());
        System.out.println(now.getSecond());
        System.out.println(now.getNano());
    }

 @Test
    public void testLocalDateTime() {
        // LocalDateTime: LocalDate + LocalTime 有年月日 时分秒
        LocalDateTime dateTime = LocalDateTime.of(2020, 4, 1, 13, 28, 59);
        System.out.println("dateTime = " + dateTime);

        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);

        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getHour());
        System.out.println(now.getSecond());
    }

对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。

 // LocalDateTime 类: 对日期时间的修改
    @Test
    public void test05() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);
        // 修改日期时间
        LocalDateTime setYear = now.withYear(2078);
        System.out.println("修改年份: " + setYear);
        System.out.println("now == setYear: " + (now == setYear));
        System.out.println("修改月份: " + now.withMonth(6));
        System.out.println("修改小时: " + now.withHour(9));
        System.out.println("修改分钟: " + now.withMinute(11));
        System.out.println( now.withYear(2021).withMonth(1).withHour(11).withMinute(11));
        // 再当前对象的基础上加上或减去指定的时间
        LocalDateTime localDateTime = now.plusDays(5);
        System.out.println("5天后: " + localDateTime);
        System.out.println("now == localDateTime: " + (now == localDateTime));
        System.out.println("10年后: " + now.plusYears(10));
        System.out.println("20月后: " + now.plusMonths(20));
        System.out.println("20年前: " + now.minusYears(20));
        System.out.println("5月前: " + now.minusMonths(5));
        System.out.println("100天前: " + now.minusDays(100));
    }
now = 2020-03-31T22:10:02.710
修改年份: 2078-03-31T22:10:02.710
now == setYear: false
修改月份: 2020-06-30T22:10:02.710
修改小时: 2020-03-31T09:10:02.710
修改分钟: 2020-03-31T22:11:02.710
2021-01-31T11:11:02.710
5天后: 2020-04-05T22:10:02.710
now == localDateTime: false
10年后: 2030-03-31T22:10:02.710
20月后: 2021-11-30T22:10:02.710
20年前: 2000-03-31T22:10:02.710
5月前: 2019-10-31T22:10:02.710
100天前: 2019-12-22T22:10:02.710

Process finished with exit code 0

日期时间的比较

// 日期时间的比较
    @Test
    public void test06() {
        // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
        LocalDate now = LocalDate.now();
        LocalDate date = LocalDate.of(2020, 4, 1);
        System.out.println(now.isBefore(date)); //true
        System.out.println(now.isAfter(date)); // false
    }

JDK 8 的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。

@Test
    public void test04(){
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format = now.format(dtf);
        System.out.println(format);  //2020-03-31 22:15:50

        // 将字符串解析为日期时间
        LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", dtf);
        System.out.println("parse = " + parse);

    }

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

 @Test
    public void test07() {
        Instant now = Instant.now();
        System.out.println("当前时间戳 = " + now);
        // 获取从1970年1月1日 00:00:00的秒
        System.out.println(now.getNano());
        System.out.println(now.getEpochSecond());
        System.out.println(now.toEpochMilli());
        System.out.println(System.currentTimeMillis());
        Instant instant = Instant.ofEpochSecond(5);
        System.out.println(instant);
    }

JDK 8 的计算日期时间差类
Duration/Period类: 计算日期时间差。
1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
2. Period:用于计算2个日期(LocalDate,年月日)的距离

// Duration/Period 类: 计算日期时间差
    @Test
    public void test08() {
        // Duration计算时间的距离
        LocalTime now = LocalTime.now();
        LocalTime time = LocalTime.of(14, 15, 20);
        Duration duration = Duration.between(time, now);
        System.out.println("相差的天数:" + duration.toDays());
        System.out.println("相差的小时数:" + duration.toHours());
        System.out.println("相差的分钟数:" + duration.toMinutes());
        // Period计算日期的距离
        LocalDate nowDate = LocalDate.now();
        LocalDate date = LocalDate.of(1998, 8, 8);
       // 让后面的时间减去前面的时间
        Period period = Period.between(date, nowDate);
        System.out.println("相差的年:" + period.getYears());
        System.out.println("相差的月:" + period.getMonths());
        System.out.println("相差的天:" + period.getDays());
    }
相差的天数:0
相差的小时数:8
相差的分钟数:492
相差的年:21
相差的月:7
相差的天:23

JDK 8 的时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
TemporalAdjuster : 时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

 /**
     * TemporalAdjuster
     */
    @Test public void testTemporalAdjuster() {
        LocalDate ld = LocalDate.now();
        println(ld.with(firstDayOfMonth()));      // 2020-03-01 月初
        println(ld.with(firstDayOfNextYear()));   // 2021-01-01 年初
        println(ld.with(lastDayOfMonth()));       // 2020-03-31 月末
        println(ld.with(lastDayOfYear()));        // 2020-12-31 年末
        println(ld.with(next(SUNDAY)));           // 2020-04-05 下周日
        println(ld.with(previousOrSame(MONDAY))); // 2020-03-30 前一个或相同的周一
    }

    private void println(Object obj) {
        System.out.println(obj);
    }

JDK 8 设置日期时间的时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。

 // 设置日期时间的时区
    @Test
    public void test10() {
        // 1.获取所有的时区ID
        // ZoneId.getAvailableZoneIds().forEach(System.out::println);
        // 不带时间,获取计算机的当前时间
        LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
        System.out.println("now = " + now);    //now = 2020-03-31T22:43:42.289

        // 2.操作带时区的类
        // now(Clock.systemUTC()): 创建世界标准时间
        ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("bz = " + bz);      //bz = 2020-03-31T14:43:42.290Z

        // now(): 使用计算机的默认的时区,创建日期时间
        ZonedDateTime now1 = ZonedDateTime.now();
        System.out.println("now1 = " + now1); // now1 = 2020-03-31T22:43:42.290+08:00[Asia/Shanghai]

        // 使用指定的时区创建日期时间
        ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
        System.out.println("now2 = " + now2); // now2 = 2020-03-31T07:43:42.293-07:00[America/Vancouver]

        // 修改时区
        // withZoneSameInstant: 即更改时区,也更改时间
        ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameInstant = " + withZoneSameInstant); //withZoneSameInstant = 2020-03-31T22:43:42.293+08:00[Asia/Shanghai]

        // withZoneSameLocal: 只更改时区,不更改时间
        ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameLocal = " + withZoneSameLocal); // withZoneSameLocal = 2020-03-31T07:43:42.293+08:00[Asia/Shanghai]

    }
posted @ 2020-03-31 23:04  天宇轩-王  阅读(367)  评论(0编辑  收藏  举报