Java 中的时间日期 API

自从 14 年发布 Java 8 之后,咱们古老 java.util.Date 终于再也不是咱们 Java 里操做日期时间的惟一的选择。java

其实 Java 里的日期时间的相关 API 一直为世猿诟病,不只在于它设计分上工不明确,每每一个类既能处理日期又能处理时间,很混乱,还在于某些年月日期的数值映射存储反人类,例如:0 对应月份一月,11 对应月份十二月,118 对应年份 2018(1900 + 118)等。git

每每咱们获得某个年月值还须要再作相应的运算才能获得准确的年月日信息,直到咱们的 Java 8 ,借鉴了第三方开源库 Joda-Time 的优秀设计,从新设计了一个日期时间 API,相比以前,能够说好用百倍,相关 API 接口所有位于包 java.time 下。github

古老的日期时间接口

表示时刻信息的 Date

世界上全部的计算机内部存储时间都使用一个 long 类型的整数,而这个整数的值就是相对于英国格林尼治标准时间(1970年1月1日0时0分0秒)的毫秒数。例如:数组

public static void main(String[] args){
    //January 1, 1970 00:00:00 GMT.
    Date date = new Date(1000);
    System.out.println(date);
}
复制代码

输出结果:bash

//1970-1-1 8:00:01
Thu Jan 01 08:00:01 CST 1970
复制代码

不少人可能会疑惑,1000 表示的是距离标准时间日后 1 秒,那为何时间却多走了 八个小时?微信

这和「时区」有关系,若是你位于英国的格林尼治区,那么结果会如预想同样,可是咱们位于中国东八区,时间要早八个小时,因此不一样时区基于的基础值不一样。ui

Date 这个类之前真的扮演过不少角色,从它的源码就能够看出来,有能够操做时刻的方法,有能够操做年月日的方法,甚至它还能管时区。能够说,日期时间的相关操做有它一我的就足够了。this

但这个世界就是这样,你管的东西多了,天然就不能面面俱到,Date 中不少方法的设计并非很合理,以前咱们也说了,甚至有点反人类。因此,如今的 Date 类中接近百分之八十的方法都已废弃,被标记为 @Deprecated。spa

sun 公司给 Date 的目前定位是,惟一表示一个时刻,因此它的内部应该围绕着那个整型的毫秒,而再也不着重于各类年历时区等信息。设计

Date 容许经过如下两种构造器实例化一个对象:

private transient long fastTime;

public Date() {
    this(System.currentTimeMillis());
}

public Date(long date) {
    fastTime = date;
}
复制代码

这里的 fastTime 属性存储的就是时刻所对应的毫秒数,两个构造器仍是很简单,若是调用的是无参构造器,那么虚拟机将以系统当前的时刻值对 fastTime 进行赋值。

还有几个为数很少没有被废弃的方法:

  • public long getTime() :返回内部存储的毫秒数
  • public void setTime(long time):从新设置内存的毫秒数
  • public boolean before(Date when):比较给定的时刻是否早于当前 Date 实例
  • public boolean after(Date when):比较给定的时刻是否晚于当前 Date 实例

还有两个方法是 jdk1.8 之后新增的,用于向 Java 8 新增接口的转换,待会介绍。

描述年历的 Calendar

Calendar 用于表示年月日等日期信息,它是一个抽象类,因此通常经过如下四种工厂方法获取它的实例对象。

public static Calendar getInstance()

public static Calendar getInstance(TimeZone zone)

public static Calendar getInstance(Locale aLocale)

public static Calendar getInstance(TimeZone zone,Locale aLocale)
复制代码

其实内部最终会调用同一个内部方法:

private static Calendar createCalendar(TimeZone zone,Locale aLocale)
复制代码

该方法须要两个参数,一个是时区,一个是国家和语言,也就是说,构建一个 Calendar 实例最少须要提供这两个参数信息,不然将会使用系统默认的时区或语言信息。

由于不一样的时区与国家语言对于时刻和年月日信息的输出是不一样的,因此这也是为何一个 Calendar 实例必须传入时区和国家信息的一个缘由。看个例子:

public static void main(String[] args){


    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());

    Calendar calendar1 = Calendar.getInstance
            (TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
    System.out.println( calendar1.get(Calendar.YEAR) + ":" +
                        calendar1.get(Calendar.HOUR) + ":" +
                        calendar1.get(Calendar.MINUTE));
    }
复制代码

输出结果:

Sat Apr 21 10:32:20 CST 2018
2018:2:32
复制代码

能够看到,第一个输出为咱们系统默认时区与国家的当前时间,而第二个 Calendar 实例咱们指定了它位于格林尼治时区(0 时区),结果也显而易见了,相差了八个小时,那是由于咱们位于东八区,时间早于 0 时区八个小时。

可能有人会疑惑了,为何第二个 Calendar 实例的输出要如此复杂的拼接,而不像第一个 Calendar 实例那样直接调用 getTime 方法简洁呢?

这涉及到 Calendar 的内部实现,咱们一块儿看看:

protected long          time;

