Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(1) Calendar


 

Java 操作日期/時間,往往會涉及到Calendar,Date,DateFormat這些類。

最近決定把這些內容系統的整理一下,這樣以后使用的時候,會更得心應手。本章的內容是主要講解“Java時間框架”以及“類Calendar”。

在學習Calendar類時,我們先對它有個整體認識,心中建立一個框架,然后再通過示例學習如何使用它。

 


Java 時間架構圖

Java 的Calendar, Date和DateFormat的關系圖如下:

說明

(01) milliseconds 表示毫秒。

       milliseconds = “實際時間” - “1970-01-01 00:00:00”。Calendar 和 Date依賴於 milliseconds,從而表示時間。

(02) Calendar表示日期/時間。

       它是一個抽象類,依賴於milliseconds。GregorianCalendar是Calendar的子類,通常我們通過Calendar.getInstance() 獲取Calendar實例時,實際上返回的是 GregorianCalendar 對象。

       Calendar和Locale關聯,而Locale代表區域;Locale的值不同時,Calendar的日期/時間也不同。

       Calendar和TimeZone關聯,而TimeZone代表時區;不同的時區,Calendar的日期/時間也不同。

(03) Date 表示日期/時間。

       它也依賴於 milliseconds實現。

       在 JDK 1.1 之前,通常是通過Data操作“年月日時分秒”。不過,由於Date的相關 API 不易於實現國際化。從 JDK 1.1 開始,應該使用 Calendar 類來操作“年月日時分秒”,同時可以通過 DateFormat 類來格式化和解析日期字符串。Date 中的相應方法已廢棄。

(04) DateFormat是格式化/解析“日期/時間”的工具類。

       它是Date的格式化工具,它能幫助我們格式化Date,進而將Date轉換成我們想要的String字符串供我們使用。

       它是一個抽象類。通常,我們通過getInstance(), getDateInstance()和getDateTimeInstance() 等獲取DateFormat實例時;實際上是返回的SimpleDateFormat對象。

 


Calendar 介紹 

Calendar 定義

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {}

Calendar 是一個抽象類。

它的實現,采用了設計模式中的工廠方法。表現在:當我們獲取Calendar實例時,Calendar會根據傳入的參數來返回相應的Calendar對象。獲取Calendar實例,有以下兩種方式:
1) 當我們通過 Calendar.getInstance() 獲取日歷時,默認的是返回的一個GregorianCalendar對象。
    GregorianCalendar是Calendar的一個實現類,它提供了世界上大多數國家/地區使用的標准日歷系統。
2) 當我們通過 Calendar.getInstance(TimeZone timezone, Locale locale) Calendar.getInstance(TimeZone timezone)Calendar.getInstance(Locale locale)獲取日歷時,是返回“對應時區(zone) 或 地區(local)等所使用的日歷”。
    例如,若是日本,則返回JapaneseImperialCalendar對象。

參考如下代碼

 1 public static Calendar getInstance()
 2 {
 3     // 調用createCalendar()創建日歷
 4     Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault());
 5     cal.sharedZone = true;
 6     return cal;
 7 }
 8 
 9 
10 public static Calendar getInstance(TimeZone zone)
11 {
12     // 調用createCalendar()創建日歷
13     return createCalendar(zone, Locale.getDefault());
14 }
15 
16 
17 public static Calendar getInstance(Locale aLocale) {
18     // 調用createCalendar()創建日歷
19     Calendar cal = createCalendar(TimeZone.getDefaultRef(), aLocale);
20     cal.sharedZone = true;
21     return cal;
22 }
23 
24 public static Calendar getInstance(TimeZone zone,
25                    Locale aLocale)
26 {
27     // 調用createCalendar()創建日歷
28     return createCalendar(zone, aLocale);
29 }
30 
31 private static Calendar createCalendar(TimeZone zone,
32                    Locale aLocale)
33 {
34     // (01) 若地區是“th”,則返回BuddhistCalendar對象
35     // (02) 若地區是“JP”,則返回JapaneseImperialCalendar對象
36     if ("th".equals(aLocale.getLanguage())
37         && ("TH".equals(aLocale.getCountry()))) {
38         return new sun.util.BuddhistCalendar(zone, aLocale);
39     } else if ("JP".equals(aLocale.getVariant())
40        && "JP".equals(aLocale.getCountry())
41        && "ja".equals(aLocale.getLanguage())) {
42         return new JapaneseImperialCalendar(zone, aLocale);
43     }        
44 
45     // (03) 否則,返回GregorianCalendar對象
46     return new GregorianCalendar(zone, aLocale);    
47 }
View Code

當我們獲取Calendar實例之后,就可以通過Calendar提供的一些列方法來操作日歷。

 


Calendar 原理和思想

我們使用Calendar,無非是操作Calendar的“年、月、日、星期、時、分、秒”這些字段。下面,我們對這些字段的的來源、定義以及計算方法進行學習。

1. Calendar 各個字段值的來源

我們使用Calendar,無非是使用“年、月、日、星期、時、分、秒”等信息。那么它是如何做到的呢?
本質上,Calendar就是保存了一個時間。如下定義:

// time 是當前時間,單位是毫秒。
// 它是當前時間距離“January 1, 1970, 0:00:00 GMT”的差值。
protected long time;

Calendar就是根據 time 計算出 “Calendar的年、月、日、星期、時、分、秒”等等信息。


