• 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:
    	range is [%d - %d] actualRange is [%d - %d].  original=%d, set(%d)=%d, roll(%d)=%d, add(%d)=%d
    ",
     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("
    testComparatorAPIs: isEuqal12=%s, isEqual13=%s, isBefore=%s, isAfter=%s, icompare=%s
    ",
    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("
    testLenientAPIs: oriLenient=%s, curLenient=%s
    ",
    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("
    testTimeAPIs: date=%s, timezone=%s, millis=%s
    ",
    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("
    testOtherAPIs: firstdayOfWeek=%s, [minimalDay, WeekOfYear]={(%s, %s), (%s, %s)} %s
    ",
    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

  • 相关阅读:
    pyqt的常用知识点记录
    Ansys采用后处理list Rusult输出位移时负号原因导致不能分列的python解决方法
    matlab调用ANSYS进行分析
    Matlab采用load加载txt文件时显示:错误使用load,无法打开要输出的文件
    Matlab绘制单元,云图
    关于chol分解的置换向量的问题及正定对称稀疏矩阵的构造
    Pr中字幕模糊的问题
    Matlab中文乱码问题
    Qt信号与槽---基于vs2013+qt5
    Microsoft To Do-安卓-Windows-Mac
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3327482.html
Copyright © 2020-2023  润新知