ブログ

Joda-Timeからjava.timeへの移行

November 12, 2021
キャメロン・グレゴール
グレーのカーペットの上に座り、膝の上でノートパソコンのキーボードを操作する3人の様子を上から見た写真。

コード(いわゆるレガシーコード)の移行は楽しい作業ではありません。完了させるには膨大な計画と労力が必要です。開発者にとって最も刺激的でモチベーションの上がる仕事とは言えませんが、レガシーコードを新しいライブラリバージョンへ移行するには、決意と適切な経験が不可欠です。Joda-Timeからjava.timeへの移行も、綿密な計画と実行が求められる作業の一つです。

Java SE 8以前から存在するJavaプロジェクトで日付や時刻の処理を行っている場合、おそらくJoda-Timeが使用されているでしょう。これはSE 8以前において、日付・時刻関数を扱うための優れたライブラリであり、事実上の標準でした。もし現在もプロジェクトでJoda-Timeを使用しており、java.timeへの移行を検討されているなら、ぜひ続きをお読みください。

Java SE 8のリリースには、java.time(JSR-310)として一般的に知られる、改良された新しい標準の日付・時刻APIが含まれました。現在、Joda-Timeプロジェクトはjava.time(JSR-310)への移行を推奨しています。

java.time(JSR-310)はJoda-Timeから多大な影響を受けていますが、後方互換性はなく、概念や用語も変更されています。そのため、Joda-Timeからjava.timeへの移行には、変更するコードの1行1行に細心の注意を払う必要があります。これは時間がかかる作業であり、もっと簡単で自動化された移行方法があればと願いたくなるほどです。

より良い移行方法があります。私たちはIntelliJプラグイン「Sensei」を使用して、定義したレシピ(ルール)に従ってコード変換を自動化する仕組みを作成しました。反復的な移行作業に追われるのではなく、再利用可能なレシピの定義に時間を使いましょう。この自動化により、レガシーなJoda-Timeコードが変換されるだけでなく、チームが新しいコードを書く際にも、IDE上で直接ガイドラインに従えるようになります。

スムーズなスタートを切れるよう、私たちは公開Senseiクックブック「Standardization on java.time (JSR-310)」を作成しました。これには、Joda-Timeからjava.timeへ負担を抑えて移行するためのレシピが含まれています。このレシピ集は今後も継続的に拡充し、より広範囲をカバーしていく予定です。

Senseiがいかに簡単にレガシーコードを移行できるか、実際の移行例をご紹介します。

Video of how to set java.time zoned date time

手作業による反復的な移行から、自動化されたコード変換へ

新しいDateTimeを作成する例を見てみましょう。これには、Joda-Timeからjava.timeへ1行のコードを移行する際に潜むいくつかの落とし穴が示されています。次に、私たちの「Standardization on java.time (JSR-310)」クックブックからSenseiレシピの一つを取り上げ、この情報がどのように集約されているか、そして同じ移行作業を他の開発者が繰り返し再利用できるようにする方法を解説します。

この例では、DateTimeフィールドの値を表す7つのint引数から、Joda-TimeのDateTimeを構築しています。

これをjava.timeの同等機能へ移行するにはどうすればよいでしょうか?

Joda-Timeのこのコンストラクタに関するjavadocには、次のように記載されています。

デフォルトのタイムゾーンでISOChronologyを使用し、日時フィールドの値からインスタンスを構築します。

最初はjava.timeにもDateTimeクラスがあると思いがちですが、実際には存在しません。「migrate from joda time to java time」でGoogle検索すると、Stephen Colebourne氏のブログ記事「Converting from Joda-Time to java.time」が上位に表示されるはずです。

これは良い出発点となり、java.time.ZonedDateTimeまたはjava.time.OffsetDateTimeを使用する方向性を示してくれます。ここで最初の疑問が生じます。「どちらを使うべきか?」Stephen氏のコメントに基づけば、おそらくZonedDateTimeでしょう。

ZonedDateTimeのjavadocを確認しても、コンストラクタは一つも見当たりません。再びStephen氏のブログ記事を読み進めると、次のように書かれています。

‍構築。Joda-Timeにはオブジェクトを受け取って型変換を行うコンストラクタがありますが、java.timeにはファクトリメソッドしか存在しません。そのため、変換はユーザー側で対応する必要があります(ただし、文字列用にはparse()メソッドが用意されています)。

したがって、静的ファクトリメソッドが必要になります。静的メソッドを検索すると、非常に近いものが見つかりますが、完全に同じではありません。