2. Calendar 各個字段的定義和初始化

Calendar 的“年、月、日、星期、時、分、秒”這些信息,一共是17個字段
我們使用Calendar,無非是就是使用這17個字段。它們的定義如下:
(字段0) public final static int ERA = 0;
說明:紀元。
取值:只能為0 或 1。0表示BC(“before Christ”,即公元前),1表示AD(拉丁語“Anno Domini”,即公元)。

(字段1) public final static int YEAR = 1;
說明:年。

(字段2) public final static int MONTH = 2;
說明:月
取值:可以為,JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER, UNDECIMBER。
         其中第一個月是 JANUARY,它為 0。

(字段3) public final static int WEEK_OF_YEAR = 3;
說明:當前日期在本年中對應第幾個星期。一年中第一個星期的值為 1。

(字段4) public final static int WEEK_OF_MONTH = 4;
說明:當前日期在本月中對應第幾個星期。一個月中第一個星期的值為 1。

(字段5) public final static int DATE = 5;
說明:日。一個月中第一天的值為 1。

(字段5) public final static int DAY_OF_MONTH = 5;
說明:同“DATE”,表示“日”。

(字段6) public final static int DAY_OF_YEAR = 6;
說明:當前日期在本年中對應第幾天。一年中第一天的值為 1。

(字段7) public final static int DAY_OF_WEEK = 7;
說明:星期幾。
取值:可以為,SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY 和 SATURDAY。
         其中,SUNDAY為1,MONDAY為2,依次類推。

(字段8) public final static int DAY_OF_WEEK_IN_MONTH = 8;
說明:當前月中的第幾個星期。
取值:DAY_OF_MONTH 1 到 7 總是對應於 DAY_OF_WEEK_IN_MONTH 1;8 到 14 總是對應於 DAY_OF_WEEK_IN_MONTH 2,依此類推。

(字段9) public final static int AM_PM = 9;
說明:上午 還是 下午
取值:可以是AM 或 PM。AM為0,表示上午;PM為1,表示下午。

(字段10) public final static int HOUR = 10;
說明:指示一天中的第幾小時。
         HOUR 用於 12 小時制時鍾 (0 - 11)。中午和午夜用 0 表示,不用 12 表示。

(字段11) public final static int HOUR_OF_DAY = 11;
說明:指示一天中的第幾小時。
         HOUR_OF_DAY 用於 24 小時制時鍾。例如,在 10:04:15.250 PM 這一時刻,HOUR_OF_DAY 為 22。

(字段12) public final static int MINUTE = 12;
說明:一小時中的第幾分鍾。
例如,在 10:04:15.250 PM這一時刻,MINUTE 為 4。

(字段13) public final static int SECOND = 13;
說明:一分鍾中的第幾秒。
例如,在 10:04:15.250 PM 這一時刻,SECOND 為 15。

(字段14) public final static int MILLISECOND = 14;
說明:一秒中的第幾毫秒。
例如,在 10:04:15.250 PM 這一時刻,MILLISECOND 為 250。

(字段15) public final static int ZONE_OFFSET = 15;
說明:毫秒為單位指示距 GMT 的大致偏移量。

(字段16) public final static int DST_OFFSET = 16;
說明:毫秒為單位指示夏令時的偏移量。

public final static int FIELD_COUNT = 17;

 

這17個字段是保存在int數組中。定義如下:

// 保存這17個字段的數組
protected int           fields[];
// 數組的定義函數
protected Calendar(TimeZone zone, Locale aLocale)
{
    // 初始化“fields數組”
    fields = new int[FIELD_COUNT];
    isSet = new boolean[FIELD_COUNT];
    stamp = new int[FIELD_COUNT];

    this.zone = zone;
    setWeekCountData(aLocale);
}

protected Calendar(TimeZone zone, Locale aLocale) 這是Calendar的構造函數。它會被它的子類的構造函數調用到,從而新建“保存Calendar的17個字段數據”的數組


3. Calendar 各個字段值的計算

下面以get(int field)為例,簡要的說明Calendar的17個字段的計算和操作。
get(int field)是獲取“field”字段的值。它的定義如下:

public int get(int field) {
    // 計算各個字段的值
    complete();
    // 返回field字段的值
    return internalGet(field);
}

說明
get(int field)的代碼很簡單。先通過 complete() 計算各個字段的值然后在通過 internalGet(field) 返回“field字段的值”

complete() 的作用就是計算Calendar各個字段的值。它定義在Calendar.java中,代碼如下:

protected void complete()
{
    if (!isTimeSet)
    updateTime();
    if (!areFieldsSet || !areAllFieldsSet) {
        computeFields(); // fills in unset fields
        areAllFieldsSet = areFieldsSet = true;
    }
}
private void updateTime() {
    computeTime();
    isTimeSet = true;
}

