Java 8の日付 – LocalDate、LocalDateTime、Instant

日時 API は Java 8 リリースの最大の特徴の1つです。Java は初めから日付と時刻に対する一貫したアプローチが欠けていましたが、Java 8 の日時 API はコアの Java API に歓迎される追加です。

なぜ新しい Java 日時 API が必要なのか?

Java 8 の日時 API を見る前に、なぜこれが必要なのかを見てみましょう。java の既存の日付および時刻関連クラスにはいくつかの問題があります。そのいくつかは次のとおりです:

  • Java の日時クラスは一貫して定義されていません。java.util と java.sql パッケージの両方に Date クラスがあります。また、フォーマットおよび解析クラスは java.text パッケージに定義されています。
  • java.util.Date には日付値と時刻値の両方が含まれていますが、java.sql.Date には日付値のみが含まれています。これを java.sql パッケージに含めるのは意味がありません。また、両方のクラスに同じ名前が付けられているため、これ自体が非常に悪い設計です。
  • 時間、タイムスタンプ、フォーマット、および解析のための明確に定義されたクラスがありません。解析およびフォーマットの必要には java.text.DateFormat 抽象クラスがあります。通常、SimpleDateFormat クラスが解析およびフォーマットに使用されます。
  • すべての日付クラスは変更可能であるため、スレッドセーフではありません。これは Java の日付およびカレンダークラスの最大の問題の1つです。
  • 日付クラスは国際化を提供せず、タイムゾーンのサポートもありません。そのため、java.util.Calendar および java.util.TimeZone クラスが導入されましたが、これらも上記にリストされたすべての問題を抱えています。

Date および Calendar クラスに定義されたメソッドには他の問題もありますが、上記の問題が明確に示すように、Java には堅牢な日付時刻 API が必要でした。そのため、Joda Time が Java 日付時刻要件の質的な置き換えとして重要な役割を果たしました。

Java 8 日付時刻設計原則

Java 8 日付時刻 API は JSR-310 の実装です。この新しい日付時刻 API のいくつかの設計原則は次のとおりです:

  1. 不変性: 新しい日付時刻 API のすべてのクラスは不変であり、マルチスレッド環境に適しています。

  2. 関心の分離: 新しい API は、人間が読み取れる日付時刻とマシン時刻(Unix タイムスタンプ)を明確に分離します。Date、Time、DateTime、Timestamp、Timezone などのためにそれぞれ別のクラスを定義します。

  3. 明確さ: メソッドは明確に定義され、すべてのクラスで同じアクションを実行します。例えば、現在のインスタンスを取得するにはnow() メソッドがあります。これらのクラスには、それらのための別個のクラスを持つのではなく、format()メソッドとparse()メソッドが定義されています。

    すべてのクラスはFactory Pattern とStrategy Pattern を使用しており、より良い処理が可能です。一度クラスのメソッドを使用すると、他のクラスとの作業も難しくありません。

  4. ユーティリティ操作:すべての新しい日付時刻APIクラスには、プラス、マイナス、フォーマット、パース、日付/時刻の個別の部分を取得するなど、一般的なタスクを実行するメソッドが付属しています。

  5. 拡張可能:新しい日時APIはISO-8601カレンダーシステムで動作しますが、他の非ISOカレンダーとも使用できます。

日時APIパッケージ

Java 8の日時APIには、以下のパッケージが含まれています。

  1. java.time:これは新しいJava日時APIの基本パッケージです。すべての主要な基本クラスがこのパッケージの一部であり、LocalDate、LocalTime、LocalDateTime、Instant、Period、Durationなどが含まれます。これらのクラスはすべて不変でスレッドセーフです。ほとんどの場合、これらのクラスで一般的な要件を処理するのに十分です。
  2. java.time.chrono:このパッケージは非ISOカレンダーシステムの汎用APIを定義します。AbstractChronologyクラスを拡張して独自のカレンダーシステムを作成できます。
  3. java.time.format:このパッケージには、日付時刻オブジェクトのフォーマットおよび解析に使用されるクラスが含まれています。ほとんどの場合、java.timeパッケージの主要なクラスがフォーマットおよび解析メソッドを提供しているため、直接これらのクラスを使用する必要はありません。
  4. java.time.temporal:このパッケージには時間オブジェクトが含まれており、日付/時間オブジェクトに関連する特定の日付や時刻を求めるために使用できます。たとえば、月の最初や最後の日を求めるためにこれらを使用できます。これらのメソッドは常に「withXXX」の形式を持っているため、これらを簡単に特定することができます。
  5. java.time.zone パッケージ:このパッケージには、異なるタイムゾーンおよびそれらの規則をサポートするクラスが含まれています。

