Skip to content

Instantly share code, notes, and snippets.

@mike-neck
Last active February 5, 2018 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-neck/04890d7d662a1f2928571f774e4759f0 to your computer and use it in GitHub Desktop.
Save mike-neck/04890d7d662a1f2928571f774e4759f0 to your computer and use it in GitHub Desktop.

今さら聞けない Date and Time API

全部を書くとすごい分量になる(そして僕がよく知らない)ので、要点だけかいつまんで提示します。

  • 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と考えるとわかりやすい。

LocalDateTime

  • 日時をあらわすクラス
  • タイムゾーン および 時差を持たない
  • 内部的にはタイムゾーンを持たない日付 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

ZonedDateTime

  • タイムゾーンつき日時をあらわすクラス
  • タイムゾーンをもつ
  • 内部には 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

OffsetDateTime

  • 時差つき日時をあらわすクラス
  • 時差をもつ
  • 内部的には 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

OffsetDateTimeLocalDateTimeZonedDateTime と同様に 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

Instant

  • ある特定の時点をあらわすクラス
  • タイムゾーンも時差ももたない(タイムゾーンは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 にどこの時刻であるかの情報を渡す必要があることです。 ここを理解しておかないと、データベースに日時を保存する際にバグを出します。


タイムゾーン/時差をあらわすクラス

ここまで ZonedDateTimeOffsetDateTime を取得するために利用していたクラスは次の二つ。

  • 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 の時差の決定

固定オフセットでない場合の 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) というメソッドで変更内容を取得できるので、確認してみてください


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment