旧日時APIとの相互変換&Java 6/7でDate-Time APIが使えるライブラリThreeTen Backport:ここが大変だよJava 8 Date-Time API(終)(3/3 ページ)
旧日時APIとの相互変換や、Date-Time APIの機能をJava 6/7でも使えるように移植したライブラリ「ThreeTen Backport」などの使い方について解説します。
Date-Time APIの機能をJava 6/7でも使えるように移植した「ThreeTen Backport」
2014年12月執筆時点で稼働中のシステムの場合、使われているJavaのバージョンはJava 8より前のものがほとんどです。そして、そのシステムで使われているバージョンは簡単に変えられないケースも多くあります。しかし、追加で開発する場合など、Java 8のDate-Time APIの機能が使えれば、効率よく開発できる場合があるかもしれません。
そのような場合、Date-Time APIの機能をJava 6/7でも使えるように移植したライブラリ「ThreeTen Backport」を使うことでDate-Time APIの機能の多くが使えるようになります。ThreeTen BackportはDate-Time APIの開発の中心人物であるStephen Colebourne氏によって管理されています。このライブラリには、いくつかの制限はあるものの(後述)、Date-Time APIで提供しているほとんどの機能が移植されています。
ThreeTen Backportの導入方法
ThreeTen Backportは他のライブラリとの依存はありません。単純にThreeTen Backportのjarを取得して、パスを通すだけで使えるようになります。それでは、このThreeTen Backportについて見ていきましょう。
ThreeTen BackportのライブラリはMavenから入手することが可能です。
<dependency> <groupId>org.threeten</groupId> <artifactId>threetenbp</artifactId> <version>1.2</version> </dependency>
また、ホームページからMavenのダウンロードページに行き、手動でライブラリのjarをダウンロードすることも可能です。
まず、ホームページより「Download」をクリックします。
次に、ダウンロードページより対象のjarをダウンロードします。
続いて、ダウンロードしたjarを任意の場所に置きます(ここではEclipseのプロジェクトに「lib」フォルダを作ってjarを置いています)。そしてパスを通す準備をします。
最後に、ThreeTen Backportのjarにパスを通します。
ThreeTen Backportの使い方
Date-Time APIのほとんどのクラスはThreeTen Backportにも用意されていて、使う側からしてみると基本的にはパッケージが違うだけで、使い方はDate-Time APIとほぼ同じです。
ただし、Date-Time APIではJava 8から導入された機能を使って実装されているものもあるので、そのようなものは別の方法で対応されています。例えば、デフォルトメソッドを使って実装されているDate-Time APIのインターフェースはThreeTen Backportでは抽象クラスに変えるなどによって対応されています。
Date-Time APIとThreeTen Backportのパッケージの対比は下記の表になります。
Date-Time API | ThreeTen Backport |
---|---|
java.time | org.threeten.bp |
java.time.chrono | org.threeten.bp.chrono |
java.time.format | org.threeten.bp.format |
java.time.temporal | org.threeten.bp.temporal |
java.time.zone | org.threeten.bp.zone |
ThreeTen Backportを使う場合は、Java 8から導入されたラムダ式など新しく追加されたJavaの機能を使わなければ、単純にパッケージが変わっただけでDate-Time APIと同じコーディングになります。
例えば、Java 8で次のコードを書いたとします。
package jp.co.atmark.datetimeapi.sample03; import java.time.LocalDateTime; import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; public class ThreetenSample01 { public static void main(String[] args) { System.out.println("LocalDateTimeの例"); LocalDateTime localDateTime1 = LocalDateTime.of(2014, 1, 1, 0, 0, 0, 0); LocalDateTime localDateTime2 = LocalDateTime.of(2014, 2, 28, 3, 4, 5, 6); System.out.println("localDateTime1=" + localDateTime1); System.out.println("localDateTime2=" + localDateTime2); System.out.println(); System.out.println("ZonedDateTimeの例"); ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneId.of("UTC")); ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneId.systemDefault()); System.out.println("zonedDateTime1=" + zonedDateTime1); System.out.println("zonedDateTime2=" + zonedDateTime2); System.out.println(); System.out.println("日数の計算"); Period period = Period.between(localDateTime1.toLocalDate(), localDateTime2.toLocalDate()); System.out.println("Period.between(localDateTime1.toLocalDate(), localDateTime2.toLocalDate())=" + period); long days = ChronoUnit.DAYS.between(localDateTime1, localDateTime2); System.out.println("ChronoUnit.DAYS.between(localDateTime1, localDateTime2)=" + days); System.out.println(); System.out.println("Formatterの例"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu/M/d HH:mm:ss"); System.out.println("formatter.format(localDateTime1)=" + formatter.format(localDateTime1)); System.out.println("formatter.format(localDateTime2)=" + formatter.format(localDateTime2)); } }
LocalDateTimeの例 localDateTime1=2014-01-01T00:00 localDateTime2=2014-02-28T03:04:05.000000006 ZonedDateTimeの例 zonedDateTime1=2014-01-01T00:00Z[UTC] zonedDateTime2=2014-02-28T03:04:05.000000006+09:00[Asia/Tokyo] 日数の計算 Period.between(localDateTime1.toLocalDate(), localDateTime2.toLocalDate())=P1M27D ChronoUnit.DAYS.between(localDateTime1, localDateTime2)=58 Formatterの例 formatter.format(localDateTime1)=2014/1/1 00:00:00 formatter.format(localDateTime2)=2014/2/28 03:04:05
これをJava 6/7で稼働するようにThreeTenBackportで書き換える場合、次のようになります。
package jp.co.atmark.datetimeapi.sample03; import org.threeten.bp.LocalDateTime; import org.threeten.bp.Period; import org.threeten.bp.ZoneId; import org.threeten.bp.ZonedDateTime; import org.threeten.bp.format.DateTimeFormatter; import org.threeten.bp.temporal.ChronoUnit; public class ThreeTenSample01 { public static void main(String[] args) { ……省略(サンプル1と同じ記述)…… } }
LocalDateTimeの例 localDateTime1=2014-01-01T00:00 localDateTime2=2014-02-28T03:04:05.000000006 ZonedDateTimeの例 zonedDateTime1=2014-01-01T00:00Z[UTC] zonedDateTime2=2014-02-28T03:04:05.000000006+09:00[Asia/Tokyo] 日数の計算 Period.between(localDateTime1.toLocalDate(), localDateTime2.toLocalDate())=P1M27D ChronoUnit.DAYS.between(localDateTime1, localDateTime2)=58 Formatterの例 formatter.format(localDateTime1)=2014/1/1 00:00:00 formatter.format(localDateTime2)=2014/2/28 03:04:05
また、LocalDateTimeクラスのような日付や時刻を表すクラスが持つfromメソッドは、TemporalQueryインターフェースを実装する際にメソッド参照として使われることが多いメソッドです。そのため、ThreeTen Backportでは日時を表す各クラスにFROMというTemporalQueryを実装した定数を持ち、メソッド参照と同じような動きをさせています。
例えば、次のコードではDate-Time APIだとメソッド参照の「OffsetTime::from」を使う箇所を「OffsetTime.FROM」で代用しています。
package jp.co.atmark.datetimeapi.sample03; import org.threeten.bp.LocalDateTime; import org.threeten.bp.OffsetTime; import org.threeten.bp.ZoneId; import org.threeten.bp.ZonedDateTime; public class ThreeTenSample02 { public static void main(String[] args) { LocalDateTime localDateTime = LocalDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles")); OffsetTime offsetTime = zonedDateTime.query(OffsetTime.FROM); System.out.println("localDateTime=" + localDateTime); System.out.println("zonedDateTime=" + zonedDateTime); System.out.println("offsetTime=" + offsetTime); } }
localDateTime=2014-12-23T01:59:19.469 zonedDateTime=2014-12-23T01:59:19.469-08:00[America/Los_Angeles] offsetTime=01:59:19.469-08:00
ThreeTen Backportの制限
ThreeTen Bakportのバージョン1.2の既知の問題として主に下記があります。
- 文字列からの解析や文字列への変換に問題がある
- ZoneIdや文字列の解析はJava 8のものほどそろっていない
- ヒジュラ歴が機能していない
特に日本で稼働するシステムを開発している場合の問題として、和暦であるJapaneseDateを使い、年号を取得するためにDateTimeFormatterを使って文字列に変換しても、年号が意図した内容で表示されません。次の例はThreeTen BakportのDateTimeFormatterを使ってJapaneseDateを出力したサンプルです。
LocalDate localDate = LocalDate.of(2014, 12, 25); JapaneseDate japaneseDate = JapaneseDate.from(localDate); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("GGGG yy/MM/dd"); System.out.println(localDate.format(formatter)); System.out.println(japaneseDate.format(formatter));
西暦 14/12/25 2 26/12/25
これと同じコードをJava 8のDate-Time APIを使って出力すると正しく表示されるようになり、次の結果になります。
西暦 14/12/25 平成 26/12/25
このようにThreeTen Backportを使う際は、Date-Time APIの機能の多くは使えるのですが、全てができるわけではないので、注意してください。
おわりに
今回はJava 8から導入されたDate-Time APIのクラスと旧日時APIのクラスとの相互変換と、Date-Time APIの機能をJava 6/7で使えるように移行したThreeTen Backportについて見てきました。
Date-Time APIの連載は今回で最後になります。これまでの連載ではJava 8から導入されたDate-Time APIについて、一般的な業務システムで使うのに必要になるであろう機能について主に見てきました。Date-Time APIは旧日時APIからさまざまな点が改善され、今後使っていく機会が増えていくことかと思います。今までお付き合いくださり、ありがとうございました。
著者紹介
長谷川 智之(はせがわ ともゆき)
株式会社ビーブレイクシステムズ開発部所属。
社内サークル執筆チーム在籍
主な執筆
- @IT連載『Javaの常識を変えるPlay framework入門』
- 日経ソフトウェア連載『コツコツ学ぶAndroidネイティブアプリ開発教室』※複数著者での連載
- @IT連載『Java 8はラムダ式でここまで変わる』
- CodeZine連載『即戦力にならないといけない人のためのJava入門』
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 初心者のためのJavaラムダ式入門とJDKのインストール、IDEの環境構築
本連載では、今までJavaの経験はあっても「ラムダ式は、まだ知らない」という人を対象にラムダ式について解説していきます。初回は、ラムダ式の概要と利点、必要性、JDK 8のセットアップ、NetBeans、IntelliJ IDEA、Eclipseの環境構築について。 - 「プログラマーって何するのが仕事なの?」と聞かれたときや、初心者がプログラミングを学ぶ前に読んでほしいマンガ「じゃまめくん」とは
人気過去連載を一冊に再編集して無料ダウンロード提供する@IT eBookシリーズ。Vol.6は、プログラミング初心者が、プログラミングを学ぶ前に読んでほしいマンガ連載『オブジェマンガ じゃまめくん』だ。 - Eclipse 3.4で超簡単Javaプログラミング基礎入門
これからプログラミングを学習したい方、Javaは難しそうでとっつきづらいという方のためのJavaプログラミング超入門連載です。最新のEclipse 3.4とJava 6を使い大幅に情報量を増やした、連載「Eclipseではじめるプログラミング」の改訂版となります - 初心者におすすめのJava入門まとめ