Java 8 日付時刻 API クラスの例

Java 日付時刻 API の重要な部分のほとんどを見てきました。今は、日付時刻 API の最も重要なクラスを例とともに見ていきましょう。

1. LocalDate

LocalDate は、デフォルトの形式 yyyy-MM-dd で日付を表す不変のクラスです。現在の日付を取得するために now() メソッドを使用できます。また、年、月、日の入力引数を提供して LocalDate インスタンスを作成することもできます。

このクラスは、特定のタイムゾーンで日付を取得するためにZoneIdを渡すnow()のオーバーロードメソッドを提供します。このクラスは、java.sql.Dateと同じ機能を提供します。

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

/**
 * LocalDateの例
 * @author pankaj
 *
 */
public class LocalDateExample {

	public static void main(String[] args) {
		
		//現在の日付
		LocalDate today = LocalDate.now();
		System.out.println("Current Date="+today);
		
		//入力引数を指定してLocalDateを作成
		LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
		System.out.println("Specific Date="+firstDay_2014);
		
		
		//無効な入力を指定して日付を作成しようとしてください
		//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
		//スレッド "main"での例外java.time.DateTimeException:
		//'2014'が閏年ではないため、無効な日付 'February 29'
		
		// "Asia / Kolkata"の現在の日付、ZoneId javadocから取得できます
		LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException:不明なタイムゾーンID:IST
		//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
		
		//基本日付、つまり1970年1月1日から日付を取得
		LocalDate dateFromBase = LocalDate.ofEpochDay(365);
		System.out.println("365th day from base date= "+dateFromBase);
		
		LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
		System.out.println("100th day of 2014="+hundredDay2014);
	}

}

コメントでLocalDateメソッドの説明が提供されています。このプログラムを実行すると、次の出力が得られます。

Current Date=2014-04-28
Specific Date=2014-01-01
Current Date in IST=2014-04-29
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10

2. LocalTime

LocalTimeは、人間が読みやすい形式の時間を表す不変のクラスです。デフォルトの形式はhh:mm:ss.zzzです。LocalDateと同様に、このクラスはタイムゾーンのサポートを提供し、時、分、秒を入力引数として渡してインスタンスを作成します。

package com.journaldev.java8.time;

import java.time.LocalTime;
import java.time.ZoneId;

/**
 * LocalTimeの例
 * @author pankaj
 *
 */
public class LocalTimeExample {

	public static void main(String[] args) {
		
		//現在時刻
		LocalTime time = LocalTime.now();
		System.out.println("Current Time="+time);
		
		//入力引数を指定してLocalTimeを作成
		LocalTime specificTime = LocalTime.of(12,20,25,40);
		System.out.println("Specific Time of Day="+specificTime);
		
		
		//無効な入力を提供して時間を作成しようとする
		//LocalTime invalidTime = LocalTime.of(25,20);
		//Exception in thread "main" java.time.DateTimeException: 
		//HourOfDayの値が無効です (有効な値は 0 - 23): 25
		
		//"Asia/Kolkata"の現在日付、ZoneId javadocから取得できます
		LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Time in IST="+timeKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));
		
		//基準日付、すなわち1970年1月1日からの日付を取得
		LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
		System.out.println("10000th second time= "+specificSecondTime);

	}

}

出力:

Current Time=15:51:45.240
Specific Time of Day=12:20:25.000000040
Current Time in IST=04:21:45.276
10000th second time= 02:46:40

