Java8日期时间API

Java 8以前日期时间API存在的问题

作为开发者,经常需要处理日期时间。在Java 8以前,相信你对 java.util.Datejava.util.Calendarjava.util.GregoiranCalendarjava.text.SimpleDateFormat 这四大类非常熟悉,它们分别用于处理日期、日历、公历、日期时间格式化。

这四个类有好多陷阱和坑,比如

  1. 非线程安全:这四大类都不是线程安全的。开发者在使用这些类时必须自己处理多线程并发问题。
  2. 设计不佳 :一方面日期和日期格式化分布在多个包中;另一方面,java.util.Date 的默认日期为1970年1月1日,没有统一性。而且 Date 类也缺少直接操作日期的相关方法。
  3. 时区处理困难:因为设计不佳,开发人员不得不编写大量代码来处理时区问题。
  4. 还有其它一些问题,如Calendar类月份从零开始计算等。

面对种种问题,Java 8 终于重新设计了所有日期时间、日历及时区相关的 API。并把它们都统一放置在 java.time 包和子包下。并作出了以下改进

  1. 新的日期时间 API 是线程安全的。不仅没有 setter 方法,而且任何对实例的变更都会返回一个新的实例,保证原来的实例不变。
  2. 新的日期时间 API 提供了大量的方法,用于修改日期时间的各个部分,并返回一个新的实例。
  3. 借鉴了第三方日期时间库joda很多的优点。
  4. 在时区方面,新的日期时间 API 引入了 ( domain ) 这个概念。

同时 Java 8 还针对原来复杂的 API 进行重新组合和拆分,分成了好多个类。

关于时间和时区

GMT和UTC

GMT,即格林尼治标准时间,也就是世界时。GMT的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时间使用。

UTC,即协调世界时。是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标,以「秒」为单位的国际原子时所综合精算而成的时间。为确保UTC与GMT相差不会超过0.9秒,在有需要的情况下(例如 1998-12-31T23:59:60Z)会在UTC内加上正或负闰秒。协调世界时区会使用 “Z” 来表示,协调世界时也会被称为 “Zulu time”。UTC现在作为世界标准时间使用。

所以,UTC与GMT基本上等同,误差不超过0.9秒。不过日常使用中,GMT 与 UTC 的功能与精确度是没有差别的。

时区

时区作为地理概念,表示 “遵守统一时间标准的一个地区”。

使用与 UTC 的偏移来表示时区,例如:中国所在时区为 UTC+08:00(又称为 Chinese Standard Time,即 “中国标准时间”)

地球自西向东旋转,东边比西边先看到太阳,东边的时间也比西边的早。为了统一世界的时间,1884年的国际经度会议规规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为零时区(基准 UTC),东1-12区,西1-12区,中国北京处于东8区(UTC+08:00),那么我们的时间会领先基准-也就是我们在早上 9 点时,伦敦是早上 1 点。

Unix时间戳

计算机中的Unix时间戳,使用自 1970-01-01T00:00:00Z(Z 即表示 UTC 时间)至今的毫秒差作为表示时间的数值,并且移除期间的“闰秒”(例如 1998-12-31T23:59:60Z),这么做当然是为了简化计算机对时间操作的复杂度。Unix 时间体系中,每天固定 86400 秒,这个时间是绝对公立的,它和时区没有任何关系。

Java 中的 Unix 时间

Java 确保:每天 24 小时、每小时 60 分、每分钟 60 秒。

Java 中获取 “当前” 时间的方法,其底层实现,全部由 java.lang.System.currentTimeMillis() 提供自 UTC 1970-01-01T00:00:00 的毫秒数。java.lang.System.currentTimeMillis() 作为 native 方法,其实现与 JVM 所在的机器相关(通常使用 NTP 协议保持更新)。

LocalDate、LocalTime、LocalDateTime

java.time.LocalDate 用于表示 “本地日期”,无 “时间”。LocalDate 不承载时区信息。

java.time.LocalTime 用于表示 “本地时间”,无 “日期”。LocalTime 不承载时区信息。

java.time.LocalDateTime 用于表示 “本地日期与时间”。LocalDateTime 不承载时区信息。

