旧日時APIとの相互変換や、Date-Time APIの機能をJava 6/7でも使えるように移植したライブラリ「ThreeTen Backport」などの使い方について解説します。
2014年3月にリリースされたJava 8では、その前まで日時を扱っていたjava.util.Dateやjava.util.Calendarなどとは異なる、全く新しい日時を扱うAPIとしてDate-Time API が追加されました。本連載では、このDate-Time APIについて一般的な業務システムを構築する際に必要な情報について簡単に見ていきます。
前々回の「ImmutableでスレッドセーフになったJavaの新しい日時APIの基礎知識」では、Date-Time APIの概要や、Java 8より前の旧日時APIから何が改善されたのかに加え、新しく追加されたさまざまなクラスについて解説しました。前回の「Java 8日時APIの主なメソッドとフォーマット用のパターン文字の使い方」ではJava 8より導入されたDate-Time APIで何ができるのかを探るため、用意されているメソッドについて見ていき、実際にどのように使うのかを解説しました。
最終回の今回は、Java 8より導入されたDate-Time APIとそれより前に使われていたjava.util.Dateやjava.util.Calendarなどの旧日時APIとの相互変換について見ていきます。また、Java 6/7でもDate-Time APIの機能が使えるためのバックポート(過去バージョンへの移植)である「ThreeTen Backport」についても見ていきます。
※本連載ではJava 8より追加されたjava.timeパッケージ以下のAPIを「Date-Time API」と呼んでいき、それより前のjava.util.Dateなどの日時のAPIを「旧日時API」と呼んでいます。また、実行環境のタイムゾーンは日本になっています。
Java 8がリリースされる前のバージョンでは、日時を扱うクラスとしてjava.util.Dateやjava.util.Calendarなどのjava.util.パッケージにあるクラスや文字列との変換を行うためのjava.text.DateFormatを実装したクラスなどが提供されていました。しかし、連載第1回で述べたように、これらの旧日時APIのクラスはスレッドセーフでなかったり、用意されているAPIの機能が乏しかったりと、さまざまな欠点があるAPIとして考えられています。
そして、Java 8より旧日時APIの欠点を補った新しい日時を扱うAPIとして、Date-Time APIが追加されました。
一方で、2014年12月の本稿執筆時点では、稼働しているほとんどのシステムがJava 8のリリース前に構築されたため、旧日時APIのものが使われているのが現状です。
仮に、新たに追加部分をJava 8のDate-Time APIを使って開発できたとしても、現在稼働しているシステムに追加する際に、新しくDate-Time APIで追加する箇所と現行の旧日時APIで稼働している箇所との接続部分で、お互いの型の変換する必要が出てきます。
他にも、システムで使用しているフレームワークがJava 8に対応していないなど、全てをDate-Time APIに置き換えることが難しい可能性もあります。そのため、新たにDate-Time APIを使って日時の処理を書き換えたとしても、どこかで旧日時APIとの変換が必要になるケースが考えられます。
ここでは、このようなDate-Time APIのクラスと旧日時APIとの変換が必要になった場合に、どのようにこれらのクラスとの型の変換を行うのかについて見ていきます。
Java 8から導入されたDate-Time APIは旧日時APIで扱っていた日付と時刻の処理とは違う、全く新しい思想でデザインされたAPIであるため、旧日時APIで行っていた処理をそのままDate-Time APIのクラスの処理に移行できるものではありません。また、変換を行うためのメソッドの多くは旧日時APIに追加されており、Date-Time APIには旧日時APIとの変換するメソッドがほとんどありません。
それでは、Date-Time APIと旧日時APIとの変換するために必要な知識として、まずは旧日時APIがどのようなクラスであるか、見てみましょう。
旧日時APIでは、日時を扱うクラスとして、エポック(1970年1月1日 00:00:00 GMT)からの経過時間を情報として持つjava.util.Dateとその経過時間とさらに年、月、日、時、分、秒、ナノ秒やタイムゾーンの情報なども持っているjava.util.Calendarを使って日時を扱ってきました。
このDateクラスはエポックからの経過時間(タイムライン)を情報として持っています。JDK 1.1より前では日時の情報からDateを生成できましたが、現在はそれらのメソッドは非推奨となっています。また、toStringで文字列を生成すると日時の情報が表示されますが、これはシステムのタイムゾーン情報から換算した日時の情報を出力しています。例えば、システムとは違うタイムゾーンのCalendarからDateを生成しても、toStringで出力される文字列にはシステムのタイムゾーン情報のものになっています。
次に、よく使われるクラスにCalendarがありますが、これは抽象クラスであり、getInstanceメソッドで実装されたCalendarクラスを返します。getInstanceメソッドの引数に特定のロケールやタイムゾーンを設定しない場合java.util.GregorianCalendarが返ってきます。
また、日本で稼働しているシステムの場合、Java 6より追加されたjava.util.JapaneseImperialCalendarが使われている可能性もあります。これはgetInstanceメソッドの引数に特定の「Locale(new Locale("ja","JP","JP"))」を渡さないと生成されないpublicではないクラスです。GregorianCalendarとは別のクラスなので注意してください。
そして、Calendarはタイムゾーンの情報としてjava.util.TimeZoneクラスを持っています。デフォルトではシステムのタイムゾーン情報が設定されていますが、getInstanceメソッドでTimeZoneを設定することで特定のタイムゾーンを持つCalendarのインスタンスを生成することが可能です。
次の例ではCalendarでの時間の持ち方を見るために、同じタイムラインを持つCalendarでタイムゾーンを変えた場合を見ています。
Date date = new Date(); long timeline = date.getTime(); // タイムラインの取得 Calendar calendar = Calendar.getInstance(); calendar.setTime(date); System.out.println("calendar=" + calendar); System.out.println(); System.out.println("タイムゾーンを変更"); Calendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); calendar2.setTimeInMillis(timeline); // 同じタイムラインを設定 System.out.println("calendar2=" + calendar2);
calendar=java.util.GregorianCalendar[time=1421547282631,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=0,WEEK_OF_YEAR=4,WEEK_OF_MONTH=4,DAY_OF_MONTH=18,DAY_OF_YEAR=18,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=11,HOUR_OF_DAY=11,MINUTE=14,SECOND=42,MILLISECOND=631,ZONE_OFFSET=32400000,DST_OFFSET=0]
calendar2=java.util.GregorianCalendar[time=1421547282631,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=0,WEEK_OF_YEAR=3,WEEK_OF_MONTH=3,DAY_OF_MONTH=17,DAY_OF_YEAR=17,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=6,HOUR_OF_DAY=18,MINUTE=14,SECOND=42,MILLISECOND=631,ZONE_OFFSET=-28800000,DST_OFFSET=0]
また、上記の各Calendarから取得したDateがタイムゾーンの影響を受けているのか見てみます。
Date date = new Date(); long timeline = date.getTime(); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); System.out.println("date.getTime()=" + date.getTime()); System.out.println("date=" + date); System.out.println(); System.out.println("タイムゾーンを変更"); Calendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); calendar2.setTimeInMillis(timeline); Date date2 = calendar2.getTime(); System.out.println("date2.getTime()=" + date2.getTime()); System.out.println("date2=" + date2);
date.getTime()=1421548039130 date=Sun Jan 18 11:27:19 JST 2015
date2.getTime()=1421548039130 date2=Sun Jan 18 11:27:19 JST 2015
このようにDateはタイムライン(エポックから経過時間)しか持っていないため、文字列で表示した場合に実行環境でのタイムゾーンで見た日時の情報が表示されるのですが、Calendarではタイムゾーンとタイムラインの情報を持っており、さらにそれらから換算されたさまざまな項目の情報を持っていることが分かります。
Copyright © ITmedia, Inc. All Rights Reserved.