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の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にパスを通します。
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 Bakportのバージョン1.2の既知の問題として主に下記があります。
特に日本で稼働するシステムを開発している場合の問題として、和暦である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からさまざまな点が改善され、今後使っていく機会が増えていくことかと思います。今までお付き合いくださり、ありがとうございました。
株式会社ビーブレイクシステムズ開発部所属。
社内サークル執筆チーム在籍
初心者のためのJavaラムダ式入門とJDKのインストール、IDEの環境構築
「プログラマーって何するのが仕事なの?」と聞かれたときや、初心者がプログラミングを学ぶ前に読んでほしいマンガ「じゃまめくん」とは
Eclipse 3.4で超簡単Javaプログラミング基礎入門Copyright © ITmedia, Inc. All Rights Reserved.