Calendar
类是一个日历抽象类,提供了一组对年月日时分秒星期
等日期信息的操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化。比如公历GregorianCalendar
,佛历BuddhistCalendar
,日本帝国历JapaneseImperialCalendar
等。从JDK1.1版本开始,在处理日期和时间时,系统推荐使用Calendar类进行实现(Date的一些方法都过时了)。在设计上,Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些,下面就介绍一下Calendar类的使用。
Calendar
类和Date
类不同,它是一个抽象类,如下:
public abstract class Calendar implements Serializable,Cloneable,Comparable<Calendar>
因此,我们并不能通过new Calendar()
的方式创建Calendar的实例。但是该类提供了getInstance
方法让我们获取实例(设计模式-工厂模式),如:
Calendar cal = Calendar.getInstance();
这里的getInstance方法有四种重载,具体如下:
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);
}
可以看到每种getInstance方法都是通过createCalendar
方法创建了 Calendar类型对象并返回给我们。 这里createCalendar方法需要两个参数,分别是TimeZone
时区类型和Locale
地区类型。 如果getInstance方法传入了相应类型的参数(具体参考java.util.Timezone
和java.util.Locale
这两个类),那么参数会继续传递给createCalendar;如果没有某个对应的参数,那么程序会自动获取机器的时区或者地区。createCalendar方法内部实现如下:
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
CalendarProvider provider = LocaleProviderAdapter
.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
这个方法内部非常的长,还用到了其他类的API。简而言之,程序会通过TimeZone
(时区)和Locale
(地区)来返回不同的Calendar子类对象。其实就是三种,如果是泰国地区就返回BuddhistCalendar
(佛历),日本地区就返回JapaneseImperialCalendar
(日本帝国历),其他国家和地区就返回GregorianCalendar
(格里高利历,即公历)。 P.S. JDK居然专门为泰国和日本做了符合当地民俗的日历,怎么没做中国的阴历呢。。。
除此之外,还有一个能够实例化的方式——用子类来实例化。Calendar的子类有GregorianCalendar,BuddhistCalendar还有JapaneseImperialCalendar等,但由于JapaneseImperialCalendar这个类的修饰符不是public(真奇怪的设计),所以我们不能访问,因此只有另外俩可用。具体实例化方式如下:
Calendar calendar = new GregorianCalendar();
Calendar calendar = new BuddhistCalendar();
用来获取实例化的Calendar对象储存的年月日时分秒星期
等等信息,complete
方法主要是对所有日期字段的值进行计算并赋值给相应变量。方法的参数通过Calendar.XXXX
的形式填写,比如要想获取年份信息就用Calendar.YEAR
、月份Calendar.MONTH
、日期Calendar.Date
、时Calendar.HOUR
、分Calendar.MINUTE
、秒Calendar.SECOND
等等。get方法内部细节如下:
/**
* 返回给定日历字段的值。 在宽松模式下,
* 所有日历字段都已标准化。 在非宽松模式下,全部
* 日历字段进行验证,并且此方法将引发一个
* 如果任何日历字段超出范围值,则为例外。该
* 规范化和验证由处理
* @param field 给定的日历字段。
* @return 给定日历字段的值。
* 如果指定的字段超出范围,则抛出ArrayIndexOutOfBoundsException
*/
public int get(int filed){
complete();
return internalGet(field);
}
其中complete()
方法是用来自动设置Calendar对象中的任何未设置的年月日时分秒星期
等字段。这里不再继续往下挖。
再来看internalGet
方法,用来返回需要获取的值,方法内部实现如下:
protected final int internalGet(int field){
return fields[field];
}
Calendar对象的时间信息如年月日时分秒星期
等都是储存在名为fields的int数组里的,internalGet
其实就是通过Calendar.XXXX
作为下标来访问数组。
set方法有四种重载,分别如下:
public void set(int field, int value){
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
public final void set(int year, int month, int date){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
}
public final void set(int year, int month, int date, int hourOfDay, int minute){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
}
public final void set(int year, int month, int date, int hourOfDay, int minute,int second){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
set(SECOND, second);
}
可以看出后三种重载其实都是对第一种set(int field,int value)
进行的简单调用,核心逻辑都在第一种里面。因此我们只看第一种。
首先看这一段:
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
其中areFieldsSet
是用来标记日期字段是否和当前设置的时间保持同步,areAllFieldsSet
用来标记是否所有的时间字段都被设置了。computeFields()
是一个抽象方法,由Calendar的子类实现,是用来将当前毫秒时间值转换为特定种类的Calendar的字段值。
这一段整体意义就是,如果日期字段有且仅有部分被规范化了,那么在更改任何字段前先计算所有字段。
再看下一行:
internalSet(field, value);
其实前面已经提到了Calendar对象的时间信息如年月日时分秒星期
等都是储存在名为fields的int数组里的,前面的internalGet
通过Calendar.XXXX
作为下标来访问数组,这里的internalSet
对应地向数组中存数据。
后面的代码:
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
这部分是对该日期对象的一些状态进行设置。
/**
* 返回一个Date对象
* Calendar
's time value (millisecond offset from the Epoch").
*
* @return a Date
representing the time value.
* @see #setTime(Date)
* @see #getTimeInMillis()
*/
public final Date getTime() {
return new Date(getTimeInMillis());
}
利用该Calendar
对象生成一个Date
对象,调用了Date(long mills)
构造方法。
其中getTimeInMills()
是获取该Calendar
对象保存的时间距1970.1.1的毫秒数。
public boolean after(Object when) {
return when instanceof Calendar
&& compareTo((Calendar)when) > 0;
}
功能与Date
类的after
方法一致,测试该Calendar对象代表的时间是否比传入时间晚。
先判断传入的Object
对象when
是否属于Calendar类型,不属于直接返回false (为啥不直接设计成参数类型为Calendar?),否则调用compareTo
方法比较时间的早晚。compareTo
内部如下:
public int compareTo(Calendar anotherCalendar) {
return compareTo(getMillisOf(anotherCalendar));
}
内部调用了另一种重载的compareTo
方法:
private int compareTo(long t) {
long thisTime = getMillisOf(this);
return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1;
}
可以发现实质上还是比较谁的时间距离1970.1.1的毫秒数更大。更大的时间肯定更晚。
public boolean before(Object when) {
return when instanceof Calendar
&& compareTo((Calendar)when) < 0;
}
只是功能和逻辑和after(Object when)
反过来,不做赘述。