Java中的Calendar日历API用法完全解析
第一部分Calendar介绍
Calendar定义:
publicabstractclassCalendarimplementsSerializable,Cloneable,Comparable<Calendar>{}
Calendar可以看作是一个抽象类。
它的实现,采用了设计模式中的工厂方法。表现在:当我们获取Calendar实例时,Calendar会根据传入的参数来返回相应的Calendar对象。获取Calendar实例,有以下两种方式:
(1)当我们通过Calendar.getInstance()获取日历时,默认的是返回的一个GregorianCalendar对象。
GregorianCalendar是Calendar的一个实现类,它提供了世界上大多数国家/地区使用的标准日历系统。
(2)当我们通过Calendar.getInstance(TimeZonetimezone,Localelocale)或Calendar.getInstance(TimeZonetimezone)或Calendar.getInstance(Localelocale)获取日历时,是返回“对应时区(zone)或地区(local)等所使用的日历”。
例如,若是日本,则返回JapaneseImperialCalendar对象。
参考如下代码:
publicstaticCalendargetInstance()
{
//调用createCalendar()创建日历
Calendarcal=createCalendar(TimeZone.getDefaultRef(),Locale.getDefault());
cal.sharedZone=true;
returncal;
}
publicstaticCalendargetInstance(TimeZonezone)
{
//调用createCalendar()创建日历
returncreateCalendar(zone,Locale.getDefault());
}
publicstaticCalendargetInstance(LocaleaLocale){
//调用createCalendar()创建日历
Calendarcal=createCalendar(TimeZone.getDefaultRef(),aLocale);
cal.sharedZone=true;
returncal;
}
publicstaticCalendargetInstance(TimeZonezone,
LocaleaLocale)
{
//调用createCalendar()创建日历
returncreateCalendar(zone,aLocale);
}
privatestaticCalendarcreateCalendar(TimeZonezone,
LocaleaLocale)
{
//(01)若地区是“th”,则返回BuddhistCalendar对象
//(02)若地区是“JP”,则返回JapaneseImperialCalendar对象
if("th".equals(aLocale.getLanguage())
&&("TH".equals(aLocale.getCountry()))){
returnnewsun.util.BuddhistCalendar(zone,aLocale);
}elseif("JP".equals(aLocale.getVariant())
&&"JP".equals(aLocale.getCountry())
&&"ja".equals(aLocale.getLanguage())){
returnnewJapaneseImperialCalendar(zone,aLocale);
}
//(03)否则,返回GregorianCalendar对象
returnnewGregorianCalendar(zone,aLocale);
}
当我们获取Calendar实例之后,就可以通过Calendar提供的一些列方法来操作日历。
第二部分Calendar的原理和思想
我们使用Calendar,无非是操作Calendar的“年、月、日、星期、时、分、秒”这些字段。下面,我们对这些字段的的来源、定义以及计算方法进行学习。
1.Calendar各个字段值的来源
我们使用Calendar,无非是使用“年、月、日、星期、时、分、秒”等信息。那么它是如何做到的呢?本质上,Calendar就是保存了一个时间。如下定义:
//time是当前时间,单位是毫秒。 //它是当前时间距离“January1,1970,0:00:00GMT”的差值。 protectedlongtime;
Calendar就是根据time计算出“Calendar的年、月、日、星期、时、分、秒”等等信息。
2.Calendar各个字段的定义和初始化
Calendar的“年、月、日、星期、时、分、秒”这些信息,一共是17个字段。
我们使用Calendar,无非是就是使用这17个字段。它们的定义如下:
(字段0)publicfinalstaticintERA=0;
说明:纪元。
取值:只能为0或1。0表示BC(“beforeChrist”,即公元前),1表示AD(拉丁语“AnnoDomini”,即公元)。
(字段1)publicfinalstaticintYEAR=1;
说明:年。
(字段2)publicfinalstaticintMONTH=2;
说明:月
取值:可以为,JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST,SEPTEMBER,OCTOBER,NOVEMBER,DECEMBER,UNDECIMBER。
其中第一个月是JANUARY,它为0。
(字段3)publicfinalstaticintWEEK_OF_YEAR=3;
说明:当前日期在本年中对应第几个星期。一年中第一个星期的值为1。
(字段4)publicfinalstaticintWEEK_OF_MONTH=4;
说明:当前日期在本月中对应第几个星期。一个月中第一个星期的值为1。
(字段5)publicfinalstaticintDATE=5;
说明:日。一个月中第一天的值为1。
(字段5)publicfinalstaticintDAY_OF_MONTH=5;
说明:同“DATE”,表示“日”。
(字段6)publicfinalstaticintDAY_OF_YEAR=6;
说明:当前日期在本年中对应第几天。一年中第一天的值为1。
(字段7)publicfinalstaticintDAY_OF_WEEK=7;
说明:星期几。
取值:可以为,SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY和SATURDAY。
其中,SUNDAY为1,MONDAY为2,依次类推。
(字段8)publicfinalstaticintDAY_OF_WEEK_IN_MONTH=8;
说明:当前月中的第几个星期。
取值:DAY_OF_MONTH1到7总是对应于DAY_OF_WEEK_IN_MONTH1;8到14总是对应于DAY_OF_WEEK_IN_MONTH2,依此类推。
(字段9)publicfinalstaticintAM_PM=9;
说明:上午还是下午
取值:可以是AM或PM。AM为0,表示上午;PM为1,表示下午。
(字段10)publicfinalstaticintHOUR=10;
说明:指示一天中的第几小时。
HOUR用于12小时制时钟(0-11)。中午和午夜用0表示,不用12表示。
(字段11)publicfinalstaticintHOUR_OF_DAY=11;
说明:指示一天中的第几小时。
HOUR_OF_DAY用于24小时制时钟。例如,在10:04:15.250PM这一时刻,HOUR_OF_DAY为22。
(字段12)publicfinalstaticintMINUTE=12;
说明:一小时中的第几分钟。
例如,在10:04:15.250PM这一时刻,MINUTE为4。
(字段13)publicfinalstaticintSECOND=13;
说明:一分钟中的第几秒。
例如,在10:04:15.250PM这一时刻,SECOND为15。
(字段14)publicfinalstaticintMILLISECOND=14;
说明:一秒中的第几毫秒。
例如,在10:04:15.250PM这一时刻,MILLISECOND为250。
(字段15)publicfinalstaticintZONE_OFFSET=15;
说明:毫秒为单位指示距GMT的大致偏移量。
(字段16)publicfinalstaticintDST_OFFSET=16;
说明:毫秒为单位指示夏令时的偏移量。
publicfinalstaticintFIELD_COUNT=17;
这17个字段是保存在int数组中。定义如下:
//保存这17个字段的数组
protectedintfields[];
//数组的定义函数
protectedCalendar(TimeZonezone,LocaleaLocale)
{
//初始化“fields数组”
fields=newint[FIELD_COUNT];
isSet=newboolean[FIELD_COUNT];
stamp=newint[FIELD_COUNT];
this.zone=zone;
setWeekCountData(aLocale);
}
protectedCalendar(TimeZonezone,LocaleaLocale)这是Calendar的构造函数。它会被它的子类的构造函数调用到,从而新建“保存Calendar的17个字段数据”的数组。
3.Calendar各个字段值的计算
下面以get(intfield)为例,简要的说明Calendar的17个字段的计算和操作。get(intfield)是获取“field”字段的值。它的定义如下:
publicintget(intfield){
//计算各个字段的值
complete();
//返回field字段的值
returninternalGet(field);
}
说明:get(intfield)的代码很简单。先通过complete()计算各个字段的值,然后在通过internalGet(field)返回“field字段的值”。
complete()的作用就是计算Calendar各个字段的值。它定义在Calendar.java中,代码如下:
protectedvoidcomplete()
{
if(!isTimeSet)
updateTime();
if(!areFieldsSet||!areAllFieldsSet){
computeFields();//fillsinunsetfields
areAllFieldsSet=areFieldsSet=true;
}
}
privatevoidupdateTime(){
computeTime();
isTimeSet=true;
}
updateTime()调用到的computeTime()定义在Calendar.java的实现类中。下面,我列出GregorianCalendar.java中computeTime()的实现:
protectedvoidcomputeTime(){
//Innon-lenientmode,performbriefcheckingofcalendar
//fieldswhichhavebeensetexternally.Throughthis
//checking,thefieldvaluesarestoredinoriginalFields[]
//toseeifanyofthemarenormalizedlater.
if(!isLenient()){
if(originalFields==null){
originalFields=newint[FIELD_COUNT];
}
for(intfield=0;field<FIELD_COUNT;field++){
intvalue=internalGet(field);
if(isExternallySet(field)){
//Quickvalidationforanyoutofrangevalues
if(value<getMinimum(field)||value>getMaximum(field)){
thrownewIllegalArgumentException(getFieldName(field));
}
}
originalFields[field]=value;
}
}
//Letthesuperclassdeterminewhichcalendarfieldstobe
//usedtocalculatethetime.
intfieldMask=selectFields();
//Theyeardefaultstotheepochstart.Wedon'tcheck
//fieldMaskforYEARbecauseYEARisamandatoryfieldto
//determinethedate.
intyear=isSet(YEAR)?internalGet(YEAR):EPOCH_YEAR;
intera=internalGetEra();
if(era==BCE){
year=1-year;
}elseif(era!=CE){
//EveninlenientmodewedisallowERAvaluesotherthanCE&BCE.
//(Thesamenormalizationruleasadd()/roll()couldbe
//appliedhereinlenientmode.Butthischeckingiskept
//unchangedforcompatibilityasof1.5.)
thrownewIllegalArgumentException("Invalidera");
}
//Ifyearis0ornegative,weneedtosettheERAvaluelater.
if(year<=0&&!isSet(ERA)){
fieldMask|=ERA_MASK;
setFieldsComputed(ERA_MASK);
}
//Calculatethetimeofday.Werelyontheconventionthat
//anUNSETfieldhas0.
longtimeOfDay=0;
if(isFieldSet(fieldMask,HOUR_OF_DAY)){
timeOfDay+=(long)internalGet(HOUR_OF_DAY);
}else{
timeOfDay+=internalGet(HOUR);
//ThedefaultvalueofAM_PMis0whichdesignatesAM.
if(isFieldSet(fieldMask,AM_PM)){
timeOfDay+=12*internalGet(AM_PM);
}
}
timeOfDay*=60;
timeOfDay+=internalGet(MINUTE);
timeOfDay*=60;
timeOfDay+=internalGet(SECOND);
timeOfDay*=1000;
timeOfDay+=internalGet(MILLISECOND);
//Convertthetimeofdaytothenumberofdaysandthe
//millisecondoffsetfrommidnight.
longfixedDate=timeOfDay/ONE_DAY;
timeOfDay%=ONE_DAY;
while(timeOfDay<0){
timeOfDay+=ONE_DAY;
--fixedDate;
}
//CalculatethefixeddatesinceJanuary1,1(Gregorian).
calculateFixedDate:{
longgfd,jfd;
if(year>gregorianCutoverYear&&year>gregorianCutoverYearJulian){
gfd=fixedDate+getFixedDate(gcal,year,fieldMask);
if(gfd>=gregorianCutoverDate){
fixedDate=gfd;
breakcalculateFixedDate;
}
jfd=fixedDate+getFixedDate(getJulianCalendarSystem(),year,fieldMask);
}elseif(year<gregorianCutoverYear&&year<gregorianCutoverYearJulian){
jfd=fixedDate+getFixedDate(getJulianCalendarSystem(),year,fieldMask);
if(jfd<gregorianCutoverDate){
fixedDate=jfd;
breakcalculateFixedDate;
}
gfd=fixedDate+getFixedDate(gcal,year,fieldMask);
}else{
gfd=fixedDate+getFixedDate(gcal,year,fieldMask);
jfd=fixedDate+getFixedDate(getJulianCalendarSystem(),year,fieldMask);
}
//Nowwehavetodeterminewhichcalendardateitis.
if(gfd>=gregorianCutoverDate){
if(jfd>=gregorianCutoverDate){
fixedDate=gfd;
}else{
//Thedateisinan"overlapping"period.Noway
//todisambiguateit.Determineitusingthe
//previousdatecalculation.
if(calsys==gcal||calsys==null){
fixedDate=gfd;
}else{
fixedDate=jfd;
}
}
}else{
if(jfd<gregorianCutoverDate){
fixedDate=jfd;
}else{
//Thedateisina"missing"period.
if(!isLenient()){
thrownewIllegalArgumentException("thespecifieddatedoesn'texist");
}
//TaketheJuliandateforcompatibility,which
//willproduceaGregoriandate.
fixedDate=jfd;
}
}
}
//millisrepresentslocalwall-clocktimeinmilliseconds.
longmillis=(fixedDate-EPOCH_OFFSET)*ONE_DAY+timeOfDay;
//ComputethetimezoneoffsetandDSToffset.Therearetwopotential
//ambiguitieshere.We'llassumea2:00am(walltime)switchovertime
//fordiscussionpurposeshere.
//1.ThetransitionintoDST.Here,adesignatedtimeof2:00am-2:59am
//canbeinstandardorinDSTdepending.However,2:00amisaninvalid
//representation(therepresentationjumpsfrom1:59:59amStdto3:00:00amDST).
//Weassumestandardtime.
//2.ThetransitionoutofDST.Here,adesignatedtimeof1:00am-1:59am
//canbeinstandardorDST.Botharevalidrepresentations(therep
//jumpsfrom1:59:59DSTto1:00:00Std).
//Again,weassumestandardtime.
//WeusetheTimeZoneobject,unlesstheuserhasexplicitlysettheZONE_OFFSET
//orDST_OFFSETfields;thenweusethosefields.
TimeZonezone=getZone();
if(zoneOffsets==null){
zoneOffsets=newint[2];
}
inttzMask=fieldMask&(ZONE_OFFSET_MASK|DST_OFFSET_MASK);
if(tzMask!=(ZONE_OFFSET_MASK|DST_OFFSET_MASK)){
if(zoneinstanceofZoneInfo){
((ZoneInfo)zone).getOffsetsByWall(millis,zoneOffsets);
}else{
intgmtOffset=isFieldSet(fieldMask,ZONE_OFFSET)?
internalGet(ZONE_OFFSET):zone.getRawOffset();
zone.getOffsets(millis-gmtOffset,zoneOffsets);
}
}
if(tzMask!=0){
if(isFieldSet(tzMask,ZONE_OFFSET)){
zoneOffsets[0]=internalGet(ZONE_OFFSET);
}
if(isFieldSet(tzMask,DST_OFFSET)){
zoneOffsets[1]=internalGet(DST_OFFSET);
}
}
//AdjustthetimezoneoffsetvaluestogettheUTCtime.
millis-=zoneOffsets[0]+zoneOffsets[1];
//Setthiscalendar'stimeinmilliseconds
time=millis;
intmask=computeFields(fieldMask|getSetStateFields(),tzMask);
if(!isLenient()){
for(intfield=0;field<FIELD_COUNT;field++){
if(!isExternallySet(field)){
continue;
}
if(originalFields[field]!=internalGet(field)){
//Restoretheoriginalfieldvalues
System.arraycopy(originalFields,0,fields,0,fields.length);
thrownewIllegalArgumentException(getFieldName(field));
}
}
}
setFieldsNormalized(mask);
}
下面,我们看看internalGet(field)的定义。如下:
protectedfinalintinternalGet(intfield){
returnfields[field];
}
从中,我们就看出,get(intfield)最终是通过internalGet(intfield)来返回值的。
而internalGet(intfield),实际上返回的是field数组中的第field个元素。这就正好和Calendar的17个元素所对应了!
总之,我们需要了解的就是:Calendar就是以一个time(毫秒)为基数,而计算出“年月日时分秒”等,从而方便我们对“年月日时分秒”等进行操作。下面,介绍以下Calendar提供的相关操作函数。
第三部分Calendar函数接口
1.Calendar的17个字段的公共接口
Calendar的这17个字段,都支持下面的公共函数接口。这些公共接口的使用示例,请参考CalendarTest.java示例中的testAllCalendarSections()函数。
(1)getMaximum(intfield)
作用:获取“字段的最大值”。注意“对比它和getActualMaximum()的区别”。示例:以“MONTH”字段来说。使用方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //获取MONTH的最大值 intmax=cal.getMaximum(Calendar.MONTH);
若要获取其它字段的最大值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(2)getActualMaximum(intfield)
作用:获取“当前日期下,该字段的最大值”。示例:以“MONTH”字段来说。使用方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //获取当前MONTH的最大值 intmax=cal.getActualMaximum(Calendar.MONTH);
若要获取其它字段的最大值,只需要将示例中的MONTH相应的替换成其它字段名即可。
注意:对比getActualMaximum()和getMaximum()的区别。参考下面的对比示例,
A、getMaximum()获取的“字段最大值”,是指在综合所有的日期,在所有这些日期中得出的“字段最大值”。
例如,getMaximum(Calendar.DATE)的目的是“获取‘日的最大值'”。综合所有的日期,得出一个月最多有31天。因此,getMaximum(Calendar.DATE)的返回值是“31”!
B、getActualMaximum()获取的“当前日期时,该字段的最大值”。
例如,当日期为2013-09-01时,getActualMaximum(Calendar.DATE)是获取“日的最大值”是“30”。当前日期是9月份,而9月只有30天。因此,getActualMaximum(Calendar.DATE)的返回值是“30”!
(3)getMinimum(intfield)
作用:获取“字段的最小值”。注意“对比它和getActualMinimum()的区别”。示例:以“MONTH”字段来说。使用方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //获取MONTH的最小值 intmin=cal.getMinimum(Calendar.MONTH);
若要获取其它字段的最小值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(4)getActualMinimum(intfield)
作用:获取“当前日期下,该字段的最小值”。示例:以“MONTH”字段来说。使用方法为: //获取Calendar实例 Calendarcal=Calendar.getInstance(); //获取MONTH的最小值 intmin=cal.getMinimum(Calendar.MONTH);
若要获取其它字段的最小值,只需要将示例中的MONTH相应的替换成其它字段名即可。
注意:在Java默认的Calendar中,虽然getMinimum()和getActualMinimum()的含义不同;但是,它们的返回值是一样的。因为Calendar的默认是返回GregorianCalendar对象,而在GregorianCalendar.java中,getMinimum()和getActualMinimum()返回值一样。
(5)get(intfield)
作用:获取“字段的当前值”。获取field字段的当前值。示例:以“MONTH”字段来说。“获取MONTH的当前值”的方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //获取“cal日历”的当前MONTH值 intMONTH=cal.get(Calendar.MONTH);
若要获取其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(6)set(intfield,intvalue)
作用:设置“字段的当前值”。设置field字段的当前值为value示例:以“MONTH”字段来说。“设置MONTH的当前值”的方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //设置“cal日历”的当前MONTH值为1988年 cal.set(Calendar.MONTH,1988);
说明:
A、1988是想要设置的MONTH的当前值。这个设置值必须是整数。
B、若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(7)add(intfield,intvalue)
作用:给“字段的当前值”添加值。给field字段的当前值添加value。示例:以“MONTH”字段来说。方法如下:
//获取Calendar实例,并设置日期为“2013-09-01” Calendarcal=Calendar.getInstance(); cal.set(Calendar.YEAR,2013); cal.set(Calendar.MONTH,8); cal.set(Calendar.DATE,1); //给“cal日历”的当前MONTH值“添加-10” cal.add(Calendar.MONTH,-10);
说明:
A、-10是添加值。
添加值可以为正数,也可以是负数。
正数表示将日期增加,负数表示将日期减少。
假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2012-10-01”。
为什么会这样呢?“2013-09-01”增加-10,也就是将日期向前减少10个月;得到的结果就是“2012-10-01”。
B、Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。
C、若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(8)roll(intfield,intvalue)
作用:回滚“字段的当前值”示例:以“MONTH”字段来说。“回滚MONTH的当前值”的方法为:
//获取Calendar实例,并设置日期为“2013-09-01” Calendarcal=Calendar.getInstance(); cal.set(Calendar.YEAR,2013); cal.set(Calendar.MONTH,8); cal.set(Calendar.DATE,1); //将“cal日历”的当前MONTH值“向前滚动10” cal.roll(Calendar.MONTH,-10);
说明:
A、-10是回滚值。
当回滚值是负数时,表示将当前字段向前滚;
当回滚值是正数时,表示将当前字段向后滚。
回滚Calendar中某一字段时,不更改更大的字段!
这是roll()与add()的根据区别!add()可能会更改更大字段,比如“使用add()修改‘MONTH'字段,可能会引起‘YEAR'字段的改变”;但是roll()不会更改更大的字段,例如“使用roll()修改‘MONTH'字段,不回引起‘YEAR'字段的改变。”
假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2013-10-01”。
为什么会这样呢?这就是因为“回滚”就是“在最小值和最大值之间来回滚动”。本例中,MONTH是9月,前回滚10,得到的值是10月,但是roll()不会改变“比MONTH”更大的字段,所以YEAR字段不会改变。所以结果是“2013-10-01”。
B、Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。
C、若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。
(9)clear(intfield)
作用:清空“字段的当前值”。所谓清空,实际上是将“field”的值设置为0;若field最小值为1,则设置为1。示例:以“MONTH”字段来说。“清空MONTH”的方法为:
//获取Calendar实例,并设置日期为“9月” Calendarcal=Calendar.getInstance(); cal.set(Calendar.MONTH,9); //清空MONTH cal.clear(Calendar.MONTH);
若要清空其它字段,只需要将示例中的MONTH相应的替换成其它字段名即可。
(10)isSet(intfield)
作用:判断“字段field”是否被设置。若调用clear()清空之后,则field变为“没有设置状态”。示例:以“MONTH”字段来说。“判断MONTH是否被设置”的方法为:
//获取Calendar实例 Calendarcal=Calendar.getInstance(); //判断MONTH是否被设置 booleanbset=cal.isSet(Calendar.MONTH);
若要判断其它字段,只需要将示例中的MONTH相应的替换成其它字段名即可。
2.Calendar的其它函数
(1)日期比较函数
Calendar的比较函数,主要有以下几个:
//比较“当前Calendar对象”和“calendar”的日期、时区等内容是否相等。 booleanequals(Objectobject) //当前Calendar对象是否早于calendar booleanbefore(Objectcalendar) //当前Calendar对象是否晚于calendar booleanafter(Objectcalendar) //比较“当前Calendar对象”和“calendar”。 //若早于“calendar”则,返回-1 //若相等,则,返回0 //若晚于“calendar”则,返回1 intcompareTo(CalendaranotherCalendar)
这些函数的使用示例,请参考CalendarTest.java示例中的testComparatorAPIs()函数。
示例:假设cal1和cal2都是Calendar的两个对象。
//它们的使用方法如下 booleanisEqual=cal1.equals(cal2); booleanisBefore=cal1.before(cal2); booleanisAfter=cal1.after(cal2); inticompare=cal1.compareTo(cal2);
(2)“宽容”函数
//设置“Calendar的宽容度” voidsetLenient(booleanvalue) //获取“Calendar的宽容度” booleanisLenient()
这些函数的使用示例,请参考CalendarTest.java示例中的testLenientAPIs()函数。
说明:
Calendar有两种解释日历字段的模式,即lenient和non-lenient。
A、当Calendar处于lenient模式时,它可接受比它所生成的日历字段范围更大范围内的值。当Calendar重新计算日历字段值,以便由get()返回这些值时,所有日历字段都被标准化。
例如,lenient模式下的GregorianCalendar将MONTH==JANUARY、DAY_OF_MONTH==32解释为February1。
B、当Calendar处于non-lenient模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。
例如,GregorianCalendar总是在1与月份的长度之间生成DAY_OF_MONTH值。如果已经设置了任何超出范围的字段值,那么在计算时间或日历字段值时,处于non-lenient模式下的GregorianCalendar会抛出一个异常。
注意:在(02)步骤中的异常,在使用set()时不会抛出,而需要在使用get()、getTimeInMillis()、getTime()、add()和roll()等函数中才抛出。因为set()只是设置了一个修改标志,而get()等方法才会引起时间的重新计算,此时才会抛出异常!
(3)"年月日(时分秒)"、Date、TimeZone、MilliSecond函数
//设置“年月日” finalvoidset(intyear,intmonth,intday) //设置“年月日时分” finalvoidset(intyear,intmonth,intday,inthourOfDay,intminute,intsecond) //设置“年月日时分秒” finalvoidset(intyear,intmonth,intday,inthourOfDay,intminute) //获取Calendar对应的日期 finalDategetTime() //设置Calendar为date finalvoidsetTime(Datedate) //获取Calendar对应的时区 TimeZonegetTimeZone() //设置Calendar对应的时区 voidsetTimeZone(TimeZonetimezone) //获取Calendar对应的milliscondes值,就是“Calendar当前日期”距离“1970-01-010:00:00GMT”的毫秒数 longgetTimeInMillis() //设置Calendar对应的milliscondes值 voidsetTimeInMillis(longmilliseconds)
这些函数的使用示例,请参考CalendarTest.java示例中的testTimeAPIs()函数。
(4)其它操作
//克隆Calendar Objectclone() //获取“每周的第一天是星期几”。例如,在美国,这一天是SUNDAY,而在法国,这一天是MONDAY。 intgetFirstDayOfWeek() //设置“每周的第一天是星期几”。例如,在美国,这一天是SUNDAY,而在法国,这一天是MONDAY。 voidsetFirstDayOfWeek(intvalue) //获取一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则此方法将返回1。如果最少天数必须是一整个星期,则此方法将返回7。 intgetMinimalDaysInFirstWeek() //设置一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则使用值1调用此方法。如果最少天数必须是一整个星期,则使用值7调用此方法。 voidsetMinimalDaysInFirstWeek(intvalue)
这些函数的使用示例,请参考CalendarTest.java示例中的testOtherAPIs()函数。
第四部分Calendar使用示例
下面,我们通过示例学习使用Calendar的API。CalendarTest.java的源码如下:
importjava.util.Date;
importjava.util.Calendar;
importjava.util.TimeZone;
importjava.util.Random;
publicclassCalendarTest{
publicstaticvoidmain(String[]args){
//测试Calendar的“17个字段的公共函数接口”
testAllCalendarSections();
//测试Calendar的“比较接口”
testComparatorAPIs();
//测试Calendar的“比较接口”
testLenientAPIs();
//测试Calendar的Date、TimeZone、MilliSecond等相关函数
testTimeAPIs();
//测试Calendar的clone(),getFirstDayOfWeek()等接口
testOtherAPIs();
}
/**
*测试“Calendar的字段”
*
*@paramcal--Calendar对象
*@paramfield--要测试的“Calendar字段”。可以为以下值:
*Calendar.YEAR,Calendar.MONTH,Calendar.DATE,...等等
*@paramtitle--标题
*/
privatestaticvoidtestSection(Calendarcal,intfield,Stringtitle){
finalRandomrandom=newRandom();
finalDatedate=cal.getTime();
finalintmin=cal.getMinimum(field);//获取"字段最小值"
finalintmax=cal.getMaximum(field);//获取“字段最大值”
finalintactualMin=cal.getActualMinimum(field);//获取"当前日期下,该字段最小值"
finalintactualMax=cal.getActualMaximum(field);//获取“当前日期下,该字段的最大值”
//获取“字段的当前值”
finalintori=cal.get(field);
//设置“字段的当前值”,并获取“设置之后的值”
finalintr1=random.nextInt(max);
cal.set(field,r1);
finalintset=cal.get(field);
try{
//回滚“字段的当前值”:在“字段最小值”和“字段最大值”之间回滚。
//“回滚值”可以为正,也可以为负。
cal.roll(field,-max);
}catch(IllegalArgumentExceptione){
//当field==Calendar.ZONE_OFFSET时,会抛出该异常!
e.printStackTrace();
}
finalintroll=cal.get(field);
//获取一个随机值
finalintsign=(random.nextInt(2)==1)?1:-1;
finalintr2=sign*random.nextInt(max);
try{
//增加“字段的当前值”,并获取“新的当前字段值”
//add的“参数值”可以为正,也可以为负。
cal.add(field,r2);
}catch(IllegalArgumentExceptione){
//当field==Calendar.ZONE_OFFSET时,会抛出该异常!
e.printStackTrace();
}
finalintadd=cal.get(field);
//打印字段信息
System.out.printf("%s:\n\trangeis[%d-%d]actualRangeis[%d-%d].original=%d,set(%d)=%d,roll(%d)=%d,add(%d)=%d\n",
title,min,max,actualMin,actualMax,ori,r1,set,-max,roll,r2,add);
}
/**
*测试Calendar的“17个字段的公共函数接口”
*/
privatestaticvoidtestAllCalendarSections(){
//00.ERA字段
testSection(Calendar.getInstance(),Calendar.ERA,"Calendar.ERA");
//01.YEAR字段
testSection(Calendar.getInstance(),Calendar.YEAR,"Calendar.YEAR");
//02.MONTH字段
testSection(Calendar.getInstance(),Calendar.MONTH,"Calendar.MONTH");
//03.WEEK_OF_YEAR字段
testSection(Calendar.getInstance(),Calendar.WEEK_OF_YEAR,"Calendar.WEEK_OF_YEAR");
//04.WEEK_OF_MONTH字段
testSection(Calendar.getInstance(),Calendar.WEEK_OF_MONTH,"Calendar.WEEK_OF_MONTH");
//05.DATE字段
testSection(Calendar.getInstance(),Calendar.DATE,"Calendar.DATE");
//06.DAY_OF_MONTH字段
testSection(Calendar.getInstance(),Calendar.DAY_OF_MONTH,"Calendar.DAY_OF_MONTH");
//07.DAY_OF_YEAR字段
testSection(Calendar.getInstance(),Calendar.DAY_OF_YEAR,"Calendar.DAY_OF_YEAR");
//08.DAY_OF_WEEK字段
testSection(Calendar.getInstance(),Calendar.DAY_OF_WEEK,"Calendar.DAY_OF_WEEK");
//09.DAY_OF_WEEK_IN_MONTH字段
testSection(Calendar.getInstance(),Calendar.DAY_OF_WEEK_IN_MONTH,"Calendar.DAY_OF_WEEK_IN_MONTH");
//10.AM_PM字段
testSection(Calendar.getInstance(),Calendar.AM_PM,"Calendar.AM_PM");
//11.HOUR字段
testSection(Calendar.getInstance(),Calendar.HOUR,"Calendar.HOUR");
//12.HOUR_OF_DAY字段
testSection(Calendar.getInstance(),Calendar.HOUR_OF_DAY,"Calendar.HOUR_OF_DAY");
//13.MINUTE字段
testSection(Calendar.getInstance(),Calendar.MINUTE,"Calendar.MINUTE");
//14.SECOND字段
testSection(Calendar.getInstance(),Calendar.SECOND,"Calendar.SECOND");
//15.MILLISECOND字段
testSection(Calendar.getInstance(),Calendar.MILLISECOND,"Calendar.MILLISECOND");
//16.ZONE_OFFSET字段
testSection(Calendar.getInstance(),Calendar.ZONE_OFFSET,"Calendar.ZONE_OFFSET");
}
/**
*测试Calendar的“比较接口”
*/
privatestaticvoidtestComparatorAPIs(){
//新建cal1,且时间为1988年
Calendarcal1=Calendar.getInstance();
cal1.set(Calendar.YEAR,1988);
//新建cal2,且时间为2000年
Calendarcal2=Calendar.getInstance();
cal2.set(Calendar.YEAR,2000);
//新建cal3,为cal1的克隆对象
Calendarcal3=(Calendar)cal1.clone();
//equals判断cal1和cal2的“时间、时区等”内容是否相等
booleanisEqual12=cal1.equals(cal2);
//equals判断cal1和cal3的“时间、时区等”内容是否相等
booleanisEqual13=cal1.equals(cal3);
//cal1是否比cal2早
booleanisBefore=cal1.before(cal2);
//cal1是否比cal2晚
booleanisAfter=cal1.after(cal2);
//比较cal1和cal2
//(01)若cal1早于cal2,返回-1
//(02)若cal1等于cal2,返回0
//(03)若cal1晚于cal2,返回1
inticompare=cal1.compareTo(cal2);
System.out.printf("\ntestComparatorAPIs:isEuqal12=%s,isEqual13=%s,isBefore=%s,isAfter=%s,icompare=%s\n",
isEqual12,isEqual13,isBefore,isAfter,icompare);
}
/**
*测试Calendar的“比较接口”
*/
privatestaticvoidtestLenientAPIs(){
Calendarcal=Calendar.getInstance();
//获取默认的“宽容度”。返回true
booleanoriLenient=cal.isLenient();
//MONTH值只能是“0-11”,这里越界。但是由于当前cal是宽容的,所以不会抛出异常
cal.set(Calendar.MONTH,50);
//设置“宽容度”为false。
cal.setLenient(false);
//获取设置后的“宽容度”
booleancurLenient=cal.isLenient();
try{
//MONTH值只能是“0-11”,这里越界。而且当前cal是不宽容的,所以会产生异常。
//但是,异常到下次计算日期时才会抛出。即,set()中不回抛出异常,而要等到get()中才会抛出异常
cal.set(Calendar.MONTH,50);
//此时,对cal进行读取。读取会导致重新计算cal的值,所以此时抛出异常!
intm2=cal.get(Calendar.MONTH);
}catch(IllegalArgumentExceptione){
e.printStackTrace();
}
System.out.printf("\ntestLenientAPIs:oriLenient=%s,curLenient=%s\n",
oriLenient,curLenient);
}
/**
*测试Calendar的Date、TimeZone、MilliSecond等相关函数
*/
privatestaticvoidtestTimeAPIs(){
Calendarcal=Calendar.getInstance();
//设置cal的时区为“GMT+8”
cal.setTimeZone(TimeZone.getTimeZone("GMT+8"));
//获取当前的cal时区
TimeZonetimezone=cal.getTimeZone();
//设置milliseconds
cal.setTimeInMillis(1279419645742l);
//获取milliseconds
longmillis=cal.getTimeInMillis();
//设置milliseconds之后,时间也改变了。
//获取cal对应的日期
Datedate=cal.getTime();
//设置时间为“1988-08-08”
cal.set(1988,08,08);
//设置时间为“1999-09-0909:09”
cal.set(1999,09,09,9,9);
//设置时间为“2000-10-1010:10:10”
cal.set(2000,10,10,10,10,10);
System.out.printf("\ntestTimeAPIs:date=%s,timezone=%s,millis=%s\n",
date,timezone,millis);
}
/**
*测试Calendar的clone(),getFirstDayOfWeek()等接口
*/
privatestaticvoidtestOtherAPIs(){
Calendarcal=Calendar.getInstance();
//克隆cal
Calendarclone=(Calendar)cal.clone();
//设置为2013-01-10。
//注:2013-01-01为“星期二”,2013-01-06为“星期天”,
clone.set(Calendar.YEAR,2013);
clone.set(Calendar.MONTH,0);
clone.set(Calendar.DATE,10);
//设置“本年的第一个星期最少包含1天”。
//则2013-01-10属于第2个星期
clone.setMinimalDaysInFirstWeek(1);
intm1=clone.getMinimalDaysInFirstWeek();
intindex1=clone.get(Calendar.WEEK_OF_YEAR);
//设置“本年的第一个星期最少包含7天”。
//则2013-01-10属于第1个星期
clone.setMinimalDaysInFirstWeek(7);
intm2=clone.getMinimalDaysInFirstWeek();
intindex2=clone.get(Calendar.WEEK_OF_YEAR);
//设置“每周的第一天是星期几”。
clone.setFirstDayOfWeek(Calendar.WEDNESDAY);
//获取“每周的第一天是星期几”。
intfirstdayOfWeek=clone.getFirstDayOfWeek();
System.out.printf("\ntestOtherAPIs:firstdayOfWeek=%s,[minimalDay,WeekOfYear]={(%s,%s),(%s,%s)}%s\n",
firstdayOfWeek,m1,index1,m2,index2,clone.getTime());
}
}
第五部分自定义的Calendar接口示例
这些接口在写日历程序时可能会用到。
源代码如下(CalendarSelfDefineTest.java):
importjava.util.Calendar;
/**
*根据Calendar的API封装的一些常用函数
*/
publicclassCalendarSelfDefineTest{
publicstaticvoidmain(String[]args){
Calendarcal=Calendar.getInstance();
//设置日期为“2013-09-18”
cal.set(2013,Calendar.SEPTEMBER,18);
//获取“年”
System.out.printf("year:%s\n",getYear(cal));
//获取“月”
System.out.printf("month:%s\n",getMonth(cal));
//获取“上月”
System.out.printf("previcoumonth:%s\n",getLastMonth(cal));
//获取“下月”
System.out.printf("nextmonth:%s\n",getNextMonth(cal));
//获取“日”
System.out.printf("day:%s\n",getDay(cal));
//获取Cal对应星期几
System.out.printf("weekday:%s\n",getWeekDay(cal));
//本月天数
System.out.printf("CurrentMonthdays:%s\n",getMonthDays(cal));
//上月天数
System.out.printf("PrevicousMonthdays:%s\n",getPrevMonthDays(cal));
//下月天数
System.out.printf("NextMonthdays:%s\n",getNextMonthDays(cal));
//获取当月第一天的星期几
System.out.printf("Firstday'weekday:%s\n",getFirstDayWeekday(cal));
//获取当前月最后一天的星期几
System.out.printf("Lastday'weekday:%s\n",getLastDayWeekday(cal));
//获取上月最后一天的星期几
System.out.printf("PrevMonthLastday'weekday:%s\n",getLastDayWeekdayOfPrevMonth(cal));
//获取下月第一天的星期几
System.out.printf("NextMonthFirstday'weekday:%s\n",getFirstDayWeekdayOfNextMonth(cal));
}
/**
*获取“年”
*
*@return例如,2013-09-18,则返回2013
*/
publicstaticintgetYear(Calendarcal){
returncal.get(Calendar.YEAR);
}
/**
*获取“月”
*
*@return返回值可以为以下值:
*JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST,SEPTEMBER,OCTOBER,NOVEMBER,DECEMBER,UNDECIMBER。
*其中第一个月是JANUARY,它为0。
*
*例如,2013-09-18,则返回8
*/
publicstaticintgetMonth(Calendarcal){
returncal.get(Calendar.MONTH);
}
/**
*获取“上一个月”
*
*@return返回值可以为以下值:
*JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST,SEPTEMBER,OCTOBER,NOVEMBER,DECEMBER,UNDECIMBER。
*其中第一个月是JANUARY,它为0。
*
*例如,2012-01-12的上一个月是“11”(即DECEMBER)。
*/
publicstaticintgetLastMonth(Calendarcal){
return(cal.get(Calendar.MONTH)+11)%12;
}
/**
*获取“下一个月”
*
*@return返回值可以为以下值:
*JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST,SEPTEMBER,OCTOBER,NOVEMBER,DECEMBER,UNDECIMBER。
*其中第一个月是JANUARY,它为0。
*
*例如,2013-12-12的下一个月是“1”(即DECEMBER)。
*/
publicstaticintgetNextMonth(Calendarcal){
return(cal.get(Calendar.MONTH)+13)%12;
}
/**
*获取“日”
*
*@return例如,2013-09-18,则返回18
*
*/
publicstaticintgetDay(Calendarcal){
returncal.get(Calendar.DATE);
}
/**
*获取“本月的天数”
*
*@return例如,2013-09-18,则返回30
*
*/
publicstaticintgetMonthDays(Calendarcal){
returncal.getActualMaximum(Calendar.DATE);
}
/**
*获取“上一个月的天数”
*
*@return例如,2013-01-18,则返回31(因为2012-12有31天)
*
*/
publicstaticintgetPrevMonthDays(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.add(Calendar.MONTH,-1);//设为“上一个月”
returntmpCal.getActualMaximum(Calendar.DATE);
}
/**
*获取“下一个月的天数”
*
*@return例如,2013-12-18,则返回31(因为2014-01有31天)
*
*/
publicstaticintgetNextMonthDays(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.add(Calendar.MONTH,1);//设为“下一个月”
returntmpCal.getActualMaximum(Calendar.DATE);
}
/**
*获取Cal对应星期几
*
*@return返回“星期几”,可以为以下值:
*SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY和SATURDAY。
*SUNDAY为1,MONDAY为2,依次类推。
*例如,2013-09-18(星期三),则返回4
*/
publicstaticintgetWeekDay(Calendarcal){
returncal.get(Calendar.DAY_OF_WEEK);
}
/**
*获取当月第一天对应星期几
*
*@returnSUNDAY为1,MONDAY为2,依次类推。
*/
publicstaticintgetFirstDayWeekday(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.set(Calendar.DATE,1);//把日期设置为当月第一天
returntmpCal.get(Calendar.DAY_OF_WEEK);
}
/**
*获取当前月最后一天对应星期几
*
*@returnSUNDAY为1,MONDAY为2,依次类推。
*/
publicstaticintgetLastDayWeekday(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.set(Calendar.DATE,1);//把日期设置为当月第一天
tmpCal.roll(Calendar.DATE,-1);//把日期设置为当月最后一天
returntmpCal.get(Calendar.DAY_OF_WEEK);
}
/**
*获取上月最后一天的星期几
*
*@returnSUNDAY为1,MONDAY为2,依次类推。
*/
publicstaticintgetLastDayWeekdayOfPrevMonth(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.set(Calendar.DATE,1);//把日期设置为当月第一天
tmpCal.add(Calendar.DATE,-1);//把日期设置为上一个月最后一天
returntmpCal.get(Calendar.DAY_OF_WEEK);
}
/**
*获取下月第一天的星期偏移
*
*@returnSUNDAY为1,MONDAY为2,依次类推。
*/
publicstaticintgetFirstDayWeekdayOfNextMonth(Calendarcal){
CalendartmpCal=(Calendar)cal.clone();//克隆cal。后面对tmpCal操作,就不会改变cal
tmpCal.add(Calendar.MONTH,1);//设为“下一个月”
tmpCal.set(Calendar.DATE,1);//设为“第一天”
returntmpCal.get(Calendar.DAY_OF_WEEK);
}
}