0%

7.4.2 Calendar类

7.4.2 Calendar类

因为Date类在设计上存在一些缺陷,所以Java提供了Calendar类来更好地处理日期和时间Calendar是一个抽象类,它用于表示日历。

推荐使用公历

历史上有着许多种纪年方法,它们的差异实在太大了,为了统一计时,全世界通常选择最普及、最通用的日历:Gregorian Calendar,也就是日常介绍年份时常用的”公元几几年”。

公历GregorianCalendar类

Calendar类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法;但它本身不能直接实例化,程序只能创建Calendar子类的实例,Java本身提供了一个GregorianCalendar类,个代表格里高利日历的子类,它代表了通常所说的公历.

可以知己扩展Calendar类

当然,也可以创建自己的Calendar子类,然后将它作为Calendar对象使用(这就是多态)。在Internet上,也有对中国农历的实现。本章不会详细介绍如何扩展Calendar子类,读者可以查看上述Calendar的源码来学习。

通过getInstance方法创建Calendar实例

Calendar类是一个抽象类,所以不能使用构造器来创建Calendar对象。但它提供了几个静态getInstance()方法来获取Calendar对象,这些方法根据TimeZone, Locale类来获取特定的Calendar,如果不指定TimeZoneLocale,则使用默认的TimeZoneLocale来创建Calendar

Calendar和Date的相互转换

CalendarDate都是表示日期的工具类,它们直接可以自由转换,如下代码所示:

通过Calendar的getTime方法直接取出Date

通过Calendar实例的getTime方法即可取得Date对象:

1
2
3
4
//创建一个默认的 Calendar对象
Calendar calendar=Calendar.getInstance();
/从 Calendar对象中取出Date对象
Date date=calendar.getTime();

先创建Calendar实例,再通过setTime方法将Date设置到Calendar实例中

因为Calendar/GregorianCalendar没有构造函数可以接收Date对象,所以必须先获得一个calendar实例,然后调用其setTime(Date date)方法将Date对象中的时间传给Calendar实例.

1
2
3
//通过Date对象获得对应的Calendar对象
Calendar calendar=Calendar.getInstance();
calendar. setTime(new Date());

Calendar类常用方法

Calendar类提供了大量访问、修改日期时间的方法,常用方法如下:

方法 描述
void add(int field, int amount) 根据日历的规则,为给定的日历字段添加或减去指定的时间量。
int get(int field) 返回指定日历字段的值。
int getActualMaximum(int field) 返回指定日历字段可能拥有的最大值。例如月,最大值为11
int getActualMinimum(int field) 返回指定日历字段可能拥有的最小值。例如月,最小值为0。
void roll( int field, int amount) add()方法类似,区别在于加上amount后超过了该字段所能表示的最大范围时,不会向上一个字段进位
void set(int field, int value) 将给定的日历字段设置为给定值。
roid set(int year, int month, int date) 设置Calendar对象的年、月、日三个字段的值。
void set(int year, int month, int date, int hourOfDay, int minute, int second) 设置Calendar对象的年、月、日、时、分、秒6个字段的值。

上面的很多方法都需要一个int类型的field参数,fieldCalendar类的类变量,如:Calendar.YEARCalendar.MONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。
需要指出的是, Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8

如下程序示范了Calendar类的常规用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取今天的日历
Calendar c = Calendar.getInstance();
// 取出年
System.out.println(c.get(YEAR));
// 取出月份
System.out.println(c.get(MONTH));
// 取出日
System.out.println(c.get(DATE));
System.out.println("-------------------------");
// 分别设置年、月、日、小时、分钟、秒
c.set(2003, 10, 23, 12, 32, 23); // 2003-11-23 12:32:23
System.out.println(c.getTime());
// 将Calendar的年前推1年
c.add(YEAR, -1); // 2002-11-23 12:32:23
System.out.println(c.getTime());
System.out.println("-------------------------");
// 将Calendar的月前推8个月
c.roll(MONTH, -8); // 2002-03-23 12:32:23
System.out.println(c.getTime());

Calendar类还有如下几个注意点。

1. add与roll的区别

add方法

