介绍Calendar操作日期

通过Calendar可以操作更改当前返回年月日-时分秒数值,虽然借助Date也能更改各类日期属性值,但是由于Date中更改日期时间的方法涉及到不推荐使用,所以我们最好还是通过Calendar来进行操作。
而早上我遇到的bug就是在使用Calendar出现的一个共用Calendar变量域所引发的日期混乱问题,乍一看觉得很奇怪,但是通过调试还是发现了根源问题所在。

一、Calendar

在介绍我早上遇到的bug之前,我觉得还是有必要介绍一下Calendar-日历类。
实际业务操作过程中,一般情况下使用Date-时间类比较多,Calendar-日历类接触的比较少,但是由于开发过程中需要借助Calendar实现基本的日历操作,并且再加上Date的各类set时间的方法被弃用了,所以开始逐步拥入Calendar的怀抱。
常规获取一个Date类引用,通过new得到:

Date date = new Date();

通过Calendar类获取Date:

// 首先获取Calendar
Calendar calendar = Calendar.getInstance();
// 通过getTime方法获取Date
Date date = calendar.getTime(); 

大势所趋,引用百度知道中关于Java中,为什么Date类被Calendar类取代了?这个问题中所答复的:Date类,可获取的变量比较少,可用性越来越少,而新的Calendar 类,可以获取很多时间方面变量,方便使用。
但是有一说一,除了需要更改时间日期之外需要优先采用Calendar类,而Date类目前还是我们实际开发过程中应用最频繁的一个时间类。

二、通过Calendar操作时间

例如,当我们回到2008年北京奥运会开幕式的那一刻:
注意月份的范围是0-11,即一月份是从0开始编号的。

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR,2008);
calendar.set(Calendar.MONTH,7);
calendar.set(Calendar.DAY_OF_MONTH,8);
calendar.set(Calendar.HOUR_OF_DAY,8);
calendar.set(Calendar.MINUTE,8);
calendar.set(Calendar.SECOND,8);
Date date = calendar.getTime();
// 打印结果:Mon Aug 08 08:08:08 CST 2008
System.out.println(date.toString());

三、使用Calendar遇到的问题

在简单地介绍完Calendar后,复现一下早上我遇到的bug。
首先我们团队有一个CalendarUtils工具类,其中大概的结构是这样的(主要罗列出现问题的两个方法):

public class CalendarUtils {

	private static final Logger logger = LoggerFactory.getLogger(CalendarUtils.class);

	static Calendar calendar = Calendar.getInstance();

	/**
	 * 功能描述:操作日期的增减
	 *
	 */
	public static Date doAddDay(Date date, Integer n) {
		if (null == date) {
			return null;
		}
		calendar.setTime(date);
		if (null != n) {
			calendar.add(Calendar.DAY_OF_MONTH, n);
		}
		return calendar.getTime();
	}

	/**
	 * 功能描述:例如 指定beginHour为8 endHour为11 表示当天的年月日8点到11点之间所有时间戳随机集包括分钟和秒数
	 */
	public static Date randomDate(int beginHour,int endHour){
		// 区分全局变量
		Calendar calendar = Calendar.getInstance();
	    // 判断输入是否符合规范
	    boolean isRight = beginHour > 0 && endHour > 0 && endHour > beginHour;
	    if (isRight){
            Random random = new Random();
			calendar.set(Calendar.HOUR_OF_DAY,beginHour+random.nextInt(endHour-beginHour));
			calendar.set(Calendar.MINUTE,random.nextInt(60));
			calendar.set(Calendar.SECOND,random.nextInt(60));
            Date result = calendar.getTime();
            return result;
        }else {
	        logger.error("开始小时数与结束小时数不符合规范!");
	        return null;
        }
	}
}

上面doAddDay()与randomDate()方法就是主要产生异常的所在,首先我们简单阅读一下以上两个方法,第一个方法是只改变年月日中的日期增减,例如在当天的日期中加1天,则调用示例为:doAddDay(new Date(),1);
而第二个方法是不改变年月日,但是改变时分秒,时钟为传入的beginHour-endHour之间的范围,分钟和秒数均为0-60的随机数,这样就保证了调用者返回当天一定时钟内的随机时间。
乍一看其实两个方法没有任何关联,而且两个方法是独立调用互不影响,其中唯一一个共用的对象引用是calendar。
我们在业务代码中for循环调用这个工具类中的randomDate(8,11)和doAddDay(new Date(),1)方法。出现了randomDate()方法出现返回日期不为当天的异常情况。

for(int i = 1; i<5; i++){
	CalendarUtils.randomDate(8,11);
	CalendarUtils.doAddDay(new Date(),1);
}

通过一步步地调试发现第一次循环中randomDate()返回的日期是正常的,第二次循环中randomDate()返回的日期就变成了明天。但是日期增加一天是doAddDay()的方法,与randomDate()并无关联,让人感到很疑惑。

我通过单独执行这两个方法得到的结果都是预期结果,但是只要同时执行上面两个方法就会出现意料之外的结果。
于是开始读代码,比较这两个方法中的关联点,开始注意到该工具类中的共用calendar引用,也就是前面提到的唯一一个共用的对象引用calendar。
问题就这样产生了,因为共用的calendar对象引用是共用的,所以第一次循环后,日期被加了1天,所以第二次循环中randomDate()方法中的日期自然就是加了1天后的日期,再执行doAddDay()方法由于重新传入一个new Date()对象,所以每一次都是当前实际日期加上1天的结果,也就符合了最开始出现异常执行结果的情况。
解决办法,将randomDate()方法中的Calendar引用做单独分离。即在方法域中单独获取一个Calendar引用:

public static Date randomDate(int beginHour,int endHour){
	// 区分全局变量
	Calendar localCalendar = Calendar.getInstance();
    // 判断输入是否符合规范
    boolean isRight = beginHour > 0 && endHour > 0 && endHour > beginHour;
    if (isRight){
           Random random = new Random();
		localCalendar.set(Calendar.HOUR_OF_DAY,beginHour+random.nextInt(endHour-beginHour));
		localCalendar.set(Calendar.MINUTE,random.nextInt(60));
		localCalendar.set(Calendar.SECOND,random.nextInt(60));
           Date result = localCalendar.getTime();
           return result;
       }else {
        logger.error("开始小时数与结束小时数不符合规范!");
        return null;
       }
}

四、总结

调试很重要,通过对照法查看异常结果出现的情况,并进行一步步分析。
要记住计算机是不会骗人的,出现预期之外的结果一定是代码有问题。

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值