JDK源码剖析之java.util.Date

点击查看JDK源码剖析系列


目录

一、Date类简介

Date类是对时间进行操作的类,从JDK1.0就开始存在,提供了针对日期进行操作的诸多方法,但是Date类在设计中有很多问题,如getYear指的是1900年以来的年数,getMonth是从0开始的,还有国际化的低支持以及一些安全和性能方面的问题使其一直饱受诟病,官方也认识到这个问题,在JKD1.1新加入了Calendar类来做时间操作(Calendar类详解见Jdk源码剖析之java.util.Calendar)。虽然基本上已经不再使用Date类,但是作为java学习而言还是有内容值得介绍。

二、Date构造函数

public Date() {
    this(System.currentTimeMillis());
}
public Date(long date) {
    fastTime = date;
}
@Deprecated
public Date(int year, int month, int date) {
    this(year, month, date, 0, 0, 0);
}
@Deprecated
public Date(int year, int month, int date, int hrs, int min) {
    this(year, month, date, hrs, min, 0);
}
@Deprecated
public Date(int year, int month, int date, int hrs, int min, int sec) {
    int y = year + 1900;
    // month is 0-based. So we have to normalize month to support Long.MAX_VALUE.
    if (month >= 12) {
        y += month / 12;
        month %= 12;
    } else if (month < 0) {
         y += CalendarUtils.floorDivide(month, 12);
        month = CalendarUtils.mod(month, 12);
    }
    BaseCalendar cal = getCalendarSystem(y);
    cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());
    cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);
    getTimeImpl();
    cdate = null;
}

无参构造函数Date()内部会自动调用System.currentTimeMills()来获取该Date对象被创建时的时间戳(long类型,代表从1970年1月1日至现在所经历的毫秒数)。

Date(long date)需要调用者手动传入时间戳,来获得一个特定时间的Date对象。fastTime作为该类的成员变量保存对象的时间戳数据,以供Date类其他函数调用。

第三种以及第四种构造函数都是对第五种构造函数的简单调用,只不过用0填充了未设置的参数,因此这里只对第五种构造函数Date(int year, int month, int date, int hrs, int min, int sec)进行分析:

@Deprecated
public Date(int year, int month, int date, int hrs, int min, int sec) {
    int y = year + 1900;
    // month is 0-based. So we have to normalize month to support Long.MAX_VALUE.
    if (month >= 12) {
        y += month / 12;
        month %= 12;
    } else if (month < 0) {
        y += CalendarUtils.floorDivide(month, 12);
        month = CalendarUtils.mod(month, 12);
    }
    BaseCalendar cal = getCalendarSystem(y);
    cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());
    cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);
    getTimeImpl();
    cdate = null;
}

这里可以看到Date类的设计缺陷之一。函数首先对输入的参数year加了1900,这才是正确的年份,比如想把时间设置位2018年,则参数应该传入118;然后对month的不正确输入做了处理,比如输入的month>12,则对month除以12并取商,再加到原来的年份上(类似于进位),最后对month除以12取余,这才是正确的月份。如果month<0,那就有些复杂,调用了CalendarUtils.floorDivide(long,long)CalendarUtils.mod(long,long),这俩方法内部实现如下:

public static final int floorDivide(int var0, int var1) {
    return var0 >= 0 ? var0 / var1 : (var0 + 1) / var1 - 1;
}
public static final int mod(int var0, int var1) {
    return var0 - var1 * floorDivide(var0, var1);
}

首先对年份进行处理,比如-12<=month<0,年份在原来基础上减1,以此类推;然后对月份进行处理,在参数月份的基础上加上已化为年单位所消耗的月份(floorDivide方法返回的是负数,负负得正),则变为了最终的月份。
接下来就更难懂了:

BaseCalendar cal = getCalendarSystem(y);

getCalendarSystem方法实现如下:

private static final BaseCalendar getCalendarSystem(int year) {
    if (year >= 1582) {
        return gcal;
    }
    return getJulianCalendar();
}

其中

private static final BaseCalendar gcal = CalendarSystem.getGregorianCalendar();

意思是说,如果年份大于等于1582年,则采用格里高利历(GregorianCalendar),否则使用儒略历(JulianCalendar)。原因见下,了解即可:

1582年10月5日 10月14日这十天在历史上是不存在的 为什么?