updateTime() 調用到的 computeTime() 定義在 Calendar.java的實現類中。下面,我列出GregorianCalendar.java中computeTime()的實現:

  1 protected void computeTime() {
  2     // In non-lenient mode, perform brief checking of calendar
  3     // fields which have been set externally. Through this
  4     // checking, the field values are stored in originalFields[]
  5     // to see if any of them are normalized later.
  6     if (!isLenient()) {
  7         if (originalFields == null) {
  8             originalFields = new int[FIELD_COUNT];
  9         }
 10         for (int field = 0; field < FIELD_COUNT; field++) {
 11             int value = internalGet(field);
 12             if (isExternallySet(field)) {
 13                 // Quick validation for any out of range values
 14                 if (value < getMinimum(field) || value > getMaximum(field)) {
 15                     throw new IllegalArgumentException(getFieldName(field));
 16                 }
 17             }
 18             originalFields[field] = value;
 19         }
 20     }
 21 
 22     // Let the super class determine which calendar fields to be
 23     // used to calculate the time.
 24     int fieldMask = selectFields();
 25 
 26     // The year defaults to the epoch start. We don't check
 27     // fieldMask for YEAR because YEAR is a mandatory field to
 28     // determine the date.
 29     int year = isSet(YEAR) ? internalGet(YEAR) : EPOCH_YEAR;
 30 
 31     int era = internalGetEra();
 32     if (era == BCE) {
 33         year = 1 - year;
 34     } else if (era != CE) {
 35         // Even in lenient mode we disallow ERA values other than CE & BCE.
 36         // (The same normalization rule as add()/roll() could be
 37         // applied here in lenient mode. But this checking is kept
 38         // unchanged for compatibility as of 1.5.)
 39         throw new IllegalArgumentException("Invalid era");
 40     }
 41         
 42     // If year is 0 or negative, we need to set the ERA value later.
 43     if (year <= 0 && !isSet(ERA)) {
 44         fieldMask |= ERA_MASK;
 45         setFieldsComputed(ERA_MASK);
 46     }
 47 
 48     // Calculate the time of day. We rely on the convention that
 49     // an UNSET field has 0.
 50     long timeOfDay = 0;
 51     if (isFieldSet(fieldMask, HOUR_OF_DAY)) {
 52         timeOfDay += (long) internalGet(HOUR_OF_DAY);
 53     } else {
 54         timeOfDay += internalGet(HOUR);
 55         // The default value of AM_PM is 0 which designates AM.
 56         if (isFieldSet(fieldMask, AM_PM)) {
 57             timeOfDay += 12 * internalGet(AM_PM);
 58         }
 59     }
 60     timeOfDay *= 60;
 61     timeOfDay += internalGet(MINUTE);
 62     timeOfDay *= 60;
 63     timeOfDay += internalGet(SECOND);
 64     timeOfDay *= 1000;
 65     timeOfDay += internalGet(MILLISECOND);
 66 
 67     // Convert the time of day to the number of days and the
 68     // millisecond offset from midnight.
 69     long fixedDate = timeOfDay / ONE_DAY;
 70     timeOfDay %= ONE_DAY;
 71     while (timeOfDay < 0) {
 72         timeOfDay += ONE_DAY;
 73         --fixedDate;
 74     }
 75 
 76     // Calculate the fixed date since January 1, 1 (Gregorian).
 77     calculateFixedDate: {
 78         long gfd, jfd;
 79         if (year > gregorianCutoverYear && year > gregorianCutoverYearJulian) {
 80             gfd = fixedDate + getFixedDate(gcal, year, fieldMask);
 81             if (gfd >= gregorianCutoverDate) {
 82                 fixedDate = gfd;
 83                 break calculateFixedDate;
 84             }
 85             jfd = fixedDate + getFixedDate(getJulianCalendarSystem(), year, fieldMask);
 86         } else if (year < gregorianCutoverYear && year < gregorianCutoverYearJulian) {
 87             jfd = fixedDate + getFixedDate(getJulianCalendarSystem(), year, fieldMask);
 88             if (jfd < gregorianCutoverDate) {
 89                 fixedDate = jfd;
 90                 break calculateFixedDate;
 91             }
 92             gfd = fixedDate + getFixedDate(gcal, year, fieldMask);
 93         } else {
 94             gfd = fixedDate + getFixedDate(gcal, year, fieldMask);
 95             jfd = fixedDate + getFixedDate(getJulianCalendarSystem(), year, fieldMask);
 96         }
 97         // Now we have to determine which calendar date it is.
 98         if (gfd >= gregorianCutoverDate) {
 99             if (jfd >= gregorianCutoverDate) {
100                 fixedDate = gfd;
101             } else {
102                 // The date is in an "overlapping" period. No way
103                 // to disambiguate it. Determine it using the
104                 // previous date calculation.
105                 if (calsys == gcal || calsys == null) {
106                     fixedDate = gfd;
107                 } else {
108                     fixedDate = jfd;
109                 }
110             }
111         } else {
112             if (jfd < gregorianCutoverDate) {
113                 fixedDate = jfd;
114             } else {
115                 // The date is in a "missing" period.
116                 if (!isLenient()) {
117                     throw new IllegalArgumentException("the specified date doesn't exist");
118                 }
119                 // Take the Julian date for compatibility, which
120                 // will produce a Gregorian date.
121                 fixedDate = jfd;
122             }
123         }
124     }
125 
126     // millis represents local wall-clock time in milliseconds.
127     long millis = (fixedDate - EPOCH_OFFSET) * ONE_DAY + timeOfDay;
128 
129     // Compute the time zone offset and DST offset.  There are two potential
130     // ambiguities here.  We'll assume a 2:00 am (wall time) switchover time
131     // for discussion purposes here.
132     // 1. The transition into DST.  Here, a designated time of 2:00 am - 2:59 am
133     //    can be in standard or in DST depending.  However, 2:00 am is an invalid
134     //    representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST).
135     //    We assume standard time.
136     // 2. The transition out of DST.  Here, a designated time of 1:00 am - 1:59 am
137     //    can be in standard or DST.  Both are valid representations (the rep
138     //    jumps from 1:59:59 DST to 1:00:00 Std).
139     //    Again, we assume standard time.
140     // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
141     // or DST_OFFSET fields; then we use those fields.
142     TimeZone zone = getZone();
143     if (zoneOffsets == null) {
144         zoneOffsets = new int[2];
145     }
146     int tzMask = fieldMask & (ZONE_OFFSET_MASK|DST_OFFSET_MASK);
147     if (tzMask != (ZONE_OFFSET_MASK|DST_OFFSET_MASK)) {
148         if (zone instanceof ZoneInfo) {
149             ((ZoneInfo)zone).getOffsetsByWall(millis, zoneOffsets);
150         } else {
151             int gmtOffset = isFieldSet(fieldMask, ZONE_OFFSET) ?
152                 internalGet(ZONE_OFFSET) : zone.getRawOffset();
153             zone.getOffsets(millis - gmtOffset, zoneOffsets);
154         }
155     }
156     if (tzMask != 0) {
157         if (isFieldSet(tzMask, ZONE_OFFSET)) {
158             zoneOffsets[0] = internalGet(ZONE_OFFSET);
159         }
160         if (isFieldSet(tzMask, DST_OFFSET)) {
161             zoneOffsets[1] = internalGet(DST_OFFSET);
162         }
163     }
164 
165     // Adjust the time zone offset values to get the UTC time.
166     millis -= zoneOffsets[0] + zoneOffsets[1];
167 
168     // Set this calendar's time in milliseconds
169     time = millis;
170 
171     int mask = computeFields(fieldMask | getSetStateFields(), tzMask);
172 
173     if (!isLenient()) {
174         for (int field = 0; field < FIELD_COUNT; field++) {
175             if (!isExternallySet(field)) {
176                 continue;
177             }
178             if (originalFields[field] != internalGet(field)) {
179                 // Restore the original field values
180                 System.arraycopy(originalFields, 0, fields, 0, fields.length);
181                 throw new IllegalArgumentException(getFieldName(field));
182             }
183         }
184     }
185     setFieldsNormalized(mask);
186 }
View Code

 