LocalDate 实例与 LocalTime 实例能够共同构建 LocalDateTime 实例,由 LocalDateTime 实例能够获取 LocalDate 实例与 LocalTime 实例。

由于 LocalDateTime 不承载时区信息,因此,其不能与 Instant 相互转换,必须提供时区信息。

获取对象的方法

获取对象的方法:

  1. 通过静态方法 :now()(获取的时间是系统当前的时间
  2. 通过静态方法:of()(方法参数可以指定时间
@Test
public void test01() {
    /* 通过静态方法 now() 返回该类的实例 */
    //获取当前的日期时分秒
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);

    //获取当前的日期
    LocalDate now1 = LocalDate.now();
    System.out.println(now1);

    //获取当前的时分秒
    LocalTime now2 = LocalTime.now();
    System.out.println(now2);

    System.out.println("=========================================");
    /* 静态方法 of() 返回该类的实例 */
    //指定日期时分秒
    LocalDateTime localDateTime = LocalDateTime.of(2048, 11, 25, 12, 00, 30);
    System.out.println(localDateTime);

    //指定日期
    LocalDate date = LocalDate.of(2020, 12, 12);
    System.out.println(date);

    //指定时分秒
    LocalTime time = LocalTime.of(14, 20, 30);
    System.out.println(time);
}

输出结果

2020-12-12T16:02:30.502
2020-12-12
16:02:30.502
=========================================
2048-11-25T12:00:30
2020-12-12
14:20:30

常用方法

与获取相关的方法(get系类的方法)

  • getYear():获取年
  • getHour():获取小时
  • getMinute():获取分钟
  • getSecond():获取秒值
  • getDayOfMonth():获得月份天数(1-31)
  • getDayOfYear():获得年份天数(1-366)
  • getDayOfWeek():获得星期几(返回一个 DayOfWeek枚举值)
  • getMonth():获得月份(返回一个 Month 枚举值)
  • getMonthValue():获得月份(1-12)
  • getYear():获得年份
@Test
public void test02() {
    //获取日期时分秒
    LocalDateTime now = LocalDateTime.now();

    //获取年份
    int year = now.getYear();
    System.out.println(year);

    //获取月份枚举
    //Month 枚举类,定义了十二个月份
    Month month = now.getMonth();
    System.out.println(month);

    //获取月份的数值
    int monthValue = now.getMonthValue();
    System.out.println(monthValue);

    //获取当天在本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    System.out.println(dayOfMonth);

    //获取小时
    int hour = now.getHour();
    System.out.println(hour);

    //获取分钟
    int minute = now.getMinute();
    System.out.println(minute);

    //获取秒值
    int second = now.getSecond();
    System.out.println(second);
}

输出结果

2020
DECEMBER
12
12
16
2
48

转换的方法

  • toLocalDate():将LocalDateTime转换为相应的LocalDate对象
  • toLocalTime():将LocalDateTime转换为相应的LocalTime对象
@Test
public void test04() {
    //获取当前年月日,时分秒
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);

    //将LocalDateTime转换为相应的LocalDate对象
    LocalDate localDate = now.toLocalDate();
    System.out.println(localDate);

    //将LocalDateTime转换为相应的LocalTime对象
    LocalTime localTime = now.toLocalTime();
    System.out.println(localTime);
}

输出结果

2020-12-12T16:07:23.045
2020-12-12
16:07:23.045

判断的方法

  • isAfter():判断一个日期是否在指定日期之后
  • isBefore():判断一个日期是否在指定日期之前
  • isEqual():判断两个日期是否相同
  • isLeapYear():判断是否是闰年(注意是LocalDate类 和 LocalDateTime类特有的方法)
@Test
public void test05() {
    //获取当前的日期
    LocalDate now = LocalDate.now();

    //指定的日期
    LocalDate of = LocalDate.of(2015, 12, 12);

    //判断一个日期是否在另一个日期之前
    boolean before = of.isBefore(now);
    System.out.println(before);

    //判断一个日期是否在另一个日期之后
    boolean after = of.isAfter(now);
    System.out.println(after);

    //判断这两个日期是否相等
    boolean after1 = now.equals(of);
    System.out.println(after1);

    //判断闰年
    boolean leapYear = of.isLeapYear();
    System.out.println(leapYear);
}

