全部を書くとすごい分量になる(そして僕がよく知らない)ので、要点だけかいつまんで提示します。
-
Date and Time API は ISO8601 の部分実装
-
時刻をあらわす4つの基本的なクラス
LocalDateTime
- 時差なし日時ZonedDateTime
- タイムゾーンつき日時OffsetDateTime
- 時差つき日時Instant
- UTCにおける1970年1月1日0:00:00 からの経過秒数
-
タイムゾーン・時差をあらわすクラス
ZoneId
- タイムゾーンZoneOffset
- 時差
-
フォーマット
DateTimeFormatter
とそのスタティックフィールドである、ISO_LOCAL_DATE
など
ぶっちゃけると、joda time と大して変わらないけど、Java の API にこれ以上 java.util.Date
とか java.util.Calendar
を使ってほしくないので、そこらへんとの互換性を joda よりも使いづらくしたAPIと考えるとわかりやすい。
- 日時をあらわすクラス
- タイムゾーン および 時差を持たない
- 内部的にはタイムゾーンを持たない日付
LocalDate
と タイムゾーンを持たない時刻LocalTime
を持つ不変クラスです
スタティックメソッドの
now()
now(ZoneId)
of(int,int,int)
〜of(int,int,int,int,int,int,int)
などを使います
// 今は 2018/2/3 の 14:30 (東京/Systemのデフォルトタイムゾーンも東京)
def now = LocalDateTime.now()
// -> 2018-02-03T14:30:04.306
def now = LocalDateTime.now(ZoneId.of('UTC'))
// -> 2018-02-03T05:30:04.306
def localDateTime = LocalDateTime.of(2018,2,4,12,12,34)
// -> 2018-02-04T12:12:34
また、 parse(CharSequence)
メソッドにより文字列からも取得できます。
def localDateTime = LocalDateTime.parse('2018-02-04T12:34:56')
// -> 2018-02-04T12:34:56
なお、 parse(CharSequence)
で取扱が可能な日時はISO8601(拡張)の形式です。
日本人がよく使う YYYY/mm/dd
を用いると java.time.format.DateTimeParseException
が発生します。
LocalDateTime.parse('2018/02/04T12:34:56')
// -> java.time.format.DateTimeParseException
- タイムゾーンつき日時をあらわすクラス
- タイムゾーンをもつ
- 内部には
LocalDateTime
(日時) とZoneId
(タイムゾーン) とZoneOffset
(時差) を持つ不変クラスです
スタティックメソッドの
now()
now(ZoneId)
of(int,int,int,int,int,int,int,ZoneId)
of(LocalDateTime,ZoneId)
などを使います
// 今は 2018/2/3 の 14:30 (東京/Systemのデフォルトタイムゾーンも東京)
def now = ZonedDateTime.now()
// -> 2018-02-03T14:30:04.306+09:00[Asia/Tokyo]
def utc = ZonedDateTime.now(ZonedId.of('UTC'))
// -> 2018-02-03T05:30:04.306Z[UTC]
def zonedDateTime = ZonedDateTime.of(2018,2,4,14,30,4,306000000, ZoneId.of('UTC'))
// -> 2018-02-04T14:30:04.306Z[UTC]
def pst8Pdt = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of('PST8PDT'))
// -> 2018-02-03T14:30:04.306-08:00[PST8PDT]
LocalDateTime
と同様に、 parse(CharSequence)
というメソッドもあり、ISO8601(拡張)形式の文字列から取得できます。
ただし、時差情報を持たない場合は java.time.format.DateTimeParseException
が発生します。
def fromString = ZonedDateTime.parse('2018-02-04T14:30:04.306+09:00')
// -> 2018-02-04T14:30:04.306+09:00
ZonedDateTime.parse('2018-02-04T14:30:04.306')
// -> java.time.format.DateTimeParseException
ZonedDateTime.parse('2018/02/04T14:30:04.306+09:00')
// -> java.time.format.DateTimeParseException
- 時差つき日時をあらわすクラス
- 時差をもつ
- 内部的には
LocalDateTime
(日時) とZoneOffset
(時差) をもつ不変クラスです
スタティックメソッドの
now()
now(ZoneId)
of(int,int,int,int,int,int,int,ZoneOffset)
of(LocalDateTime,ZoneOffset)
などを使います。
// 今は 2018/2/3 の 14:30 (東京/Systemのデフォルトタイムゾーンも東京)
def now = OffsetDateTime.now()
// -> 2018-02-04T14:30:04.306+09:00
def pst8Pdt = OffsetDateTime.now(ZoneId.of('PST8PDT'))
// -> 2018-02-03T21:30:04.306-08:00
def offsetDateTime = OffsetDateTime.of(2018, 2, 4, 14, 30, 4, 306_000_000, ZoneOffset.ofHours(9))
// -> 2018-02-04T14:30:04.306+09:00
def utc = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC)
// -> 2018-02-04T14:30:04.306Z
OffsetDateTime
も LocalDateTime
や ZonedDateTime
と同様に parse(CharSequence)
というメソッドがあります。
def offsetDateTime = OffsetDateTime.parse('2018-02-04T14:30:04.306+09:00')
// -> 2018-02-04T14:30:04.306+09:00
OffsetDateTime.parse('2018-02-04T14:30:04.306')
// -> java.time.format.DateTimeParseException
- ある特定の時点をあらわすクラス
- タイムゾーンも時差ももたない(タイムゾーンはUTC固定と考えたほうがよいです)
- 内部的には 1970-01-01T00:00:00Z からの経過秒数(epoch second)とナノ秒部分をあらわす
int
の値をもつ不変クラスです java.util.Date
に最も近いクラスで、java.util.Date
との変換もできます(というか、それ以外の用途を見たことがない)
以下のスタティックメソッドありますが、前述の通りこのクラスを使いまわすような用途があまりないので、ほとんど使いません。
now()
ofEpochSecond(long)
// 今は 2018/2/3 の 14:30 (東京/Systemのデフォルトタイムゾーンも東京)
def now = Instant.now()
// -> 2018-02-04T05:30:04.306Z
def instant = Instant.ofEpochSecond(1517754604L)
// -> 2018-02-04T14:30:04.306Z
どのようなシーンで使うかというと、 LocalDateTime
から Date
への変換や、 Date
から ZonedDateTime
の変換などのケースに使います。
ここで注意しておきたいのは、 Instant
は UTC でのエポック秒(+ナノ秒)をあらわすので、
LocalDateTime
から Instant
に変換する場合は、 LocalDateTime
にどこの時刻であるかの情報を渡す必要があることです。
ここを理解しておかないと、データベースに日時を保存する際にバグを出します。
ここまで ZonedDateTime
や OffsetDateTime
を取得するために利用していたクラスは次の二つ。
ZoneId
(タイムゾーン)ZoneOffset
(時差)
これらの違いは次のとおり。
ZoneOffset
- UTC からの固定された(変更されることのない)時間のずれをあらわす
ZoneId
- 固定オフセット -
ZoneOffset
と同じの UTC からの固定された時間のズレ - 地理的地域(javadocより) - 地理、政治、季節、宗教(?) などによって決定される時差の集合(1つ以上の時差決定ルール(
ZoneRules
)をもつ)
- 固定オフセット -
以下のメソッドを用いて生成します
systemDefault()
of(String)
ofHours(int)
ofHoursMinutes(int, int)
(ZoneOffset
のみ)
// 今は 2018/2/3 の 14:30 (東京/Systemのデフォルトタイムゾーンも東京)
println ZoneId.systemDefault()
// -> Asia/Tokyo
println ZoneId.of('+09:00')
// -> +09:00
println ZoneId.of('Asia/Tokyo')
// -> Asia/Tokyo
println ZoneId.of('UTC')
println ZoneId.of('Chiba', [Chiba: 'Asia/Tokyo'])
// -> Asia/Tokyo
println ZoneOffset.of('+09:00')
// -> +09:00
println ZoneOffset.ofHours(9)
// -> +09:00
println ZoneOffset.ofHoursMinutes(9,30)
// -> +09:30
println ZoneOffset.UTC
// -> Z
println ZoneOffset.of('Z')
// -> Z
固定オフセットでない場合の ZoneId
での時差は ZoneRules
にあるテーブルと現在の日時によって算出されます。
たとえば、日本では 1948/4/28に公布された夏時刻法により、
毎年5月第一土曜日24時(日曜日1時)〜9月第二土曜日25時(日曜日0時)に1時間時刻を進ませていたようです(夏時刻法)
(ただし、Java API の提供する ZoneOffsetTransition
があっていない)
def tokyo = ZoneId.of('Asia/Tokyo')
def before = ZonedDateTime.of(1948,5,1,23,0,0,0, tokyo)
// -> 1948-05-01T23:00+09:00[Asia/Tokyo]
before.plusHours(1)
// -> 1948-05-02T00:00+09:00[Asia/Tokyo]
before.plusHours(2)
// -> 1948-05-02T01:00+09:00[Asia/Tokyo]
before.plusHours(2).plusMinutes(59)
// -> 1948-05-02T01:59+09:00[Asia/Tokyo]
before.plusHours(2).plusMinutes(60)
// -> 1948-05-02T03:00+10:00[Asia/Tokyo]
詳しくは getTransition(LocalDateTime)
というメソッドで変更内容を取得できるので、確認してみてください