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

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

特定の時刻を表すクラスInstant

 Date and Time APIには、従来のクラスDateに相当する特定時刻のスナップショットを表すクラスとしてInstantが用意されている。クラスDateと同様、1970年1月1日の00時00分00秒を起点(これを「エポックタイム」と呼ぶ)にして、そこからの経過時間をナノ秒単位の値で保持する。エポックタイム以前の時刻は負数として表される。

 Instantのインスタンスは、「ある時刻から何秒後」という形式で取得する。例えば、現時点から1時間後のInstantオブジェクトを取得したい場合は次のように書く。

【リスト8:1時間後のInstantオブジェクトを取得するコード】
Instant.now().plusHours(1);

 Instant.nowは現在時刻のInstantオブジェクトを取得するメソッドであり、plusHours(1)により、そこから1時間後のInstantオブジェクトを取得している。

 このほか、エポックタイムから現在までに経過した秒数を取得するコードや、InstantをLocalDateTimeに変換するコードの例は次のようになる。

【リスト9:クラスInstantの使用例】
//Add 1 hour to the current time:, Instant oneHourLater = Instant.now().plusHours(1); //How many seconds have occurred since the beginning of the Java epoch. long secondsFromEpoch = Instant.ofEpochSecond(0L).until(Instant.now(), ChronoUnit.SECONDS); //Instant to LocalDateTime Instant timestamp; LocalDateTime ldt = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault()); System.out.printf("%s %d %d at %d:%d%n", ldt.getMonth(), ldt.getDayOfMonth(), ldt.getYear(), ldt.getHour(), ldt.getMinute());
●出力
MAY 30 2013 at 18:21

日付や時刻の設定を行う──パッケージjava.time.temporal

 Date and Time APIには、java.timeのサブパッケージとしてjava.time.temporalというパッケージが用意されている。同パッケージで定義されているのは、日付や時刻の設定を行うための基本となるインタフェースだ。ベースになるのは次の2つのインタフェースである。

  • インタフェースTemporal:日付や時刻に対する値の読み取りや設定の方法を定義したインタフェース
  • インタフェースTemporalAccessor:インタフェースTemporalの読み取り専用版

 なお、LocalDateをはじめとする日付や時刻を表すクラスは、これらのインタフェースをimplementsしている。

 また、このパッケージには、日付や時刻のクラスに値を設定する際に便利なユーティリティ(Adjuster)としてクラスTemporalAdjustersが用意されている。日付/時刻のクラスにメソッドwithを使って値を設定する際に同クラスを使うと、「月の開始日」や「月の最後の日」、「次の年の最初の日」などといったかたちで日付を指定することができる。例えば、2000年10月15日を基点にして各種の日付を取得するコードは次のようになる。

【リスト10:クラスTemporalAdjustersの使用例】
LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15); DayOfWeek dotw = date.getDayOfWeek(); System.out.printf("%s is on a %s%n", date, dotw); System.out.printf("first day of Month: %s%n”, date.with(TemporalAdjusters.firstDayOfMonth())); System.out.printf("first Monday of Month: %s%n”, date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))); System.out.printf("last day of Month: %s%n”, date.with(TemporalAdjusters.lastDayOfMonth())); System.out.printf("first day of next Month: %s%n”, date.with(TemporalAdjusters.firstDayOfNextMonth())); System.out.printf("first day of next Year: %s%n”, date.with(TemporalAdjusters.firstDayOfNextYear()));
●出力
2000-10-15 is on a SUNDAY first day of Month: 2000-10-01 first Monday of Month: 2000-10-02 last day of Month: 2000-10-31 first day of next Month: 2000-11-01 first day of next Year: 2001-01-01

 なお、このようなAdjusterを自前で定義することもできる。インタフェースTemporalAdjusterをimplementsしたクラスに独自のメソッドadjustIntoを実装すればよい。例えば、「次の給料日」を求めるAdjusterの定義は次のようになる。

【リスト11:「次の給料日」を求めるAdjusterのメソッドadjustIntoの定義】
public Temporal adjustInto(Temporal input) { LocalDate date = LocalDate.from(input); int day; if (date.getDayOfMonth() < 15) { day = 15; } else { day=date.with(TemporalAdjusters.lastDayOfMonth()). getDayOfMonth(); } date = date.withDayOfMonth(day); if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) { date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); } return input.with(date); }
●上のAdjusterを使って次の給料日を取得するコード
LocalDate nextPayday = date.with(new PaydayAdjuster());
●出力
※ 給料日の支給基準日は毎月15日と月末で、それらが休日の場合はその直前の平日。2013年6月15日と30日はそれぞれ土曜、日曜であるため、次のような結果になる Given the date: 2013 Jun 3 the next payday: 2013 Jun 14 Given the date: 2013 Jun 18 the next payday: 2013 Jun 28

時間の経過/間隔を扱う──クラスDuration、ChronoUnit

 ある時刻からの経過時間や、2つの時刻の間隔を扱うといったケースではクラスDurationを使用する。このクラスを使うことで、あるInstantを基準にして指定時間だけ経過した時刻のInstantオブジェクトを取得したり、2つのInstant間の時間の長さを計算したりすることができる。クラスDurationの使用例は次のようになる。

【リスト12:クラスDurationの使用例】
Instant t1, t2; ... long ns = Duration.between(t1, t2).toNanos(); //Add 10 seconds to an Instant: Instant start; ... Duration gap = Duration.ofSeconds(10); Instant later = start.plus(gap); Instant previous, current, gap; ... current = Instant.now(); if (previous != null) { gap = ChronoUnit.MILLIS.between(previous,current); } ...

 ご覧のとおり、2つのInstant(時間)の間の長さを求めるにはメソッドbetweenを使う。また、Durationオブジェクトを直接生成するには、時刻を指定してメソッドofSecondsなどを呼び出す。そして、時間の間隔を表す単位を指定したい場合にはクラスChronoUnitを使用する。

 時間と同様に、日にちの経過や間隔を扱いたいという場合には、クラスPeriodを使用する。次に示すのは、同クラスを使って次の誕生日までの日数を求めるコードの例だ。

