必修! Date and Time API──Java SE 8の新日時APIの基本を学ぶ

Oracle Java & Developers編集部
2015-07-24 18:30:00
  • このエントリーをはてなブックマークに追加

Java SE 8では、日付や時刻を扱うための新APIとして「Date and Time API」が追加された。同APIの主なポイントについて、米国オラクルのJavaエバンジェリスト、アンジェラ・カイセド氏が解説する。

米国オラクル Javaエバンジェリストのアンジェラ・カイセド氏
米国オラクル Javaエバンジェリストのアンジェラ・カイセド氏

 2014年3月にリリースされたJava SE 8では、ラムダ式をはじめいくつかの重要な新機能が追加されたが、その1つに「Date and Time API」がある。JSR 310として標準化された同APIは、Javaプログラムにおける日付や時間の取り扱いを大幅に改善する、極めて重要な機能だ。米国オラクルでJavaエバンジェリストを務めるアンジェラ・カイセド氏による同APIの解説をお届けする。

※本記事は、日本オラクルが2015年4月に開催した「Java Day Tokyo 2015」におけるアンジェラ・カイセド氏のセッション「The Java Time API - What You Need To Know」の内容を基に構成しています。

なぜ新しい日付/時刻用APIが必要だったのか?

 Java SEには、古くからクラスjava.util.Date、java.util.Calendarを中心とする日付/時刻を扱うためのAPIが用意されていた。それにもかかわらず、なぜ今になって新しいAPIが追加されたのか? その理由を、カイセド氏は次のように説明する。

 「新しいAPIを作った理由は、これまでのAPIが日時を正確に扱うには不完全であり、さまざまな問題を抱えていたからです。私たちは、それらの問題を解消した新しいAPIを設計する必要性に迫られていました。そうして生まれたのがJSR 310: Data and Time APIなのです」

 JSR 310は、主に次のような特徴を備える。これらは、まさに従来のAPIに不足していたものだ。

  • 扱いやすく、豊かな表現が可能な設計(パイプライン処理が行える)
  • インスタンスがImmutable
  • スレッドセーフ
  • 型付けが強い

 なお、"新しいAPI"と表現しているように、JSR 310は既存のAPIの変更ではなく、従来のものとは別に、まったく新たなパッケージやクラス、インタフェースが定義されている。

ISOカレンダーを扱うための基本となるクラス群──パッケージjava.time

 Date and Time APIの基本となるのは、ISOカレンダーを扱うためのクラス群であり、これらはパッケージjava.timeに集約されている。新しいクラスと従来のクラスとの対応関係は次のようになる。

【表1:新しい日付/時刻関連クラスと従来のクラスの対応関係】

java.time ISO Calendar java.util Calendar
Instant Date
LocalDate,
LocalTime,
LocalDateTime
Calendar
ZonedDateTime Calendar
OffsetDateTime, OffsetTime, Calendar
ZoneId, ZoneOffset, ZoneRules TimeZone
Week Starts on Monday (1 .. 7)
enum MONDAY, TUESDAY, … SUNDAY
Week Starts on Sunday  (1 .. 7)
int values SUNDAY, MONDAY, … SATURDAY
12 Months  (1 .. 12)
enum JANUARY, FEBRUARY, …, DECEMBER
12 Months (0 .. 11)
int values JANUARY, FEBRUARY, … DECEMBER

 また、新しいAPIでは、メソッド名をわかりやすくするための整理が行われており、基本的に次に示す命名規則にのっとっているという。

【表2:Date and Time APIのメソッド名の命名規則】

接頭辞 メソッド・タイプ 備考
of static factory ファクトリは、まず入力値を検証してインスタンスを生成する(変換は行わない)
from static factory 入力パラメータをターゲット・クラスのインスタンスに変換する(入力値の情報が失われる可能性がある)
date
dateNow
static factory 引数または現在時刻を基に日時を生成する
parse static factory ターゲット・クラスのインスタンスを生成するために、入力文字列を解析する
format instance オブジェクトの値を文字列として整形するためにフォーマッタを使用する
get instance ターゲット・オブジェクトの状態の一部を返す
with instance 変更要素が1つのターゲット・オブジェクトのコピーを返す(値は不変)
plus
minus
instance 日時を加算または減算したうえでターゲット・オブジェクトのコピーを返す
to instance オブジェクトを別の型に変換する
at instance オブジェクトを別のオブジェクトと連結する
isBefore
isAfter
isEqual
Instance オブジェクトの日時を別のオブジェクトと比較する

 ISOカレンダーのための主なクラスには、次のようなものがある。


※クリックすると拡大画像が見られます

 このうち、名前が「Local」で始まるクラスが最も基本的なものであり、これらは時差やタイムゾーンをサポートしていない。時差やタイムゾーンを考慮する必要がある場合は、名前が「Offset」で始まる2つのクラスや、クラスZonedDateTimeを使う必要がある。

 また、上記のほかに曜日を表す列挙型としてクラスDayOfWeekが、月を表す列挙型としてクラスMonthが用意されている。クラスDayOfWeekでは月曜から日曜までのそれぞれに1から7の定数が割り当てられており、クラスMonthでは1月から12月までのそれぞれに1から12の定数が割り当てられている。これは従来の定数の割り当て方とは異なる(従来は日曜が1、1月が0だった)ので注意が必要だ。

 次に示すのは、クラスLocalDateの使用例である。LocalDateは年月日を表すクラスであり、時、分、秒なしで日付だけを扱う場合にはこのクラスを使用する。

【リスト1:クラスLocalDateの使用例】
LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY)); DayOfWeek dotw = LocalDate.of(2012, Month.JULY, 9).getDayOfWeek();
●戻り値
Monday

 また、次のコードはクラスYearMonthの使用例だ。YearMonthは年月を表すクラスであり、この例ではメソッドlengthOfMonthによって指定した月の日数を取得している。

