旧日時APIとの相互変換&Java 6/7でDate-Time APIが使えるライブラリThreeTen Backportここが大変だよJava 8 Date-Time API(終)(3/3 ページ)

» 2015年04月07日 05時00分 公開
[長谷川智之株式会社ビーブレイクシステムズ]
前のページへ 1|2|3       

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));
    }
}
サンプル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
実行結果1

 これを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と同じ記述)……
 
    }
}
サンプル2
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
実行結果2

 また、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
実行結果(ThreeTen Backportから)

 これと同じコードをJava 8のDate-Time APIを使って出力すると正しく表示されるようになり、次の結果になります。

西暦 14/12/25
平成 26/12/25
実行結果(Date-Time APIから)

 このように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からさまざまな点が改善され、今後使っていく機会が増えていくことかと思います。今までお付き合いくださり、ありがとうございました。

著者紹介

長谷川 智之(はせがわ ともゆき)

株式会社ビーブレイクシステムズ開発部所属。

社内サークル執筆チーム在籍

主な執筆


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。