【リスト13:クラスPeriodの使用例】
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1); LocalDate nextBDay = birthday.withYear(today.getYear()); //If your birthday has occurred this year already, add 1 to the year. if (nextBDay.isBefore(today) || nextBDay.isEqual(today)) { nextBDay = nextBDay.plusYears(1); } Period p = Period.between(today, nextBDay); long p2 = ChronoUnit.DAYS.between(today, nextBDay); System.out.println("There are " + p.getMonths() + " months, and " + p.getDays() + " days until your next birthday. (" + p2 + " total)");
●出力
There are 7 months, and 2 days until your next birthday. (216 total)

 ご覧のとおり、クラスDurationの場合と同様にクラスChronoUnitを使って単位を指定し、メソッドbetweenで日付間の日数を取得している。

ISO以外の地域ごとの暦を使う

 Date and Time APIで注目すべき機能の1つに、和暦やヒジュラ暦といったISOベースではない地域ごとの暦をサポートしていることが挙げられる。また、独自の暦も容易に作成することができる。

 例えば、和暦に関してはJapaneseDateというクラスが用意されている。ISOカレンダーから他の暦のカレンダーへの変換は、次のようにメソッドfromを呼び出すだけでよい。

【リスト14:ISO以外の暦のカレンダーを作成する】
LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); JapaneseDate jdate = JapaneseDate.from(date); HijrahDate hdate = HijrahDate.from(date); MinguoDate mdate = MinguoDate.from(date); ThaiBuddhistDate tdate = ThaiBuddhistDate.from(date);

 なお、JapaneseDateなど地域ごとの暦を扱うクラスは、すべてインタフェースChronoLocalDateをimplementsしている。

 「Date and Time APIに標準で用意された日付/時刻クラスでもChronoLocalDateを利用しており、例えばクラスLocalDateTimeはChronoLocalDateを型パラメータに持つインタフェースChronoLocalDateTimeをimplementsしていますし、クラスZonedDateTimeも同様にChronoLocalDateを型パラメータに持つインタフェースChronoZonedDateTimeをimplementsしています」(カイセド氏)

 つまり、インタフェースChronoLocalDateを活用すれば、暦の種類に依存しない実装を行うことも可能だということだ。

書式を指定した日付/時刻の出力やパース──クラスDateTimeFormatter

 日付/時刻をISO形式以外の任意の書式で出力したり、任意の書式の文字列を日付/時刻にパースしたりする際には、クラスDateTimeFormatterを使う。同クラスのインスタンスは、クラスDateTimeFormatterBuilderを使って生成する。クラスDateTimeFormatterBuilderには、日付/時刻の書式を設定したり、解析用パターンを構成したりするためのメソッドが定義されている。それらのメソッドを組み合わせて呼び出すことで任意の書式を作れるのだ。

 独自のDateTimeFormatterクラスを作り、それを使ってパースと出力を行うプログラムの例は次のようになる。

【リスト15:独自のDateTimeFormaterクラスを使った書式指定の例】
DateTimeFormatter format = new DateTimeFormatterBuilder() .appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NEVER) .optionalStart() .appendLiteral(":").appendValue(MINUTE_OF_HOUR, 2) .optionalStart() .appendLiteral(":").appendValue(SECOND_OF_MINUTE, 2) .optionalEnd() .optionalEnd() .parseDefaulting(MINUTE_OF_HOUR, 1) .parseDefaulting(SECOND_OF_MINUTE, 0) .toFormatter(); LocalTime date = LocalTime.parse(s, format); System.out.printf(" Parsed %10s --- %7s%n", s, date.format(fmt));
●出力
Parsed 9 → 9:01:00 Parsed 09:05 → 9:05:00 Parsed 09:30:59 → 9:30:59 Parsed 23:00 → 23:00:00

 これは「時間:分:秒」という書式を定義した例となる。時間の部分は必須であり、これは1桁または2桁の数字に、分および秒の部分は「:」に続く2桁の数字になる。分と秒の定義の前後には、それぞれメソッドoptionalStartとoptionalEndがあるが、これらは省略可能だ。省略した場合のデフォルト値はメソッドparseDefaultingで設定されており、分は「01」、秒は「00」となる。

 文字列をパースして日付/時刻のクラスのインスタンスを生成するには、それぞれのクラスのメソッドparseを使用する。また、任意の書式で出力する場合はDateTimeFormatterを指定してメソッドformatを呼び出し、対応する書式の文字列に変換すればよい。

 このように、クラスDateTimeFormatterは高い柔軟性を備えるが、実用面を考えると少々複雑だ。カイセド氏によれば、この部分はもっとシンプルなかたちにするよう改善に取り組んでいるとのことだ。

 以上、駆け足となったが、カイセド氏の解説により、Java SE 8で導入されたDate and Time APIの主なポイントを紹介した。同APIは非常に規模が大きなものだが、ここで掲示したコードも参考にして、新機能の習得を進めていただきたい。

 「新しいDate and Time APIは極めて堅牢かつスレッドセーフに作られています。多くの試行錯誤を繰り返した結果、非常に柔軟性の高いAPIに仕上がりました。皆さんも、ぜひこの新しいAPIを1日も早く使いこなしてください」(カイセド氏)

このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]