在编写业务应用程序时,我经常需要操纵日期。 在我最近从事的保险行业中,正确的日期计算尤为重要。 我对java.util.Calendar有点不安。 如果您使用此类来处理日期/时间值,您就会知道它很麻烦。 因此,当我听说Joda-Time(Java应用程序的替代日期/时间库)时,我决定进行调查。 长话短说:我很高兴自己做到了。
Joda-Time使时间和日期值易于管理,操纵和理解。 实际上,易于使用是Joda的主要设计目标。 其他目标包括可扩展性,全面的功能集以及对多个日历系统的支持。 而且Joda与JDK可以100%互操作,因此您不需要替换所有Java代码,只需替换进行日期/时间计算的部分即可。
在乔达伞下
Joda实际上是针对Java语言的许多替换API的总体项目,因此从技术上讲,使用Joda和Joda-Time作为同义词是不正确的。 但是在撰写本文时,Joda-Time API似乎是唯一正在积极开发中的Joda API。 考虑到Joda总括项目的当前状态,我认为将Joda-Time简称为Joda是安全的。
本文介绍了Joda并向您展示了如何使用它。 我将介绍以下主题:
日期/时间替换库的情况乔达的关键概念创建Joda-Time对象操纵时间,乔达风格格式化时间,乔达风格您可以下载演示这些概念的示例应用程序的源代码。
为什么要打扰乔达? 考虑创建一个任意的时间点,例如2000年1月1日午夜。我如何创建一个代表该时间点的JDK对象? java.util.Date ? 好吧,不是真的,因为自JDK 1.1之后的每个Java版本的Javadoc都规定应使用java.util.Calendar 。 Date中不推荐使用的构造方法的数量严重限制了创建此类对象的方法的数量。
Date确实具有一个构造函数,但是,您可以使用它来创建一个对象,该对象表示除“ now”之外的即时时间。 该方法将自1970年1月1日午夜格林威治标准时间(GMT)(也称为epoch )以来的毫秒数作为参数,以校正时区。 考虑到Y2K在软件开发业务中的重要性,您可能会认为我会把这种价值投入到内存中,但是我没有。 Date太多了。
那Calendar呢? 我将以这种方式创建必要的实例:
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.在瞬间添加90天并以JDK方式打印结果
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的代码行为五行)。
现在假设我要在距Y2K 45天的那一天之后,打印出该月的一周中最后一天的日期。 坦白说,我什至不想尝试使用Calendar解决这个问题。 使用JDK进行像这样的简单日期计算实在是太痛苦了。 几年前的这一刻,我第一次意识到Joda-Time的力量。 使用Joda,计算代码类似于清单3:
清单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的类能够生成(尽管有时会有点circuit回,如您所见) java.util.Date (和Calendar )的实例。 这使您可以保留现有的依赖JDK的代码,但在进行日期/时间计算时,可以使用Joda进行繁重的工作。
例如,在完成清单3中的计算之后,要做的回到JDK就是要做清单4中所示的更改:
清单4.将Joda计算结果输入到JDK对象中
Calendar calendar = Calendar.getInstance(); DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); dateTime = dateTime.plusDays(45).plusMonths(1).dayOfWeek().withMaximumValue(); System.out.println(dateTime.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代表精确的时间,以毫秒为单位。 该定义与JDK一致,这就是任何Joda Instant子类与JDK Date和Calendar类兼容的原因。
一般地定义它: 瞬间是时间线是独特的,只发生一次一个点,可以以有意义的方式只能出现一次这样的日子进行构建。
我将在本文中提到的部分或部分时间片段就是:时间的部分片段。 瞬时指定了相对于纪元的确切时间,而部分时间段则指的是可以“滑移”到可以应用于多个瞬时的时刻。 例如, 6月2日可能适用于任何一年中6月第二天(使用公历)的任何时刻。 同样,在一年中的任何一天,每天晚上11:06都可以申请一次。 即使未指定时间瞬间,部分时间片段也可能很有用。
我喜欢将不完整的时间片段视为重复的圆上的一个点,因此,如果我考虑的日期构造可以有意义的方式多次出现(即重复),那么它就是不完整的。
乔达内部构造的关键-也是其设计的核心-是年代学 (其概念被同名抽象类捕获)。 从根本上说,时间顺序是一个日历系统,是一种计算时间的特殊方式,并且是进行日期计算的框架。 Joda支持的时间顺序示例包括:
ISO(默认)科普特人朱利安伊斯兰教Joda-Time 1.6支持八种时序,每种时序都用作特定日历系统的计算引擎。
时区是相对于英国格林威治的地理位置,用于计算时间。 要准确知道事件的发生时间,知道事件的位置也很重要。 任何严格的时间计算都必须考虑时区(或相对于GMT),除非相对时间计算发生在同一时区内(即使这样,如果事件对另一个时区的参与者也很重要)。
DateTimeZone是Joda库用于封装此位置概念的类。 可以进行许多日期和时间计算而无需考虑时区,但是了解DateTimeZone如何影响Joda所做的事情仍然很重要。 大部分时间都使用默认时间,该默认时间是从运行代码的计算机的系统时钟中获取的。
现在,我将向您介绍一些采用该库时通常会使用的Joda类,并展示如何创建它们的实例。
可变的Joda类
我不是可变实用程序类的忠实拥护者。 我只是认为用例不支持它们的广泛使用。 但是,如果您确定确实需要使用可变的Joda类,则本节中的信息仍将对您的项目有所帮助。 Readable和ReadWritable API之间的唯一区别是ReadWritable类更改封装的日期/时间值的能力,因此在此不做介绍。
您将在本节中看到的所有实现都有几个构造函数,可用来初始化封装的日期/时间。 它们分为四类:
使用系统时间。使用各个字段以该特定实现支持的最精细的精度来指定即时(或部分时间段)。指定瞬间(或部分时间段),以毫秒为单位。使用另一个对象(例如, java.util.Date或另一个Joda对象)。我将为您要研究的第一个类介绍这些构造函数: DateTime 。 当您使用其他Joda类的相应构造函数时,那里的信息将很好地传递。
重载方法
如果创建DateTime实例而不提供Chronology或DateTimeZone ,则Joda将使用ISOChronology (默认设置)和DateTimeZone (来自系统设置程序)。 但是,Joda ReadableInstant子类的所有构造函数都包含采用Chronology或DateTimeZone的重载方法。 本文提供的应用程序的示例代码向您展示了如何使用这些重载方法(请参阅下载 )。 我不会在这里详细介绍它们,因为它们是如此简单。 但是,我确实鼓励您试用示例应用程序,以了解编写应用程序代码的难易程度,以便您可以即时交换掉Joda的Chronology和DateTimeZone ,而不会影响其余的代码。
Joda通过ReadableInstant类实现了即时的概念。 表示时间不变时刻的Joda类是该类的子类。 (最好将其命名为ReadOnlyInstant ,我相信这是设计人员想要传达的含义。换句话说, ReadableInstant表示无法修改的时间。)其中两个子类是DateTime和DateMidnight :
DateTime :这是您最常使用的类。 它封装了毫秒级的即时时间。 DateTime始终与DateTimeZone关联,如果未指定,则默认为运行代码的计算机的时区。有多种方法构造DateTime对象。 此构造函数使用系统时间:
DateTime dateTime = new DateTime(); 通常,我尽量避免使用系统时钟来初始化应用程序的时间,而倾向于外部化设置应用程序代码使用的系统时间。 该示例应用程序执行以下操作: DateTime dateTime = SystemFactory.getClock().getDateTime(); 这使使用不同日期/时间测试代码变得更加简单:我不需要更改代码以通过它运行不同的日期方案,因为时间是在SystemClock的实现中设置的,而与我的应用程序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类可以容纳的所有字段。 您可以将其用作特定类运行所在的粒度级别的快速指南。
下一个构造函数指定自纪元以来的瞬间(以毫秒为单位)。 它从JDK Date对象的毫秒值创建一个DateTime对象,该Date以来的时间定义(以毫秒为单位)与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所示:
清单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 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, 2009LocalDate替代了Joda YearMonthDay版本中使用的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 :此类表示绝对的数学跨度,以毫秒为单位。 此类的方法可用于通过标准数学转换(例如,每分钟60秒和一天24小时)转换为标准单位,例如秒,分钟和小时。仅当您要表示时间跨度时才使用Duration的实例,而您不关心该特定时间跨度何时发生,或者您可以方便地以毫秒为单位处理该跨度。
Period :此类表示与“ Duration ”相同的概念,但是用“人”的术语,例如年,月和周。您可以使用一个Period ,当你不关心,当一段时间内必然发生,或者当它是能够检索描述的时间由封装跨度各个领域更重要Period 。
Interval :此类表示特定的时间范围,并带有确定的瞬间来标记其边界。 Interval是半开放的 ,这意味着时间Interval封装的Interval跨度包括该跨度的开始但不包括结束。当表示在时间连续体内以确定点开始和结束的时间跨度很重要时,可以使用“ Interval 。
既然您已经了解了如何创建一些更有用的Joda类,我将向您展示如何使用它们来执行日期计算。 然后,您将看到Joda如何轻松地与JDK进行互操作。
如果只需要占位符用于日期/时间信息,则JDK可以,但是使用它进行日期/时间计算很麻烦。 这就是乔达大放异彩的地方。 我将向您展示一些简单的示例。
假设给定当前系统日期,我想计算上个月的最后一天。 在这种情况下,我不在乎时间,因为我只需要年/月/日,如清单6所示:
清单6.使用Joda计算日期
LocalDate now = SystemFactory.getClock().getLocalDate(); LocalDate lastDayOfPreviousMonth =\ now.minusMonths(1).dayOfMonth().withMaximumValue();您可能想知道清单6中的dayOfMonth()调用dayOfMonth()称之为property 。 属性是Joda对象的属性。 它根据它所代表的熟悉的结构命名,并且出于进行计算的目的而用于访问该结构。 属性是Joda计算能力的关键。 到目前为止,您所研究的所有四个Joda类都具有这样的属性。 一些例子是:
yearOfCenturydayOfYearmonthOfYeardayOfMonthdayOfWeek我将遍历清单6中的示例,向您确切显示发生了什么。 首先,我从当前月份中减去一个月以获得“上个月”。 接下来,我询问dayOfMonth属性的最大值,这使我到月底。 请注意,这些调用被链接在一起(请记住,Joda ReadableInstant子类是不可变的),因此您只需要捕获链中最后一个方法调用的结果即可获得整个计算的结果。
当我不需要知道计算的中间结果时,我会与Joda一起使用这种模式。 (我以相同的方式使用JDK的BigDecimal 。)假设您想要11月(任何特定年份)的第一个星期一之后的第一个星期二的日期。 清单7显示了如何:
清单7.计算11月第一个星期一之后的第一个星期二
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 ,将其dayOfWeek属性设置为Monday总是四舍五入,这样在月初增加六天就可以得到第一个星期一。 加一天,您将有第一个星期二。 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);(是的,我可能只是91天添加到now ,但那有什么好玩的?)
从现在开始,您可以按照以下方法计算156秒:
DateTime now = SystemFactory.getClock().getDateTime(); DateTime then = now.plusSeconds(156);这段代码计算了五年前2月的最后一天:
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-Time对象中看到的那样,您可以从JDK Calendar或Date创建本文中看到的所有Joda类。 同样,可以从您看到的任何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();要创建一个代表从SystemClock获得的LocalDate的Date对象,如清单9所示,您必须首先将其转换为DateMidnight对象,然后只需将DateMidnight对象要求为Date 。 (当然,生成的Date对象的时间部分将设置为午夜。)
JDK互操作性已内置在Joda API中,因此,如果将接口绑定到JDK,则无需完全替换它们。 例如,您可以使用Joda进行繁重的工作,并使用JDK进行接口。
使用JDK格式化打印日期非常可靠,但我一直认为它应该更简单。 这是Joda设计师正确的另一项功能。 要格式化Joda对象,请调用其toString()方法,并根据需要传递标准ISO-8601或JDK兼容的控制字符串,以告诉Joda如何格式化它。 不再创建单独的SimpleDateFormat对象(尽管Joda确实为受虐狂提供了DateTimeFormatter类)。 一次调用Joda对象的toString()方法即可完成。 我将向您展示一些示例。
清单10使用了ISODateTimeFormat静态方法:
清单10.使用ISO-8601
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您还可以传递与JDK兼容的SimpleDateFormat格式字符串,如清单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有关可传递给Joda对象的toString()方法的JDK SimpleDateFormat兼容格式字符串的更多信息,请参见Javadoc中的joda.time.format.DateTimeFormat 。
在处理日期时,Joda是一种非常有效的工具。 无论您需要计算日期,打印日期还是解析日期,Joda都是您工具箱中的便捷补充。 在本文中,我以Joda作为JDK日期/时间替换库为例。 接下来,您了解了一些Joda概念,然后介绍了如何使用Joda进行日期计算和格式化。
Joda-Time产生了一些相关的项目,您可能会发现有帮助。 现在可以为Grails Web开发框架使用Joda-Time插件。 joda-time-jpox项目旨在使用DataNucleus持久性引擎添加持久化Joda-Time对象所需的映射。 Google Web Toolkit的Joda-Time实现(称为Goda-Time)也取得了一些进展,但由于许可问题,在本文撰写之时暂缓执行。 浏览参考资料了解更多信息。
翻译自: https://www.ibm.com/developerworks/java/library/j-jodatime/index.html