项目当中,我们经常会涉及到对时间的处理。Date 类最主要的作用就是获得当前时间,同时这个类里面也具有设置时间以及一些其他的功能,但是由于本身设计的问题,这些方法却遭到众多批评,不建议使用,而是更推荐使用 Calendar 类进行时间和日期的处理。

原理简介

Java中提供了Calendar这个专门用于对日期进行操作的类,那么这个类有什么特殊的地方呢,首先我们来看Calendar的声明:

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

该类被abstract所修饰,说明不能通过new的方式来获得实例,对此,Calendar提供了一个类方法getInstance,以获得此类型的一个通用的对象,getInstance方法返回一个Calendar对象(该对象为Calendar的子类对象),其日历字段已由当前日期和时间初始化:

Calendar nowTime = Calendar.getInstance();

之所以说返回的是Calendar的子类对象,是因为每个国家地区都有自己的一套日历算法,比如西方国家的第一个星期大部分为星期日,而中国则为星期一。我们来看看getInstance方法获取实例的源码:

public static Calendar getInstance(){
	Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
	cal.sharedZone = true;
	return cal;
}

其中参数TimeZone.getDefaultRef()代表的就是时区,createCalendar方法就是根据不同国家地区返回对应的日期子类。

常用方法

为了更加便捷的对日期进行操作,Calendar类对YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。

下面看看Calendar常用的方法,我们分三大类进行总结:

1. 获取时间
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class CalenderTest {
	//获取时间:以目前系统时间 :2019:01:27 16:27:27 为例
	@Test
	public void testGetCalender() {
		Calendar cal = Calendar.getInstance();    							//Calendar类的实例化
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");	//时间的格式化

		//当前系统时间
		Date date = cal.getTime();								//date=Sun Jan 27 16:27:27 CST 2019
		String nowTime = sdf.format(cal.getTime());				//nowTime=2019-01-27 16:27:27

		//当前年     
		int year = cal.get(Calendar.YEAR);						//year=2019

		//当前月 Calendar.MONTH从0开始 ,使用时通常会+1  
		int month = cal.get(Calendar.MONTH) + 1;				//month=1

		//当前日:两种方法等价
		int day_of_month = cal.get(Calendar.DAY_OF_MONTH);		//day_of_month=27   
		int day = cal.get(Calendar.DATE);  						//day=27

		//获取当月day的最大值!!
		int max_day_of_month = cal.getActualMaximum(Calendar.DAY_OF_MONTH);	//max_day_of_month=31

		//当前时钟:24小时制
		int hour24 = cal.get(Calendar.HOUR_OF_DAY);				//hour24=16

		//当前时钟:12小时制     
		int hour12 = cal.get(Calendar.HOUR); 					//hour12=4

		//当前:分钟     
		int minute = cal.get(Calendar.MINUTE);   				//minute=27

		//当前秒     
		int second = cal.get(Calendar.SECOND);  				//second=27

		//星期几:用数字(1~7)表示(星期日~星期六),使用时通常会-1
		int day_of_week = cal.get(Calendar.DAY_OF_WEEK) - 1;	//day_of_week=0

		//上午-0;下午-1     
		int amOrPm = cal.get(Calendar.AM_PM);  					//amOrPm=1

		//当前年的第几周
		int week_of_year = cal.get(Calendar.WEEK_OF_YEAR);		//week_of_year=5

		//当前月的星期数
		int week_of_month = cal.get(Calendar.WEEK_OF_MONTH);	//week_of_month=5

		//当前月中的第几个星期     
		int day_of_week_in_month = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); //day_of_week_in_month=4

		//当前年的第几天     
		int day_of_year = cal.get(Calendar.DAY_OF_YEAR);		//day_of_year=27
	}
}
2. 设置时间
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class CalenderTest {
	//设置时间:以目前系统时间 :2019:01:27 16:27:27 为例
	@Test
	public void testSetCalender() {
		Calendar cal = Calendar.getInstance();    							//Calendar类的实例化
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");	//时间的格式化

		//1.当前系统时间
		Date date = cal.getTime();							//data=Sun Jan 27 16:27:27 CST 2019
		String nowTime = sdf.format(cal.getTime());			//nowTime=2019-01-27 16:27:27

		//2.设置时间:不设置项,默认Calendar已经存在日期
		cal.set(Calendar.HOUR_OF_DAY, 11);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 30);
		String date1 = sdf.format(cal.getTime());			//time=2019-01-27 11:30:30

		//3.设置时间:全部设置,则输出自定义时间
		cal.set(Calendar.YEAR, 2018);
		cal.set(Calendar.MARCH, 7);
		cal.set(Calendar.DATE, 21);
		String date2 = sdf.format(cal.getTime());			//time=2018-08-21 11:30:30

		//4.时间重置:Date date = new date();
		String newDate1 = sdf.format(cal.getTime());		//newDate1=2018-08-21 11:30:30
		String newDate2 = sdf.format(new Date());			//newDate2=2019:01:27 16:27:27
	}
}
3. 时间运算
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class CalenderTest {
	//时间运算:以目前系统时间 :2019:01:27 16:27:27 为例
	@Test
	public void testAddCalender() {
		Calendar cal = Calendar.getInstance();    							//Calendar类的实例化
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");	//时间的格式化
		
		String nowTime = sdf.format(cal.getTime());		//nowTime=2019-01-27 16:27:27
		
		cal.add(Calendar.YEAR, 1);					
		String time1 = sdf.format(cal.getTime());		//年:2020-01-27 16:27:27

		cal.add(Calendar.MONTH, 2);					
		String time2 = sdf.format(cal.getTime());		//月:2020-03-27 16:27:27

		cal.add(Calendar.DATE, 5);					
		String time3 = sdf.format(cal.getTime());		//日:2020-04-01 16:27:27

		cal.add(Calendar.HOUR_OF_DAY, -1);			
		String time4 = sdf.format(cal.getTime());		//时:2020-04-01 15:27:27

		cal.add(Calendar.MINUTE, 1);				
		String time5 = sdf.format(cal.getTime());		//分:2020-04-01 15:28:27

		cal.add(Calendar.SECOND, 1);				
		String time6 = sdf.format(cal.getTime());		//秒:2020-04-01 15:27:28
	}
}