输出结果

true
false
false
false

增减年月日时分秒的方法(plus/minus系列的方法)

增加相关的方法

  • plusYears(int offset):增加指定年份
  • plusMonths(int offset):增加指定月份
  • plusWeeks(int offset):增加指定周
  • plusDates(int offset):增加指定日
  • plusHours(int offset):增加指定时
  • plusMinuets(int offset):增加指定分
  • plusSeconds(int offset):增加指定秒
  • plusNanos(int offset):增加指定纳秒

减少相关的方法

  • minusYears(int offset):减少指定年
  • minusMonths(int offset):减少指定月
  • minusWeeks(int offset):减少指定周
  • minusDates(int offset):减少指定日
  • minusHours(int offset):减少指定时
  • minusMinuets(int offset):减少指定分
  • minusSeconds(int offset):减少指定秒
  • minusNanos(int offset):减少指定纳秒
@Test
public void test07() {
    //增加时间量的方法 plusXXX系类的方法 返回的是一个新的日期对象
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
    //可以给当前的日期增加时间量
    LocalDateTime newDate = now.plusYears(1);
    int year = newDate.getYear();
    System.out.println(year);

    System.out.println("================================");
    //减去时间量的方法minusXXX 系列的方法 返回的是一个新的日期对象
    LocalDate now1 = LocalDate.now();
    System.out.println(now1);
    LocalDate newDate2 = now1.minusDays(10);
    int dayOfMonth = newDate2.getDayOfMonth();
    System.out.println(dayOfMonth);
}

输出结果

2020-12-12T16:12:43.228
2021
================================
2020-12-12
2

指定年月日时分秒的方法

  • with(TemporalAdjuster adjuster):指定特殊时间
  • withYear(int year):指定年
  • withDayOfYear(int dayOfYear):指定日
  • withMonth(int month):指定月
  • withDayOfMonth(int dayOfMonth):指定日
@Test
public void test08() {
    //指定某个日期的方法 with()方法
    LocalDate now2 = LocalDate.now();
    System.out.println(now2);
    LocalDate localDate = now2.withYear(2014);
    System.out.println(localDate);

    // TemporalAdjusters工具类,提供了一些获取特殊日期的方法
    LocalDate with = now2.with(TemporalAdjusters.firstDayOfMonth());
    System.out.println(with);
    LocalDate with1 = now2.with(TemporalAdjusters.firstDayOfNextMonth());
    System.out.println(with1);

    //获取这个月的第几个星期几是几号,比如 TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.FRIDAY)
    // 代表的意思是这个月的第二个星期五是几号
    LocalDate with2 = now2.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.FRIDAY));
    System.out.println(with2);
}

输出结果

2020-12-12
2014-12-12
2020-12-01
2021-01-01
2020-12-11

将日期格式化为字符串的方法

  • format():格式化字符串
@Test
public void test03() {
    //获取当前日期时分秒
    LocalDateTime now = LocalDateTime.now();

    //默认格式  年-月-日T时:分:秒
    System.out.println(now);

    //指定格式
    DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
    //传入格式
    String dateStr = now.format(ofPattern);
    System.out.println(dateStr);
}

输出结果

2020-12-12T16:06:12.705
2020年12月12日 16时06分12秒

解析字符串为日期时间的方法

  • paser(String str):将一个日期字符串解析成日期对象,注意字符串日期的写法的格式要正确,否则解析失败
  • paser(String str, DateTimeFormatter formatter):将字符串按照参数传入的格式进行解析
@Test
public void test06() {
    //给出一个符合默认格式要求的日期字符串
    String dateStr = "2020-01-01";

    //把日期字符串解析成日期对象 如果日期字符串时年月日 解析时用  LocalDate
    LocalDate parse = LocalDate.parse(dateStr);
    System.out.println(parse);

    System.out.println("===========================================");
    //给出一个符合默认格式要求的 时分秒 字符串
    String dateTimeStr = "14:20:30";

    //把 时分秒 字符串解析成时分秒对象
    LocalTime parse1 = LocalTime.parse(dateTimeStr);
    System.out.println(parse1);

    System.out.println("=========================================");
    //给出一个符合默认格式要求的 日期时分秒 字符串
    String str = "2018-12-12T14:20:30";

    //把 日期时分秒 字符串解析成时分秒对象
    LocalDateTime parse2 = LocalDateTime.parse(str);
    System.out.println(parse2);

    System.out.println("========================================");
    //给出一个自定义日期时分秒格式字符串
    String dateStr2 = "2020年12月12日 12:13:14";

    //给出一个自定义解析格式
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");

    //按照指定的格式去解析
    LocalDateTime parse3 = LocalDateTime.parse(dateStr2, formatter);
    System.out.println(parse3);
}

