zoukankan      html  css  js  c++  java
  • Java 8——日期时间工具库(java.time)

    一.前言

    在介绍Java SE 8中新的日期时间库前,先了解下Java 8之前的日期时间工具的诟病。

    在Java SE 8前,日期时间工具库在java.util包中,包括:

    • java.util.Date:表示日期和时间
    • java.util.Calendar以及其实现子类:表示各种日历系统,常用的是格林威治日历java.util.GregorianCalendar
    • java.util.TimeZone以及其实现子类:表示时区偏移量和夏令时

    以及辅助其进行格式化和解析的工具库在java.text包中,包括:

    • java.text.DateFormat:格式化日期时间和解析日期时间的工具抽象类
    • java.text.SimpleDateFormat:DateDateFormat的实现

    从以上的简述中,对java 8之前的日期时间库,有所宏观视觉。下面简要总结下其设计上的瑕疵和被开发者无限吐槽的诟病:

    • 从以上的api上看,java 8之前的日期时间工具库缺乏年、月、日、时间、星期的单独抽象;
    • Dater日期时间类既描述日期又描述时间,耦合,且Date不仅在java.util包中存在,在java.sql中也存在,重复名称,容易导致bug发生;
    • api的设计上晦涩,难用,不够生动,难以以自然人类的思维理解日期时间。年月日需要从Calendar中获取。q;
    • 最被开发者抱怨的是类型不安全,Calendar类中全局属性是可变的,在多线程访问时,会存在线程安全问题。SimpleDateFormat格式化和解析日期,需要使用年月日时分秒,所以持有了Calendar属性,导致其也是非线程安全;
    // 以下都是Calendar中持有的全局属性
    // 这些全局属性都是可变的,提供了set
    protected int fields[];
    transient private int stamp[];
    protected long time;
    protected boolean  isTimeSet;
    
    // 在其子类GregorianCalendar中
    private transient int[] zoneOffsets;
    
    // setTime方法会调用此方法
    // 该方法中修改了上述的很多全局属性
    public void setTimeInMillis(long millis) {
            // If we don't need to recalculate the calendar field values,
            // do nothing.
            if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet
                && (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) {
                return;
            }
            time = millis;
            isTimeSet = true;
            areFieldsSet = false;
            computeFields();
            areAllFieldsSet = areFieldsSet = true;
    }
    

    所以在多线程环境中使用Calendar是非线程安全,多个线程修改其属性域会发生数据一致性和可见性问题。

    在DateFormat中持有了Calendar属性,用于解析和格式化日期:

    // 从注释上看,Calendar用于计算日期时间域
    /**
     * The {@link Calendar} instance used for calculating the date-time fields
     * and the instant of time. This field is used for both formatting and
     * parsing.
     *
     * <p>Subclasses should initialize this field to a {@link Calendar}
     * appropriate for the {@link Locale} associated with this
     * <code>DateFormat</code>.
     * @serial
     */
    protected Calendar calendar;
    
    // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);
    
        boolean useDateFormatSymbols = useDateFormatSymbols();
    
        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }
    
            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;
    
            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;
    
            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }
    

    format方法中设置了全局成员Calendar的time,多线程访问时每次都会改变Calendar类,导致format格式化时会出现线程安全问题。所以DateFormat和其子类SimpleDateFormat都是非类型安全。

    这个可以说是被开发者极度抱怨的。所以每次在使用日期格式工具时大多数都会重新new或者使用ThreadLocal。

    基于此诸多问题,java设计者终于在Java SE 8中引入了新的日期时间库。新的日期时间库的易用程度会让你振服!下面开始进入主题,Java SE 8中的日期时间库java.time。

    二.概览

    先认识下joda项目,joda项目包括:

    • Joda-Time - Basic types for Date and Time
    • Joda-Money - Basic types for Money
    • Joda-Beans - Next generation JavaBeans
    • Joda-Convert - String to Object conversion
    • Joda-Collect - Additional collection data structures

    其中joda time是日期时间三方库,但是在java 8之前joda time其实是标准的日期时间库,其出色的语义表达,易用易于理解的api,类型安全的特性,大受开发者的追捧。而且其日历系统遵循的是IOS_8601国际标准,同时还包括其他的非标准的日历系统。支持时区、持续时间、格式化和解析功能。

    在java 8之前可以依赖joda time三方库,使用其日期时间库。

    但在java 8中提出了JSR 310: Date and Time API规范,该规范即新版的日期时间库java.time规约。可以说JSR-310的设计上汲取了大量的joda time的特性。新版本的日期时间库基于JSR 310: Date and Time API被开发,java.time是基于国际化标准日历系统(International Organization for Standardization)ISO_8601,同时java.time.chrono支持对全球日历系统的扩展。

    JSR-310中设计的java.time包括年、月、星期、日期时间、持续时间段、瞬时、时钟、时区的抽象及处理。且api的设计上使用易读易于理解的名称和设计模式,让使用者欣然接受。而且提供旧版和新版api之间的互通以处理兼容性问题。

    下面看张概览图,从宏观角度了解下java.time

    • 第一层是对年、月、月中日、星期的抽象;
    • 第二层是对日期、日期时间、时区的抽象,其中时区分为时区Id(Europe/Paris)和时区偏移量(Z/+hh:mm/-hh:mm);
    • 第三层是对区域时间和便宜时间的抽象;
    • 第四层是对瞬时和时钟的抽象;
    • 第五层是对时序时段和持续周期的抽象
    • 右侧层是辅助工具类,如:日期时间格式、日期时间调整器、其他的日历系统;

    java 8中日期时间库共分为五个package:

    • java.time:基于ISO_8601日历系统实现的日期时间库
    • java.time.chrono:全球日历系统扩展库,可以自行扩展
    • java.time.format:日期时间格式,包含大量预定义的格式,可以自行扩展
    • java.time.zone:时区信息库
    • java.time.temporal:日期时间调整辅助库

    关于日期时间库的使用详细过程,推荐查看oracle提供的java教程The Java™ Tutorials——Trail: Date Time

    也可以查看我的github中java 8新特性代码:lixyou/java

    三.java.time优点

    1.设计

    java.time中使用了大量的设计模式

    • 工厂模式:now()工厂方法直接生成当前日期时间或者瞬时;of()工厂方法根据年月日时分秒生成日期或者日期时间;
    • 装饰模式:时区时间ZoneDateTime/便宜时间OffsetDateTime,都是在LocalDateTime的基础上加上时区/偏移量的修饰成为时区时间,然后可以进行时区转换;
    • 建造者模式:Calendar中加入建造者类,用于生成新的Calendar对象;

    2.命名

    • java 8中的日期时间库类名、方法名命名上都是极其形象生动,易于理解,让开发者极易于使用——语义清晰精确!如:LocalDate中提供的now表示现在的日期,of用于年月日组成的日期(这里和英文中的of意义非常贴切),plus/minus加减等等;

    3.合理的接口设计

    • LocalDate表示日期,由年月日组成,提供了获取所在年,所在月,所在日的api,提供所在一年的第几天api,用于比较日期前后api,替换年份、月份、日的api,这些api使得日期或者日期时间的处理上得到的功能上的极大提升;
    • 抽象出年、月、日、星期、日期、日期时间、瞬时、周期诸多接口,对事物本质有了细腻的抽象,并提供了相互转换的能力——提供极强的处理能力和语言表达能力;
    • 对于遗留的日期时间库Calendar/Date/Timezone和新的日期时间库的互通性;
    • 将全球的非标准日历系统单独抽象并支持扩展,从标准日历系统中隔离(符合设计原则:对修改关闭,对扩展开放)

    四.补充

    • 时区:时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
      世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
      理论时区以被15整除的子午线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时。东边的时区时间比西边的时区时间早。为了避免日期的紊乱,提出国际日期变更线的概念
      但是,为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区。请参见时区列表。

    • 子午线:经线也称子午线,和纬线一样是人类为度量而假设出来的辅助线,定义为地球表面连接南北两极的大圆线上的半圆弧。任两根经线的长度相等,相交于南北两极点。每一根经线都有其相对应的数值,称为经度。经线指示南北方向。

    • 本初子午线:即0度经线,亦称格林尼治子午线或本初经线,是经过英国格林尼治天文台的一条经线(亦称子午线)。本初子午线的东西两边分别定为东经和西经,于180度相遇。

    • 国际标准ISO 8601:是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1988”与2000年的第二版“ISO8601:2000”。
      年由4位数字组成YYYY,或者带正负号的四或五位数字表示±YYYYY。以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
      月、日用两位数字表示:MM、DD。
      只使用数字为基本格式。使用短横线"-"间隔开年、月、日为扩展格式。
      ISO 8601:2004不再允许缺省(默认)世纪仅用两位数字表示年,这会与小时数的表示相混淆。而遵循ISO 8601:2000的GB/T 7408-2005,尚还存在这一问题。

    • 协调世界时英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC):是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。中华民国采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》(与ISO 8601类似)称之为世界协调时间。中华人民共和国采用ISO 8601:2000的国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》中亦称之为协调世界时。
      协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒[4],并不遵守夏令时。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。
      如果时间是以协调世界时(UTC)表示,则在时间后面直接加上一个“Z”(不加空格)。“Z”是协调世界时中0时区的标志。因此,“09:30 UTC”就写作“09:30Z”或是“0930Z”。“14:45:15 UTC”则为“14:45:15Z”或“144515Z”。

    • UTC偏移量:UTC偏移量用以下形式表示:±[hh]:[mm]、±[hh][mm]、或者±[hh]。如果所在区时比协调世界时早1个小时(例如柏林冬季时间),那么时区标识应为“+01:00”、“+0100”或者直接写作“+01”。这也同上面的“Z”一样直接加在时间后面。
      "UTC+8"表示当协调世界时(UTC)时间为凌晨2点的时候,当地的时间为2+8点,即早上10点。

    • 格林尼治平时(英语:Greenwich Mean Time,GMT):是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
      自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
      格林尼治平时的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。

    参考

    Trail: Date Time
    Class DateTimeFormatter
    Java 8新特性终极指南
    Is java.util.Calendar thread safe or not?Ask
    协调世界时
    时区
    ISO 8601
    经线
    [时区列表](https://zh.wikipedia.org/wiki/ %E6%97%B6%E5%8C%BA%E5%88%97%E8%A1%A8#UTC%EF%BC%88WET_-%E6%AD%90%E6%B4%B2%E8%A5%BF%E9%83%A8%E6%99%82%E5%8D%80%EF%BC%8CGMT-_%E6%A0%BC%E6%9E%97%E5%A8%81%E6%B2%BB%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4%EF%BC%89)

  • 相关阅读:
    苹果一体机发射Wi-Fi
    iphone 屏蔽系统自动更新,消除设置上的小红点
    data parameter is nil 异常处理
    copy与mutableCopy的区别总结
    java axis2 webservice
    mysql 远程 ip访问
    mysql 存储过程小问题
    mysql游标错误
    is not writable or has an invalid setter method错误的解决
    Struts2中关于"There is no Action mapped for namespace / and action name"的总结
  • 原文地址:https://www.cnblogs.com/lxyit/p/9442135.html
Copyright © 2011-2022 走看看