Java 基础教程

Java 面向对象

Java 高级教程

Java 笔记

Java 日期和时间


光阴似箭,我们可以很容易地设置一个起点,然后向前和向后以秒来计时。那为什么处理时间会如此之难呢?问题出在人类自身上。如果我们只需告诉对方:“1523793600 时来见我,别迟到!”,那么一切都会很简单。但是我们希望时间能够和朝夕与季节挂钩,这就使事情变得复杂了。Java 1.0 有一个 Date 类,事后证明它过于简单了,当 Java 1.1 引入 Calendar 类之后,Date 类中的大部分方法就被弃用了。但是,Calendar 的 API 还不够给力,它的实例是易变 的,并且它没有处理诸如闰秒这样的问题。第 3 次升级很吸引人,那就是 Java SE 8 中引入的 java.time API,它修正了过去的缺陷,并且应该会服役相当长的一段时间。

日期时间相关类

JDK 1.0 - java.util.Date 类

java.util 包提供了 Date 类来封装当前的日期和时间。Date 类其内部存储一个 long 类型的整数,而这个整数的值就是相对于英国格林尼治标准时间(1970年1月1日0时0分0秒)的毫秒数。现在的 Date 类中接近百分之八十的方法都已废弃,被标记为 @Deprecated。目前给 Date 的定位是,唯一表示一个时刻,所以它的内部应该围绕着那个整型的毫秒,而不再着重于各种年历时区等信息。

Date 允许通过以下两种构造器实例化一个对象:

    private transient long fastTime;

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

    public Date(long date) {
        fastTime = date;
    }

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

Date 对象创建以后,可以调用一些方法,以下列出了为数不多没有被废弃的方法。

版本 方法和描述
1.0

public long getTime()

返回自 1970 年 1 月 1 日 00:00:00 GMT 以来,此 Date 对象表示的毫秒数。

1.0

public void setTime(long time)

用自 1970 年 1 月 1 日 00:00:00 GMT 以后,time 毫秒数设置时间和日期。

1.0

public boolean before(Date when)

当调用此方法的 Date 对象在指定日期之前,则返回 true,否则返回 false。

1.0

public boolean after(Date when)

当调用此方法的 Date 对象在指定日期之后,则返回 true,否则返回 false。

1.2

public int compareTo(Date anotherDate)

比较当调用此方法的 Date 对象和指定日期。两者相等时,返回 0。调用对象在指定日期之前,则返回负数。调用对象在指定日期之后,则返回正数。

1.8

public static Date from(Instant instant)

将 Java 8 中的新日期时间 Instant 对象转换为 Date 对象。

1.8

public Instant toInstant()

将当前的 Date 对象转换为 Java 8 中新日期时间 Instant 对象。

JDK 1.1 - java.util.Calendar 类

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

    public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }

    public static Calendar getInstance(TimeZone zone)
    {
        return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
    }

    public static Calendar getInstance(Locale aLocale)
    {
        return createCalendar(TimeZone.getDefault(), aLocale);
    }

    public static Calendar getInstance(TimeZone zone,
                                       Locale aLocale)
    {
        return createCalendar(zone, aLocale);
    }

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

    private static Calendar createCalendar(TimeZone zone, Locale aLocale)

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

因为不同的时区与国家语言对于时刻和年月日信息的输出是不同的,所以这也是为什么一个 Calendar 实例必须传入时区和国家信息的一个原因。

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;

    public final static int DAY_OF_MONTH = 5;

    public final static int DAY_OF_YEAR = 6;

    public final static int DAY_OF_WEEK = 7;

    public final static int DAY_OF_WEEK_IN_MONTH = 8;

    public final static int AM_PM = 9;

    public final static int HOUR = 10;

    public final static int HOUR_OF_DAY = 11;

    public final static int MINUTE = 12;

    public final static int SECOND = 13;

    public final static int MILLISECOND = 14;

    ......
    protected int           fields[];

    protected boolean       isSet[];

有关日期的所有相关信息都存储在属性数组中,而这些静态常量的值往往表示的就是一个索引值。

java 8 java.time API

Java 8 中一个新增的重要特性就是引入了新的时间和日期 API,它们被包含在 java.time 包中。借助新的时间和日期 API 可以以更简洁的方法处理时间和日期。

在 Java 8 之前,所有关于时间和日期的 API 都存在各种使用方面的缺陷,主要有:

  1. Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且它们都不是线程安全的。
  2. 用于格式化日期的类 DateFormat 被放在 java.text 包中,它是一个抽象类,所以我们需要实例化一个 SimpleDateFormat 对象来处理日期格式化,并且 DateFormat 也是非线程安全,这意味着如果你在多线程程序中调用同一个 DateFormat 对象,会得到意想不到的结果。
  3. 对日期的计算方式繁琐,而且容易出错,因为月份是从 0 开始的,从 Calendar 中获取的月份需要加一才能表示当前月份。

由于以上这些问题,出现了一些三方的日期处理框架,例如 Joda-Time,date4j 等开源项目。但是,Java 需要一套标准的用于处理时间和日期的框架,于是 Java 8 中引入了新的日期 API。新的日期 API 是 JSR-310 规范的实现,Joda-Time 框架的作者正是 JSR-310 的规范的倡导者,所以能从 Java 8 的日期 API 中看到很多 Joda-Time 的特性。

Java 8 的日期和时间类包含 LocalDate、LocalTime、Instant、Duration 以及 Period,这些类都包含在 java.time 包中。

LocalDate 和 LocalTime

LocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 LocalDate 的静态方法 of() 创建一个实例,LocalDate 也包含一些方法用来获取年份,月份,天,星期几等:

LocalDate localDate = LocalDate.of(2017, 1, 4);     // 初始化一个日期:2017-01-04
int year = localDate.getYear();                     // 年份:2017
Month month = localDate.getMonth();                 // 月份:JANUARY
int dayOfMonth = localDate.getDayOfMonth();         // 月份中的第几天:4
DayOfWeek dayOfWeek = localDate.getDayOfWeek();     // 一周的第几天:WEDNESDAY
int length = localDate.lengthOfMonth();             // 月份的天数:31
boolean leapYear = localDate.isLeapYear();          // 是否为闰年:false

也可以调用静态方法 now() 来获取当前日期:

LocalDate now = LocalDate.now();

LocalTime 和 LocalDate 类似,他们之间的区别在于 LocalDate 不包含具体时间,而 LocalTime 包含具体时间,例如:

LocalTime localTime = LocalTime.of(17, 23, 52);     // 初始化一个时间:17:23:52
int hour = localTime.getHour();                     // 时:17
int minute = localTime.getMinute();                 // 分:23
int second = localTime.getSecond();                 // 秒:52

LocalDateTime

LocalDateTime 类是 LocalDate 和 LocalTime 的结合体,可以通过 of() 方法直接创建,也可以调用 LocalDate 的 atTime() 方法或 LocalTime 的 atDate() 方法将 LocalDate 或 LocalTime 合并成一个 LocalDateTime

LocalDateTime ldt1 = LocalDateTime.of(2017, Month.JANUARY, 4, 17, 23, 52);

LocalDate localDate = LocalDate.of(2017, Month.JANUARY, 4);
LocalTime localTime = LocalTime.of(17, 23, 52);
LocalDateTime ldt2 = localDate.atTime(localTime);

LocalDateTime 也提供用于向 LocalDate 和 LocalTime 的转化:

LocalDate date = ldt1.toLocalDate();
LocalTime time = ldt1.toLocalTime();

Instant

Instant 用于表示一个时间戳,它与我们常使用的 System.currentTimeMillis() 有些类似,不过 Instant 可以精确到纳秒(Nano-Second),System.currentTimeMillis() 方法只精确到毫秒(Milli-Second)。如果查看 Instant 源码,发现它的内部使用了两个常量,seconds 表示从 1970-01-01 00:00:00 开始到现在的秒数,nanos 表示纳秒部分(nanos 的值不会超过 999,999,999)。Instant 除了使用 now() 方法创建外,还可以通过 ofEpochSecond方法创建:

Instant instant = Instant.ofEpochSecond(120, 100000);

ofEpochSecond() 方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从 1970-01-01 00:00:00 开始后两分钟的10万纳秒的时刻,控制台上的输出为:

1970-01-01T00:02:00.000100Z

Duration

Duration 的内部实现与 Instant 类似,也是包含两部分:seconds 表示秒,nanos 表示纳秒。两者的区别是 Instant 用于表示一个时间戳(或者说是一个时间点),而 Duration 表示一个时间段,所以 Duration 类中不包含 now() 静态方法。可以通过 Duration.between() 方法创建 Duration 对象:

LocalDateTime from = LocalDateTime.of(2017, Month.JANUARY, 5, 10, 7, 0);    // 2017-01-05 10:07:00
LocalDateTime to = LocalDateTime.of(2017, Month.FEBRUARY, 5, 10, 7, 0);     // 2017-02-05 10:07:00
Duration duration = Duration.between(from, to);     // 表示从 2017-01-05 10:07:00 到 2017-02-05 10:07:00 这段时间

long days = duration.toDays();              // 这段时间的总天数
long hours = duration.toHours();            // 这段时间的小时数
long minutes = duration.toMinutes();        // 这段时间的分钟数
long seconds = duration.getSeconds();       // 这段时间的秒数
long milliSeconds = duration.toMillis();    // 这段时间的毫秒数
long nanoSeconds = duration.toNanos();      // 这段时间的纳秒数

Duration 对象还可以通过 of() 方法创建,该方法接受一个时间段长度,和一个时间单位作为参数:

Duration duration1 = Duration.of(5, ChronoUnit.DAYS);       // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS);  // 1000毫秒

Period

Period 在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段,比如 2 年 3 个月 6天:

Period period = Period.of(2, 3, 6);

Period 对象也可以通过 between() 方法创建,值得注意的是,由于 Period 是以年月日衡量时间段,所以 between() 方法只能接收 LocalDate 类型的参数:

// 2017-01-05 到 2017-02-05 这段时间
Period period = Period.between(
                LocalDate.of(2017, 1, 5),
                LocalDate.of(2017, 2, 5));

相关内容