下面,我們看看internalGet(field)的定義。如下:

protected final int internalGet(int field) {
    return fields[field];
}

從中,我們就看出,get(int field) 最終是通過 internalGet(int field)來返回值的。
而 internalGet(int field) ,實際上返回的是field數組中的第field個元素。這就正好和Calendar的17個元素所對應了!

 

總之,我們需要了解的就是:Calendar就是以一個time(毫秒)為基數,而計算出“年月日時分秒”等,從而方便我們對“年月日時分秒”等進行操作。下面,介紹以下Calendar提供的相關操作函數。

 


Calendar函數接口

1. Calendar的17個字段的公共接口

Calendar的這17個字段,都支持下面的公共函數接口。
這些公共接口的使用示例,請參考CalendarTest.java 示例中的 testAllCalendarSections() 函數。

1) getMaximum(int field)

作用:獲取“字段的最大值”。注意“對比它和 getActualMaximum() 的區別”。
示例:以“MONTH”字段來說。使用方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 獲取MONTH的最大值
int max = cal.getMaximum(Calendar.MONTH);

若要獲取其它字段的最大值,只需要將示例中的MONTH相應的替換成其它字段名即可。


2) getActualMaximum(int field)

作用:獲取“當前日期下,該字段的最大值”。
示例:以“MONTH”字段來說。使用方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 獲取當前MONTH的最大值
int max = cal.getActualMaximum(Calendar.MONTH);

若要獲取其它字段的最大值,只需要將示例中的MONTH相應的替換成其它字段名即可。

 

注意對比getActualMaximum() 和 getMaximum() 的區別。參考下面的對比示例,

(01) getMaximum() 獲取的“字段最大值”,是指在綜合所有的日期,在所有這些日期中得出的“字段最大值”
      例如,getMaximum(Calendar.DATE)的目的是“獲取‘日的最大值’”。綜合所有的日期,得出一個月最多有31天。因此,getMaximum(Calendar.DATE)的返回值是“31”!
(02) getActualMaximum() 獲取的“當前日期時,該字段的最大值”。
     例如,當日期為2013-09-01時,getActualMaximum(Calendar.DATE)是獲取“日的最大值”是“30”。當前日期是9月份,而9月只有30天。因此,getActualMaximum(Calendar.DATE)的返回值是“30”!


3) getMinimum(int field)

作用:獲取“字段的最小值”。注意“對比它和 getActualMinimum() 的區別”。
示例:以“MONTH”字段來說。使用方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 獲取MONTH的最小值
int min = cal.getMinimum(Calendar.MONTH);

若要獲取其它字段的最小值,只需要將示例中的MONTH相應的替換成其它字段名即可。


4) getActualMinimum(int field)

作用:獲取“當前日期下,該字段的最小值”。
示例:以“MONTH”字段來說。使用方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 獲取MONTH的最小值
int min = cal.getMinimum(Calendar.MONTH);

若要獲取其它字段的最小值,只需要將示例中的MONTH相應的替換成其它字段名即可。

