Scala で時間 (日付 + 時刻 + オフセット + タイムゾーン) を扱う

heroImage

1. はじめに

執筆時点では、Scala 標準パッケージにも Toolkit にも時間を扱う機能が導入されていません。そのため、Scala で時間を扱う際は、Java の資産を使うか、nscala-time などの外部パッケージを利用する必要があります。本記事では、Java の資産を使って時間を扱う方法について記述します。

2. Java パッケージ

時間を扱う Java パッケージとしては、java.util.Date パッケージ、java.util.Calendar パッケージ、java.time パッケージなどがあります。このうち、java.util.Date パッケージと java.util.Calendar パッケージは、初期の Java で導入されたレガシーパッケージなので、強い採用理由がない限り、使用は避けた方が良いと思います。一方、java.time パッケージは Java 8 で導入された比較的モダンなパッケージです。java.time パッケージの特徴としては、不変 (Immutable) オブジェクトを使用しており、スレッドセーフであることが挙げられます。

java.time パッケージの中で、よく使うクラスは以下の 5 つです。それぞれのクラスは、以下のような特徴を持ちます。

各クラスをインスタンスしたオブジェクトが持つ情報量を、式で表すと以下の通りになります。

LocalDate + LocalTime = LocalDateTime < OffsetDateTime < ZonedDateTime

3. 現在時間

now メソッドで現在時間を取得できます。上記で挙げた 5 つのクラスは、全て now メソッドを継承しています。

1
import java.time.{
2
LocalDate,
3
LocalTime,
4
LocalDateTime,
5
OffsetDateTime,
6
ZonedDateTime
7
}
8
9
println(LocalDate.now()) // 2024-02-25
10
println(LocalTime.now()) // 06:13:00.207692456
11
println(LocalDateTime.now()) // 2024-02-25T06:13:00.207930269
12
println(OffsetDateTime.now()) // 2024-02-25T06:13:00.208647959Z
13
println(ZonedDateTime.now()) // 2024-02-25T06:13:00.209137270Z[GMT]

4. 加算減算

加算減算は、plus メソッドと minus メソッドを使います。

1
import java.time.LocalDate
2
3
val today = LocalDate.now()
4
val tomorrow = today.plusDays(1) // 1日後
5
val yesterday = today.minusDays(1) // 1日前
6
7
println(today) // 2024-02-27
8
println(tomorrow) // 2024-02-28
9
println(yesterday) // 2024-02-26

上記のコードでは、Days を使用しています。他にも、HoursMinutesMonthsNanosSecondsWeeksYears が実装されています。詳しくは、LocalDate クラスのドキュメント 1 を参照ください。

5. フォーマット

時間 (LocalDateTime 型) と String 型を双方向に変換する場合は、DateTimeFormatter クラスを使用します。

時間 (LocalDateTime 型) → String 型

1
import java.time.LocalDateTime
2
import java.time.format.DateTimeFormatter
3
4
val dateTime = LocalDateTime.now()
5
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒")
6
val formattedDateTime = dateTime.format(formatter)
7
println(formattedDateTime) // 2024年02月27日 14時10分18秒

String 型 → 時間 (LocalDateTime 型)

1
import java.time.LocalDateTime
2
import java.time.format.DateTimeFormatter
3
4
val dateString = "2024年02月27日 14時10分18秒"
5
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒")
6
val date = LocalDateTime.parse(dateString, formatter)
7
println(date) // 2024-02-27T14:10:18

String 型から時間 (LocalDateTime 型) に変換する際、フォーマットパターンを明示的に記述しないといけません。Day.js だと、ある程度、暗黙的に変換してくれます。暗黙的な変換に慣れていると、少し不便に感じます。

6. タイムスタンプ

時間 (ZonedDateTime 型) → タイムスタンプ (Long 型)

1
import java.time.ZonedDateTime
2
3
val zonedDateTime = ZonedDateTime.now()
4
val timestamp = zonedDateTime.toInstant.toEpochMilli
5
6
println(timestamp) // 1709043232904

タイムスタンプ (Long 型) → ZonedDateTime 型

1
import java.time.{Instant, ZoneId, ZonedDateTime}
2
3
val timestamp: Long = 1709043232904L
4
val instant = Instant.ofEpochMilli(timestamp)
5
val zoneId = ZoneId.systemDefault()
6
val zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId)
7
8
println(zonedDateTime) // 2024-02-27T23:13:52.904+09:00[Asia/Tokyo]

7. おわりに

本記事で解説した内容は java.time パッケージのごく一部です。java.time パッケージについて詳しく知りたい場合は、公式ドキュメント 2 を参照してください。

「Scala + 時間」や「Java + 時間」で検索すると、未だに java.util.Date パッケージと java.util.Calendar パッケージの解説記事が上位に表示されます。上記でも述べましたが、この 2 つはレガシーパッケージなので、強い採用理由が無い限りは、java.time パッケージを使用することを推奨します。

java.time パッケージは、java.util.Date パッケージと java.util.Calendar パッケージに比べるとモダンなパッケージです。それでも Java 8 がリリースされたのが 2014 年なので、古臭いと感じる実装部分も多々あります。

現状、Scala Toolkit で時間ライブラリの検討はされていないみたいですが、今後、どうなるかわかりません。期待しながら気長に待ちましょう。


最後に、Scala の学習にオススメの書籍を紹介します。

ScalaScalaScala

  1. Java Platform SE 8, LocalDate:https://docs.oracle.com/javase/jp/8/docs/api/java/time/LocalDate.html

  2. Java Platform SE 8, java.time:https://docs.oracle.com/javase/jp/8/docs/api/java/time/package-summary.html