元のJoda-TimeのDateTimeコンストラクタと同様に7つのint型引数を持ちますが、注意しないと重要な点を見落とします。7番目の引数はミリ秒ではなくナノ秒を期待するようになっています。これは、java.timeがJoda-Timeよりも精度を高め、ナノ秒単位で時間を計測するようになったためです。これは見落としがちな重要な詳細です。さらに、このメソッドはZoneIdを必要とするため、なぜ以前は不要だったのに今は必要なのかという疑問が生じます。

元のコンストラクタのJavadocにはデフォルトのタイムゾーンを使用すると記載されていましたが、デフォルトのZoneIdを取得する方法はあるのでしょうか?

The ZoneIdのJavadoc にはコンストラクタに関する記述はありませんが、静的メソッドを確認すると systemDefault()

ZoneIdの問題は解決しましたが、ミリ秒からナノ秒への変換はどうすべきでしょうか。java.util.concurrent.TimeUnitを使って変換できるかもしれません。

このメソッドはlong型を返しますが、私たちのメソッドはint型を期待しているため、変換の問題が残ります。単純に掛け算をしてみるのはどうでしょうか。

これでも動作しますが、少し場違いな印象を受けます。お気づきかもしれませんが、たった1行のコードを移行するためにかなりの時間と労力を費やしました。想像できるように、手作業で行うべき編集は他にもたくさんあり、状況は改善されません。

しかし、java.time APIをもう少し詳しく調べれば、より洗練された解決策が見つかるはずです。

ZonedDateTimeにはミリ秒を直接設定する明示的な方法はありませんが、with(TemporalField field, long newValue)メソッドを使い、TemporalFieldとしてChronoField.MILLI_OF_SECONDを指定することで実現可能です。

Javadocには、ナノ秒への変換を自動的に行うと記載されています。

このフィールドを使用して値を設定する場合、NANO_OF_SECONDに値を1,000,000倍して設定するのと同じ動作になります。

したがって、ファクトリメソッドのナノ秒引数に0を指定し、withメソッドを使用することで、元の値とミリ秒をすべて保持したZonedDateTimeを作成できます。

最終的な結果を見ると、コードを1行変更しただけのように見えますが、この1つの移行を調査するために費やした努力は、これだけでは伝わりません!

‍

レシピを作成して移行を迅速かつ簡単に

Senseiを使えば、苦労して得たこの知識を他の開発者と共有できます。これらの要件をすべて網羅したレシピを作成することで、Senseiユーザーはマウスをクリックするだけでこの移行を実行できるようになります。

Senseiのレシピは、主に3つのセクションで構成されています。

  • メタデータ
  • 検索
  • 利用可能な修正

この呼び出しをjava.time相当のコードに移行するのに役立つSenseiレシピ(YAMLレシピとしても表示可能)を見てみましょう。

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);

メタデータセクション

メタデータセクションには、レシピに関する情報と、その使用方法が記載されています。

検索セクション

Senseiレシピの検索セクションでは、そのレシピを適用するコード要素を指定します。

search:
 instanceCreation:
   args:
     1:
       type: int
     2:
       type: int
     3:
       type: int
     4:
       type: int
     5:
       type: int
     6:
       type: int
     7:
       type: int
   argCount: 7
   type: org.joda.time.DateTime

‍

この検索セクションでは、以下の設定を行っています。

  • 検索対象は instanceCreation、 つまりコンストラクタの使用箇所です。注:他にも多くの検索ターゲットが利用可能
  • です。コンストラクタには7つの引数が必要であり、これは argCount プロパティで指定されています。
  • args 1〜7はint型である必要があります。
  • 検索対象のコンストラクタは type org.joda.time.DateTime です。

‍

利用可能な修正セクション

availableFixesセクションでは、一致したコード要素に適用できる1つ以上の修正を指定できます。各修正には複数のアクションを含めることができ、今回の例では2つのアクションを実行する単一の修正を定義しています。

‍

  • 修正の name は「Quickfixes」メニューに表示され、このクイックフィックスを適用した際に何が起こるかを説明します。
  • actionsリストは、このクイックフィックスによって実行されるアクションを示します。
  • rewrite アクションは、Mustacheテンプレートを使用してコード要素を書き換えます。変数や文字列置換関数を使用できます。
  • modifyAssignedVariable アクションは、このコンストラクタが変数の値の代入に使用されているかどうかを確認します。使用されている場合、このアクションは変数の宣言型を type

‍

