任何企业应用程序都须要处理时间问题。应用程序须要知道当前的时间点和下一个时间点,有时它们还必须计算这两个时间点之间的路径。使用 JDK 完成这项任务将很是痛苦和繁琐。如今来看看 Joda Time,一个面向 Java™ 平台的易于使用的开源时间/日期库。正如您在本文中了解的那样,Joda-Time 轻松化解了处理日期和时间的痛苦和繁琐。
在编写企业应用程序时,我经常须要处理日期。而且在个人最新项目中 — 保险行业 — 纠正日期计算尤为重要。使用 java.util.Calendar
让我有些不安。若是您也曾使用这个类处理过日期/时间值,那么您就知道它使用起来有多麻烦。所以当我接触到 Joda-Time — 面向 Java 应用程序的日期/时间库的替代选择 — 我决定研究一下。其结果是:我很庆幸我这么作了。html
Joda-Time 令时间和日期值变得易于管理、操做和理解。事实上,易于使用是 Joda 的主要设计目标。其余目标包括可扩展性、完整的特性集以及对多种日历系统的支持。而且 Joda 与 JDK 是百分之百可互操做的,所以您无需替换全部 Java 代码,只须要替换执行日期/时间计算的那部分代码。java
![]() |
|
本文将介绍并展现如何使用它。我将介绍如下主题:安全
您能够 下载 演示这些概念的样例应用程序的源代码。框架
为何要使用 Joda?考虑建立一个用时间表示的某个随意的时刻 — 好比,2000 年 1 月 1 日 0 时 0 分。我如何建立一个用时间表示这个瞬间的 JDK 对象?使用 java.util.Date
?事实上这是行不通的,由于自 JDK 1.1 以后的每一个 Java 版本的 Javadoc 都声明应当使用 java.util.Calendar
。Date
中不同意使用的构造函数的数量严重限制了您建立此类对象的途径。工具
然而,Date
确实有一个构造函数,您能够用来建立用时间表示某个瞬间的对象(除 “如今” 之外)。该方法使用距离 1970 年 1 月 1 日子时格林威治标准时间(也称为 epoch)以来的毫秒数做为一个参数,对时区进行校订。考虑到 Y2K 对软件开发企业的重要性,您可能会认为我已经记住了这个值 — 可是我没有。Date
也不过如此。测试
那么 Calendar
又如何呢?我将使用下面的方式建立必需的实例:spa
Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); |
使用 Joda,代码应该相似以下所示:插件
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); |
这一行简单代码没有太大的区别。可是如今我将使问题稍微复杂化。假设我但愿在这个日期上加上 90 天并输出结果。使用 JDK,我须要使用清单 1 中的代码:
清单 1. 以 JDK 的方式向某一个瞬间加上 90 天并输出结果
Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); SimpleDateFormat sdf = new SimpleDateFormat("E MM/dd/yyyy HH:mm:ss.SSS"); calendar.add(Calendar.DAY_OF_MONTH, 90); System.out.println(sdf.format(calendar.getTime())); |
使用 Joda,代码如清单 2 所示:
清单 2. 以 Joda 的方式向某一个瞬间加上 90 天并输出结果
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS"); |
二者之间的差距拉大了(Joda 用了两行代码,JDK 则是 5 行代码)。
如今假设我但愿输出这样一个日期:距离 Y2K 45 天以后的某天在下一个月的当前周的最后一天的日期。坦白地说,我甚至不想使用 Calendar
处理这个问题。使用 JDK 实在太痛苦了,即便是简单的日期计算,好比上面这个计算。正是多年前的这样一个时刻,我第一次领略到 Joda-Time 的强大。使用 Joda,用于计算的代码如清单 3 所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek() .withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS"); |
清单 3 的输出为:
Sun 03/19/2000 00:00:00.000 |
若是您正在寻找一种易于使用的方式替代 JDK 日期处理,那么您真的应该考虑 Joda。若是不是这样的话,那么继续痛苦地使用 Calendar
完成全部日期计算吧。当您作到这一点后,您彻底能够作到使用几把剪刀修建草坪并使用一把旧牙刷清洗您的汽车。
JDK Calendar
类缺少可用性,这一点很快就能体会到,而 Joda 弥补了这一不足。Joda 的设计者还作出了一个决定,我认为这是它取得成功的构建:JDK 互操做性。Joda 的类可以生成(可是,正如您将看到的同样,有时会采用一种比较迂回的方式)java.util.Date
的实例(和 Calendar
)。这使您可以保留现有的依赖 JDK 的代码,可是又可以使用 Joda 处理复杂的日期/时间计算。
例如,完成 清单 3 中的计算后。我只须要作出如清单 4 所示的更改就能够返回到 JDK 中:
Calendar calendar = Calendar.getInstance(); DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek() .withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS"); calendar.setTime(dateTime.toDate()); |
就是这么简单。我完成了计算,可是能够继续在 JDK 对象中处理结果。这是 Joda 的一个很是棒的特性。
![]() ![]() |
![]()
|
Joda 使用如下概念,它们能够应用到任何日期/时间库:
我将针对 Joda 依次讨论每个概念。
我在本文讨论的 Joda 类具备不可变性,所以它们的实例没法被修改。(不可变类的一个优势就是它们是线程安全的)。我将向您展现的用于处理日期计算的 API 方法所有返回一个对应 Joda 类的新实例,同时保持原始实例不变。当您经过一个 API 方法操做 Joda 类时,您必须捕捉该方法的返回值,由于您正在处理的实例不能被修改。您可能对这种模式很熟悉;好比,这正是 java.lang.String
的各类操做方法的工做方式。
Instant
表示时间上的某个精确的时刻,使用从 epoch 开始计算的毫秒表示。这必定义与 JDK 相同,这就是为何任何 Joda Instant
子类均可以与 JDK Date
和 Calendar
类兼容的缘由。
更通用一点的定义是:一个瞬间 就是指时间线上只出现一次且惟一的一个时间点,而且这种日期结构只能以一种有意义的方式出现一次。
一个局部时间,正如我将在本文中将其称为局部时间片断同样,它指的是时间的一部分片断。瞬间性指定了与 epoch 相对的时间上的一个精确时刻,与此相反,局部时间片断指的是在时间上能够来回 “移动” 的一个时刻,这样它即可以应用于多个实例。好比,6 月 2 日 能够应用于任意一年的 6 月份(使用 Gregorian 日历)的次日的任意瞬间。一样,11:06 p.m. 能够应用于任意一年的任意一天,而且天天只能使用一次。即便它们没有指定一个时间上的精确时刻,局部时间片断仍然是有用的。
我喜欢将局部时间片断看做一个重复周期中的一点,这样的话,若是我正在考虑的日期构建能够以一种有意义的方式出现屡次(即重复的),那么它就是一个局部时间。
Joda 本质 — 以及其设计核心 — 的关键就是年表(它的含义由一个同名抽象类捕捉)。从根本上讲,年表是一种日历系统 — 一种计算时间的特殊方式 — 而且是一种在其中执行日历算法的框架。受 Joda 支持的年表的例子包括:
Joda-Time 1.6 支持 8 种年表,每一种均可以做为特定日历系统的计算引擎。
时区是值一个相对于英国格林威治的地理位置,用于计算时间。要了解事件发生的精确时间,还必须知道发生此事件的位置。任何严格的时间计算都必须涉及时区(或相对于 GMT),除非在同一个时区内发生了相对时间计算(即时这样时区也很重要,若是事件对于位于另外一个时区的各方存在利益关系的话)。
DateTimeZone
是 Joda 库用于封装位置概念的类。许多日期和时间计算均可以在不涉及时区的状况下完成,可是仍然须要了解 DateTimeZone
如何影响 Joda 的操做。默认时间,即从运行代码的机器的系统时钟检索到的时间,在大部分状况下被使用。
![]() ![]() |
![]()
|
如今,我将展现在采用该库时会常常遇到的一些 Joda 类,并展现如何建立这些类的实例。
![]() |
|
本节中介绍的全部实现都具备若干构造函数,容许您初始化封装的日期/时间。它们能够分为 4 个类别:
java.util.Date
,或者是另外一个 Joda 对象)。我将在第一个类中介绍这些构造函数: DateTime
。当您使用其余 Joda 类的相应构造函数时,也可使用这里介绍的内容。
![]() |
|
Joda 经过 ReadableInstant
类实现了瞬间性这一律念。表示时间上的不可变瞬间的 Joda 类都属于这个类的子类。(将这个类命名为ReadOnlyInstant
可能更好,我认为这才是设计者须要传达的意思)。换句话说,ReadableInstant
表示时间上的某一个不可修改的瞬间)。其中的两个子类分别为 DateTime
和 DateMidnight
:
DateTime
:这是最经常使用的一个类。它以毫秒级的精度封装时间上的某个瞬间时刻。DateTime
始终与 DateTimeZone
相关,若是您不指定它的话,它将被默认设置为运行代码的机器所在的时区。 可使用多种方式构建 DateTime
对象。这个构造函数使用系统时间:
DateTime dateTime = new DateTime(); |
DateTime dateTime = SystemFactory.getClock().getDateTime(); |
SystemClock
实现的内部设置的,而不是在应用程序的内部。(我能够修改系统时间,可是那实在太痛苦了!) 下面的代码使用一些字段值构建了一个 DateTime
对象:
DateTime dateTime = new DateTime( 2000, //year 1, // month 1, // day 0, // hour (midnight is zero) 0, // minute 0, // second 0 // milliseconds ); |
正如您所见,Joda 可使您精确地控制建立 DateTime
对象的方式,该对象表示时间上的某个特定的瞬间。每个 Joda 类都有一个与此相似的构造函数,您在此构造函数中指定 Joda 类能够包含的全部字段。您能够用它快速了解特定类在哪种粒度级别上操做。
下一个构造函数将指定从 epoch 到某个时刻所通过的毫秒数。它根据 JDK Date
对象的毫秒值建立一个 DateTime
对象,其时间精度用毫秒表示,由于 epoch 与 Joda 是相同的:
java.util.Date jdkDate = obtainDateSomehow(); long timeInMillis = jdkDate.getTime(); DateTime dateTime = new DateTime(timeInMillis); |
而且这个例子与前例相似,惟一不一样之处是我在这里将 Date
对象直接传递给构造函数:
java.util.Date jdkDate = obtainDateSomehow(); dateTime = new DateTime(jdkDate); |
Joda 支持使用许多其余对象做为构造函数的参数,用于建立 DateTime
,如清单 5 所示:
DateTime
的构造函数 // Use a Calendar java.util.Calendar calendar = obtainCalendarSomehow(); dateTime = new DateTime(calendar); // Use another Joda DateTime DateTime 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); |
注意,若是您准备使用 String
(必须通过解析),您必须对其进行精确地格式化。参考 Javadoc,得到有关 Joda 的 ISODateTimeFormat
类的更多信息(参见 参考资料)。
DateMidnight
:这个类封装某个时区(一般为默认时区)在特定年/月/日的午夜时分的时刻。它基本上相似于 DateTime
,不一样之处在于时间部分老是为与该对象关联的特定 DateTimeZone
时区的午夜时分。您将在本文看到的其余类都遵循与 ReadableInstant
类相同的模式(Joda Javadoc 将显示这些内容),所以为了节省篇幅,我将不会在如下小节介绍这些内容。
应用程序所需处理的日期问题并不所有都与时间上的某个完整时刻有关,所以您能够处理一个局部时刻。例如,有时您比较关心年/月/日,或者一天中的时间,甚至是一周中的某天。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 如何轻松地与 JDK 进行互操做。
若是您只是须要对日期/时间信息使用占位符,那么 JDK 彻底能够胜任,可是它在日期/时间计算方面的表现十分糟糕,而这正是 Joda 的长处。我将向您展现一些简单的例子。
假设在当前的系统日期下,我但愿计算上一个月的最后一天。对于这个例子,我并不关心一天中的时间,由于我只须要得到年/月/日,如清单 6 所示:
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
的最大值,它使我获得这个月的最后一天。注意,这些调用被链接到一块儿(注意 JodaReadableInstant
子类是不可变的),这样您只须要捕捉调用链中最后一个方法的结果,从而得到整个计算的结果。
当计算的中间结果对我不重要时,我常常会使用这种计算模式。(我以相同的方式使用 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 Date
和 Calendar
类。可是幸好有 Joda,我能够执行任何须要的日期算法,而后再转换回 JDK 类。这将二者的优势集中到一块儿。您在本文中看到的全部 Joda 类均可以从 JDK Calendar
或 Date
建立,正如您在 建立 Joda-Time 对象 中看到的那样。出于一样的缘由,能够从您所见过的任何 Joda 类建立 JDK Calendar
或 Date
。
清单 8 展现了从 Joda ReadableInstant
子类转换为 JDK 类有多么简单:
清单 8. 从 Joda 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 所示:
清单 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 处理接口。
![]() ![]() |
![]()
|
使用 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-0500 20090906T080000-0500 2009249T080000.000-0500 2009W367T080000.000-0500 |
您也能够传递与 SimpleDateFormat
JDK 兼容的格式字符串,如清单 11 所示:
清单 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.000PM 06-Sep-2009 14:30:00 Sunday 06 September, 2009 14:30:00PM 09/06/2009 14:30 America/Chicago 09/06/2009 14:30 -0500 |
查看 Javadoc 中有关 joda.time.format.DateTimeFormat
的内容,得到与 JDK SimpleDateFormat
兼容的格式字符串的更多信息,而且能够将其传递给 Joda 对象的 toString()
方法。
![]() ![]() |
![]()
|
谈到日期处理,Joda 是一种使人惊奇的高效工具。不管您是计算日期、打印日期,或是解析日期,Joda 都将是工具箱中的便捷工具。在本文中,我首先介绍了 Joda,它能够做为 JDK 日期/时间库的替代选择。而后介绍了一些 Joda 概念,以及如何使用 Joda 执行日期计算和格式化。
Joda-Time 衍生了一些相关的项目,您可能会发现这些项目颇有用。如今出现了一个针对 Grails Web 开发框架的 Joda-Time 插件。joda-time-jpox 项目的目标就是添加一些必需的映射,以使用 DataNucleus 持久化引擎持久化 Joda-Time 对象。而且,一个针对 Google Web Toolkit(也称为 Goda-Time)的 Joda-Time 实现目前正在开发当中,可是在撰写本文之际由于许可问题而被暂停。访问 参考资料 得到更多信息。
![]() ![]() |
![]()
|
描述 名字 大小 下载方法 源代码
j-jodatime.zip | 812KB | HTTP |
![]() |
||
![]() |
关于下载方法的信息 | ![]() |