検索

キーワード


【Java】Java8からの日時計算/日時比較クラスの使い方を解説

  • 公開日:2021-01-26 08:21:20
  • 最終更新日:2021-01-25 18:39:20
【Java】Java8からの日時計算/日時比較クラスの使い方を解説

こんにちは、研修を終えたばかりの駆け出しエンジニアの伊藤です。
東京ITカレッジのJava研修で学んだ内容を復習も兼ねて記事にしたいと思います。
今回は、Java8から採用された新APIを使った日時比較と計算の方法について説明します
この記事では、最低限覚えておいてほしいクラスとして、基本的な操作方法やメソッドを紹介します。

日時取得やフォーマットについては別記事でご紹介しますので、関連記事を参考にしてください。

Javaやプログラムについて勉強し始めた方の参考になれば幸いです!
(eclipseを使用して計算を行っています)


関連記事:よく使う日時クラス解説まとめ

     Java8からの現在や指定日時取得 9クラスの基本と使い方を解説

     Java8からの日時フォーマットクラスの使い方解説

     日時取得の Date/Calendar クラスの基本と使い方を解説

     Date/Calendarの日時フォーマット/日時比較/日時計算クラス使い方解説

     日付時刻の変換方法(新旧 API 相互変換、Data/Calendar の変換)

     処理時間/経過時間計測(ミリ秒)に使える2つのメソッドを解説



日付/時刻の比較(equals、compareTo、is-(isBefore、isAfter、isEqual))


日時の比較には、5つのメソッドが用意されています。


日付/時刻の比較に関するメソッド


① compareTo:メソッドの呼び出し元の値(data1)が引数(date2)と比べ、

「過去(前):-1」「未来(後):1」「同日、同時刻:0」を返す

(JavaDocには「戻り値:コンパレータ値、小さい場合は負、大きい場合は正」とありますが、プログラム上では上記の数値が結果として反映されます)

 date1.compareTo(date2)

② equals:メソッドの呼び出し元の値(date1)が引数(date2)と同日であれば 

       ture 違っていれば false  が返る

 date1.equals(date2)

isEqual:メソッドの呼び出し元の値(date1)が引数(date2)と同日であれば

       ture 違っていれば、false  が返る

 isEqualメソッド :data1とdata2は同日(同時刻)	
	date1.isEqual(date2)

   

④ isBefore:メソッドの呼び出し元の値(date1)が引数(date2)と比べ、過去(前)であれば

       ture 違っていれば、false  が返る

 isBeforeメソッド:date1はdate2より過去(前)	
	date1.isBefore(date2)	

 

isAfter:メソッドの呼び出し元の値(date1)が引数(date2)と比べ、未来(後)であれば

      ture 違っていれば、false  が返る

 isAfterメソッド :data1はdata2より未来(後)	
	date1.isAfter(date2)	


equalsメソッド と isEqualメソッド は、同日時比較を行いますが比較の情報元が違っているため、プログラムの書き方によっては違うものが返ってきます。

詳しくは、比較サンプルプログラムをご覧ください。

・compareTo、equals:エポック秒(UNIX時間)からの経過時間および年月日等の単位でも比較
・is		     :エポック秒(UNIX時間)からの経過時間で比較


日付/時刻の比較メソッドによる違いの実例

詳しい違いはサンプルプログラムで見てみましょう。

//比較サンプル1
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class EqualsSample {

	public static void main(String[] args) {
		ZonedDateTime date1 = ZonedDateTime.of(2020, 7, 10, 10, 10, 10, 100, ZoneId.of("Europe/Belfast"));
		Instant ins = Instant.from(date1);
		ZonedDateTime date2 = ZonedDateTime.ofInstant(ins, ZoneId.of("Asia/Tokyo"));
		long millis = date1.toInstant().toEpochMilli();
		long millis2 = date2.toInstant().toEpochMilli();
		System.out.println("---取得結果--------------------");
		System.out.println("①取得日時(ヨーロッパ) : " + date1 + "   (経過時間 : " + millis);
		System.out.println("②取得日時(日本)       : " + date2 + "       (経過時間 : " + millis2);
		//比較
		System.out.println("①.compareTo②    : " + date1.compareTo(date2));
		System.out.println("①.equals②       : " + date1.equals(date2));
		System.out.println("①.isEqual②      : " + date1.isEqual(date2));
		System.out.println("①.isBefore②     : " + date1.isBefore(date2));
		System.out.println("①.isAfter②      : " + date1.isAfter(date2));
	}
}