レシピを使用したコード変換

レシピを作成して有効にすると、コードがスキャンされ、適用可能なセグメントがハイライト表示されます。

In the screenshot below we can see the target constructor has been marked by Sensei. Hovering over the marked constructor shows us the Recipe shortDescription and the Quickfix option Migrate to java.time.ZonedDateTime

Migrate New Date Time

‍

Migrate to java.time.ZonedDateTime クイックフィックスを選択すると、レシピで指定したアクションに従ってコードが変換されます。

Zoned Date Time With Year Month Day Hour

‍

Senseiによる一括移行とチーム全体のコーディング標準化

上記の例からわかるように、たった1行のコードの移行にも、苦労して得た知識が必要になることがあります。Senseiはその知識を、チーム内で共有可能な実行可能なレシピやクックブックに変えることができます。一度限りの移行スプリントを計画することも、Joda-Timeコードを見つけるたびにjava.timeへ段階的に変換していくことも可能です。レシピの有効/無効を切り替えて段階的に移行を進めたり、Senseiがスキャンするファイルの範囲を調整したりと、柔軟な対応によりコード移行の負担を軽減できます。

ライブラリの移行は、Senseiを使ってプロジェクトを標準化する多くの方法のほんの一例に過ぎません。プルリクエストや自身のコーディング中に頻繁に遭遇するアンチパターンや、手動で行っているコード変換を常に監視できます。開発者が見落としがちな コーディングガイドライン がある場合、それをレシピに変換することで、承認済みのコード変換を開発者が自信を持って適用できるようになります。

If you have any questions, we’d love to hear from you! Join us on Slack at: sensei-scw.slack.com

タグライン

AI主導の開発をリリース前にガバナンスする

AIを活用したリスク測定、コミット時のセキュアコーディングポリシーの強制、そしてSDLC全体を通じたセキュアなデリバリーの加速を実現します。

デモを予約する
リソースライブラリ

他のブログ記事を見る

セキュアコーディング、AIガバナンス、ソフトウェアリスク管理に関する専門的なコンテンツをご覧ください。

すべて表示
導入事例
フィルターラベル
これはdivブロック内のテキストです。

Supercharged Security Awareness: How Tournaments are Inspiring Developers at Erste Group

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

詳細はこちら
導入事例
フィルターラベル
これはdivブロック内のテキストです。

Security as culture: How Blue Prism cultivates world-class secure developers

Learn how Blue Prism, the global leader in intelligent automation for the enterprise, used Secure Code Warrior's agile learning platform to create a security-first culture with their developers, achieve their business goals, and ship secure code at speed

詳細はこちら
導入事例
フィルターラベル
これはdivブロック内のテキストです。

セキュリティ文化の統一:Sageはいかにしてチャンピオンプログラムを構築したか

Sageがどのようにして柔軟かつ関係性を重視したアプローチでセキュリティを強化し、200名以上のセキュリティチャンピオンを育成して、目に見えるリスク低減を実現したのかをご紹介します。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

2026年サイバーセキュリティ意識向上月間:企業防衛に不可欠なAIセキュリティの理解

主要なAI企業の多くが、安全対策が追いつくまでの間、AI開発を世界的に減速させるよう公に呼びかける事態となっています。しかし現実には、深刻なセキュリティ問題はすでに顕在化しています。AI導入に伴う金銭的コストやリスクが増大し続ける中、多くのセキュリティリーダーや経営陣は差し迫った状況に追い込まれています。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

イネーブラー8:プログラムのブランディング

セキュアコーディングプログラムを成功させるには、優れたコンテンツ以上のものが必要です。イネーブラー8では、ブランディングによって参加を「憧れのステータスを得る体験」へと変える方法を解説します。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

すべての従業員が今、AIサイバーセキュリティの最前線に立っている

エンタープライズテクノロジーの状況は、手遅れになるまで誰も予測できなかったほどの速さで変化しています。私たちは今、人間が書いたコードと基本的なコパイロット支援の時代から、エージェント型開発ライフサイクル(ADLC)の時代へと正式に足を踏み入れました。自律型AIエージェントは、複数の機能にわたってかつてない効率性をもたらす一方で、全く新しい種類のセキュリティリスクや規制リスクも引き起こしています。

詳細はこちら

AIを活用した開発をリリース前にセキュアに

開発者のリスクを可視化し、ポリシーを適用することで、ソフトウェア開発ライフサイクル全体における脆弱性を未然に防ぎます。

デモを予約する
該当する項目が見つかりません。