儒略历是古罗马的恺撒大帝和在公元前46年制定的,365年作为一年,单月为大月31天,双月为小月30天,2月29天。4年一闰,称为“儒略历”或“恺撒历”。当时,罗马帝国判死刑的罪犯都在二月份处决,古罗马人因此把二月叫做“凶月”,从二月里减去一天,使二月不和其它月份相同是理所当然的。但令人遗憾的是,当时那些颁发历书的祭司们,却不了解改历的实质。结果,可笑的是,当时罗马执掌颁布历书的祭司竟把原来历法上规定的“每隔三年置闰”误解为“每三年置一闰”。从公元前45年起,到公元前9年为止,这之间本应设置10个闰年,他们却设置了13个闰年。公元前9年,人们终于发现这一差错,这时恺撒的外甥奥古斯都执掌政权,他纠正了这个错误,才停止了“三年一闰”。奥古斯都下令改正过来,改到次年(公元前8年)才置闰年。当改正这种闰年的错误时已经多闰了3年,为了去掉着多闰的3年,奥古斯都又下令停闰3年,即以公元前5年、公元前1年、公元前4年仍为平年,以后恢复了每4年一闰的规定了。奥古斯都为了宣扬这一功劳,仿效儒略·恺撒的做法,下令把自己出生的儒略历中的8月改称为奥古斯都月(这一名称在西方沿用到今天)。8月后的大,小月份都翻转过来了,9月为30天,10月为31天,11月为30天,12月为31天,这种置月方式一直沿用至今。如此一来,一年多出了一天,于是也从二月份29天里再减去一天,二月份只剩下28天了 1582年罗马教皇格里高利对”儒略历”又进行修改,规定被4整除的年为闰年,但逢百之年只有能被400除尽才能是闰年。这就是使用至今的“格里历”。这样做是为了使历年与回归年相接近。回归年的周期是365.2425天。儒略历一年的平均长度为365.25日,比回归年(365.2425天)长11分14秒,自公元325年(该年采用儒略历作为宗教日历)积累到十六世纪末,春分日由3月21日提早到3月11日。于是罗马教皇格里高利十三世(Gregorius XⅢ)于1582年10月4日还下令将次日(即原10月5日)定为10月15日,把春分日又恢复为3月21日。这样,1582年的10月5日-14日这十天就成了“不存在”的日子,变为历史的空白。

从以上可以看到,这么做其实是因为西历历法因历史原因遗留下来的坑

cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());

这一行简单,针对电脑或其他设备的时区返回该时区的Date对象。

cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);

这一行对之前输入并处理后的日期进行规范化(normalize),可以看到month加了1,这才是日常理解的真正月份(原参数取值范围0-11)。

下一行代码:

getTimeImpl();

这个方法内部实现:

private final long getTimeImpl() {
    if (cdate != null && !cdate.isNormalized()) {
        normalize();
    }
    return fastTime;
}

可以看到如果cdate对象不为空且cdate没有规范化,就执行使之规范化的方法normalize(),最后返回fastTime(记录1970.1.1至Date对象代表的时间的总毫秒数)。

cdate = null;

最后把cdate对象赋null,节约资源。

三、Date常用函数

1.after(Date date)

public boolean after(Date when) {
    return getMillisOf(this) > getMillisOf(when);
}

该函数用来对比date是否比传入的参数when在时间上更晚,如果更晚则返回true,否则返回false。其中getMillisOf方法用来获取某Date对象储存的时间的时间戳。

2.before(Date date)

public boolean before(Date when) {
    return getMillisOf(this) < getMillisOf(when);
}

意义和上面的after()反过来,不再赘述。

3.compareTo(Date date)

public int compareTo(Date anotherDate) {
    long thisTime = getMillisOf(this);
    long anotherTime = getMillisOf(anotherDate);
    return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
}

这个方法是为了sort(排序)方法服务的,compareTo方法的三种返回值(-1,0,1)的意义不在此赘述。此方法是通过比较两个date对象时间距离1970.1.1的毫秒数来确定谁大谁小的。

4.getTime()

public long getTime() {
    return getTimeImpl();
}

getTimeImpl()内部:

private final long getTimeImpl() {
    if (cdate != null && !cdate.isNormalized()) {
        normalize();
    }
    return fastTime;
}

其实就是返回保存了该Date对象时间戳的成员变量fastTime(该date对象时间距离1970.1.1的毫秒数)。

猜你喜欢

转载自blog.csdn.net/qq_33829547/article/details/80243818