public final Date getTime() {
    return new Date(getTimeInMillis());
}
复制代码

和 Date 同样,Calendar 的内部也维护着一个时刻信息,而 getTime 方法其实是根据这个时刻构建了一个 Date 对象并返回的。

而通常咱们构建 Calendar 实例的时候都不会传入一个时刻信息,因此这个 time 的值在实例初始化的时候,程序会根据系统默认的时区和当前时间计算获得一个毫秒数并赋值给 time。

因此,全部未手动修改 time 属性值的 Calendar 实例的内部,time 的值都是当时系统默认时区的时刻数值。也就是说,getTime 的输出结果是不会理会当前实例所对应的时区信息的,这也是我以为 Calendar 设计的一个缺陷所在,由于这样会致使两个不一样时区 Calendar 实例的 getTime 输出值只取决于实例初始化时系统的运行时刻。

Calendar 中也定义了不少静态常量和一些属性数组:

public final static int ERA = 0;

public final static int YEAR = 1;

public final static int MONTH = 2;

public final static int WEEK_OF_YEAR = 3;

public final static int WEEK_OF_MONTH = 4;

public final static int DATE = 5;
....
复制代码
protected int           fields[];

protected boolean       isSet[];
...
复制代码

有关日期的全部相关信息都存储在属性数组中,而这些静态常量的值每每表示的就是一个索引值,经过 get 方法,咱们传入一个属性索引,返回获得该属性的值。例如:

Calendar myCalendar = Calendar.getInstance();
int year = myCalendar.get(Calendar.YEAR);
复制代码

这里的 get 方法实际上就是直接取的 fields[1] 做为返回值,而 fields 属性数组在 Calendar 实例初始化的时候就已经由系统根据时区和语言计算并赋值了,注意,这里会根据你指定的时区进行计算,它不像 time 始终是依照的系统默认时区

我的以为 Calendar 的设计有优雅的地方,也有不合理的地方,毕竟是个「古董」了,终将被替代。

DateFormat 格式化转换

从咱们以前的一个例子中能够看到,Calendar 想要输出一个预期格式的日期信息是很麻烦的,须要本身手动拼接。而咱们的 DateFormat 就是用来处理格式化字符串和日期时间之间的转换操做的。

DateFormat 和 Calendar 同样,也是一个抽象类,咱们须要经过工厂方式产生其实例对象,主要有如下几种工厂方法:

//只处理时间的转换
public final static DateFormat getTimeInstance()

//只处理日期的转换
public final static DateFormat getDateInstance()

//既能够处理时间,也能够处理日期
public final static DateFormat getDateTimeInstance()
复制代码

固然,它们各自都有各自的重载方法,具体的咱们待会儿看。

DateFormat 有两类方法,format 和 parse。

public final String format(Date date)

public Date parse(String source)
复制代码

format 方法用于将一个日期对象格式化为字符串,parse 方法用于将一个格式化的字符串装换为一个日期对象。例如:

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    DateFormat dateFormat = DateFormat.getDateTimeInstance();
    System.out.println(dateFormat.format(calendar.getTime()));
}
复制代码

输出结果:

2018-4-21 16:58:09
复制代码

显然,使用工厂构造的 DateFormat 实例并不可以自定义输出格式化内容,即输出的字符串格式是固定的,不能知足某些状况下的特殊需求。通常咱们会直接使用它的一个实现类,SimpleDateFormat。

SimpleDateFormat 容许在构造实例的时候传入一个 pattern 参数,自定义日期字符的输出格式。例如:

public static void main(String[] args){    
    DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    System.out.println(dateFormat.format(new Date()));
}
复制代码

输出结果:

2018年04月21日
复制代码

其中,

  • yyyy:年份用四位进行输出
  • MM:月份用两位进行输出
  • dd:两位表示日信息
  • HH:两位来表示小时数
  • mm:两位表示分钟数
  • ss:两位来表示秒数
  • E:表示周几,若是 Locale 在中国则会输出 星期x,若是在美国或英国则会输出英文的星期
  • a:表示上午或下午

固然,对于字符串转日期也是很方便的,容许自定义模式,但必须遵照本身制定的模式,不然程序将没法成功解析。例如:

public static void main(String[] args){
    String str = "2018年4月21日 17点17分 星期六";
    DateFormat sDateFormat = new SimpleDateFormat("yyyy年M月dd日 HH点mm分 E");
    sDateFormat.parse(str);
    System.out.println(sDateFormat.getCalendar().getTime());
}
复制代码

输出结果:

Sat Apr 21 17:17:00 CST 2018
复制代码

显然,程序是正确的解析的咱们的字符串并转换为 Calendar 对象存储在 DateFormat 内部的。

总的来讲,Date、Calendar 和 DateFormat 已经可以处理通常的时间日期问题了,可是不可避免的是,它们依然很繁琐,很差用。

限于篇幅,咱们下篇将对比 Java 8 的新式日期时间 API,你会发现它更加优雅的设计和简单的操做性。


文章中的全部代码、图片、文件都云存储在个人 GitHub 上:

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

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

image
相关文章
相关标签/搜索