3. LocalDateTime

LocalDateTimeは、デフォルトの形式であるyyyy-MM-dd-HH-mm-ss.zzzで日時を表す不変の日時オブジェクトです。LocalDateとLocalTimeの入力引数を取るファクトリーメソッドを提供し、LocalDateTimeインスタンスを作成します。

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class LocalDateTimeExample {

	public static void main(String[] args) {
		
		//現在の日付
		LocalDateTime today = LocalDateTime.now();
		System.out.println("Current DateTime="+today);
		
		//LocalDateとLocalTimeを使用した現在の日付
		today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
		System.out.println("Current DateTime="+today);
		
		//入力引数を指定してLocalDateTimeを作成する
		LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
		System.out.println("Specific Date="+specificDate);
		
		
		//無効な入力を提供して日付を作成しようとしてみる
		//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
		//Exception in thread "main" java.time.DateTimeException: 
		//HourOfDayの無効な値です(有効な値は0から23です): 25

		
		//"Asia/Kolkata"の現在の日付、ZoneIdのjavadocから取得できます
		LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));
		
		//基準日(1970年1月1日)からの日付を取得する
		LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
		System.out.println("10000th second time from 01/01/1970= "+dateFromBase);

	}

}

すべての例で、日付/時刻を作成するために無効な引数を提供した場合、java.time.DateTimeExceptionがスローされることがわかりました。これはRuntimeExceptionなので、明示的にキャッチする必要はありません。

私たちはまた、ZoneIdを渡すことで日付/時刻データを取得できることも確認しました。サポートされているZoneId値のリストをそのJavaDocから取得できます。上記のクラスを実行すると、以下の出力が得られます。

Current DateTime=2014-04-28T16:00:49.455
Current DateTime=2014-04-28T16:00:49.493
Specific Date=2014-01-01T10:10:30
Current Date in IST=2014-04-29T04:30:49.493
10000th second time from 01/01/1970= 1970-01-01T02:46:40

4. Instant

Instantクラスは、機械可読な時間形式で動作します。Instantは、UNIXタイムスタンプ形式で日付時刻を格納します。

package com.journaldev.java8.time;

import java.time.Duration;
import java.time.Instant;

public class InstantExample {

	public static void main(String[] args) {
		//現在のタイムスタンプ
		Instant timestamp = Instant.now();
		System.out.println("Current Timestamp = "+timestamp);
		
		//タイムスタンプからInstantを取得
		Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
		System.out.println("Specific Time = "+specificTime);
		
		//期間の例
		Duration thirtyDay = Duration.ofDays(30);
		System.out.println(thirtyDay);
	}

}

出力:

Current Timestamp = 2014-04-28T23:20:08.489Z
Specific Time = 2014-04-28T23:20:08.489Z
PT720H

Java 8 Date API ユーティリティ

ほとんどの日時の主要なクラスは、日数、週数、月数などのプラス/マイナスのユーティリティメソッドを提供します。日付を調整するためのTemporalAdjusterおよび2つの日付間の期間を計算するためのその他のユーティリティメソッドもあります。

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class DateAPIUtilities {

	public static void main(String[] args) {
		
		LocalDate today = LocalDate.now();
		
		//年を取得し、うるう年かどうかを確認する
		System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
		
		//2つのLocalDateを前後で比較する
		System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));
		
		//LocalDateからLocalDateTimeを作成する
		System.out.println("Current Time="+today.atTime(LocalTime.now()));
		
		//プラスとマイナスの演算
		System.out.println("10 days after today will be "+today.plusDays(10));
		System.out.println("3 weeks after today will be "+today.plusWeeks(3));
		System.out.println("20 months after today will be "+today.plusMonths(20));

		System.out.println("10 days before today will be "+today.minusDays(10));
		System.out.println("3 weeks before today will be "+today.minusWeeks(3));
		System.out.println("20 months before today will be "+today.minusMonths(20));
		
		//日付を調整するためのTemporal adjusters
		System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth()));
		LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
		System.out.println("Last date of this year= "+lastDayOfYear);
		
		Period period = today.until(lastDayOfYear);
		System.out.println("Period Format= "+period);
		System.out.println("Months remaining in the year= "+period.getMonths());		
	}
}