注意事项

1. Calendar的容错性

我们直接举例说明:
调用Calendar对象的set()方法改变指定时间字段的值时,有可能传入一个不合法的参数,例如:为MONTH字段设置13,这将会导致什么结果呢?

//Calendar:容错性测试
	@Test
	public void testCalender() {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Calendar calendar = Calendar.getInstance();

		// Calendar容错性:true-打开;false-关闭
		calendar.setLenient(false);

		calendar.set(Calendar.MONTH, 13);
		System.out.println("A="+simpleDateFormat.format(calendar.getTime()));	// A标记

	}

当打开calendar.setLenient(true)时(默认):

  • A处代码可以正常运行,因为将MONTH字段设置为13,将会使YEAR字段计算加1;
  • 输出结果:A=2020-02-12 13:59:33。

当关闭calendar.setLenient(false)时:

  • A处将会导致运行异常,因为设置的MONTH字段超出了所允许的范围。

关键在于程序中注释有“容错性”的代码行:
Calendar提供一个setLenient用于设置它的容错性,Calendar默认支持比较好的容错性,通过setLenient(false)关闭Calendar的容错性,让它进行严格的参数检查。

Calendar有两种解释日历字段的模式:lenient模式,non-lenient模式
当处于lenient模式时,每个字段都可接受超出范围的值;
当处于non-lenient模式时,每个字段都进行严格的参数检查,不接收超出字段的值。

2. set()的延迟性

set(int field, int value)方法日历的某个字段修改为value值,此外它还设置了一个内部成员变量,以指示日历字段field已经被更改。尽管日历字段field是立即更改的,但该Calendar所代表的事件却不会立即修改,直到下次调用get()、getTime()、getTimeInMillis()、add()或roll()时才会重新计算日历的时间。

这被称为set()方法的延迟修改,采用延迟修改的优势是多次调用set()不会触发多次不必要的运算,下面来看一个例子:

// set():延迟修改性
	@Test
	public void testCalender1() {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Calendar calendar = Calendar.getInstance();

        calendar.set(2019, 2, 31);

        calendar.set(Calendar.MONTH, 8);

 		//包含getTime()方法,注释掉此行代码,运行结果截然不同
        System.out.println("A="+simpleDateFormat.format(calendar.getTime())); // A标记

        calendar.set(Calendar.DATE, 5);

        System.out.println("B="+simpleDateFormat.format(calendar.getTime())); // B标记
	}

打印结果:

  • 注释掉 A行 之前:(set一次,get一次,一步一运算)
    A=2019-10-01 13:27:55
    B=2019-10-05 13:27:55
  • 注释掉 A行 之后:(set两次,get一次,最后再运算)
    B=2019-09-05 13:33:21

小结

  1. Calendar 类区分不同的时区输出时间格式;
  2. Calendar 类的计算优势非常明显,不需要考虑每个月有多少天,非常便利。举个例子:如果要计算昨天的日期,你要考虑昨天是不是某月的最后一天,某年的最后一天,要考虑平年、闰年,考虑上个月是31天,30天,29天还是28天…但是Calendar类省去了你所有的顾虑,直接减1天就可以输出正确的日期了;
  3. Calendar 类使用时要注意它的容错性和延迟性。