注意:在Java默認的Calendar中,雖然 getMinimum() 和 getActualMinimum() 的含義不同;但是,它們的返回值是一樣的。因為Calendar的默認是返回GregorianCalendar對象,而在GregorianCalendar.java中,getMinimum() 和 getActualMinimum() 返回值一樣。


5) get(int field)

作用:獲取“字段的當前值”。獲取field字段的當前值。
示例:以“MONTH”字段來說。“獲取MONTH的當前值”的方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 獲取“cal日歷”的當前MONTH值
int MONTH = cal.get(Calendar.MONTH);

若要獲取其它字段的當前值,只需要將示例中的MONTH相應的替換成其它字段名即可。


6) set(int field, int value)

作用:設置“字段的當前值”。設置field字段的當前值為value
示例:以“MONTH”字段來說。“設置MONTH的當前值”的方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 設置“cal日歷”的當前MONTH值為 1988年
cal.set(Calendar.MONTH, 1988);

說明
(01) 1988 是想要設置的MONTH的當前值。這個設置值必須是整數。
(02) 若要設置其它字段的當前值,只需要將示例中的MONTH相應的替換成其它字段名即可。


7) add(int field, int value)

作用:給“字段的當前值”添加值。給field字段的當前值添加value。
示例:以“MONTH”字段來說。方法如下:

// 獲取Calendar實例,並設置日期為“2013-09-01”
Calendar cal = 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);

說明
(01) -10 是添加值。
      添加值可以為正數,也可以是負數。
      正數表示將日期增加,負數表示將日期減少。

     假設:現在cal的值是“2013-09-01”,現在我們將MONTH字段值增加-10。得到的結果是:“2012-10-01”。
             為什么會這樣呢?“2013-09-01”增加-10,也就是將日期向前減少10個月;得到的結果就是“2012-10-01”。
(02) Calendar的17個字段中:除了回滾Calendar.ZONE_OFFSET時,會拋出IllegalArgumentException異常;其它的字段都支持該操作。
(03) 若要設置其它字段的當前值,只需要將示例中的MONTH相應的替換成其它字段名即可。


8) roll(int field, int value)

作用:回滾“字段的當前值”
示例:以“MONTH”字段來說。“回滾MONTH的當前值”的方法為:

// 獲取Calendar實例,並設置日期為“2013-09-01”
Calendar cal = 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);

說明
(01) -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”。
(02) Calendar的17個字段中:除了回滾Calendar.ZONE_OFFSET時,會拋出IllegalArgumentException異常;其它的字段都支持該操作。
(03) 若要設置其它字段的當前值,只需要將示例中的MONTH相應的替換成其它字段名即可。


9) clear(int field)

作用:清空“字段的當前值”。所謂清空,實際上是將“field”的值設置為0;若field最小值為1,則設置為1。
示例:以“MONTH”字段來說。“清空MONTH”的方法為:

// 獲取Calendar實例,並設置日期為“9月”
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 9);
// 清空MONTH
cal.clear(Calendar.MONTH);

若要清空其它字段,只需要將示例中的MONTH相應的替換成其它字段名即可。


10) isSet(int field)

作用:判斷“字段field”是否被設置。若調用clear()清空之后,則field變為“沒有設置狀態”。
示例:以“MONTH”字段來說。“判斷MONTH是否被設置”的方法為:

// 獲取Calendar實例
Calendar cal = Calendar.getInstance();
// 判斷MONTH是否被設置
boolean bset = cal.isSet(Calendar.MONTH);

若要判斷其它字段,只需要將示例中的MONTH相應的替換成其它字段名即可。

 

2. Calendar的其它函數

1) 日期比較函數

Calendar的比較函數,主要有以下幾個:

// 比較“當前Calendar對象”和“calendar” 的日期、時區等內容是否相等。
boolean equals(Object object)
// 當前Calendar對象 是否 早於calendar
boolean before(Object calendar)
// 當前Calendar對象 是否 晚於calendar
boolean after(Object calendar)
// 比較“當前Calendar對象”和“calendar”。
// 若 早於 “calendar” 則,返回-1
// 若 相等, 則,返回0
// 若 晚於 “calendar” 則,返回1
int compareTo(Calendar anotherCalendar) 

這些函數的使用示例,請參考CalendarTest.java示例中的 testComparatorAPIs() 函數。

示例:假設cal1 和 cal2 都是Calendar的兩個對象。

// 它們的使用方法如下
boolean isEqual = cal1.equals(cal2);
boolean isBefore = cal1.before(cal2);
boolean isAfter = cal1.after(cal2);
int icompare = cal1.compareTo(cal2);

 

2) “寬容”函數

// 設置“Calendar的寬容度”
void setLenient(boolean value)
// 獲取“Calendar的寬容度”
boolean isLenient()

這些函數的使用示例,請參考CalendarTest.java示例中的 testLenientAPIs() 函數。
說明
Calendar 有兩種解釋日歷字段的模式,即 lenient 和 non-lenient。
(01) 當 Calendar 處於 lenient 模式時,它可接受比它所生成的日歷字段范圍更大范圍內的值。當 Calendar 重新計算日歷字段值,以便由 get() 返回這些值時,所有日歷字段都被標准化。
      例如,lenient 模式下的 GregorianCalendar 將 MONTH == JANUARY、DAY_OF_MONTH == 32 解釋為 February 1。