输出结果

2020-01-01
===========================================
14:20:30
=========================================
2018-12-12T14:20:30
========================================
2020-12-12T12:13:14

TemporalAdjuster接口 - 时间调节器

前面看到的所有日期操作都是相对比较直接的。有的时候,你需要进行一些更加灵活复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,就需要时间调节器 TemporalAdjuster,可以更加灵活地处理日期。TemporalAdjusters 工具提供了一些通用的功能,并且你还可以新增你自己的功能。

@Test
public void testTemporalAdjuster() {
    LocalDate now = LocalDate.now();
    //指定日期
    //对于一些特殊的日期,可以通过一个工具类TemporalAdjusters 来指定
    //见名知意,本月第一天
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfMonth();
    LocalDate with = now.with(temporalAdjuster);
    System.out.println(with);
    //下周周末
    TemporalAdjuster next = TemporalAdjusters.next(DayOfWeek.SUNDAY);
    LocalDate with1 = now.with(next);
    System.out.println(with1);
    System.out.println("===================================");


    LocalDate now1 = LocalDate.now();
    //自定义日期 - 下一个工作日
    LocalDate with2 = now1.with(new TemporalAdjuster() {
        @Override
        //参数 nowDate 当前的日期对象
        public Temporal adjustInto(Temporal nowDate) {
            //向下转型
            LocalDate date = (LocalDate) nowDate;
            if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
                LocalDate localDate = date.plusDays(3);
                return localDate;
            } else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
                LocalDate localDate = date.plusDays(2);
                return localDate;
            } else {
                LocalDate localDate = date.plusDays(1);
                return localDate;
            }
        }
    });
    System.out.println("下一个工作日是:" + with2);
}

输出结果

2020-12-01
2020-12-13
===================================
下一个工作日是:2020-12-14

Duration类 - 用于计算两个“时间”间隔的类

Duration 表示一个时间段,Duration 包含两部分:seconds 表示秒,nanos 表示纳秒,它们的组合表达了时间长度。

因为 Duration 表示时间段,所以 Duration 类中不包含 now() 静态方法。注意,Duration 不包含毫秒这个属性。

Duration只能处理两个LocalTime, LocalDateTime, ZonedDateTime; 如果传入的是LocalDate,将会抛出异常

常用API

  • 静态方法 between():计算两个时间的间隔,默认是
  • toDays():将时间转换为以天为单位的
  • toHours():将时间转换为以时为单位的
  • toMinutes():将时间转换为以分钟为单位的
  • toMillis():将时间转换为以毫秒为单位的
  • toNanos():将时间转换为以纳秒为单位的
@Test
public void test10() {
    //计算时间的间隔
    Instant start = Instant.now();
    for (int i = 0; i < 100000; i++) {
        // System.out.println(i);
    }
    Instant end = Instant.now();
    Duration duration = Duration.between(start, end);
    long l = duration.toNanos();

    //间隔的时间
    System.out.println("循环耗时:" + l + "纳秒");
}

输出结果

循环耗时:1000000纳秒

Period类 - 用于计算两个“日期”间隔的类

Period 在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段。Duration 用于计算两个时间间隔,Period 用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。

  • 静态方法 between():计算两个日期之间的间隔
  • getYears():获取年份
  • getMonths():获取月份
  • getDays():获取天数
@Test
public void test11() {
    //计算两个日期的间隔
    LocalDate birthday = LocalDate.of(2012, 12, 12);
    LocalDate now = LocalDate.now();

    //我从出生到现在,有多少岁,零几个月,几天
    //计算两个日期的间隔
    Period between = Period.between(birthday, now);
    int years = between.getYears();
    int months = between.getMonths();
    int days = between.getDays();
    System.out.println("玛雅人的地球都消灭了" + years + "年" + months + "月" + days + "天了...");
}

