Joda-Time:是什么,日期库以及Java 8的替代品

作者: IT Sectr 发布日期: 2026-07-14 阅读时间: 10 分钟

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 — 用于处理日期和时间的库,Java 8中java.time的前身
  • 不可变类 — DateTime、LocalDate、LocalTime、LocalDateTime确保线程安全
  • 时区支持 — 内置时区数据库,自动更新
  • 迁移 — java.time(JSR-310)完全取代Joda-Time,库作者推荐迁移
  • Android — Joda-Time从API 1开始工作,但对于Android 8+(API 26)更推荐使用java.time

什么是Joda-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所特有的。不可变设计也使库无需额外同步即可实现线程安全。

Joda-Time的基本类

该库为不同场景提供了几个主要类:DateTime — 带时区的完整日期和时间,LocalDate — 只有日期没有时间,LocalTime — 只有时间没有日期,LocalDateTime — 不带时区的日期和时间。此外,还包括用于处理间隔(Interval)、周期(Period)和持续时间(Duration)的类,可以计算不同计量单位下两个时间点之间的差异。每个类都实现ReadableInstant或ReadablePartial接口以实现统一处理。

Joda-Time的出现历史

Java中标准日期API的问题自平台诞生以来就众所周知。java.util.Datejava.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。

对Java生态系统的影响

Joda-Time不仅解决了日期问题——它改变了Java中API设计的方法。该库展示的不可变值对象概念在Java 8中被采纳为所有新API的标准。此外,Joda-Time的流行向Oracle表明,社区不愿意容忍低质量的标准库——在Joda-Time之后,Java中用于处理文件(NIO.2)、时间(java.time)和可选值(Optional)的API被重新设计。

Joda-Time库的主要功能

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表示以毫秒为单位的精确持续时间,不依赖于日历,适用于测量操作的执行时间。

java
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); // 真

Joda-Time和java.time的比较

java.time(JSR-310)包由同一位作者——Stephen Colebourne——开发,吸收了Joda-Time的最佳理念,并考虑了十年的运营经验。两个API都使用不可变类,支持时区,并提供丰富的操作集。然而,java.time完全集成到Java标准库中,不需要外部依赖,并使用更一致的命名系统:Instant代替ReadableInstant,LocalDate代替LocalDate(名称保留),ZonedDateTime代替DateTime。

特性Joda-Timejava.time
发布年份20042014(Java 8)
状态维护(maintenance)积极开发
基础日期/时间DateTimeZonedDateTime
仅日期LocalDateLocalDate
不可变性
格式化DateTimeFormatterDateTimeFormatter
日期差异Period / DurationPeriod / 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

从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将从项目依赖中完全移除。

kotlin
// 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倍。

Joda-Time代码示例

让我们看一下在Android应用程序中使用Joda-Time的实际示例。该库特别适用于根据本地标准格式化日期、计算用户年龄、确定事件持续时间和计算两个日期之间的间隔等任务。所有示例都使用不可变对象——每次调用返回一个新实例,原始对象保持不变。

kotlin
// 计算用户年龄
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以毫秒精度测量两个时间点之间的持续时间。

java
// 检查间隔重叠
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,它有什么用?

Joda-Time — 是一个在Java 8发布前创建的用于在Java中处理日期和时间的库。它修正了java.util.Date和java.util.Calendar的缺陷:可变性、月份从0开始编号、缺乏完整的时区支持和非线程安全的格式化。

Joda-Time和java.time有什么区别?

Java.time(JSR-310)——是Joda-Time的继任者,由同一位作者开发。主要区别:java.time内置于Java 8+标准库中,使用更一致的命名系统,支持替代日历系统,并包含用于处理UTC时间的Instant类。

在新项目中是否值得使用Joda-Time?

不,对于新项目建议使用java.time。Joda-Time自2014年以来一直处于维护模式,不再获得新功能。对于API 26+的Android,java.time原生可用,对于旧版本,使用desugaring——一种允许在API 19+设备上使用java.time的机制。

如何将代码从Joda-Time迁移到java.time?

迁移分阶段进行:在抽象中隔离Joda-Time,首先替换LocalDate和LocalTime,然后替换DateTime → ZonedDateTime,最后阶段替换Period、Duration和Interval。OpenRewrite工具通过JodaTimeToJavaTime配方支持自动迁移,可将速度提高2-3倍。

Joda-Time的哪些类对应于java.time?

DateTime → ZonedDateTime,LocalDate → LocalDate(相同),LocalTime → LocalTime,LocalDateTime → LocalDateTime,DateTimeZone → ZoneId,Interval → 没有直接对应(使用Instant + Duration组合),Period → Period,Duration → Duration。方法名称也很接近:plusDaysminusMonthswithZone在两个库中都可用。

总结

  • Joda-Time — Java的日期和时间库,在Java 8发布前成为标准,并直接影响了JSR-310规范
  • 不可变设计 — 所有Joda-Time类都是不可变的,这保证了线程安全并防止了对象的意外修改
  • 关键类 — DateTime、LocalDate、LocalTime、LocalDateTime、Period、Duration和Interval涵盖了所有日期和时间处理场景
  • 时区支持 — 内置超过200个时区的数据库,自动处理夏令时转换和更新
  • 迁移到java.time — 推荐所有项目进行;分阶段进行,使用OpenRewrite工具和内置IDE工具
  • Android兼容性 — Joda-Time从API 1开始工作,但对于API 26+更推荐使用java.time,在旧版本上可通过desugaring使用
  • 历史意义 — Joda-Time改变了Java中API设计的方法,向社区展示了不可变值对象的优势

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读