Joda-Timeは、Stephen Colebourneが手間のかかるjava.util.Dateやjava.util.Calendarの代替として作成した、Javaで日付や時刻を扱うためのライブラリです。Java 8のリリース以前、このライブラリはAndroidアプリケーションを含む産業レベルの開発における日付処理の事実上の標準でした。Oracle Java Magazine(2023)によると、Android API 26のリリース後2年以内に60%以上のプロジェクトがJoda-Timeからjava.timeに移行しました。
ポイント
Joda-Timeは、Java言語でjava.util.Dateやjava.util.Calendarクラスの品質高い代替を提供するオープンソースライブラリ (Apache 2.0) です。このプロジェクトは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は初めてのパブリックリリースの前に、1年以上にわたって製品プロジェクトでJoda-Timeをテストしました。初版は2004年にリリースされ、すぐにコミュニティの注目を集めました。2010年までに、SpringやHibernateなどのフレームワークを含む大多数のJavaプロジェクトで、このライブラリは標準的な依存関係となりました。Joda-Timeの成功は、Java 8に含まれたJSR-310仮格 (Date and Time API) に直接影響を与えました — 同じ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(2つの瞬間の間の期間)、Period(カレンダー単位での差)、Duration(ミリ秒での正確な長さ)。
Joda-TimeのDateTimeFormatterクラスは、java.text.SimpleDateFormatとは異なり、スレッドセーフな日付フォーマットを提供します。フォーマッタは、パターン(例えば、“yyyy-MM-dd HH:mm:ss”)またはロケール出力のためのスタイル(SHORT、MEDIUM、LONG、FULL)を通じて作成できます。また、フォーマッタのキャッシングをサポートしており、高負荷アプリケーションでのパフォーマンスが向上します。文字列からの日付解析は、厳密さの制御で実行され、不完全または不正な日付を許可または拒否できます。
Joda-Timeの強みの一つは、時間インターバルの扱いです。Intervalクラスは、2つの瞬間の間の正確な時間の期間を表し、置き合わせや結合、収められているかの確認をサポートします。PeriodはIntervalとは異なり、カレンダー単位で動作します。例えば、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標準ライブラリに完全統合されており、外部依存は不要で、より一貫した名称付けシステムを使用しています。ReadableInstantの代わりにInstant、LocalDateの代わりにLocalDate(名称は保持)、DateTimeの代わりにZonedDateTimeです。
| 特徴 | Joda-Time | java.time |
|---|---|---|
| リリース年 | 2004 | 2014 (Java 8) |
| 状態 | メンテナンス | 現在も開発中 |
| 基本日付/時刻 | DateTime | ZonedDateTime |
| 日付のみ | LocalDate | LocalDate |
| 不変性 | はい | はい |
| フォーマット | DateTimeFormatter | DateTimeFormatter |
| 日付の差 | Period / Duration | Period / Duration |
java.timeは、Joda-Timeのいくつかのアーキテクチャ的決定を修正しています。まず、java.timeはnullを返すメソッドと欠落値の混乱を解消し、代わりにOptionalを使用します。次に、Chronologyインターフェースを通じて、代替カレンダーシステム(日本、タイ、イスラム)のサポートが追加されています。さらに、java.timeにはカレンダーに縛られずにUTC時刻を扱うためのInstantクラスが含まれており、ネットワークプロトコルやデータベースとの統合が簡素化されます。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。主な変更は、オブジェクトの作成方法とメソッド名に関係しています。例えば、`now.toDate()`の代わりに`Date.from(instant)`を使い、`DateTime.now()`は`ZonedDateTime.now()`に置き換えられます。
移行は、Joda-Timeを薄い抽象レイヤーにアイソレートすることから始めることをお勧めします。これにより、コードベース全体を触らずに、ライブラリを段階的に置き換えることができます。最初のステップは通常、LocalDateとLocalTimeの扱いを置き換えることです。これらのクラスは、両ライブラリ間の違いが最も少ないからです。第2段階ではDateTime(ZonedDateTimeへ)とタイムゾーンを移行します。第3段階では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 (Moderne作) はorg.openrewrite.java.migrate.JodaTimeToJavaTimeレシピを通じて自動移行をサポートします。プログラムのロジックを保持しながら、クラス、メソッド、インポートを置き換えます。Gradleプロジェクトでは、de.fayard.refreshVersionsプラグインにも移行ルールが含まれています。手動では、IDEAを使って移行するのが最も簡単です。Java Time Migration AssistantプラグインがJoda-Timeの呼出しをハイライトし、対応するjava.timeの置き換えを提案します。手動の置き換えに比べて2–3倍速くなります。
AndroidアプリケーションでJoda-Timeを使った実践的な例を見てみましょう。このライブラリは、ローカル標準に従った日付フォーマット、ユーザーの年齢計算、イベントの期間確定、2つの日付間のインターバル計算に特に役立ちます。すべての例では不変オブジェクトを使用しており、各呼出しで新しいインスタンスが返され、元のオブジェクトは変わりません。
// ユーザーの年齢を計算
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の操作をサポートしており、abutsはインターバルが境界で接しているかを示します。Durationは、2つの瞬間の間の長さをミリ秒単位で測るために使用されます。
// インターバルの重なりを確認
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); // ヌル (隙間なし)
よくある質問
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年からメンテナンスモードで、新しい機能は追加されていません。Android API 26+では、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アプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。