通过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;
}
}
四、总结
调试很重要,通过对照法查看异常结果出现的情况,并进行一步步分析。
要记住计算机是不会骗人的,出现预期之外的结果一定是代码有问题。
参考资料: