1.概要

このチュートリアルでは、Java 8で導入された新しいDate Time APIを活用するためにコードをリファクタリングする方法を学びます。

2.一目でわかる新しいAPI

Javaで日付を処理するのは大変でした。 JDKが提供していた古い日付ライブラリには、

java.util.Date、java.util.Calendar

、および

java.util.Timezone

の3つのクラスしかありませんでした。

これらは最も基本的なタスクにのみ適していました。さらに複雑な場合でも、開発者はサードパーティのライブラリを使用するか、大量のカスタムコードを作成する必要がありました。

  • Java 8は完全に新しいDate Time API



    java.util.time。


    )を発表しました。この新しいAPIは、日付と時刻の処理を劇的に簡素化し、古い日付ライブラリの多くの欠点を修正しました。

** 1.1. APIの明確さ

**

新しいAPIの最初の利点は

明快さ

です – APIは非常に明確、簡潔、そして理解しやすいです。フィールド番号など、古いライブラリに見られる多くの矛盾はありません(カレンダーの月は0から始まり、曜日は1から始まります)。


1.2. APIの柔軟性

もう1つの利点は柔軟性です –

複数の時間表現を扱う

。古い日付ライブラリには

java.util.Date

という単一の時間表現クラスしか含まれていませんでした。これは、その名前にもかかわらず、実際にはタイムスタンプです。 Unixエポックから経過したミリ秒数のみを格納します。

新しいAPIにはさまざまな時間表現があり、それぞれ異なるユースケースに適しています。


  • Instant

    – ある時点を表す(タイムスタンプ)


  • LocalDate

    – 日付を表します(年、月、日)


  • LocalDateTime



    LocalDate

    と同じですが、

ナノ秒の精度
**

OffsetDateTime



LocalDateTime

と同じですが、タイムゾーンオフセットがあります


  • LocalTime

    – ナノ秒の精度で日付なしの時間

情報
**

ZonedDateTime



OffsetDateTime

と同じですが、タイムゾーンが含まれます

ID
**

OffsetLocalTime



LocalTime

と同じですが、タイムゾーンオフセットがあります。


  • MonthDay

    – 年と時間を除いた月と日


  • YearMonth

    – 月と年、日も時間もなし


  • Duration

    – 秒、分、時間で表される時間。

ナノ秒の精度
**

期間

– 日数、月数、年数で表した時間

** 1.3. 不変性とスレッドセーフ

もう1つの利点は、Java 8 Date Time APIのすべての時間表現が** 不変であり、したがってスレッドセーフであることです。

すべての変更メソッドは、元のオブジェクトの状態を変更するのではなく、新しいコピーを返します。


java.util.Date

などの古いクラスはスレッドセーフではなく、非常に微妙な同時実行性のバグを引き起こす可能性があります。


1.4. メソッドチェーン

すべての変更メソッドを連鎖させることができ、複雑な変換を1行のコードで実装できます。

ZonedDateTime nextFriday = LocalDateTime.now()
  .plusHours(1)
  .with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
  .atZone(ZoneId.of("PST"));


2例

以下の例は、新旧両方のAPIを使用して一般的なタスクを実行する方法を示します。

  • 現在時刻を取得する**

----//Old
Date now = new Date();
//New
ZonedDateTime now = ZonedDateTime.now();
----

  • 特定の時間を表す**

----//Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();
//New
LocalDate birthDay = LocalDate.of(1990, Month.DECEMBER, 15);
----

  • 特定のフィールドを抽出する**

----//Old
int month = new GregorianCalendar().get(Calendar.MONTH);
//New
Month month = LocalDateTime.now().getMonth();
----

  • 時間の増減

----//Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR__OF__DAY, -5);
Date fiveHoursBefore = calendar.getTime();
//New
LocalDateTime fiveHoursBefore = LocalDateTime.now().minusHours(5);
----

  • 特定のフィールドを変更する**

----//Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, Calendar.JUNE);
Date inJune = calendar.getTime();
//New
LocalDateTime inJune = LocalDateTime.now().withMonth(Month.JUNE.getValue());
----

  • 切り捨て**

切り捨ては、指定されたフィールドより小さいすべての時間フィールドをリセットします。以下の例では、minutesとそれ以下のすべてがゼロに設定されます。

----//Old
Calendar now = Calendar.getInstance();
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Date truncated = now.getTime();
//New
LocalTime truncated = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
----

  • タイムゾーン変換**

----//Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
Date centralEastern = calendar.getTime();
//New
ZonedDateTime centralEastern = LocalDateTime.now().atZone(ZoneId.of("CET"));
----

  • 2つの時点間のタイムスパンを取得する**

----//Old
GregorianCalendar calendar = new GregorianCalendar();
Date now = new Date();
calendar.add(Calendar.HOUR, 1);
Date hourLater = calendar.getTime();
long elapsed = hourLater.getTime() - now.getTime();
//New
LocalDateTime now = LocalDateTime.now();
LocalDateTime hourLater = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(now, hourLater);
----

  • 時間のフォーマットと解析**

DateTimeFormatterは、スレッドセーフであり、追加機能を提供する古いSimpleDateFormatに代わるものです。

----//Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);
//New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
----

  • 月の日数**

----//Old
Calendar calendar = new GregorianCalendar(1990, Calendar.FEBRUARY, 20);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY__OF__MONTH);
//New
int daysInMonth = YearMonth.of(1990, 2).lengthOfMonth();
----


3従来のコードとの対話

多くの場合、ユーザーは古い日付ライブラリに依存しているサードパーティのライブラリとの相互運用性を保証する必要があります。

Java 8では、古い日付ライブラリクラスは、新しい日付APIから対応するオブジェクトに変換するメソッドで拡張されました。新しいクラスは同様の機能を提供します。

Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
Date dateFromInstant = Date.from(Instant.now());
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
Instant instantFromDate = new Date().toInstant();
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("PST").toZoneId();


4結論

この記事では、Java 8で利用可能な新しいDate Time APIについて調べました。

廃止予定のAPIと比較してその利点を調べ、複数の例を使用して違いを指摘しました。

新しいDate Time APIの機能のほんの少し表面が傷つけられていることに注意してください。新しいAPIによって提供されるツールの全範囲を発見するために公式ドキュメントを通読するようにしてください。

コード例はhttps://github.com/eugenp/tutorials/tree/master/java-dates[GitHubプロジェクト]にあります。