---取得結果--------------------
①取得日時(ヨーロッパ) : 2020-07-10T10:10:10.000000100+01:00[Europe/Belfast]   (経過時間 : 1594372210000
②取得日時(日本)       : 2020-07-10T18:10:10.000000100+09:00[Asia/Tokyo]       (経過時間 : 1594372210000
①.compareTo②    : -1
①.equals②       : false
①.isEqual②      : true
①.isBefore②     : false
①.isAfter②      : false



Instantクラス は、タイムスタンプとしてその時点のインスタンスを取得します。

サンプル1では、equals と isEqual では同日時比較ですが返ってきた結果は違っています。
この理由として上記の解説であった通り、比較元が違うことが要因です。

サンプル1では、date1 のインスタンスと date2 のインスタンスは同じ経過時間の情報を保持しているところがポイントです。
date2 は date1 で取得したインスタンスの時差のみを変更しているだけで、long で取得したミリ秒を見ると同じ値が結果として返ってきています。

その為、isEqual では Instant のエポック秒(UNIX時間)からの経過時間の情報から比較をしているため、ture が返ってきているのです。
しかし、equals では年月日等の単位も比較をするため、取得日時では時差が発生しているので時間が変わり、false が返っています。

・compareTo、equals の比較対象: 2020-07-10T10:10:10.000000100+01:00[Europe/Belfast]
		    		       2020-07-10T18:10:10.000000100+09:00[Asia/Tokyo]


・isの比較対象	 : 経過時間 : 1594372210000
              経過時間 : 1594372210000


では、以下のサンプルプログラムでは比較結果はどう変わるでしょうか?

//比較サンプル2
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class EqualsSample2 {

	public static void main(String[] args) {
		ZonedDateTime date1 = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 00, ZoneId.of("Europe/Belfast"));
		ZonedDateTime date2 = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 00, ZoneId.of("Asia/Tokyo"));
		long millis = date1.toInstant().toEpochMilli();
		long millis2 = date2.toInstant().toEpochMilli();
		System.out.println("---取得結果--------------------");
		System.out.println("①取得日時(ヨーロッパ) : " + date1 + "   (経過時間 : " + millis);
		System.out.println("②取得日時(日本)       : " + date2 + "  (経過時間 : " + millis2);
		//比較
		System.out.println("①.compareTo②     : " + date1.compareTo(date2));
		System.out.println("①.equals②        : " + date1.equals(date2));
		System.out.println("①.isEqual②       : " + date1.isEqual(date2));
		System.out.println("①.isBefore②      : " + date1.isBefore(date2));
		System.out.println("①.isAfter②       : " + date1.isAfter(date2));
	}
}