输出结果

玛雅人的地球都消灭了8年0月0天了...

Instant 时间戳类

java.time.Instant 时间线上的一个瞬时点,承载纳秒级精度的 Unix 时间戳,其 String toString() 方法基于 ISO-8601 进行格式化。Instant 不承载时区信息。

获取对象的方法:now():注意默认获取出来的是默认时区,和我们相差八个小时(因为我们在东八时区

设置偏移量的方法:atOffset()

获取系统默认时区时间的方法:atZone():方法的参数是要一个时区的编号(可以通过时区编号类获取ZonedDateTime类的对象)

get系列的方法

  • getEpochSecond():获取从1970-01-01 00:00:00当前时间秒值
  • toEpochMilli():获取从1970-01-01 00:00:00当前时间毫秒值
  • getNano():把获取到的当前时间的秒数 换算成纳秒

ofEpoch系列方法

  • ofEpochSecond():给计算机元年增加秒数
  • ofEpochMilli():给计算机元年增加毫秒数
@Test
public void test09() {
    //  Instant 时间戳类从1970 -01 - 01 00:00:00 截止到当前时间的毫秒值
    Instant now = Instant.now();
    System.out.println(now); //获取的是默认时区,获取的不是中国 的时区

    //获取当前时区的,我们可以添加偏移量,返回偏移过后的日期
    OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetDateTime);
    System.out.println("===========================");

    //从1970 - 01 - 01 00:00:00 截止到当前时间的毫秒值
    long l = System.currentTimeMillis();
    System.out.println(l);
    long time = new Date().getTime();
    System.out.println(time);

    //JDK1.8 Instant 时间戳类从1970 -01 - 01 00:00:00 截止到当前时间的毫秒值
    Instant now1 = Instant.now();

    //toEpochMilli():从1970 -01 - 01 00:00:00 截止到当前时间间隔的毫秒值
    long l1 = now1.toEpochMilli();
    System.out.println(l1);

    //获取从1970 -01 - 01 00:00:00 截止到当前时间间隔的秒值
    long epochSecond = now1.getEpochSecond();
    System.out.println(epochSecond);

    System.out.println("==========================");
    //给计算机元年增加相应的时间量
    Date date = new Date(1000 * 60 * 60 * 24);
    System.out.println(date);

    //现在 给计算机元年增加相应的时间量
    //5. ofEpochSecond() 方法 给计算机元年增加秒数
    //ofEpochMilli() 给计算机元年增加毫秒数
    Instant instant = Instant.ofEpochMilli(1000 * 60 * 60 * 24);
    System.out.println(instant);

    //ofEpochSecond() 方法 给计算机元年增加秒数
    Instant instant1 = Instant.ofEpochSecond(60 * 60 * 24);
    System.out.println(instant1);
}

输出结果

2020-12-12T08:48:46.480Z
2020-12-12T16:48:46.480+08:00
===========================
1607762926539
1607762926540
1607762926540
1607762926
==========================
Fri Jan 02 08:00:00 CST 1970
1970-01-02T00:00:00Z
1970-01-02T00:00:00Z

Clock - 时钟系统

Clock 是时钟系统,用于查找当前时刻。你可以用它来获取某个时区下当前的日期或者时间。可以用 Clock 来替代旧的 System.currentTimeInMillis() 与 TimeZone.getDefault() 方法。

@Test
public void testClock() {
    //系统默认时间
    Clock clock = Clock.systemDefaultZone();
    System.out.println(clock.instant().toString());

    //世界协调时UTC
    Clock clock1 = Clock.systemUTC();
    //通过Clock获取当前时刻
    System.out.println("当前时刻为:" + clock1.instant());
    //获取clock对应的毫秒数,与System.currentTimeMillis()输出相同
    System.out.println(clock1.millis());
    System.out.println(System.currentTimeMillis());
    System.out.println(new Date(System.currentTimeMillis()).toString());

    //在clock基础上增加6000秒,返回新的Clock
    Clock clock2 = Clock.offset(clock1, Duration.ofSeconds(6000));

    //纽约时间
    Clock clock3 = Clock.system(ZoneId.of("America/New_York"));
    System.out.println("Current DateTime with NewYork clock: " + LocalDateTime.now(clock3));
    System.out.println(clock3.millis());
}