出力:

Year 2014 is Leap Year? false
Today is before 01/01/2015? true
Current Time=2014-04-28T16:23:53.154
10 days after today will be 2014-05-08
3 weeks after today will be 2014-05-19
20 months after today will be 2015-12-28
10 days before today will be 2014-04-18
3 weeks before today will be 2014-04-07
20 months before today will be 2012-08-28
First date of this month= 2014-04-01
Last date of this year= 2014-12-31
Period Format= P8M3D
Months remaining in the year= 8

Java 8の日付の解析とフォーマット

日付を異なる形式にフォーマットして、次に文字列を解析して日時オブジェクトを取得することは非常に一般的です。

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateParseFormatExample {

	public static void main(String[] args) {
		
		//フォーマットの例
		LocalDate date = LocalDate.now();
		//デフォルトの形式
		System.out.println("Default format of LocalDate="+date);
		//特定の形式
		System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
		System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		
		LocalDateTime dateTime = LocalDateTime.now();
		//デフォルトの形式
		System.out.println("Default format of LocalDateTime="+dateTime);
		//特定の形式
		System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
		System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		Instant timestamp = Instant.now();
		//デフォルトの形式
		System.out.println("Default format of Instant="+timestamp);
		
		//解析の例
		LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",
				DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
		System.out.println("Default format after parsing = "+dt);
	}

}

出力:

Default format of LocalDate=2014-04-28
28::Apr::2014
20140428
Default format of LocalDateTime=2014-04-28T16:25:49.341
28::Apr::2014 16::25::49
20140428
Default format of Instant=2014-04-28T23:25:49.342Z
Default format after parsing = 2014-04-27T21:39:48

Javaの日付APIのレガシー日時サポート

ほとんどのアプリケーションでレガシーの日時クラスが使用されているため、後方互換性が必要です。そのため、レガシークラスを新しいクラスに変換したり、その逆を行うためのいくつかのユーティリティメソッドがあります。

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class DateAPILegacySupport {

	public static void main(String[] args) {
		
		//日付をインスタントに変換
		Instant timestamp = new Date().toInstant();
		//今後、インスタントをLocalDateTimeや他の類似のクラスに変換できます
		LocalDateTime date = LocalDateTime.ofInstant(timestamp, 
						ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
		System.out.println("Date = "+date);
		
		//カレンダーをインスタントに変換
		Instant time = Calendar.getInstance().toInstant();
		System.out.println(time);
		//タイムゾーンをZoneIdに変換
		ZoneId defaultZone = TimeZone.getDefault().toZoneId();
		System.out.println(defaultZone);
		
		//特定のカレンダーからZonedDateTimeを取得
		ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
		System.out.println(gregorianCalendarDateTime);
		
		//日付APIからレガシークラスへの変換
		Date dt = Date.from(Instant.now());
		System.out.println(dt);
		
		TimeZone tz = TimeZone.getTimeZone(defaultZone);
		System.out.println(tz);
		
		GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
		System.out.println(gc);
		
	}

}

出力:

Date = 2014-04-28T16:28:54.340
2014-04-28T23:28:54.395Z
America/Los_Angeles
2014-04-28T16:28:54.404-07:00[America/Los_Angeles]
Mon Apr 28 16:28:54 PDT 2014
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

レガシーのTimeZoneGregorianCalendarクラスのtoString()メソッドが非常に冗長で使いづらいことがわかります。

結論

I like this new Date Time API a lot. Some of the most used classes will be LocalDate and LocalDateTime. It’s very easy to work with the new classes. And, having similar methods that does a particular job makes it easy to find. It will take some time from moving legacy classes to new Date Time classes, but I believe it will be worth the time and effort.

Source:
https://www.digitalocean.com/community/tutorials/java-8-date-localdate-localdatetime-instant