add(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。

  • 如果需要增加某字段的值,则让amount为正数;
  • 如果需要减少某字段的值,则让amount为负数即可

add方法的规则

add(int field, int amount)有如下两条规则。

  1. 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大.

    1
    2
    3
    4
    5
    6
    System.out.println("-------------------------");
    Calendar cal1 = Calendar.getInstance();
    cal1.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
    System.out.println(cal1.getTime());
    cal1.add(MONTH, 6); // 2003-8-23 => 2004-2-23
    System.out.println(cal1.getTime());
  2. 如果下一级字段也需要改变,那么该字段会修正到变化最小的值.

    1
    2
    3
    4
    5
    6
    System.out.println("-------------------------");
    Calendar cal2 = Calendar.getInstance();
    cal2.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
    // 因为进位到后月份改为2月,2月没有31日,自动变成29日
    cal2.add(MONTH, 6); // 2003-8-31 => 2004-2-29
    System.out.println(cal2.getTime());

对于上面的例子,8-31就会变成2-29。因为MONTH的下一级字段是DATE,从31到29改变最小。所以上面2003-8-31的MONTH字段增加6后,不是变成2004-3-2,而是变成2004-2-29。

roll方法的规则

roll()的规则与add()的处理规则不同:当被修改的字段超出它允许的范围时,上一级字段不会增大

1
2
3
4
5
6
System.out.println("-------------------------");
Calendar cal3 = Calendar.getInstance();
cal3.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
// MONTH字段“进位”,但YEAR字段并不增加
cal3.roll(MONTH, 6); // 2003-8-23 => 2003-2-23
System.out.println(cal3.getTime());

下一级字段的处理规则与add()方法相似:

1
2
3
4
5
6
7
System.out.println("-------------------------");
Calendar cal4 = Calendar.getInstance();
cal4.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
// MONTH字段“进位”后变成2,2月没有31日,
// YEAR字段不会改变,2003年2月只有28天
cal4.roll(MONTH, 6); // 2003-8-31 => 2003-2-28
System.out.println(cal4.getTime());

2. 设置Calendar的容错性

调用Calendar对象的set()方法来改变指定时间字段的值时,有可能传入一个不合法的参数

setLenient方法

Calendar提供了一个setLeniente(false)用于设置它的容错性, Calendar默认支持较好的容错性,通过setLenient(false)可以关闭Calendar的容错性,让它进行严格的参数检查。

两种解释日历的模式

Calendar有两种解释日历字段的模式:lenient模式和non-lenient模式。
Calendar处于lenient模式时,每个时间字段可接受超出它允许范围的值;
Calendar处于non-lenient模式时,如果为某个时间字段设置的值超出了它允许的取值范围,程序将会抛出异常。

例如为MONTH字段设置13,这将会导致怎样的后果呢?看如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.*;
import static java.util.Calendar.*;

public class LenientTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
// 结果是YEAR字段加1,MONTH字段为1(二月)
cal.set(MONTH, 13); // ①
System.out.println(cal.getTime());

// 关闭容错性
cal.setLenient(false);
// 导致运行时异常
cal.set(MONTH, 13); // ②
System.out.println(cal.getTime());
}
}

上面程序①②两处的代码完全相似,但它们运行的结果不一样:
①处代码可以正常运行,因为设置MONTH字段的值为13,将会导致YEAR字段加1;
②处代码将会导致运行时异常,因为设置的MONTH字段值超出了MONTH字段允许的范围。

3. set()方法延迟修改

set(field, value)方法将日历字段field更改为value,此外它还设置了一个内部成员变量,以指示日历字段field已经被更改。尽管日历字段field是立即更改的,但Calendar所代表的时间却不会立即修改,直到下次调用get()getTime()getTimeInMillis()add()roll()时才会重新计算日历的时间。这被称为set方法的延迟修改,采用延迟修改的优势是多次调用set()不会触发多次不必要的计算。

下面程序演示了set()方法延迟修改的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import java.util.*;
import static java.util.Calendar.*;

public class LazyTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.set(2003, 7, 31); // 2003-8-31
// 将月份设为9,但9月31日不存在。
// 如果立即修改,系统将会把cal自动调整到10月1日。
cal.set(MONTH, 8);

// 下面代码输出10月1日
System.out.println(cal.getTime()); //①

// 设置DATE字段为5
cal.set(DATE, 5); // ②
System.out.println(cal.getTime()); // ③
}
}

上面程序中创建了代表2003-8-31的Calendar对象,当把这个对象的MONTH字段加1后应该得到2003-10-1(因为9月没有31日),如果程序在①号代码处输出当前Calendar里的日期,也会看到输出2003-10-1,③号代码处将输出2003-10-5

如果程序将①处代码注释起来,因为Calendarset()方法具有延迟修改的特性,即调用set()方法后Calendar实际上并未计算真实的日期,它只是使用内部成员变量表记录MONTH字段被修改为8,接着程序设置DATE字段值为5,程序内部再次记录DATE字段为5—就是9月5日,因此看到③处输出2003-9-5。

原文链接: 7.4.2 Calendar类