输出结果

2020-12-12T09:05:07.025Z
当前时刻为:2020-12-12T09:05:07.036Z
1607763907036
1607763907036
Sat Dec 12 17:05:07 CST 2020
Current DateTime with NewYork clock: 2020-12-12T04:05:07.040
1607763907041

ZonedDate、ZonedTime、ZonedDateTime - 带时区的日期时间

这个三个类方法及用法和 LocalDate、 LocalTime、 LocalDateTime 基本一样,只不过ZonedDate、ZonedTime、ZonedDateTime 这三个带有特定时区

ZoneId - 世界时区类

Java 使用 ZoneId 来标识不同的时区。时区从基准 UTC 开始的一个固定偏移。ZoneId 的子类 ZoneOffset,代表了这种从伦敦格林威治零度子午线开始的时间偏移,也就是时差。

常用API

  • getAvailableZoneIds():获取世界各个地方的时区的集合
  • systemDefault():获取系统默认时区的ID
  • of(String zoneName):根据各个地区的时区ID名创建对象
@Test
public void test13() {
    //ZoneID 世界时区类
    //获取世界各地的时区编号。
    Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
    for (String availableZoneId : availableZoneIds) {
        System.out.println(availableZoneId);
    }

    System.out.println("=====================");
    //获取系统的默认时区编号
    ZoneId zoneId = ZoneId.systemDefault();
    System.out.println(zoneId);

    //获取其他国家的日期
    LocalDateTime now = LocalDateTime.now();
    //获取指定时区的日期时间
    ZoneId zoneId1 = ZoneId.of("Europe/Monaco");
    ZonedDateTime zonedDateTime = now.atZone(zoneId1);  //获得指定时区的当前时间
    System.out.println(zonedDateTime);

    System.out.println("=====================");
    //根据时区,获取该地区的日期
    LocalDateTime now1 = LocalDateTime.now(ZoneId.of("America/Phoenix"));  //获得指定时区的当前时间(不带时区信息)
    System.out.println(now1);
}

输出结果

America/Toronto
Asia/Singapore
Australia/Lindeman
America/Los_Angeles
SystemV/EST5EDT
Pacific/Majuro
America/Argentina/Buenos_Aires
Europe/Nicosia
Pacific/Guadalcanal
Europe/Athens
US/Pacific
Europe/Monaco
=====================
Asia/Shanghai
2020-12-12T20:56:27.214+01:00[Europe/Monaco]
=====================
2020-12-12T05:56:27.225

DateTimeFormatter类 - 用于解析日期字符串和格式化日期输出

DateTimeFormatter用于解析日期字符串和格式化日期输出,创建格式化器最简单的方法是通过 DateTimeFormatter 的静态工厂方法以及常量。

在java8之前,我们进行时间格式化主要是使用SimpleDateFormat,而在java8中,主要是使用DateTimeFormatter,java8中,预定义了一些标准的时间格式,我们可以直接将时间转换为标准的时间格式

常用API

  • ofPattern(“yyyy-MM-dd”):静态方法,通过给定格式获取对象
  • format():把一个日期对象的默认格式 格式化成指定的格式字符串
@Test
public void test12() {
    // 之前格式化日期的类  new SimpleDateFormat()
    //JDK1.8 DateTimeFormatter
    //指定格式 静态方法 ofPattern()
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    // DateTimeFormatter 自带的格式方法
    LocalDateTime now = LocalDateTime.now();

    //把日期对象,格式化成字符串
    String format = formatter.format(now);
    //刚才的方式是使用的日期自带的格式化方法
    String format1 = now.format(formatter);
    System.out.println(format);
    System.out.println(format1);
}

输出结果

2020-12-12 17:28:50
2020-12-12 17:28:50

格式化输出 & 字符串解析

java.time.format.DateTimeFormatter 能够进行 TemporalAccessor 类型(包括:LocalDateLocalTimeLocalDateTimeZonedDateTime)的格式化输出。同时,LocalDateLocalTimeLocalDateTimeZonedDateTime 提供了静态的 parse 方法,能够进行字符串解析。

LocalDateLocalTimeLocalDateTimeZonedDateTime 允许基于类型的默认格式进行格式化输出和字符串解析。

类型默认格式示例
Instant2017-11-23T10:15:30.00Z
LocalDate2017-11-23
LocalTime10:15:30
LocalDateTime2017-11-23T10:15:30
ZonedDateTime2017-11-23T10:15:30+01:00[Asia/Shanghai]

使用SimpleDateFormat的正确姿势

方法一:在需要执行格式化的地方都新建SimpleDateFormat实例,使用局部变量来存放SimpleDateFormat实例

public static String formatDate(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
}

这种方法可能会导致短期内创建大量的SimpleDateFormat实例,如解析一个excel表格里的字符串日期。

方法二:为了避免创建大量的SimpleDateFormat实例,往往会考虑把SimpleDateFormat实例设为静态成员变量,共享SimpleDateFormat对象。这种情况下就得对SimpleDateFormat添加同步。

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static String formatDate(Date date) {
    synchronized (sdf) {
        return sdf.format(date);
    }
}

这种方法的缺点也很明显,就是在高并发的环境下会导致解析被阻塞。

方法三(推荐):要在高并发环境下能有比较好的体验,可以使用ThreadLocal来限制SimpleDateFormat只能在线程内共享,这样就避免了多线程导致的线程安全问题。

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

public static String formatDate(Date date) {
    return threadLocal.get().format(date);
}

Java8 日期时间类与Date类的相互转化

在转换中,我们需要注意,因为java8之前Date是包含日期和时间的,而LocalDate只包含日期,LocalTime只包含时间,所以与Date在互转中,势必会丢失日期或者时间,或者会使用起始时间。如果转LocalDateTime,那么就不存在信息误差。

Date和Instant互相转换

@Test
public void test18() {
    //Date与Instant互相转换
    Instant instant  = Instant.now();
    Date date = Date.from(instant);
    System.out.println(date);

    Instant instant2 = date.toInstant();
    System.out.println(instant2);
}

输出结果

Sat Dec 12 21:18:13 CST 2020
2020-12-12T13:18:13.129Z

Date与LocalDateTime互相转换

@Test
public void test19() {
    //Date - LocalDateTime
    Date date = new Date();
    System.out.println("current date: " + date);

    LocalDateTime localDateTime1 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    System.out.println("localDateTime1: " + localDateTime1);

    LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    System.out.println("localDateTime2: " + localDateTime2);

    //LocalDateTime - Date
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println("localDateTime: " + localDateTime);

    Date date1 = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    System.out.println("date1: " + date1);
}

输出结果

current date: Sat Dec 12 21:27:47 CST 2020
localDateTime1: 2020-12-12T21:27:47.592
localDateTime2: 2020-12-12T21:27:47.592
localDateTime: 2020-12-12T21:27:47.652
date1: Sat Dec 12 21:27:47 CST 2020

Date与LocalDate互相转换

@Test
public void test20() {
    Date date = new Date();
    System.out.println("current date: " + date);

    // Date -LocalDate
    LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    System.out.println("localDate: " + localDate);

    // LocalDate -Date
    LocalDate localDate1 = LocalDate.now();
    //因为LocalDate不包含时间,所以转Date时,会默认转为当天的起始时间,00:00:00
    Instant instant = localDate1.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
    Date date1 = Date.from(instant);
    System.out.println("date1: " + date1);
}

输出结果

current date: Sat Dec 12 21:37:51 CST 2020
localDate: 2020-12-12
date1: Sat Dec 12 00:00:00 CST 2020

Date转换为LocalTime

@Test
public void test21() {
    Date date = new Date();
    System.out.println("current date: " + date);

    // Date - LocalTime
    LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
    System.out.println("localTime: " + localTime);
}

输出结果

current date: Sat Dec 12 21:40:47 CST 2020
localTime: 21:40:47.596

GregorianCalendar与ZonedDateTime相互转换

@Test
public void test22() {
    //GregorianCalendar与ZonedDateTime相互转换
    ZonedDateTime zonedDateTime = new GregorianCalendar().toZonedDateTime();
    System.out.println("zonedDateTime: " + zonedDateTime);
    GregorianCalendar calendar = GregorianCalendar.from(zonedDateTime);
    System.out.println("calendar: " + calendar.getTime());
}

输出结果

zonedDateTime: 2020-12-12T21:55:08.286+08:00[Asia/Shanghai]
calendar: Sat Dec 12 21:55:08 CST 2020

Java 8日期时间类图

img

类图所示,java.time.temporal 提供的接口:

  1. TemporalField:日期与时间 “字段”,例如:2017-11-18 中的 18 “天”
  2. TemporalUnit:时间 “单位”,例如:1 年 13 天的 13 “天”
  3. TemporalAccessor:“时间相关” 对象的 “只读” 接口
  4. Temporal:“时间相关” 对象的 “读写” 接口,继承自 TemporalAccessor
  5. TemporalAdjusterTemporal 类型对象 “设置 & 调整” 的函数式接口
  6. TemporalAmount:时间段

java.time 提供的类:

  1. InstantLocalDateLocalTimeLocalDateTimeZonedDateTime:实现 TemporalTemporalAdjuster 接口
  2. DurationPeriod:实现 TemporalAmount 接口

Java 8时间日期 API 中的设计模式

  • 工厂模式:now()、of() 等工厂方法直接生成日期或者日期时间。

  • 策略模式:LocalDate/LocalTime/LocalDateTime/ZonedDateTime,针对日期、时间、日期和时间、带时区的日期时间,使用具体的时间日期类处理。策略模式在设计一整套东西时,对开发者特别友好。

    前面也提到,所有新的日期时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分。一旦你使用了其中某个类的方法,那么非常容易上手其他类的使用。

  • 构建者模式:Java 8 开始在 Calendar 中加入了构建者类,可以按如下方式生成新的 Calendar 对象。

这里设计模式与标准的教科书式的设计模式可能有所区别,所以我们在使用设计模式时也应灵活处理,不是一成不变的。

Java 8的日期时间API总结

前面详细介绍了Java 8的日期时间API,现在进行简单总结一下。

新的时间与日期 API 中很重要的一点是,它定义清楚了基本的时间与日期的概念,比方说日期、时间、瞬时时间、持续时间、时区及时间段。它们都是基于 ISO8601 日历系统,它是世界民用历法,也就是我们所说的公历。

java.time包下主要包含下面几个主要的类:

  • LocalDate:表示不带时间的日期,比如:2016-10-20
  • LocalTime:表示不带日期的时间,比如:23:12:10
  • LocalDateTime:日期时间,比如:2016-10-20 23:14:21
  • TemporalAdjuster : 时间调节器
  • TemporalAdjusters:获得指定日期时间等,如当月的第一天、今年的最后一天等
  • Duration:持续时间,计算两个“时间”的间隔
  • Period:日期间隔,计算两个“日期”的间隔
  • Instant:Unix 时间,它代表的是时间戳,比如 2018-01-14T02:20:13.592Z
  • Clock:时钟,获取某个时区下的瞬时时间
  • ZoneId:时区id,例如 Asia/Shanghai
  • ZonedDateTime:带时区的日期时间
  • DateTimeFormatter:时间格式化

新的 API 区分各种日期时间概念并且各个概念使用相似的方法定义模式,这种相似性非常有利于 API 的学习。总结一下一般的方法规律:

  • of:静态工厂方法,用于创建实例
  • now:静态工厂方法,用当前时间创建实例
  • parse:静态工厂方法,从字符串解析得到对象实例
  • get:获取对象的部分状态
  • is:检查某些东西的是否是 true,例如比较时间前后
  • with:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型)
  • plus:返回一个时间增加了的时间日期对象拷贝
  • minus:返回一个时间减少了的时间日期对象拷贝
  • to:把当前时间日期对象转换成另外一个,可能会损失部分状态。
  • at:把这个对象与另一个对象组合起来,例如:date.atTime(time)
  • format:将时间日期格式化为字符串

最后再次声明,Java 8 中新的时间与日期 API 中的所有类都是不可变且线程安全的,任何修改操作都会返回一个新的实例,而之前 java.util.Date、Calendar 以及 SimpleDateFormat 这些关键的类都不是线程安全的。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页