参考:
简介: 任何企业应用程序都需要处理时间问题。应用程序需要知道当前的时间点和下一个时间点,有时它们还必须计算这两个时间点之间的路径。使用 JDK 完成这项任务将非常痛苦和繁琐。现在来看看 Joda Time,一个面向 Java™ 平台的易于使用的开源时间/日期库。正如您在本文中了解的那样,Joda-Time 轻松化解了处理日期和时间的痛苦和繁琐。
2000 年 1 月 1 日 0 时 0 分
使用 Joda,代码应该类似如下所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
以 Joda 的方式向某一个瞬间加上 90 天并输出结果
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(90).toString("YYYY/MM/dd HH:mm:ss.SSS"));输出:2000/03/31 00:00:00.000
直接将不同对象传递给 DateTime
的构造函数
// Use a Calendar
java.util.Calendar calendar = obtainCalendarSomehow();dateTime = new DateTime(calendar);// Use another Joda DateTimeDateTime anotherDateTime = obtainDateTimeSomehow();dateTime = new DateTime(anotherDateTime);// Use a String (must be formatted properly)String timeString = "2006-01-26T13:30:00-06:00";dateTime = new DateTime(timeString);timeString = "2006-01-26";dateTime = new DateTime(timeString);
ReadablePartial
应用程序所需处理的日期问题并不全部都与时间上的某个完整时刻有关,因此您可以处理一个局部时刻。例如,有时您比较关心年/月/日,或者一天中的时间,甚至是一周中的某天。Joda 设计者使用 ReadablePartial
接口捕捉这种表示局部时间的概念,这是一个不可变的局部时间片段。用于处理这种时间片段的两个有用类分别为 LocalDate
和 LocalTime
:
LocalDate
:该类封装了一个年/月/日的组合。当地理位置(即时区)变得不重要时,使用它存储日期将非常方便。例如,某个特定对象的出生日期 可能为 1999 年 4 月 16 日,但是从技术角度来看,在保存所有业务值的同时不会了解有关此日期的任何其他信息(比如这是一周中的星期几,或者这个人出生地所在的时区)。在这种情况下,应当使用LocalDate
。样例应用程序使用
SystemClock
来获取被初始化为系统时间的LocalDate
的实例:LocalDate localDate = SystemFactory.getClock().getLocalDate();
也可以通过显式地提供所含的每个字段的值来创建
LocalDate
:LocalDate localDate = new LocalDate(2009, 9, 6);// September 6, 2009
LocalDate
替代了在早期 Joda 版本中使用的YearMonthDay
。LocalTime
:这个类封装一天中的某个时间,当地理位置不重要的情况下,可以使用这个类来只存储一天当中的某个时间。例如,晚上 11:52 可能是一天当中的一个重要时刻(比如,一个 cron 任务将启动,它将备份文件系统的某个部分),但是这个时间并没有特定于某一天,因此我不需要了解有关这一时刻的其他信息。样例应用程序使用
SystemClock
获取被初始化为系统时间的LocalTime
的一个实例:LocalTime localTime = SystemFactory.getClock().getLocalTime();
也可以通过显式地提供所含的每个字段的值来创建
LocalTime
:LocalTime localTime = new LocalTime(13, 30, 26, 0);// 1:30:26PM
时间跨度
了解特定的时刻或是某个局部时间片段将非常有用,但是如果能够表达一段时间跨度的话,通常也很有用。Joda 提供了三个类来简化这个过程。您可以选择用于表示不同跨度的类:
Duration
:这个类表示一个绝对的精确跨度,使用毫秒为单位。这个类提供的方法可以用于通过标准的数学转换(比如 1 分钟 = 60 秒,1 天 = 24 小时),将时间跨度转换为标准单位(比如秒、分和小时)。您只在以下情况使用
Duration
的实例:您希望转换一个时间跨度,但是您并不关心这个时间跨度在何时发生,或者使用毫秒处理时间跨度比较方便。Period
:这个类表示与Duration
相同的概念,但是以人们比较熟悉的单位表示,比如年、月、周。您可以在以下情况使用
Period
:您并不关心这段时期必须在何时发生,或者您更关心检索单个字段的能力,这些字段描述由Period
封装的时间跨度。Interval
:这个类表示一个特定的时间跨度,将使用一个明确的时刻界定这段时间跨度的范围。Interval
为半开 区间,这表示由Interval
封装的时间跨度包括这段时间的起始时刻,但是不包含结束时刻。可以在以下情况使用
Interval
:需要表示在时间连续区间中以特定的点开始和结束的一段时间跨度。
以 Joda 的方式处理时间
现在,您已经了解了如何创建一些非常有用的 Joda 类,我将向您展示如何使用它们执行日期计算。接着您将了解到 Joda 如何轻松地与 JDK 进行互操作。
日期计算
如果您只是需要对日期/时间信息使用占位符,那么 JDK 完全可以胜任,但是它在日期/时间计算方面的表现十分糟糕,而这正是 Joda 的长处。我将向您展示一些简单的例子。
假设在当前的系统日期下,我希望计算上一个月的最后一天。对于这个例子,我并不关心一天中的时间,因为我只需要获得年/月/日,如清单 6 所示:
清单 6. 使用 Joda 计算日期 LocalDate now = SystemFactory.getClock().getLocalDate();LocalDate lastDayOfPreviousMonth =\ now.minusMonths(1).dayOfMonth().withMaximumValue(); |
您可能对清单 6 中的 dayOfMonth()
调用感兴趣。这在 Joda 中被称为属性(property)。它相当于 Java 对象的属性。属性是根据所表示的常见结构命名的,并且它被用于访问这个结构,用于完成计算目的。属性是实现 Joda 计算威力的关键。您目前所见到的所有 4 个 Joda 类都具有这样的属性。一些例子包括:
yearOfCentury
dayOfYear
monthOfYear
dayOfMonth
dayOfWeek
我将详细介绍清单 6 中的示例,以向您展示整个计算过程。首先,我从当前月份减去一个月,得到 “上一个月”。接着,我要求获得 dayOfMonth
的最大值,它使我得到这个月的最后一天。注意,这些调用被连接到一起(注意 Joda ReadableInstant
子类是不可变的),这样您只需要捕捉调用链中最后一个方法的结果,从而获得整个计算的结果。
当计算的中间结果对我不重要时,我经常会使用这种计算模式。(我以相同的方式使用 JDK 的 BigDecimal
)。假设您希望获得任何一年中的第 11 月的第一个星期二的日期,而这天必须是在这个月的第一个星期一之后。清单 7 展示了如何完成这个计算:
LocalDate now = SystemFactory.getClock().getLocalDate();LocalDate electionDate = now.monthOfYear() .setCopy(11) // November .dayOfMonth() // Access Day Of Month Property .withMinimumValue() // Get its minimum value .plusDays(6) // Add 6 days .dayOfWeek() // Access Day Of Week Property .setCopy("Monday") // Set to Monday (it will round down) .plusDays(1); // Gives us Tuesday |
清单 7 的注释帮助您了解代码如何获得结果。.setCopy("Monday")
是整个计算的关键。不管中间 LocalDate
值是多少,将其 dayOfWeek
属性设置为 Monday 总是能够四舍五入,这样的话,在每月的开始再加上 6 天就能够让您得到第一个星期一。再加上一天就得到第一个星期二。Joda 使得执行此类计算变得非常容易。
下面是其他一些因为使用 Joda 而变得超级简单的计算:
以下代码计算从现在开始经过两个星期之后的日期:
DateTime now = SystemFactory.getClock().getDateTime();DateTime then = now.plusWeeks(2); |
您可以以这种方式计算从明天起 90 天以后的日期:
DateTime now = SystemFactory.getClock().getDateTime();DateTime tomorrow = now.plusDays(1);DateTime then = tomorrow.plusDays(90); |
(是的,我也可以向 now
加 91 天,那又如何呢?)
下面是计算从现在起 156 秒之后的时间:
DateTime now = SystemFactory.getClock().getDateTime();DateTime then = now.plusSeconds(156); |
下面的代码将计算五年后的第二个月的最后一天:
DateTime now = SystemFactory.getClock().getDateTime();DateTime then = now.minusYears(5) // five years ago .monthOfYear() // get monthOfYear property .setCopy(2) // set it to February .dayOfMonth() // get dayOfMonth property .withMaximumValue();// the last day of the month |
这样的例子实在太多了,我向您已经知道了如何计算。尝试操作一下样例应用程序,亲自体验一下使用 Joda 计算任何日期是多么有趣。
JDK 互操作性
我的许多代码都使用了 JDK Date
和 Calendar
类。但是幸亏有 Joda,我可以执行任何必要的日期算法,然后再转换回 JDK 类。这将两者的优点集中到一起。您在本文中看到的所有 Joda 类都可以从 JDK Calendar
或 Date
创建,正如您在 中看到的那样。出于同样的原因,可以从您所见过的任何 Joda 类创建 JDK Calendar
或 Date
。
清单 8 展示了从 Joda ReadableInstant
子类转换为 JDK 类有多么简单:
DateTime
类创建 JDK 类 DateTime dateTime = SystemFactory.getClock().getDateTime();Calendar calendar = dateTime.toCalendar(Locale.getDefault());Date date = dateTime.toDate();DateMidnight dateMidnight = SystemFactory.getClock() .getDateMidnight();date = dateMidnight.toDate(); |
对于 ReadablePartial
子类,您还需要经过额外一步,如清单 9 所示:
LocalDate
的 Date
对象 LocalDate localDate = SystemFactory.getClock().getLocalDate();Date date = localDate.toDateMidnight().toDate(); |
要创建 Date
对象,它表示从清单 9 所示的 SystemClock
中获得的 LocalDate
,您必须首先将它转换为一个 DateMidnight
对象,然后只需要将 DateMidnight
对象作为 Date
。(当然,产生的 Date
对象将把它自己的时间部分设置为午夜时刻)。
JDK 互操作性被内置到 Joda API 中,因此您无需全部替换自己的接口,如果它们被绑定到 JDK 的话。比如,您可以使用 Joda 完成复杂的部分,然后使用 JDK 处理接口。
以 Joda 方式格式化时间
使用 JDK 格式化日期以实现打印是完全可以的,但是我始终认为它应该更简单一些。这是 Joda 设计者进行了改进的另一个特性。要格式化一个 Joda 对象,调用它的 toString()
方法,并且如果您愿意的话,传递一个标准的 ISO-8601 或一个 JDK 兼容的控制字符串,以告诉 JDK 如何执行格式化。不需要创建单独的 SimpleDateFormat
对象(但是 Joda 的确为那些喜欢自找麻烦的人提供了一个 DateTimeFormatter
类)。调用 Joda 对象的 toString()
方法,仅此而已。我将展示一些例子。
清单 10 使用了 ISODateTimeFormat
的静态方法:
DateTime dateTime = SystemFactory.getClock().getDateTime();dateTime.toString(ISODateTimeFormat.basicDateTime());dateTime.toString(ISODateTimeFormat.basicDateTimeNoMillis());dateTime.toString(ISODateTimeFormat.basicOrdinalDateTime());dateTime.toString(ISODateTimeFormat.basicWeekDateTime()); |
清单 10 中的四个 toString()
调用分别创建了以下内容:
20090906T080000.000-050020090906T080000-05002009249T080000.000-05002009W367T080000.000-0500 |
您也可以传递与 SimpleDateFormat
JDK 兼容的格式字符串,如清单 11 所示:
SimpleDateFormat
字符串 DateTime dateTime = SystemFactory.getClock().getDateTime();dateTime.toString("MM/dd/yyyy hh:mm:ss.SSSa");dateTime.toString("dd-MM-yyyy HH:mm:ss");dateTime.toString("EEEE dd MMMM, yyyy HH:mm:ssa");dateTime.toString("MM/dd/yyyy HH:mm ZZZZ");dateTime.toString("MM/dd/yyyy HH:mm Z");09/06/2009 02:30:00.000PM06-Sep-2009 14:30:00Sunday 06 September, 2009 14:30:00PM09/06/2009 14:30 America/Chicago09/06/2009 14:30 -0500 |
查看 Javadoc 中有关 joda.time.format.DateTimeFormat
的内容,获得与 JDK SimpleDateFormat
兼容的格式字符串的更多信息,并且可以将其传递给 Joda 对象的 toString()
方法。