Joda-Time — 是一个用于在Java中处理日期和时间的库,由Stephen Colebourne创建,作为不方便的java.util.Date和java.util.Calendar的替代品。在Java 8发布之前,该库是工业开发中处理日期的事实标准,包括Android应用程序。根据Oracle Java Magazine(2023)的数据,在Android API 26发布后的两年内,超过60%的项目从Joda-Time迁移到了java.time。
要点
Joda-Time — 是一个开源库(Apache 2.0),为Java语言中的java.util.Date和java.util.Calendar类提供了高质量的替代品。该项目由Stephen Colebourne于2004年创立,以应对标准日期API的众多问题,包括可变性、格式化的复杂性以及有限的时区支持。该库在Java社区中得到广泛传播,被数千个项目使用,包括大型企业系统和Android应用程序,其中内置的日期处理工具特别不方便。自2014年Java 8发布和java.time(JSR-310)包出现以来,该库已转为维护模式——不再添加新功能,现有功能完全由新API覆盖。
Joda-Time建立在不可变性原则之上:每个日期或时间对象在创建后不能被修改。任何操作——添加一天、更改月份、设置时区——都会返回一个新对象,而原始对象保持不变。这种方法完全消除了与无意修改对象相关的错误类别,这是可变的java.util.Date和java.util.Calendar所特有的。不可变设计也使库无需额外同步即可实现线程安全。
该库为不同场景提供了几个主要类:DateTime — 带时区的完整日期和时间,LocalDate — 只有日期没有时间,LocalTime — 只有时间没有日期,LocalDateTime — 不带时区的日期和时间。此外,还包括用于处理间隔(Interval)、周期(Period)和持续时间(Duration)的类,可以计算不同计量单位下两个时间点之间的差异。每个类都实现ReadableInstant或ReadablePartial接口以实现统一处理。
Java中标准日期API的问题自平台诞生以来就众所周知。java.util.Date和java.util.Calendar类存在根本性缺陷:月份从0开始编号,Date同时表示日期和时间,Calendar是可变的且需要复杂的配置。通过SimpleDateFormat进行格式化不是线程安全的——在多线程应用程序中导致日期解析错误。Stephen Colebourne在处理大量使用日期的项目时,决定创建一个能够解决所有这些问题的替代方案。
根据InfoQ(2014)的采访,Colebourne在首次公开发布前一年多就在工业项目中测试了Joda-Time。第一个版本于2004年发布,立即引起了社区的关注。到2010年,该库已成为大多数Java项目的标准依赖项,包括Spring和Hibernate等框架。Joda-Time的成功直接影响到了JSR-310(Date and Time API)规范,该规范被纳入Java 8——而规范的作者正是同一位Stephen Colebourne。
Joda-Time不仅解决了日期问题——它改变了Java中API设计的方法。该库展示的不可变值对象概念在Java 8中被采纳为所有新API的标准。此外,Joda-Time的流行向Oracle表明,社区不愿意容忍低质量的标准库——在Joda-Time之后,Java中用于处理文件(NIO.2)、时间(java.time)和可选值(Optional)的API被重新设计。
Joda-Time提供了一套在Java 8之前标准库中不可用的功能。主要优势包括支持200多个时区并自动处理夏令时,丰富的格式化API支持模式和区域设置,以及以不同单位(年、月、日、小时)计算日期之间的周期。特别值得关注的是间隔系统:Interval(两个时间点之间的间隔)、Period(日历单位的差异)和Duration(以毫秒为单位的精确持续时间)。
Joda-Time中的DateTimeFormatter类提供了线程安全的日期格式化——与java.text.SimpleDateFormat不同。格式化程序可以通过模式(例如"yyyy-MM-dd HH:mm:ss")或样式(SHORT、MEDIUM、LONG、FULL)创建,以进行本地化输出。该库还支持格式化程序缓存,这加快了在高负载应用程序中的工作。从字符串解析日期时可以进行严格性控制——可以允许或禁止不完整或不正确的日期。
Joda-Time的强项之一是处理时间间隔。Interval类表示两个时间点(Instant)之间的精确时间段,支持交集、并集和成员检查操作。与Interval不同,Period使用日历单位操作——例如,3月1日和4月1日之间的差异正好是1个月,尽管在持续时间上可能是28-31天。Duration表示以毫秒为单位的精确持续时间,不依赖于日历,适用于测量操作的执行时间。
import org.joda.time.*;
import org.joda.time.format.*;
DateTime now = new DateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
String formatted = fmt.print(now);
DateTime nextWeek = now.plusWeeks(1);
Period period = new Period(now, nextWeek);
int days = period.getDays(); // 天 = 7
Interval interval = new Interval(now, nextWeek);
boolean contains = interval.contains(now); // 真
java.time(JSR-310)包由同一位作者——Stephen Colebourne——开发,吸收了Joda-Time的最佳理念,并考虑了十年的运营经验。两个API都使用不可变类,支持时区,并提供丰富的操作集。然而,java.time完全集成到Java标准库中,不需要外部依赖,并使用更一致的命名系统:Instant代替ReadableInstant,LocalDate代替LocalDate(名称保留),ZonedDateTime代替DateTime。
| 特性 | Joda-Time | java.time |
|---|---|---|
| 发布年份 | 2004 | 2014(Java 8) |
| 状态 | 维护(maintenance) | 积极开发 |
| 基础日期/时间 | DateTime | ZonedDateTime |
| 仅日期 | LocalDate | LocalDate |
| 不可变性 | 是 | 是 |
| 格式化 | DateTimeFormatter | DateTimeFormatter |
| 日期差异 | Period / Duration | Period / Duration |
java.time修正了Joda-Time的几个架构决策。首先,java.time消除了返回null的方法和缺失值之间的混淆——而是使用Optional。其次,通过Chronology接口添加了对替代日历系统(日本、泰国、伊斯兰)的支持。第三,java.time包含Instant类,用于在不依赖日历的情况下处理UTC时间点——这简化了与网络协议和数据库的集成。对于Android,建议从API 26(Android 8.0)开始使用java.time,或通过desugaring用于旧版本。
从Joda-Time迁移到java.time——这是库的作者们努力使其尽可能安全和可预测的过程。大多数类都有直接的对应关系:DateTime → ZonedDateTime,LocalDate → LocalDate(名称相同),LocalTime → LocalTime,DateTimeZone → ZoneId,Period → Period,Duration → Duration。主要变化涉及对象的创建方式和方法的名称——例如,使用`Date.from(instant)`代替`now.toDate()`,`DateTime.now()`替换为`ZonedDateTime.now()`。
建议从将Joda-Time隔离在一个薄抽象层开始迁移——这样可以在不影响整个代码库的情况下逐步替换库。第一步通常是替换LocalDate和LocalTime的使用——这些类在库之间的差异最小。第二阶段迁移DateTime(到ZonedDateTime)和时区。第三阶段替换Interval、Period和Duration——这里需要仔细检查考虑夏令时的行为。迁移完成后,Joda-Time将从项目依赖中完全移除。
// Joda-Time(迁移前)
val dateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"))
val formatted = dateTime.toString("dd.MM.yyyy")
val plusDay = dateTime.plusDays(1)
// java.time(迁移后)
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val formatted2 = zonedDateTime.format(
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
val plusDay2 = zonedDateTime.plusDays(1)
有几种工具可以简化从Joda-Time到java.time的过渡。OpenRewrite(by Moderne)通过org.openrewrite.java.migrate.JodaTimeToJavaTime配方支持自动迁移——它替换类、方法和导入,同时保持程序逻辑。对于Gradle项目,de.fayard.refreshVersions插件也包含迁移规则。手动迁移使用IDEA最为简便:Java Time Migration Assistant插件会高亮Joda-Time调用并建议相应的java.time替换,与手动替换相比可将速度提高2-3倍。
让我们看一下在Android应用程序中使用Joda-Time的实际示例。该库特别适用于根据本地标准格式化日期、计算用户年龄、确定事件持续时间和计算两个日期之间的间隔等任务。所有示例都使用不可变对象——每次调用返回一个新实例,原始对象保持不变。
// 计算用户年龄
val birthDate = LocalDate.parse("1990-05-15")
val today = LocalDate.now()
val age = Years.yearsBetween(birthDate, today).getYears()
// 年龄 == 36
// 使用用户区域设置格式化
val dateTime = DateTime.now()
val formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault())
val localized = formatter.print(dateTime)
Joda-Time中的间隔可以有效解决检查时间段交叉、计算重叠和查找最近事件的任务。例如,在开发日历应用程序时,可以检查新事件是否与现有事件重叠。Interval类支持contains、overlaps、gap和abuts操作——最后一个显示间隔是否在边界处接触。使用Duration以毫秒精度测量两个时间点之间的持续时间。
// 检查间隔重叠
Interval meeting1 = new Interval(
new DateTime(2026, 7, 21, 10, 0),
new DateTime(2026, 7, 21, 11, 0)
);
Interval meeting2 = new Interval(
new DateTime(2026, 7, 21, 10, 30),
new DateTime(2026, 7, 21, 11, 30)
);
boolean overlaps = meeting1.overlaps(meeting2); // 真
Duration gap = meeting1.gap(meeting2); // null(无间隙)
常见问题
Joda-Time — 是一个在Java 8发布前创建的用于在Java中处理日期和时间的库。它修正了java.util.Date和java.util.Calendar的缺陷:可变性、月份从0开始编号、缺乏完整的时区支持和非线程安全的格式化。
Java.time(JSR-310)——是Joda-Time的继任者,由同一位作者开发。主要区别:java.time内置于Java 8+标准库中,使用更一致的命名系统,支持替代日历系统,并包含用于处理UTC时间的Instant类。
不,对于新项目建议使用java.time。Joda-Time自2014年以来一直处于维护模式,不再获得新功能。对于API 26+的Android,java.time原生可用,对于旧版本,使用desugaring——一种允许在API 19+设备上使用java.time的机制。
迁移分阶段进行:在抽象中隔离Joda-Time,首先替换LocalDate和LocalTime,然后替换DateTime → ZonedDateTime,最后阶段替换Period、Duration和Interval。OpenRewrite工具通过JodaTimeToJavaTime配方支持自动迁移,可将速度提高2-3倍。
DateTime → ZonedDateTime,LocalDate → LocalDate(相同),LocalTime → LocalTime,LocalDateTime → LocalDateTime,DateTimeZone → ZoneId,Interval → 没有直接对应(使用Instant + Duration组合),Period → Period,Duration → Duration。方法名称也很接近:plusDays、minusMonths、withZone在两个库中都可用。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。