让时间处理简单化 【第三方扩展类库org.apache.commons.lang.time】

时间:2023-04-27 10:58:08

JAVA的时间日期处理一直是一个比较复杂的问题,大多数程序员都不能很轻松的来处理这些问题。首先Java中关于时间的类,从 JDK 1.1 开始,Date的作用很有限,相应的功能已由Calendar与DateFormat代替。使用Calendar类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。

Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

TimeZone : 表示时区偏移量。

Locale : 表示了特定的地理、政治和文化地区。需要 Locale 来执行其任务的操作称为语言环境敏感的操作,它使用 Locale 为用户量身定制信息。

SimpleDateFormat : 主要是用来格式化Date,用过之后就会发现,它其实不完善,对Calendar提供的支持很少.

经历几个项目发现apache提供的第三方扩展类库,org.apache.commons.lang.time包比较好用,可以将程序中时间处理变的简单一点,提高你的开发效率,下面介绍下常用的方法和具体使用。

org.apache.commons.lang.time 包括以下几个类:

a. DateFormatUtils        【格式化Calendar与Date并依赖于 FastDateFormat】

b. DateUtils                  【围绕Calendar与Date的实用方法】

c. DurationFormatUtils 【毫秒数格式化Calendar与Date】

d. FastDateFormat        【线程安全的SimpleDateFormat】

e. StopWatch               【提供一个方便的定时的API 】

1. DateFormatUtils 和 FastDateFormat

DateFormatUtils相对来说比较简单,它的方法全部都是static的,所以不需要用构造器创建新的实例,但它构造器却是public的,这并不是说我们应该在程序中使用它,官方文档已说明,它是为了与其它工具的集成的准备的。它包括的方法主要就是format。主要用途就是根据传入的pattern格式化Date或Calendar。也可以有一些附加的参数,如Locale,TimeZone。

函数定义:

format(java.util.Calendar calendar, java.lang.String pattern, java.util.TimeZone timeZone, java.util.Locale locale)

原来项目中是这样写的:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowDay = sdf.format(new Date());

现在可以这样写:

String nowDay = DateFormatUtils.format(nowDay,"yyyy-MM-dd HH:mm:ss");

DateFormatUtils预定义的日期格式有9种之多,基本能满足项目时间格式化的要求,当然也可以自己自定义格式:

  public static final FastDateFormat ISO_DATETIME_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
public static final FastDateFormat ISO_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd");
public static final FastDateFormat ISO_DATE_TIME_ZONE_FORMAT = FastDateFormat.getInstance("yyyy-MM-ddZZ");
public static final FastDateFormat ISO_TIME_FORMAT = FastDateFormat.getInstance("'T'HH:mm:ss");
public static final FastDateFormat ISO_TIME_TIME_ZONE_FORMAT = FastDateFormat.getInstance("'T'HH:mm:ssZZ");
public static final FastDateFormat ISO_TIME_NO_T_FORMAT = FastDateFormat.getInstance("HH:mm:ss");
public static final FastDateFormat ISO_TIME_NO_T_TIME_ZONE_FORMAT = FastDateFormat.getInstance("HH:mm:ssZZ");

所以上面的也可以这样写:

DateFormatUtils.ISO_DATETIME_FORMAT.format(date);

DateFormatUtils 预定义的日期格式,通过公有的静态final字段来暴露这些常量简化了类的使用,但是却有一些地方是需要注意的,在这方面FastDateFormat做得还是相当不错的。要使final字段真正的不可变,

a. 这些字段是基本类型的值(int,String等),

b. 指向一个不可变对象的引用。

c. 需要注意的特殊情况是,长度为零的数组总是可变的,要么通过不可变的Collection来包装,

要么使数组变成私有的,并添加一个公有的方法,返回一个数组的备份,这在effective java上的Item 15上有详细的介绍。上面的final 字段代表一个不可变的FastDateFormat,然而要让FastDateFormat字段真正的不可变,

FastDateFormat内部必须遵循相应的规则才可以。

a. 不要提供能修改对象状态的方法

b. 确保类不会被继承

c. 让所有字段都成为static final字段

d. 确保所有可变的组件不能被访问

详细介绍可参考Effective java第二版的Item 15。仔细查看,会发现FastDateFormat都遵循这些规则,而Effective java 2rd 的出版也在commons-lang 2.4 开发之后,这说明 commons-lang的代码质量还是相当值得肯定的。
     其实DateFormateUtils内部细节实现完全依靠FastDateFormat,DateFormateUtils只是把一些常用的格式化功能单独组织起来,让日期时间的使用变得简单,毕竟大多数时候用户查看API时,如果有太多的方法,会给他们纷繁复杂的感觉。如果需要更加强大灵活的日期格式化功能,可以直接使用FastDateFormat,FastDateFormat这个类编写比较复杂,它有自己的一套解析规则,同时又使用了DateFormat类的一些规则,如Pattern。与SimpleDateFormat不同,
FastDateFormat是线程安全,所以这个类在多线程的服务环境中特别有用。
      虽然它们都继承自java.text.Format,其实FastDateFormat相当于DateFormat与SimpleDateFormat的合并,只是功能更强大而已。如果是从DateFormat迁移到FastDateFormat的话,还是有一些地方需要注意的,
比如,Date(String date)被DateFormat.parse(String s) 取代了,仔细查看,FastDateFormat并没有类似的方法,其实准确的说,把日期解析放在DateFormat本身就不太合理,不看文档的话,大多数人都会认为Date应该提供该功能。所以commons-lang把它放在了DateUtils 类中,对应方法则更加的强大:

eg:

public static Date parseDate(String str, String parsePatterns[]);
String partterns[] = new String[5];
partterns[0] = "yyyy-MM-dd";
DateUtils.parseDate("2015-09-28", partterns);

因parsePatterns可以包括多种pattern,只要满足其中的一种即可。如果使用SimpleDateFormat,则先要通过SimpleDateFormat(String str)创建实例,然后通过applyPattern(String pattern)来变换不同的pattern。

2. DateUtils

DateUtils提供了很多很方便的功能,减轻了使用Date的复杂性。把原来需用Calendar才能完成的功能统一集中了起来,也就是说没有对应的CalendarUtils类。在JDK中,Date与Calendar概念本身就有些混淆,只是为了保持兼容性才引入的Calendar。相对于Calendar提供的方法,DateUtils提供了更加合理的方法,

对时间的单个字段操作变得更加的容易。如需要修改时间Date的某个字段,必须先获得Date对象实例,再传入Calendar,才能修改,如:

eg:

//方法已经过时了
public static Date add(Date date, int calendarField, int amount) {
if (date == null) {
throw new IllegalArgumentException("The date must not be null");
}
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(calendarField, amount);
return c.getTime();
}

在这方面commons-lang的确做得很完善,现在可以这样写:

eg:

 1     /**
2 * @Description: 给当前日期的特定字段,进行增量设置,0为当前,1 为 +1,-1 -1 4 addYears(new Date(), 1) 输出2016
5 */
6 public static Date addYears(Date date, int amount)
10 public static Date addMonths(Date date, int amount)
14 public static Date addWeeks(Date date, int amount) 18 public static Date addDays(Date date, int amount)
22 public static Date addHours(Date date, int amount)
26 public static Date addMinutes(Date date, int amount)
30 public static Date addSeconds(Date date, int amount)
34 public static Date addMilliseconds(Date date, int amount)
38 public static Date add(Date date, int calendarField, int amount) {
39 if (date == null) {
40 throw new IllegalArgumentException("The date must not be null");
41 }
42 Calendar c = Calendar.getInstance();
43 c.setTime(date);
44 c.add(calendarField, amount);
45 return c.getTime();
46 }
47 /**
48 * @Description: 全量设置日期特定字段,
49 * @date:2015年9月18日上午11:41:35
50 * eg: setYears(new Date(), 2015)
51 */
52 public static Date setYears(Date date, int amount)
56 public static Date setMonths(Date date, int amount) 60 public static Date setDays(Date date, int amount)
64 public static Date setHours(Date date, int amount)
68 public static Date setMinutes(Date date, int amount)
72 public static Date setSeconds(Date date, int amount)
76 public static Date setMilliseconds(Date date, int amount)
80 private static Date set(Date date, int calendarField, int amount) {
81 if (date == null) {
82 throw new IllegalArgumentException("The date must not be null");
83 }
84 Calendar c = Calendar.getInstance();
85 c.setLenient(false);
86 c.setTime(date);
87 c.set(calendarField, amount);
88 return c.getTime();
89 }

方法名也非常的直观,使用也更加方便了。但有一些方法不是很好理解,就像下面这个函数:

eg:

 public static long getFragmentInSeconds(Date date,int fragment)

 这个方法的作用是:返回一个指定时间的秒数。关键的是参数fragment,它的作用非常重要。它的值必须是Calendar的时间常量字段。如Calendar.MONTH ,

 需要注意的是,小时必须用24小时制的,即Calendar.HOUR_OF_DAY ,而不能用Calendar.HOUR字段。
/*
* 如现在是2014-10-23 13:27:00,那么
* DateUtils.getFragmentInDays(new Date(), Calendar.MONTH)返回23,表示从当月起已经过去23天了,
* DateUtils.getFragmentInDays(new Date(), Calendar.YEAR)返回296,表示从当年起已经过去296天了,
* DateUtils.getFragmentInHours(new Date(), Calendar.DATE)返回13,表示从今天起已经过去13个小时了
*/

当然在DateUtils也有一些一看就知道是做什么的函数,我在这里列一下,不具体给例子了:

@Description: 判断是否是同一天
public static boolean isSameDay(Calendar cal1, Calendar cal2) @Description: Date 转 Calendar
public static Calendar toCalendar(Date date); @Description: 从日期特定字段开始四舍五入
public static Calendar round(Calendar date, int field); * @Description: 格式化截取日期,从给定字段开始
public static Date truncate(Date date, int field); @Description: 从日期特定字段开始向上舍入
public static Date ceiling(Date date, int field); eg :
假设当前时间 2015-09-14 ceiling(new Date(),Calendar.MONTH); 输出 2015-10-01 round(new Date(), Calendar.MONTH); 输出 2015-09-01 假设当前时间 2015-09-16,超过半月,上面输出结果是一致的 @Description: 比较日历对应字段是否相等
public static boolean truncatedEquals(Calendar cal1, Calendar cal2, int field);

3. DurationFormatUtils 和 StopWatch

相对于这些增强已有功能的类,还有一些对常用功能进行补充的类,如DurationFormatUtils ,这个类主要的作用就是处理时间的片断,主要包括两种方法:formatDuration和formatPeriod。如:

eg:

 formatDuration(long durationMillis, java.lang.String format)

 通过传入一个毫秒数与日期格式(如:yyyy-MM-dd HH:mm:ss),它会返回一个对应日期的字符串形式。

 当然Date类本身有一个与这类似的方法,即Date(String s)方法,用于创建一个Date实例,但它只是创建一个Date实例,如果要转换成相应的String,还要经过一些步骤才行。

 需要注意的是,此日期片断能表示的最大单位为天,用法也很简单:
String pattern = "yyyy-MM-dd HH:mm:ss";
long durationMillis = (10+20*60+13*3600+4*24*3600) * 1000;
String formatDate = DurationFormatUtils.formatDuration(durationMillis, pattern);
System.out.println(formatDate);

需要注意的是日期格式的小时必须用HH,而不能用hh 。
     formatPeriod方法用于计算两个时间之间的片断,然后转化成相应的日期字符串类型,即能表示的最大单位

 1 public static java.lang.String formatPeriod(long startMillis, long endMillis, java.lang.String format)
2
3
4 String[] parsePatterns = {"yyyy-MM-dd HH:mm:ss"};
5 String str = "2009-09-29 15:30:12";
6 String str2 = "2010-09-30 15:40:18";
7 Date date = DateUtils.parseDate(str, parsePatterns);
8 Date date2 = DateUtils.parseDate(str2, parsePatterns);
9 long durationMillis = DateUtils.getFragmentInMilliseconds(date, Calendar.YEAR);
10 long durationMillis2 =DateUtils.getFragmentInMilliseconds(date2,Calendar.YEAR);
11
12 String s = DurationFormatUtils.formatPeriod(durationMillis, durationMillis2,"yyyy-MM-dd HH:mm:ss")
13
14 其中s的值为:0000-00-01 00:10:06

StopWatch 是一个计时器,具体举个简单的例子:

         StopWatch stWatch = new StopWatch();
stWatch.start();
/**
* 从周日开始,获取一周的 Calendar 对象
*/
Iterator<?> itr = iterator(new Date(),RANGE_MONTH_MONDAY);
while (itr.hasNext()) {
Calendar gCal = (Calendar) itr.next();
System.out.println(formatDateObject(gCal.getTime(),"yyyy-MM-dd HH:mm:ss"));
}
stWatch.stop();
System.out.println("花费时间 >>" + stWatch.getTime() + ",毫秒");