(02) 當 Calendar 處於 non-lenient 模式時,如果其日歷字段中存在任何不一致性,它都會拋出一個異常。
     例如,GregorianCalendar 總是在 1 與月份的長度之間生成 DAY_OF_MONTH 值。如果已經設置了任何超出范圍的字段值,那么在計算時間或日歷字段值時,處於 non-lenient 模式下的 GregorianCalendar 會拋出一個異常。
    注意:在(02)步驟中的異常,在使用set()時不會拋出,而需要在使用get()、getTimeInMillis()、getTime()、add() 和 roll() 等函數中才拋出。因為set()只是設置了一個修改標志,而get()等方法才會引起時間的重新計算,此時才會拋出異常!


3) "年月日(時分秒)"、Date、TimeZone、MilliSecond函數

// 設置“年月日”
final void     set(int year, int month, int day)
// 設置“年月日時分”
final void     set(int year, int month, int day, int hourOfDay, int minute, int second)
// 設置“年月日時分秒”
final void     set(int year, int month, int day, int hourOfDay, int minute)
// 獲取Calendar對應的日期
final Date     getTime()
// 設置Calendar為date
final void    setTime(Date date)
// 獲取Calendar對應的時區
TimeZone     getTimeZone()
// 設置Calendar對應的時區
void     setTimeZone(TimeZone timezone)
// 獲取Calendar對應的milliscondes值,就是“Calendar當前日期”距離“1970-01-01 0:00:00 GMT”的毫秒數
long     getTimeInMillis()
// 設置Calendar對應的milliscondes值
void     setTimeInMillis(long milliseconds)

這些函數的使用示例,請參考CalendarTest.java示例中的 testTimeAPIs() 函數。

 

4) 其它操作

// 克隆Calendar
Object clone()
// 獲取“每周的第一天是星期幾”。例如,在美國,這一天是 SUNDAY,而在法國,這一天是 MONDAY。
int getFirstDayOfWeek()
// 設置“每周的第一天是星期幾”。例如,在美國,這一天是 SUNDAY,而在法國,這一天是 MONDAY。
void setFirstDayOfWeek(int value)
// 獲取一年中第一個星期所需的最少天數,例如,如果定義第一個星期包含一年第一個月的第一天,則此方法將返回 1。如果最少天數必須是一整個星期,則此方法將返回 7。
int getMinimalDaysInFirstWeek()
// 設置一年中第一個星期所需的最少天數,例如,如果定義第一個星期包含一年第一個月的第一天,則使用值 1 調用此方法。如果最少天數必須是一整個星期,則使用值 7 調用此方法。
void setMinimalDaysInFirstWeek(int value)

這些函數的使用示例,請參考CalendarTest.java示例中的 testOtherAPIs() 函數。

 

 


Calendar 示例 

下面,我們通過示例學習使用Calendar的API。CalendarTest.java的源碼如下:

  1 import java.util.Date;
  2 import java.util.Calendar;
  3 import java.util.TimeZone;
  4 import java.util.Random;
  5 
  6 /**
  7  * Calendar的API測試程序
  8  *
  9  * @author skywang 
 10  * @email kuiwu-wang@163.com
 11  */
 12 public class CalendarTest {
 13 
 14     public static void main(String[] args) {
 15 
 16         // 測試Calendar的“17個字段的公共函數接口”
 17         testAllCalendarSections() ;
 18 
 19         // 測試Calendar的“比較接口”
 20         testComparatorAPIs() ;
 21 
 22         // 測試Calendar的“比較接口”
 23         testLenientAPIs() ;
 24 
 25         // 測試Calendar的Date、TimeZone、MilliSecond等相關函數
 26         testTimeAPIs() ;
 27 
 28         // 測試Calendar的clone(),getFirstDayOfWeek()等接口
 29         testOtherAPIs() ;
 30 
 31     }
 32 
 33 
 34     /**
 35      * 測試“Calendar的字段”
 36      *
 37      * @param cal   -- Calendar對象
 38      * @param field -- 要測試的“Calendar字段”。可以為以下值:
 39      *   Calendar.YEAR, Calendar.MONTH, Calendar.DATE, ... 等等
 40      * @param title -- 標題
 41      * @author skywang (kuiwu-wang@163.com)
 42      */
 43     private static void testSection(Calendar cal, int field, String title) {
 44         final Random random = new Random();
 45         final Date date = cal.getTime();
 46 
 47         final int min = cal.getMinimum(field);    // 獲取"字段最小值"
 48         final int max = cal.getMaximum(field);    // 獲取“字段最大值”
 49 
 50         final int actualMin = cal.getActualMinimum(field);    // 獲取"當前日期下,該字段最小值"
 51         final int actualMax = cal.getActualMaximum(field);    // 獲取“當前日期下,該字段的最大值”
 52 
 53         // 獲取“字段的當前值”
 54         final int ori = cal.get(field);            
 55 
 56         // 設置“字段的當前值”, 並獲取“設置之后的值”
 57         final int r1 = random.nextInt(max);
 58         cal.set(field, r1);                
 59         final int set = cal.get(field);            
 60         try {
 61             // 回滾“字段的當前值”:在“字段最小值”和“字段最大值”之間回滾。
 62             // “回滾值”可以為正,也可以為負。
 63             cal.roll(field, -max);            
 64         } catch (IllegalArgumentException e) {
 65             // 當field == Calendar.ZONE_OFFSET時,會拋出該異常!
 66             e.printStackTrace();
 67         }
 68         final int roll = cal.get(field);            
 69 
 70         // 獲取一個隨機值
 71         final int sign = ( random.nextInt(2) == 1) ? 1 : -1;
 72         final int r2 = sign * random.nextInt(max);
 73         try {
 74             // 增加“字段的當前值” ,並獲取“新的當前字段值”
 75             // add的“參數值”可以為正,也可以為負。
 76             cal.add(field, r2);            
 77         } catch (IllegalArgumentException e) {
 78             // 當field == Calendar.ZONE_OFFSET時,會拋出該異常!
 79             e.printStackTrace();
 80         }
 81         final int add = cal.get(field);
 82 
 83 
 84 
 85         // 打印字段信息
 86         System.out.printf("%s:\n\trange is [%d - %d] actualRange is [%d - %d].  original=%d, set(%d)=%d, roll(%d)=%d, add(%d)=%d\n",
 87                title, min, max, actualMin, actualMax, ori, r1, set, -max, roll, r2, add);
 88     }
 89 
 90     /**
 91      * 測試Calendar的“17個字段的公共函數接口”
 92      *
 93      * @author skywang (kuiwu-wang@163.com)
 94      */
 95     private static void testAllCalendarSections() {
 96         // 00. ERA 字段
 97         testSection(Calendar.getInstance(), Calendar.ERA, "Calendar.ERA");
 98         // 01. YEAR 字段
 99         testSection(Calendar.getInstance(), Calendar.YEAR, "Calendar.YEAR");
100         // 02. MONTH 字段
101         testSection(Calendar.getInstance(), Calendar.MONTH, "Calendar.MONTH");
102         // 03. WEEK_OF_YEAR 字段
103         testSection(Calendar.getInstance(), Calendar.WEEK_OF_YEAR, "Calendar.WEEK_OF_YEAR");
104         // 04. WEEK_OF_MONTH 字段
105         testSection(Calendar.getInstance(), Calendar.WEEK_OF_MONTH, "Calendar.WEEK_OF_MONTH");
106         // 05. DATE 字段
107         testSection(Calendar.getInstance(), Calendar.DATE, "Calendar.DATE");
108         // 06. DAY_OF_MONTH 字段
109         testSection(Calendar.getInstance(), Calendar.DAY_OF_MONTH, "Calendar.DAY_OF_MONTH");
110         // 07. DAY_OF_YEAR 字段
111         testSection(Calendar.getInstance(), Calendar.DAY_OF_YEAR, "Calendar.DAY_OF_YEAR");
112         // 08. DAY_OF_WEEK 字段
113         testSection(Calendar.getInstance(), Calendar.DAY_OF_WEEK, "Calendar.DAY_OF_WEEK");
114         // 09. DAY_OF_WEEK_IN_MONTH 字段
115         testSection(Calendar.getInstance(), Calendar.DAY_OF_WEEK_IN_MONTH, "Calendar.DAY_OF_WEEK_IN_MONTH");
116         // 10. AM_PM 字段
117         testSection(Calendar.getInstance(), Calendar.AM_PM, "Calendar.AM_PM");
118         // 11. HOUR 字段
119         testSection(Calendar.getInstance(), Calendar.HOUR, "Calendar.HOUR");
120         // 12. HOUR_OF_DAY 字段
121         testSection(Calendar.getInstance(), Calendar.HOUR_OF_DAY, "Calendar.HOUR_OF_DAY");
122         // 13. MINUTE 字段
123         testSection(Calendar.getInstance(), Calendar.MINUTE, "Calendar.MINUTE");
124         // 14. SECOND 字段
125         testSection(Calendar.getInstance(), Calendar.SECOND, "Calendar.SECOND");
126         // 15. MILLISECOND 字段
127         testSection(Calendar.getInstance(), Calendar.MILLISECOND, "Calendar.MILLISECOND");
128         // 16. ZONE_OFFSET 字段
129         testSection(Calendar.getInstance(), Calendar.ZONE_OFFSET, "Calendar.ZONE_OFFSET");
130     }
131 
132     /**
133      * 測試Calendar的“比較接口”
134      *
135      * @author skywang (kuiwu-wang@163.com)
136      */
137     private static void testComparatorAPIs() {
138         // 新建cal1 ,且時間為1988年
139         Calendar cal1 = Calendar.getInstance();
140         cal1.set(Calendar.YEAR, 1988);
141         // 新建cal2 ,且時間為2000年
142         Calendar cal2 = Calendar.getInstance();
143         cal2.set(Calendar.YEAR, 2000);
144         // 新建cal3, 為cal1的克隆對象
145         Calendar cal3 = (Calendar)cal1.clone();
146 
147         // equals 判斷 cal1和cal2的“時間、時區等”內容是否相等
148         boolean isEqual12 = cal1.equals(cal2);
149         // equals 判斷 cal1和cal3的“時間、時區等”內容是否相等
150         boolean isEqual13 = cal1.equals(cal3);
151         // cal1是否比cal2早
152         boolean isBefore = cal1.before(cal2);
153         // cal1是否比cal2晚
154         boolean isAfter = cal1.after(cal2);
155         // 比較cal1和cal2
156         // (01) 若cal1 早於 cal2,返回-1
157         // (02) 若cal1 等於 cal2,返回0
158         // (03) 若cal1 晚於 cal2,返回1
159         int icompare = cal1.compareTo(cal2);
160         
161         System.out.printf("\ntestComparatorAPIs: isEuqal12=%s, isEqual13=%s, isBefore=%s, isAfter=%s, icompare=%s\n",
162                isEqual12, isEqual13, isBefore, isAfter, icompare);
163     }
164 
165     /**
166      * 測試Calendar的“比較接口”
167      *
168      * @author skywang (kuiwu-wang@163.com)
169      */
170     private static void testLenientAPIs() {
171             Calendar cal = Calendar.getInstance();
172 
173             // 獲取默認的“寬容度”。返回true
174             boolean oriLenient = cal.isLenient();
175             // MONTH值只能是“0-11”,這里越界。但是由於當前cal是寬容的,所以不會拋出異常
176             cal.set(Calendar.MONTH, 50);    
177 
178             // 設置“寬容度”為false。
179             cal.setLenient(false);
180             // 獲取設置后的“寬容度”
181             boolean curLenient = cal.isLenient();
182             try {
183             // MONTH值只能是“0-11”,這里越界。而且當前cal是不寬容的,所以會產生異常。
184             // 但是,異常到下次計算日期時才會拋出。即,set()中不回拋出異常,而要等到get()中才會拋出異常
185             cal.set(Calendar.MONTH, 50);
186             // 此時,對cal進行讀取。讀取會導致重新計算cal的值,所以此時拋出異常!
187             int m2 = cal.get(Calendar.MONTH);    
188         } catch (IllegalArgumentException e) {
189             e.printStackTrace();
190         }
191 
192         System.out.printf("\ntestLenientAPIs: oriLenient=%s, curLenient=%s\n",
193                oriLenient, curLenient);
194     }
195 
196     /**
197      * 測試Calendar的Date、TimeZone、MilliSecond等相關函數
198      *
199      * @author skywang (kuiwu-wang@163.com)
200      */
201     private static void testTimeAPIs() {
202         Calendar cal = Calendar.getInstance();
203 
204         // 設置cal的時區為“GMT+8”
205         cal.setTimeZone(TimeZone.getTimeZone("GMT+8"));
206         // 獲取當前的cal時區
207         TimeZone timezone = cal.getTimeZone();
208 
209         // 設置 milliseconds
210         cal.setTimeInMillis(1279419645742l);
211         // 獲取 milliseconds
212         long millis = cal.getTimeInMillis();
213         // 設置 milliseconds之后,時間也改變了。
214         // 獲取cal對應的日期
215         Date date = cal.getTime();
216 
217         // 設置時間為“1988-08-08”
218         cal.set(1988, 08, 08);
219         // 設置時間為“1999-09-09 09:09”
220         cal.set(1999, 09, 09, 9, 9);
221         // 設置時間為“2000-10-10 10:10:10”
222         cal.set(2000, 10, 10, 10, 10, 10);
223 
224         System.out.printf("\ntestTimeAPIs: date=%s, timezone=%s, millis=%s\n",
225                date, timezone, millis);
226     }
227 
228     /**
229      * 測試Calendar的clone(),getFirstDayOfWeek()等接口
230      *
231      * @author skywang (kuiwu-wang@163.com)
232      */
233     private static void testOtherAPIs() {
234         Calendar cal = Calendar.getInstance();
235         // 克隆cal
236         Calendar clone = (Calendar)cal.clone();
237 
238         // 設置 為 2013-01-10。 
239         // 注:2013-01-01 為“星期二”,2013-01-06為“星期天”,
240         clone.set(Calendar.YEAR, 2013);
241         clone.set(Calendar.MONTH, 0);
242         clone.set(Calendar.DATE, 10);
243         // 設置“本年的第一個星期最少包含1天”。
244         // 則2013-01-10屬於第2個星期
245         clone.setMinimalDaysInFirstWeek(1);
246         int m1 = clone.getMinimalDaysInFirstWeek();
247         int index1 = clone.get(Calendar.WEEK_OF_YEAR);
248 
249         // 設置“本年的第一個星期最少包含7天”。
250         // 則2013-01-10屬於第1個星期
251         clone.setMinimalDaysInFirstWeek(7);
252         int m2 = clone.getMinimalDaysInFirstWeek();
253         int index2 = clone.get(Calendar.WEEK_OF_YEAR);
254 
255         // 設置“每周的第一天是星期幾”。
256         clone.setFirstDayOfWeek(Calendar.WEDNESDAY);
257         // 獲取“每周的第一天是星期幾”。
258         int firstdayOfWeek = clone.getFirstDayOfWeek();
259 
260         System.out.printf("\ntestOtherAPIs: firstdayOfWeek=%s, [minimalDay, WeekOfYear]={(%s, %s), (%s, %s)} %s\n",
261                firstdayOfWeek, m1, index1, m2, index2, clone.getTime());
262     }
263 }

 

OK。今天先到此為止!

明天再繼續總結。

 


更多內容

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(1) Calendar

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(2) 自己封裝的Calendar接口

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(3) Date

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(4) DateFormat

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(5) SimpleDateFormat

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(6) Locale

Java Calendar,Date,DateFormat,TimeZone,Locale等時間相關內容的認知和使用(7) TimeZone


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM