• Java 8 的时间日期 API


    上一篇文章『Java 的时间日期 API』中,我们学习了由 Date、Calendar,DateFormat 等组成的「传统时间日期 API」,但是传统的处理接口设计并不是很友好,不易使用。终于,Java 8 借鉴第三方优秀开源库 Joda-time,重新设计了一套 API。

    那么本篇文章就来简单学习一下新式的时间日期处理接口。

    表示时刻的 Instant

    Instant 和 Date 一样,表示一个时间戳,用于描述一个时刻,只不过它较 Date 而言,可以描述更加精确的时刻。并且 Instant 是时区无关的。

    Date 最多可以表示毫秒级别的时刻,而 Instant 可以表示纳秒级别的时刻。例如:

    • public static Instant now():根据系统当前时间创建一个 Instant 实例,表示当前时刻
    • public static Instant ofEpochSecond(long epochSecond):通过传入一个标准时间的偏移值来构建一个 Instant 实例
    • public static Instant ofEpochMilli(long epochMilli):通过毫秒数值直接构建一个 Instant 实例

    看看代码:

    public static void main(String[] args){
        //创建 Instant 实例
        Instant instant = Instant.now();
        System.out.println(instant);
    
        Instant instant1 = Instant.ofEpochSecond(20);
        System.out.println(instant1);
    
        Instant instant2 = Instant.ofEpochSecond(30,100);
        System.out.println(instant2);
    
        Instant instant3 = Instant.ofEpochMilli(1000);
        System.out.println(instant3);
    }
    

    输出结果:

    2018-04-23T02:43:10.973Z
    1970-01-01T00:00:20Z
    1970-01-01T00:00:30.000000100Z
    1970-01-01T00:00:01Z
    

    可以看到,Instant 和 Date 不同的是,它是时区无关的,始终是格林零时区相关的,也即是输出的结果始终格林零时区时间。

    处理日期的 LocalDate

    不同于 Calendar 既能处理日期又能处理时间,java.time 的新式 API 分离开日期和时间,用单独的类进行处理。LocalDate 专注于处理日期相关信息。

    LocalDate 依然是一个不可变类,它关注时间中年月日部分,我们可以通过以下的方法构建和初始化一个 LocalDate 实例:

    • public static LocalDate now():截断当前系统时间的年月日信息并初始化一个实例对象
    • public static LocalDate of(int year, int month, int dayOfMonth):显式指定年月日信息
    • public static LocalDate ofYearDay(int year, int dayOfYear):根据 dayOfYear 可以推出 month 和 dayOfMonth
    • public static LocalDate ofEpochDay(long epochDay):相对于格林零时区时间的日偏移量

    看看代码:

    public static void main(String[] args){
        //构建 LocalDate 实例
        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);
    
        LocalDate localDate1 = LocalDate.of(2017,7,22);
        System.out.println(localDate1);
    
        LocalDate localDate2 = LocalDate.ofYearDay(2018,100);
        System.out.println(localDate2);
    
        LocalDate localDate3 = LocalDate.ofEpochDay(10);
        System.out.println(localDate3);
    }
    

    输出结果:

    2018-04-23
    2017-07-22
    2018-04-10
    1970-01-11
    

    需要注意一点,LocalDate 会根据系统中当前时刻和默认时区计算出年月日的信息。

    除此之外,LocalDate 中还有大量关于日期的常用方法:

    • public int getYear():获取年份信息
    • public int getMonthValue():获取月份信息
    • public int getDayOfMonth():获取当前日是这个月的第几天
    • public int getDayOfYear():获取当前日是这一年的第几天
    • public boolean isLeapYear():是否是闰年
    • public int lengthOfYear():获取这一年有多少天
    • public DayOfWeek getDayOfWeek():返回星期信息
    • 等等

    这些方法都见名知意,此处不再赘述。

    处理时间的 LocalTime

    类似于 LocalDate,LocalTime 专注于时间的处理,它提供小时,分钟,秒,毫微秒的各种处理,我们依然可以通过类似的方式创建一个 LocalTime 实例。

    • public static LocalTime now():根据系统当前时刻获取其中的时间部分内容
    • public static LocalTime of(int hour, int minute):显式传入小时和分钟来构建一个实例对象
    • public static LocalTime of(int hour, int minute, int second):通过传入时分秒构造实例
    • public static LocalTime of(int hour, int minute, int second, int nanoOfSecond):传入时分秒和毫微秒构建一个实例
    • public static LocalTime ofSecondOfDay(long secondOfDay):传入一个长整型数值代表当前日已经过去的秒数
    • public static LocalTime ofNanoOfDay(long nanoOfDay):传入一个长整型代表当前日已经过去的毫微秒数

    同样的,LocalTime 默认使用系统默认时区处理时间,看代码:

    public static void main(String[] a){
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);
    
        LocalTime localTime1 = LocalTime.of(23,59);
        System.out.println(localTime1);
    
        LocalTime localTime2 = LocalTime.ofSecondOfDay(10);
        System.out.println(localTime2);
    }
    

    输出结果:

    13:59:03.723
    23:59
    00:00:10
    

    当然,LocalTime 中也同样封装了很多好用的工具方法,例如:

    • public int getHour()
    • public int getMinute()
    • public int getSecond()
    • public int getNano()
    • public LocalTime withHour(int hour):修改当前 LocalTime 实例中的 hour 属性并重新返回一个新的实例
    • public LocalTime withMinute(int minute):类似
    • public LocalTime withSecond(int second)
    • 等等

    LocalDateTime 类则是集成了 LocalDate 和 LocalTime,它既能表示日期,又能表述时间信息,方法都类似,只是有一部分涉及时区的转换内容,我们待会说。

    时区相关的日期时间处理 ZonedDateTime

    无论是我们的 LocalDate,或是 LocalTime,甚至是 LocalDateTime,它们基本是时区无关的,内部并没有存储时区属性,而基本用的系统默认时区。往往有些场景之下,缺乏一定的灵活性。

    ZonedDateTime 可以被理解为 LocalDateTime 的外层封装,它的内部存储了一个 LocalDateTime 的实例,专门用于普通的日期时间处理。此外,它还定义了 ZoneId 和 ZoneOffset 来描述时区的概念。

    ZonedDateTime 和 LocalDateTime 的一个很大的不同点在于,后者内部并没有存储时区,所以对于系统的依赖性很强,往往换一个时区可能就会导致程序中的日期时间不一致。

    而后者则可以通过传入时区的名称,使用 ZoneId 进行匹配存储,也可以通过传入与零时区的偏移量,使用 ZoneOffset 存储时区信息。

    所以,构建一个 ZonedDateTime 实例有以下几种方式:

    • public static ZonedDateTime now():系统将以默认时区计算并存储日期时间信息
    • public static ZonedDateTime now(ZoneId zone):指定时区
    • public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone):指定日期时间和时区
    • public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone)
    • public static ZonedDateTime ofInstant(Instant instant, ZoneId zone):通过时刻和时区构建实例对象
    • 等等

    看代码:

    public static void main(String[] a){
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        System.out.println(zonedDateTime);
    
        LocalDateTime localDateTime = LocalDateTime.now();
        ZoneId zoneId = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime,zoneId);
        System.out.println(zonedDateTime1);
    
        Instant instant = Instant.now();
        ZoneId zoneId1 = ZoneId.of("GMT");
        ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(instant,zoneId1);
        System.out.println(zonedDateTime2);
    }
    

    输出结果:

    2018-04-23T16:10:29.510+08:00[Asia/Shanghai]
    2018-04-23T16:10:29.511-07:00[America/Los_Angeles]
    2018-04-23T08:10:29.532Z[GMT]
    

    简单解释一下,首先第一个输出应该没什么问题,系统保存当前系统日期和时间以及默认的时区。

    第二个小例子,LocalDateTime 实例保存了时区无关的当前日期时间信息,也就是这里的年月日时分秒,接着构建一个 ZonedDateTime 实例并传入一个美国时区(西七区)。你会发现输出的日期时间为西七区的 16 点 29 分。

    像这种关联了时区的日期时间就很能够解决那种,换时区导致程序中时间错乱的问题。因为我关联了时区,无论你程序换到什么地方运行了,日期+时区 本就已经唯一确定了某个时刻,就相当于我在存储某个时刻的时候,我说明了这是某某时区的某某时间,即便你换了一个地区,你也不至于把这个时间按自己当前的时区进行解析并直接使用了吧。

    第三个小例子就更加的直接明了了,构建 ZonedDateTime 实例的时候,给定一个时刻和一个时区,而这个时刻值就是相对于给定时区的标准时间所经过的毫秒数。

    有关 ZonedDateTime 的其他日期时间的处理方法和 LocalDateTime 是一样的,因为 ZonedDateTime 是直接封装了一个 LocalDateTime 实例对象,所以所有相关日期时间的操作都会间接的调用 LocalDateTime 实例的方法,我们不再赘述。

    格式化日期时间

    Java 8 的新式日期时间 API 中,DateTimeFormatter 作为格式化日期时间的主要类,它与之前的 DateFormat 类最大的不同就在于它是线程安全的,其他的使用上的操作基本类似。我们看看:

    public static void main(String[] a){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(formatter.format(localDateTime));
    
        String str = "2008年08月23日 23:59:59";
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
        LocalDateTime localDateTime2 = LocalDateTime.parse(str,formatter2);
        System.out.println(localDateTime2);
    
    }
    

    输出结果:

    2018年04月23日 17:27:24
    2008-08-23T23:59:59
    

    格式化主要有两种情况,一种是将日期时间格式化成字符串,另一种则是将格式化的字符串装换成日期时间对象。

    DateTimeFormatter 提供将 format 方法将一个日期时间对象转换成格式化的字符串,但是反过来的操作却建议使用具体的日期时间类自己的 parse 方法,这样可以省去类型转换的步骤。

    时间差

    现实项目中,我们也经常会遇到计算两个时间点之间的差值的情况,最粗暴的办法是,全部幻化成毫秒数并进行减法运算,最后在转换回日期时间对象。

    但是 java.time 包中提供了两个日期时间之间的差值的计算方法,我们一起看看。

    关于时间差的计算,主要涉及到两个类:

    • Period:处理两个日期之间的差值
    • Duration:处理两个时间之间的差值

    例如:

    public static void main(String[] args){
        LocalDate date = LocalDate.of(2017,7,22);
        LocalDate date1 = LocalDate.now();
        Period period = Period.between(date,date1);
        System.out.println(period.getYears() + "年" +
                period.getMonths() + "月" +
                period.getDays() + "天");
    
        LocalTime time = LocalTime.of(20,30);
        LocalTime time1 = LocalTime.of(23,59);
        Duration duration = Duration.between(time,time1);
        System.out.println(duration.toMinutes() + "分钟");
    }
    

    输出结果:

    0年9月1天
    209分钟
    

    显然,年月日的日期间差值的计算使用 Period 类足以,而时分秒毫秒的时间的差值计算则需要使用 Duration 类。

    最后,关于 java.time 包下的新式日期时间 API,我们简单的学习了下,并没有深入到源码实现层次进行介绍,因为底层涉及大量的系统接口,涉及到大量的抽象类和实现类,有兴趣的朋友可以自行阅读 jdk 的源码深入学习。


    文章中的所有代码、图片、文件都云存储在我的 GitHub 上:

    (https://github.com/SingleYam/overview_java)

    欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。

    image

  • 相关阅读:
    172. Factorial Trailing Zeroes
    96. Unique Binary Search Trees
    95. Unique Binary Search Trees II
    91. Decode Ways
    LeetCode 328 奇偶链表
    LeetCode 72 编辑距离
    LeetCode 226 翻转二叉树
    LeetCode 79单词搜索
    LeetCode 198 打家劫舍
    LeetCode 504 七进制数
  • 原文地址:https://www.cnblogs.com/yangming1996/p/8921381.html
Copyright © 2020-2023  润新知