JavaSE8 Goldへの道(Upgrade to Java SE 8 Programmer 1Z0-810 試験対策)12回目です。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
Java8では
これを3回に分けて紹介していきます。
Date/TimeAPIの全てのクラスは不変(イミュータブル)でスレッドセーフに設計されています。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
Java8では
java.util.Date
、java.util.Calendar
などに代わる新しい「Date/Time API」が整備されました。これを3回に分けて紹介していきます。
特徴
従来のクラス群は不変でない(ミュータブル)、スレッドセーフでないという問題がありました。Date/TimeAPIの全てのクラスは不変(イミュータブル)でスレッドセーフに設計されています。
基本となるInstantクラス
時系列のある一時点を表します。
標準Javaエポック(1970-01-01T00:00:00Z)からの経過秒とナノ秒を保持しています。
注意として、タイムゾーンがUTC固定です。(ISO 8601形式で末尾"Z"はUTCを表す)
生成方法の例(いつも参考にしているこちらのサイトのサンプルコードより。以降同様。)
// 現在時刻(UTC) Instant now = Instant.now(); System.out.println(now); // Unixタイムスタンプから(2015-07-13T20:45:44.404Z) Instant fromUnixTimestamp = Instant.ofEpochSecond(1436820344); // 同じ時刻をミリ秒で指定 Instant fromEpochMilli = Instant.ofEpochMilli(1436820344404L); // ISO 8601形式の文字列からパース Instant fromIso8601 = Instant.parse("2015-07-10T12:00:00Z"); System.out.println(fromIso8601); // toString()はISO 8601形式で返す String toIso8601 = now.toString(); System.out.println(toIso8601); // Javaエポック1970-01-01T00:00:00Zからの秒数 long toUnixTimestamp = now.getEpochSecond(); System.out.println(toUnixTimestamp); // エポック1970-01-01T00:00:00Zからのミリ秒数 long toEpochMillis = now.toEpochMilli(); System.out.println(toEpochMillis);
実行結果
2018-04-23T08:15:12.371Z
2015-07-10T12:00:00Z
2018-04-23T06:35:26.015Z
1524465326
1524465326015
2015-07-10T12:00:00Z
2018-04-23T06:35:26.015Z
1524465326
1524465326015
タイムゾーンが固定なので、そのまま使うと日本時間とずれます。
他にも加算・減算を行う
これらのメソッドは以降で紹介するクラスにも実装されています。
イミュータブルなので結果は全て新しいインスタンスを返します。
(ただし
他にも加算・減算を行う
plus
,minus
メソッド、比較を行うisAfter
,isBefore
メソッド、ある属性を特定の値を指定(月の最初の日など)するwith
メソッドなどがあります。これらのメソッドは以降で紹介するクラスにも実装されています。
イミュータブルなので結果は全て新しいインスタンスを返します。
Instant
を基本として、3つの日時を表すクラスが存在します。(ただし
Instant
がスーパークラスというわけではありません)3つの日時クラス
LocalDateTime
タイムゾーン情報を持たない日時クラスです。
内部に
LocalDate
とLocalTime
クラスのインスタンスを持ちます。これらは独立してそれぞれ日付のみ/時刻のみを表すクラスとして存在します。生成方法の例
// Date with time LocalDateTime currentDateTime = LocalDateTime.now(); System.out.println("Current date and time : " + currentDateTime); // 2015-09-15 10:15 LocalDateTime sept15th = LocalDateTime.of(2015, 9, 15, 10, 15); System.out.println("September 15th : " + sept15th); // 2015-12-25 12:00 LocalDateTime christmas2015 = LocalDateTime.of(2015, Month.DECEMBER, 25, 12, 0); System.out.println("Christmas 2015 : " + christmas2015); // Instantから生成 Instant n = Instant.now(); System.out.println(n); System.out.println(LocalDateTime.ofInstant(n, ZoneId.systemDefault()));実行結果
Current date and time : 2018-04-23T17:15:12.370
September 15th : 2015-09-15T10:15
Christmas 2015 : 2015-12-25T12:00
2018-04-23T08:15:12.371Z
2018-04-23T17:15:12.371
September 15th : 2015-09-15T10:15
Christmas 2015 : 2015-12-25T12:00
2018-04-23T08:15:12.371Z
2018-04-23T17:15:12.371
of
メソッドには他にもたくさんの引数パターンがあります。月にはMonth
列挙型を渡すものとintで指定するものがあり、intの方は従来と違い1~12で指定します。「タイムゾーン情報を持たない」と言いながら
Instant
から生成する際にZoneId
を引数に取るのはなんか変な感じがしますが、LocalDateTime
が従来のDate
のようにシステムデフォルトのタイムゾーンで生成されるものと考えれば良いでしょう。LocalDateTime#now
メソッドはデフォルトタイムゾーンで現在日時を生成します。ZonedDateTime
タイムゾーン情報を持つ日時クラスです。タイムゾーンは
ZoneId
というクラスで保持しています。ややこしいですが、ZonedDateやZonedTimeというクラスはありません。
ZonedDateTime
のみです。生成には
now
メソッドでフォルトのタイムゾーンかZoneId
を指定して生成するか、of
メソッドにLocalDateTime
とZoneId
も追加で引数に渡します。生成方法の例
ZoneId minsk = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+3)); ZoneId berlin = ZoneId.of("Europe/Berlin"); // Current date and time LocalDateTime dateTime = LocalDateTime.now(); System.out.println("Here : " + dateTime); ZonedDateTime minskDateTime = ZonedDateTime.of(dateTime, minsk); System.out.println("Minsk : " + minskDateTime); // Current date and time in Berlin, Germany ZonedDateTime berlinDateTime = minskDateTime.withZoneSameInstant(berlin); System.out.println("Berlin : " + berlinDateTime);
実行結果
Here : 2018-04-23T17:44:12.897
Minsk : 2018-04-23T17:44:12.897+03:00[GMT+03:00]
Berlin : 2018-04-23T16:44:12.897+02:00[Europe/Berlin]
Minsk : 2018-04-23T17:44:12.897+03:00[GMT+03:00]
Berlin : 2018-04-23T16:44:12.897+02:00[Europe/Berlin]
withZoneSameInstant
メソッドはインスタントを保持したまま別のタイムゾーンを使ってコピーを生成します。結果示す日時は変更されます。
OffsetDateTime
試験範囲としては載っていないのですが、一応これも説明しておきます。
ゾーン情報は持たず、オフセットのみを保持します。これは
ZoneOffset
として表されています。生成時も
of
メソッドにはZoneOffset
を引数に取ります。とてもややこしいことに、OffsetDateはないのに
OffsetTime
だけ存在します。時間量を表すクラス
Period
日付ベースの時間量を表すクラスです。
生成には
of~
メソッドを使います。引数には年、月、日、もしくは週数を指定できます。nowメソッドはありません。「量」なので、マイナスの値も取り得ます。
参考サイトのサンプルコード
LocalDate today = LocalDate.now(); LocalDate birthday = LocalDate.of(1974, Month.SEPTEMBER, 15); Period p = Period.between(birthday, today); long p2 = ChronoUnit.DAYS.between(birthday, today); System.out.printf("You are %d years, %d months, and %d days old (%d days total)", p.getYears(), p.getMonths(), p.getDays(), p2);
実行結果
You are 43 years, 7 months, and 8 days old (15926 days total)
between
メソッドは2つのDateTime
の差を求めます。逆に、各日時クラスの
plus(TemporalAmount)
,minus(TemporalAmount)
の引数に渡して計算ができます。(
Period
と次に説明するDuration
クラスはTemporalAmount
インタフェースを実装しています)タイムゾーンを考慮する場合、
ZonedDateTime#plus
などを使う必要があります。Period
自身にもplus~
,minus~
,with~
メソッドがあり、新たな時間量を計算できます。Duration
時刻ベースの時間量を表すクラスです。秒とナノ秒で保持します。
Duration
も生成にはof~
メソッドを使います。nowメソッドはありません。これもマイナスの値を取り得ます。
参考サイトのサンプルコード
// Current time Instant now1 = Instant.now(); // Wait __appropximately__ 1 second Thread.sleep(1000); // Current time Instant now2 = Instant.now(); // Calculate real duration long ns = Duration.between(now1, now2).toNanos(); System.out.println("Duration (ns) : " + ns);
実行結果
Duration (ns) : 1001000000
「約」と強調している通り、自分のPCがでは1秒ぴったりではありませんでした。
前述の通り、各日時クラスの
前述の通り、各日時クラスの
plus(TemporalAmount)
,minus(TemporalAmount)
の引数に渡して計算ができます。Duration
自身の各計算メソッドがあるのもPeriod
と同じです。java.util.Dateとの相互変換
java.util.Date
との相互変換ですが、Date/TimeAPIの方にはありません。Date
クラスの方にfrom
,toInstant
メソッドが追加されています。public static Date from(Instant instant) public Instant toInstant()
また、
Calendar
クラスにtoInstant
メソッドだけが追加されています。Instant
にはタイムゾーン情報がないのでCalendar
は作れないということでしょう。と、い、う、わ、け、で
Date/Time APIその1は基本のクラス群の解説でした。
基本となる
Instant
と3つの日時クラスLocalDateTime
,ZonedDateTime
,OffsetDateTime
の関係、時間量を表す2つのクラスPeriod
,Duration
をしっかり押さえておきましょう。今回も以下のサイトとGoldの通常試験の参考書を参考にしています。