【リスト2:クラスYearMonthの使用例】
YearMonth date = YearMonth.now(); System.out.printf("%s: %d%n", date, date.lengthOfMonth()); YearMonth date2 = YearMonth.of(2010, Month.FEBRUARY); System.out.printf("%s: %d%n", date2, date2.lengthOfMonth());
●出力
2013-06: 30 2010-02: 28

 日付なしで時刻だけを扱う場合には、クラスLocalTimeを使用する。次のコードは、同クラスを使って時刻を連続して出力するコードの例である。

【リスト3:クラスLocalTimeの使用例】
LocalTime thisSec; for (;;) { thisSec = LocalTime.now(); // implementation of display code is left to the reader display(thisSec.getHour(), thisSec.getMinute(), thisSec.getSecond()); }

 一方、日付と時刻を合わせて扱う場合にはクラスLocalDateTimeを使用する。次に示すのは同クラスの使用例だ。

【リスト4:クラスLocalDateTimeの使用例】
System.out.printf("now: %s%n", LocalDateTime.now()); System.out.printf("Apr 15, 1994 @ 11:30am: %s%n”, LocalDateTime.of(1994, Month.APRIL, 15, 11, 30)); System.out.printf("now (from Instant): %s%n”, LocalDateTime.ofInstant(Instant.now(),ZoneId.systemDefault())); System.out.printf("6 months from now: %s%n”, LocalDateTime.now().plusMonths(6)); System.out.printf("6 months ago: %s%n”, LocalDateTime.now().minusMonths(6));
●出力
now: 2013-07-24T17:13:59.985 Apr 15, 1994 @ 11:30am: 1994-04-15T11:30 now (from Instant): 2013-07-24T17:14:00.479 6 months from now: 2014-01-24T17:14:00.480 6 months ago: 2013-01-24T17:14:00.481

タイムゾーンと時差を扱うためのクラス群

 タイムゾーンと時差を扱うためのAPIも一新された。カイセド氏が紹介したタイムゾーンの取り扱いに関係するクラスをまとめると、次のようになる。

  • クラスZoneId:タイムゾーンを一意的に区別するためのタイムゾーンIDを表す。InstantとLocalTimeZoneの変換ルールを決めるために使われる
  • クラスZoneOffset:UTC/グリニッジからの時差を表す
  • クラスZonedDateTime:タイムゾーンを含む日時を扱う
  • クラスOffsetDateTime:タイムゾーンIDを持たず、時差だけを含む日時を扱う
  • クラスOffsetTime:タイムゾーンIDを持たず、時差だけを含む時間を扱う

 「先にご紹介したクラスLocalDateやLocalDateTimeなどは時差をサポートしていないため、時差を考慮する必要があるプログラムではこれらのクラスを使用します。その際、特にタイムゾーンを考慮する必要がある場合には、クラスZonedDateTimeを使う必要があります」(カイセド氏)

 例えば、米国サンフランシスコから東京へ飛行機で移動する際、出発および到着の現地時間を取得するプログラムをクラスZonedDateTimeを使って書くと、次のようになる。

【リスト5:クラスZonedDateTimeの使用例(出発時刻を取得)】
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a"); // Leaving from San Francisco on July 20, 2013, at 7:30 p.m. LocalDateTime leaving = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); ZoneId leavingZone = ZoneId.of("America/Los_Angeles"); ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone); try { String out1 = departure.format(format); System.out.printf("LEAVING: %s (%s)%n", out1, leavingZone); } catch (DateTimeException exc) { System.out.printf("%s can't be formatted!%n", departure); throw exc; }
●出力
LEAVING: Jul 20 2013 07:30 PM (America/Los_Angeles)
【リスト6:クラスZonedDateTimeの使用例(到着時刻を取得)】
ZoneId arrivingZone = ZoneId.of("Asia/Tokyo"); // Flight is 10 hours and 50 minutes, or 650 minutes ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone).plusMinutes(650); try { String out2 = arrival.format(format); System.out.printf("ARRIVING: %s (%s)%n", out2, arrivingZone); } catch (DateTimeException exc) { System.out.printf("%s can't be formatted!%n", arrival); throw exc; } if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant())) System.out.printf(" (%s daylight saving time will be in effect.)%n”, arrivingZone); else System.out.printf(" (%s standard time will be in effect.)%n”, arrivingZone); }
●出力
ARRIVING: Jul 21 2013 10:20 PM (Asia/Tokyo) (Asia/Tokyo standard time will be in effect.)

 ご覧のとおり、出発地はサンフランシスコであるため、タイムゾーンを「America/Los_Angeles」として時刻を取得する。一方、到着地は東京なので、タイムゾーンに「Asia/Tokyo」を指定して時刻を取得する。

 また、クラスOffsetDateTimeを使ったコードの例は次のようになる。

【リスト7:クラスOffsetDateTimeの使用例】
LocalDateTime localDate = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); ZoneOffset offset = ZoneOffset.of("-08:00"); OffsetDateTime offsetDate = OffsetDateTime.of(localDate, offset); OffsetDateTime lastThursday = offsetDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY)); System.out.printf("The last Thursday in July 2013 is the %sth.%n", lastThursday.getDayOfMonth());
●出力
The last Thursday in July 2013 is the 25th.

 このコードでは、まず2013年7月20日19時30分を表すLocalDateTimeオブジェクトを生成したうえで、そこからマイナス8時間の時差を設定したOffsetDateTimeオブジェクトを生成している。そして、後ほど紹介するクラスTemporalAdjustesを使って「7月の最後の木曜日」の日付を特定している。