Java实用经验总结--日期、数字篇 1. 日期部分 对于像日期、时间和钱这样的对象来说,不同的国家、地区都有不同的显示格式。即便是同一地区,也可能存在差异。但是在不考虑国家化,时间格式相对固定的情形下,对于时间的处理还是相对比较简单的。在我最近所作的一个小程序里面,遇到了一些与日期有关的且不考虑国际化和复杂格式的问题。例如如何求两个日期所差的天数,所差的月数;将日期类转化为规定格式的字符串,将规定格式的日期字符串转成相应的日期类等等。下面我就以源码的形式逐一介绍上面提到的问题,需要注意的是这些代码都源于我做的一个名为DateUtil的类,其中独立的变量都是其中的成员变量,函数都是其成员函数: 1.1.成员变量简介: 要想对日期进行格式化首先就需要一个DateFormat类的实例,如果没有特殊需求的话,SimpleDateFormat类就是一个不错的选择,它可以将日期类格式化为在其构造函数中指定的日期格式。例如,如果我们想要将日期格式化为类似于2007-07-25这样的格式,我们就可以如下定义: /**以yyyy-MM-dd的形式显示格式 **/ SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat sFullFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 这里需要注意的是指定的日期格式一定要是”yyyy-MM-dd”,而不能是”YYYY-MM-DD”,否则的就不能正常显示。对于这个问题我没有深究,如果有对这个问题有研究的朋友欢迎留言。 下面的两个成员变量分别是日期分隔符字符串和字符串分隔器,专门用来解析字符串格式的日期。 /** *程序中主要的日期分隔符为"-"和"/",且日期序列为“年/月/日”型,其内容缺一不可 * 例如:09/02/02或2009-02-02 **/ publicstaticfinal String DATE_SEPARATOR ="-/"; /**作日期分析之用*/ static StringTokenizer sToken; 1.2.取得两个日期之间所差天数的方法 鉴于java.util.Date类的绝大多数方法都不建议使用了,所以我也就不能够利用Date里面方便的getYear(),getMonth(),getDay()方法来计算日期差了—现在的JRE都可以自动升级,谁知道哪天SUN突然把Date这些API去掉了,那我就欲哭无泪了。不过话又说回来就算是能够使用这些方法,我们似乎也不太好算,因为每个月的日期数都不一样,如果单纯用两个日期的年月日信息来计算日期差还真是有些麻烦。基于以上两点原因,我在我的程序里采用了GregorianCalendar做为日期计算的主力军。这个类有一些很实用的方法,如get(int),这个方法可以获得当前日期类的各项日期指标。比如我们有一个日期类名为gcDate,要获取它所在的年,月,日,至需要这么做:gcDate.get(Calendar.YEAR), gcDate.get(Calendar.MONTH)以及gcDate.get(Calendar.DATE)。不过由于使用这种方法获得的日期和月份都是日期类所指定的年份的,所以如果我们知道两个日期在同一年份的话才能使用gcDate1.get(Calendar.DATE)- gcDate2.get(Calendar.DATE)来获得日期差,否则就不能这么做。 所以如果想要获得不在同一年份的日期差的话就需要用到另一个有用的方法:GregorianCalendar.add(int, int),这个方法可以让我们在日期类指定的日期指标(如年,月,日等)上加上一个数字,这个数字可以是正数也可以为负数。 其中第一个参数指定所要增加的指标,第二个参数指定增加的数量。例如我们调用gcDate.add(Calendar.DATE,1)的话,如果gcDate原来代表的时间为2007-07-24,那么调用之后就变成2007-07-25了。于是我们就可以这样计算日期差:让日期比较小的那个日期用add函数逐渐“逼近”那个较大的日期,直到两个日期完全相等为止。计数器中包含的数量即为两个日期的差值。 下面我给出了多个的计算日期差的方法,主要包含两个版本,一个版本参数为格式化字符串,另一个版本参数为GregorianCalendar。功能包括计算“今天与未来的某一天之间的日期差”和“给定两个日期的日期差”。主要的计算集中在最后一个daysBetween函数上,该函数接受两个GregorianCalendar类作为参数,并可以计算出两个日期之间的日期差,如果用户给出的较大的日期和较小的日期顺序颠倒的话,该函数会返回一个负数值。 /** *返回未来的某一天和今天所差的日期数 *注意,这里要clone一个新的日期以免对原始日期类造成的修改。 *而在daysBetween(GregorianCalendarpFormer,GregorianCalendarpLatter)就 *直接处理而不进行clone动作,因为这里已经做了:) **/ publicstaticint daysBetween(GregorianCalendar pFurtherDay){ GregorianCalendar vToday = new GregorianCalendar(); GregorianCalendar vFurtherDay = (GregorianCalendar) pFurtherDay.clone(); return daysBetween(vToday,vFurtherDay); } /**上面函数的String版本 **/ publicstaticint daysBetween(String pFurtherDayStr){ GregorianCalendar vFurtherDay = DateUtil.parse2Cal(pFurtherDayStr); GregorianCalendar vToday = new GregorianCalendar(); return daysBetween(vToday,vFurtherDay); } /**返回较晚的时间(latter)与较早的时间(former)所差的天数**/ publicstaticint daysBetween(String pFormerStr,String pLatterStr){ GregorianCalendar pFormer = DateUtil.parse2Cal(pFormerStr); GregorianCalendar pLatter = DateUtil.parse2Cal(pLatterStr); return daysBetween(pFormer,pLatter); } /**返回较晚的时间(latter)与较早的时间(former)所差的天数**/ publicstaticint daysBetween(GregorianCalendar pFormer,GregorianCalendar pLatter){ GregorianCalendar vFormer = pFormer,vLatter = pLatter; boolean vPositive = true; if( pFormer.before(pLatter) ){ vFormer = pFormer; vLatter = pLatter; }else{ vFormer = pLatter; vLatter = pFormer; vPositive = false; } vFormer.set(Calendar.MILLISECOND,0); vFormer.set(Calendar.SECOND,0); vFormer.set(Calendar.MINUTE,0); vFormer.set(Calendar.HOUR_OF_DAY,0); vLatter.set(Calendar.MILLISECOND,0); vLatter.set(Calendar.SECOND,0); vLatter.set(Calendar.MINUTE,0); vLatter.set(Calendar.HOUR_OF_DAY,0); int vCounter = 0; while(vFormer.before(vLatter)){ vFormer.add(Calendar.DATE, 1); vCounter++; } if( vPositive) return vCounter; else return -vCounter; } 1.3.两个日期的月份差 获得两个日期的月份差的方法与获得日期差基本一致。但需要注意的是,计算月份差不能简单用before()来进行计算。还需要考虑到他们的年份及月份是否同时相等,只有在这种情况下,才能获得月份差的正确数值。下面同样给出了月份差的两个版本的多个函数,与日期差基本一致,这里就不再赘述。 /** *给定两个时间相差的月数 */ //本月和未来一个月的月份差 publicstaticint monthsBetween(GregorianCalendar pFurtherMonth){ GregorianCalendar vToday = new GregorianCalendar(); GregorianCalendar vFurtherMonth = (GregorianCalendar) pFurtherMonth.clone(); return monthsBetween(vToday,vFurtherMonth); } /**给定月分和本月的月份差**/ publicstaticint monthsBetween(String pFurtherMonth){ GregorianCalendar vToday = new GregorianCalendar(); GregorianCalendar vFurtherMonth = DateUtil.parse2Cal(pFurtherMonth); return monthsBetween(vToday,vFurtherMonth); } /**给定两个时间相差的月数,String版**/ publicstaticint monthsBetween(String pFormerStr,String pLatterStr){ GregorianCalendar vFormer = DateUtil.parse2Cal(pFormerStr); GregorianCalendar vLatter = DateUtil.parse2Cal(pLatterStr); return monthsBetween(vFormer,vLatter); } publicstaticint monthsBetween(GregorianCalendar pFormer,GregorianCalendar pLatter){ GregorianCalendar vFormer = pFormer,vLatter = pLatter; boolean vPositive = true; if( pFormer.before(pLatter) ){ vFormer = pFormer; vLatter = pLatter; }else{ vFormer = pLatter; vLatter = pFormer; vPositive = false; } int vCounter = 0; while(vFormer.get(vFormer.YEAR) != vLatter.get(vLatter.YEAR) || vFormer.get(vFormer.MONTH) != vLatter.get(vLatter.MONTH)){ vFormer.add(Calendar.MONTH, 1); vCounter++; } if( vPositive) return vCounter; else return -vCounter; } 1.4.格式转换 将日期类转换成制定格式的字符串只需要调用DateFormat.format()即可。而反过来就比较麻烦,我们需要对字符串进行分析,找出其年,月,日的值分别为何,然后再用GregorianCalendar(vYear,vMonth,vDayOfMonth)构建一个新的日期类。 /** 将日期变为字符串格式 **/ publicstatic String format(GregorianCalendar pCal){ returnsDateFormat.format(pCal.getTime()); } publicstatic String format(Date pDate){ returnsDateFormat.format(pDate); } publicstatic String fullFormat(Date pDate){ returnsFullFormat.format(pDate); } /**将字符串格式的日期转换为Calender**/ publicstatic GregorianCalendar parse2Cal(String pDateStr){ sToken = new StringTokenizer(pDateStr,DATE_SEPARATOR); int vYear = Integer.parseInt(sToken.nextToken()); //GregorianCalendar的月份是从0开始算起的,变态!! int vMonth = Integer.parseInt(sToken.nextToken())-1; int vDayOfMonth = Integer.parseInt(sToken.nextToken()); returnnew GregorianCalendar(vYear,vMonth,vDayOfMonth); } /**将字符串类型的日期(yyyy-MM-dd)转换成Date**/ publicstatic Date parse2Date(String pDate){ try { returnsDateFormat.parse(pDate); }catch(ParseException ex) { returnnull; } } 1.5.其他 这部分主要是一些常用方法的包装,包括获得今天是本月的第几天,本月是本年的第几个月,获得给定字符串日期的月份数,以及获得下面这个月份区间[本月,本月+pZoneSize]里的月份列表。即如果本月为6月,pZoneSize=6,则获得的月份列表应该为{6,7,8,9,10,11,12} /**返回今天是本月的第几天**/ publicstaticint dayOfMonthOfToday(){ GregorianCalendar vTodayCal = new GregorianCalendar(); return vTodayCal.get(vTodayCal.DAY_OF_MONTH); } /**返回本月是本年的第几个月**/ publicstaticint monthOfYear(){ GregorianCalendar vTodayCal = new GregorianCalendar(); return vTodayCal.get(vTodayCal.MONTH)+1; } //返回给定日期的月份 publicstatic String getMonth(String pFormattedDate){ StringTokenizer vSt = new StringTokenizer(pFormattedDate,"-"); vSt.nextToken();//跳过年份 int val = Integer.parseInt(vSt.nextToken()); return val+""; } /**获得从本月开始到本月+pZoneSize区间内的月数**/ publicstatic String[] monthList(int pZoneSize){ String[] vMonthList = new String[pZoneSize]; GregorianCalendar vTodayCal = new GregorianCalendar(); for(int i = 0; i < pZoneSize; i++){ vMonthList[i] = String.valueOf(vTodayCal.get(vTodayCal.MONTH)+1); vTodayCal.roll(vTodayCal.MONTH,true); } return vMonthList; } 2. 数字部分: 这一部分我的工作比较简单,首先是格式化给定的Double变量,让其只显示两位小数。其次是由于我所做的程序付款额都是以万作为单位的,所以在必要的时候将其转为以万为单位的,带有两位小数的数字。由于比较简单,这里就不多说,各位看代码就好: /**仅显示小数点后两位的Formater**/ publicstatic DecimalFormat formatter = new DecimalFormat("####.##"); /**将给定的数字变成小数点后两位的字符串**/ publicstatic String format(double pSrcVal){ returnformatter.format(pSrcVal); } /**将原始数据除以10000所得的结果**/ publicstatic String fromat2Myriad(double pSrcVal){ returnformatter.format(pSrcVal/10000.0); } /**将原始数据除以10000所得的结果**/ publicstatic String fromat2Myriad(String pSrcVal){ returnformatter.format(Double.parseDouble(pSrcVal)/10000.0); } /**将给定的数字保留两位小数返回**/ publicstaticdouble format2Double(double pSrcVal){ return (double)Math.round(pSrcVal*100)/100.0; }