---取得結果--------------------
①取得日時(ヨーロッパ) : 2020-01-01T00:00Z[Europe/Belfast]   (経過時間 : 1577836800000
②取得日時(日本)       : 2020-01-01T00:00+09:00[Asia/Tokyo]  (経過時間 : 1577804400000
①.compareTo②     : 1
①.equals②        : false
①.isEqual②       : false
①.isBefore②      : false
①.isAfter②       : true

サンプル2では、同じ日時をそえぞれ指定しインスタンスを取得しています。

しかし、サンプル1では「1594372210000」が経ったそれぞれの同日時。サンプル2は同時刻ではあるものの、経過時間が違う同日時。と比較が変わっています。

その為、経過時間も違ってきています。


取得結果で、①は②よりも未来、という結果に?と思った方がいるかと思います。

経過時間を見れば①の方が数値が大きい為“未来”ということは分かると思いますが、日時だけ見ても分かりづらく感じるかもしれません。

ここが時差のややこしいところです。


(1)日本で2020年1月1日0時0分0秒を迎えた時、ヨーロッパでは2019年12月31日15時0分0秒です。

(2)ヨーロッパが2020年1月1日0時0分0秒を迎えた時、日本では2020年1月1日9時0分0秒です。


上記の(1)(2)の日本の日時を比べてみると、


(1)2020年1月1日0時0分0秒

(2)2020年1月1日9時0分0秒


こう見ると分かりやすいですね。

確実に、ヨーロッパの時間が未来ということになります。


※サンプル1とサンプル2でヨーロッパの時差に違いがありますが、それはサマータイム導入のためです。


関連記事:処理時間/経過時間計測(ミリ秒)に使える2つのメソッドを解説




日付/時刻の演算(plus、minus)― plus、minus、with

日付/時刻に対する演算として、加算や減算を行う計算用のメソッドが用意されています。


日付/時刻の演算(加算、減算)メソッド

plusメソッド

 インスタンスが持つ日時情報に加算をして、新しい日時情報のインスタンスを生成する

	LocalDate 変数名1 = LocalDate.of(int, int, int);	//int, int, int-西暦,月,日
	LocalDate plusYearsDate = 変数名1.plusYears(long);	//long-加算年数


minusメソッド

 インスタンスが持つ日時情報に減算をして、新しい日時情報のインスタンスを生成する

  LocalDate 変数名1 = LocalDate.of(int, int, int);	//int, int, int-暦,月,日
  LocalDate minusYearsDate = 変数名1.minusYears(long);	//long-減算年数


ここで紹介しているplusYearsメソッド、minusYearsメソッド以外にも、月を加減算するplusMonthsメソッド、minusMonthsメソッド、日を加減算するメソッドplusDaysメソッド、minusDaysメソッドなどがあります。

詳細はJavadoc『LocalDateクラス』をご確認ください。


plusメソッド(TemporalAmountを使用)

TemporalAmountインタフェースを実装するPeriodクラスを使って、加減算を行うことができます。

指定した時間量を加算して、この日付に基づくLocalDateを返します。

  Period 変数名1 = Period.ofDays(int);
  LocalDate ld = LocalDate.of(int, int, int).plus(変数名1);

Periodクラスは、時間量を表すクラスです。Periodクラス内では、年・月・日(各int)で値を保持します。

minus(TemporalAmount) メソッドで減算をすることもできます。


plusメソッド(ChronoUnit定数使用)

	LocalDate 変数名1 = LocalDate.of(int, int, int);
	LocalDate plusYearsDate = 変数名1.plus(long, TemporalUnit);	//減算は、plusをminusに変える

TemporalUnitは、日間、時間などの単位です。


新APIの日時操作には、日時のフィールドを表す列挙型定数(ChronoUnit)が用意されています。

TemporalUnitには、計算の単位として渡すChronoUnitの定数を指定します。

<ChronoUnit定数>

 年 :YEARS
 月 :MONTHS
 週 :WEEKS
 日 :DAYS
 時 :HOURS
 分 :MINUTES
 秒 :SECONDS

各定数の単語には最後にSが付くので、ChronoUnitを使ってソースを書く場合はSの書き忘れに気を付けてください。

計算の単位として渡すChronoUnitの列挙型は、TemporalUnitインタフェースになります。
上記のTemporalAmountが単位の量を表すのに対し、TemporalUnitは単位自体を表しているところが違いです。


加算減算の他に、インスタンスとして持つ日時データに対して、一部要素の変更を行うメソッドもあります。

withメソッド

 インスタンスが持つ日時情報に引数で指定した値に変え、新しいインスタンスを生成するメソッド(一部要素の変更)

  LocalDate 変数名1 = LocalDate.of(int, int, int);	//int, int, int-西暦,月,日
  LocalDate withYearsDate = 変数名1.withYears(long);	//long-変更年数


withメソッド(ChronoField定数使用)

	LocalDate 変数名1 = LocalDate.of(int, int, int);	       //int, int, int-西暦,月,日
	LocalDate withYearsDate = 変数名1.with(ChronoField.YEAR,long); //long-変更年数

これは、指定されたフィールドの値を変更して、この日付に基づくLocalDateを返します。

これを使用すると、年、月、月の日(DAY_OF_MONTH)、などのサポートされているフィールドを変更できます。

サポートされているフィールドの一つとしてChronoFieldがあります。

<列挙型ChronoField定数>

 年 :YEAR
 月 :MONTH_OF_YEAR
 週 :DAY_OF_WEEK(1…月曜日、7…日曜日)
 日 :DAY_OF_MONTH(その月の1日からその月の最終日まで)
   :DAY_OF_YEAR (その年の1日からその年の最終日まで。うるう年がある年は1~366までの指定となる)
 時 :HOUR_OF_DAY
 分 :MINUTE_OF_HOUR
 秒 :SECOND_OF_MINUTE	

例えば、DAY_OF_WEEKの動きとしてJavaDocでは、「指定された曜日を使ってLocalDateを返します。日付は、月曜日から日曜日までの週の範囲内で最大6日間、前後に調整されます。」と記述されています。

列挙型ChronoFieldの動きについてはJavaDoc『クラスLocalDate』に載っています。

また他にも多くの定数が用意されています。
詳しくは『列挙型ChronoUnit』『列挙型ChronoField』のJavaDocをご覧ください。


日付/時刻の演算(加算、減算)メソッド 実例

以下の日付/時刻の演算サンプルプログラムをご確認ください。

//演算サンプルプログラム
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;

public class CalculationSample {

	public static void main(String[] args) {
		//pulsメソッド(加算)
		System.out.println("---取得結果--------------------");
		System.out.println("---pulsメソッド------------------");
		LocalDate ld = LocalDate.of(2020, 7, 7);
		System.out.println("取得日時               : " + ld);
		LocalDate plusld = ld.plusYears(1);	// 1年後
		plusld = plusld.plusMonths(1);	// 1月後
		plusld = plusld.plusDays(1);	// 1日後
		System.out.println("加算日時        : " + plusld);
		LocalDate plusCU = ld.plus(1, ChronoUnit.YEARS);	// 1年後
		plusCU = plusCU.plus(1, ChronoUnit.MONTHS);	// 1月後
		plusCU = plusCU.plus(1, ChronoUnit.DAYS);	// 1日後
		System.out.println("加算日時(ChronoUnit) : " + plusCU);
        Period period = Period.ofDays(1);	// 1日後
		LocalDate plusTA = ld.plus(period);
		System.out.println("加算日時(TemporalAmount) : " + plusTA);
		//minusメソッド(減算)
		System.out.println("---minusメソッド-----------------");
		ZonedDateTime zdt = ZonedDateTime.now();
		System.out.println("現在日時 : " + zdt);
		ZonedDateTime minuszdt = zdt.minusHours(1);	// 1時間前
		minuszdt = minuszdt.minusMinutes(1);	// 1分前
		System.out.println("減算日時 : " + minuszdt);
		//withメソッド(変更)
		System.out.println("---withメソッド------------------");
		ZonedDateTime zdt1 = ZonedDateTime.of(2020,07,07,10,10,10,100, ZoneId.of("Europe/Paris"));
		ZonedDateTime withzdt = zdt1.withDayOfMonth(10);	//7月10日に変更
		System.out.println("変更日時              : " + withzdt);
		ZonedDateTime zdt2 = ZonedDateTime.of(2020,07,07,10,10,10,100, ZoneId.of("Europe/Paris"));
		ZonedDateTime withzdt2 = zdt2.with(ChronoField.DAY_OF_MONTH, 10);	//7月10日に変更
		System.out.println("変更日時(ChronoField) : " + withzdt2);
	}
}


---取得結果--------------------
---pulsメソッド------------------
取得日時               : 2020-07-07
加算日時        : 2021-08-08
加算日時(ChronoUnit) : 2021-08-08
加算日時(TemporalAmount) : 2020-07-08
---minusメソッド-----------------
現在日時 : 2020-09-11T19:04:47.268328+09:00[GMT+09:00]
減算日時 : 2020-09-11T18:03:47.268328+09:00[GMT+09:00]
---withメソッド------------------
変更日時              : 2020-07-10T10:10:10.000000100+02:00[Europe/Paris]
変更日時(ChronoField) : 2020-07-10T10:10:10.000000100+02:00[Europe/Paris]


変更されたオブジェクトが追加され、最終的な日時演算結果が出力されています。

引数にはマイナス値も指定することができるため、plusDays(-1)のように指定すると減算、minusDays(-1)と指定すると加算になります。どうしてもマイナス値で指定しなくては!ということがない限り、通常のplusメソッド、minusメソッドの使用が分かりやすいでしょう。

※LocalDateTimeクラス、LocalTimeクラス、OffsetTimeクラス、OffsetDateTimeクラス共に同じ方法で演算ができます。

ただし、LocalDateは日付(年月日)を表すクラスなので、誤って「plus(1, ChronoUnit.HOUR_OF_DAY)」などの時刻を表すフィールドを指定しないよう気を付けてください。コンパイルは通りますが実行時にエラーが出ます。(LocalTime、OffsetTimeに日付を表すフィールドを指定した場合もエラーになります)



まとめ

日時比較や計算の方法は分かったでしょうか?

日付/時刻の比較、演算にはそれぞれ多くのメソッドが用意されています。

扱うシステムにもよりますが、日付の計算(加算、減算)と比較は頻繁に登場する機能のひとつです。

それぞれの場面で適切なメソッドを選択できるように、特徴を押さえて正しいコーディングができるようになりましょう。


関連記事:よく使う日時クラス解説まとめ

     Java8からの現在や指定日時取得 9クラスの基本と使い方を解説

     Java8からの日時フォーマットクラスの使い方解説

     日時取得の Date/Calendar クラスの基本と使い方を解説

     Date/Calendarの日時フォーマット/日時比較/日時計算クラス使い方解説

     日付時刻の変換方法(新旧 API 相互変換、Data/Calendar の変換)

     処理時間/経過時間計測(ミリ秒)に使える2つのメソッドを解説


【著者】

伊藤

Javaを研修で3か月学んだ、駆け出しのエンジニアです。
現在は、ベンダー資格を取得するため、勉強を日課にできるよう努力中です。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様