Ch.7 日付・時刻API
日付時刻クラス
import java.time.*; public class DataTimeSample { public static void main(String[] args){ System.out.println("LocalDate :" + LocalDate.now()); // 'LocalDate :2021-03-28' System.out.println("LocalTime :" + LocalTime.now()); // 'LocalTime :09:07:54.907153500' System.out.println("LocalDateTime :" + LocalDateTime.now()); // 'LocalDateTime :2021-03-28T09:07:54.908154500' System.out.println("OffsetTime : " + OffsetTime.now()); // 'OffsetTime : 09:07:54.909152500+09:00' System.out.println("OffsetDateTime : " + OffsetDateTime.now()); // 'OffsetDateTime : 2021-03-28T09:07:54.910151400+09:00' System.out.println("ZoneOffset.systemDefault() : " + ZoneOffset.systemDefault()); //'ZoneOffset.systemDefault() : Asia/Tokyo' System.out.println("ZonedId :" + ZoneId.systemDefault()); // 'ZonedId :Asia/Tokyo' } }
LocalDateTime
LocalDateTimeオブジェクトの生成で指定する月には【Month】クラスの列挙型が利用得できる。 また、LocalDateクラスとLocalTimeクラスのオブジェクトを組み合わせて【LocalDateTime】クラスのインスタンスを作成できる。
public static void createInstance(){ //System.out.println(LocalDate.of(2018, Month.FEBRUARY, 29)); // 'Invalid date 'February 29' as '2018' is not a leap year' System.out.println(LocalDate.of(2018, Month.FEBRUARY, 28)); //'2018-02-28' System.out.println(LocalDateTime.of(2019, Month.MARCH, 12, 23, 59)); //'2019-03-12T23:59' System.out.println(LocalDateTime.of(2019, Month.MARCH, 12, 23, 59).getMonth()); //'MARCH' System.out.println(LocalDateTime.of(2019, Month.MARCH, 12, 23, 59).getMonthValue()); //'3' System.out.println(LocalDateTime.of(LocalDate.of(2020,11,12), LocalTime.of(20,23))); //'2020-11-12T20:23' }
getMonth
メソッドでは列挙型で月を取得できる。
フォーマッタ
日付・時刻のフォーマッタは日付・時刻オブジェクトに渡して文字列化することも、フォーマッタオブジェクトに日付・時刻オブジェクトを渡して文字列化することも出来る。
public static void formatter(){ LocalDateTime myTime = LocalDateTime.of(2019, Month.MARCH, 12, 23, 59); System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(myTime)); //'2019-03-12T23:59:00' System.out.println(myTime.format(DateTimeFormatter.ISO_DATE_TIME)); //'2019-03-12T23:59:00' }
後者の方がオブジェクト指向的で良いかな、と思ったりする。
フォーマッタは手作り出来る。
第二引数にLocale
定数を渡すと様々な言語に翻訳して表示できる。
public static void myFormatter(){ LocalDateTime thisTime = LocalDateTime.now(); System.out.println(thisTime.format(DateTimeFormatter.ofPattern("MMMMdd"))); //'3月28' System.out.println(thisTime.format(DateTimeFormatter.ofPattern("MMMMdd", Locale.KOREA))); //'3월28' System.out.println(thisTime.format(DateTimeFormatter.ofPattern("今年始まってから: D日"))); //'今年始まってから: 87日' System.out.println(thisTime.format(DateTimeFormatter.ofPattern("今月 W週目の E曜日"))); //'今月 5回目の 日曜日' System.out.println(LocalDate.parse("2020ねん04がつ20にち10じ05ふん", DateTimeFormatter.ofPattern("yyyyねんMMがつddにちHHじmmふん"))); //'2020-04-20' }
フォーマッタに利用できるパターンは下記に記載されている。
DateTimeFormatter (Java Platform SE 8)
日付・時刻の計算
日付と時刻のオブジェクトはイミュータブルオブジェクトであるため、計算してもそのオブジェクトの値が変わることは無い。
public static void calculate(){ LocalDate thisDate = LocalDate.now(); System.out.println(thisDate.plusMonths(1).plusDays(3));//'2021-05-01' System.out.println(thisDate);//'2021-03-28' LocalDateTime thisDateTime = LocalDateTime.now(); System.out.println(thisDateTime.minusHours(45).plusSeconds(200));//'2021-03-26T14:41:12.627712400' System.out.println(thisDateTime);//'2021-03-28T11:37:52.627712400' }
ZonedDateTimeとOffsetDateTime
ZonedDateTime
はDateTimeオブジェクトにタイムゾーン情報を付与できる。
タイムゾーン情報を付与するだけで、渡したDateTimeオブジェクトの日付・時刻情報自体を変更することは無い。
public static void zoned(){ LocalDateTime sampleTime = LocalDateTime.of(2020,1,1,00,01); ZonedDateTime melbourneDateTime = ZonedDateTime.of(sampleTime, ZoneId.of("Australia/Melbourne")); ZonedDateTime japanDateTime = ZonedDateTime.of(sampleTime, ZoneId.systemDefault()); ZonedDateTime perthDateTime = ZonedDateTime.of(sampleTime, ZoneId.of("Australia/Perth")); System.out.println(melbourneDateTime); //'2020-01-01T00:01+11:00[Australia/Melbourne]' System.out.println(japanDateTime); //'2020-01-01T00:01+09:00[Asia/Tokyo]' System.out.println(perthDateTime); //'2020-01-01T00:01+08:00[Australia/Perth]' DateTimeFormatter austraiaFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL); System.out.println(austraiaFormat.format(melbourneDateTime)); //'2020年1月1日水曜日 0時01分00秒 オーストラリア東部夏時間' System.out.println(austraiaFormat.format(perthDateTime)); //'2020年1月1日水曜日 0時01分00秒 オーストラリア西部標準時' DateTimeFormatter shortFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT); System.out.println(shortFormat.format(sampleTime)); //'2020/01/01 0:01' System.out.println(austraiaFormat.format(sampleTime)); //'Exception in thread "main" java.time.DateTimeException Unable to extract ZoneId from temporal 2020-01-01T00:01' }
DateTimeFormatter.ofLocalizedDateTime
を利用してフォーマットにFormatStyle.FULL
やFormatStyle.LONG
を指定することで、ゾーン情報を付与した文字列に変換してくれる。
FormatStyle.short
やFormatStyle.MEDIUM
ではゾーン情報が付与されないので渡す時刻情報にゾーン情報が存在しなくても例外にはならない。
public static void offset(){ LocalDateTime sampleTime = LocalDateTime.of(2020,1,1,00,01); OffsetDateTime originalTime = OffsetDateTime.of(sampleTime, ZoneOffset.of("+02:00")); System.out.println(originalTime); DateTimeFormatter fullFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL); System.out.println(fullFormat.format(originalTime));//'2020-01-01T00:01+02:00' //'Exception in thread "main" java.time.DateTimeException: Unable to extract ZoneId from temporal 2020-01-01T00:01+02:00' }
夏時間
夏時間によってゾーン情報の時差が変更になっている。
public static void summerTime(){ LocalDate startDate = LocalDate.of(2018, 10, 7); LocalTime startTime = LocalTime.of(1,59); ZonedDateTime melbourneTime = ZonedDateTime.of(startDate, startTime, ZoneId.of("Australia/Melbourne")); System.out.println(melbourneTime + " 1.0h 後 ->" + melbourneTime.plusHours(1)); //'2018-10-07T01:59+10:00[Australia/Melbourne] 1.0h 後 ->2018-10-07T03:59+11:00[Australia/Melbourne]' LocalDate endDate = LocalDate.of(2019,4,7); LocalTime endTime = LocalTime.of(2,59); ZonedDateTime melbourneTimeEnd = ZonedDateTime.of(endDate, endTime, ZoneId.of("Australia/Melbourne")); System.out.println(melbourneTimeEnd + " 1.0h 後 ->" + melbourneTimeEnd.plusHours(1)); //'2019-04-07T02:59+11:00[Australia/Melbourne] 1.0h 後 ->2019-04-07T02:59+10:00[Australia/Melbourne]' }
基本的な事項であるが、夏時間開始時は「睡眠時間が短くなり(時間のスキップが発生し)」、夏時間終了時は「睡眠時間が長くなる(時間の重複が発生する)」。
Periodクラス
時間の間隔を年・月・日の単位で保持する。 オブジェクトは年・月・日の間隔をそれぞれ別に取り出すことが出来る。
public static void period(){ LocalDate startDate = LocalDate.of(2018, 10, 7); LocalTime startTime = LocalTime.of(1,59); LocalDate endDate = LocalDate.of(2020, 5, 5); LocalTime endTime = LocalTime.of(1,3); Period myPeriod = Period.between(startDate, endDate); System.out.println(myPeriod);//'P1Y6M28D' System.out.println(myPeriod.getYears()); //'1' System.out.println(myPeriod.getMonths());//'6' System.out.println(myPeriod.getDays()); //'28' //Period myPeriod = Period.between(LocalDateTime.of(startDate, startTime), LocalDateTime.of(endDate, endTime)); ZonedDateTime melbourneTime = ZonedDateTime.of(startDate, startTime, ZoneId.of("Australia/Melbourne")); ZonedDateTime japanDateTime = ZonedDateTime.of(startDate,startTime, ZoneId.systemDefault()); //Period period = Period.between(melbourneTime, japanDateTime); }
Periodは日・月・年単位の期間しか持てないため、LocalDateTime
やZonedDateTime
同士の期間は計算できない。
Period同士の計算が可能。計算に時間や秒単位の処理が発生しないため、PeriodとLocalDateTmeの計算も可能。
public static void periodCalc(){ LocalDate start = LocalDate.now(); LocalDate end = LocalDate.of(1999,8,12); Period thisPeriod = Period.between(end, start); System.out.println(thisPeriod); //'P21Y7M16D' System.out.println(thisPeriod.plus(Period.ofYears(10))); //'P31Y7M16D' System.out.println(thisPeriod.plus(Period.ofYears(10).plus(Period.ofMonths(3)))); //'P31Y10M16D' System.out.println(thisPeriod.plus(Period.ofYears(10).plus(Period.ofMonths(3))).plus(Period.ofDays(10))); //'P31Y10M26D' System.out.println(Period.between(start, end)); //'P-21Y-7M-16D' System.out.println(end.plus(thisPeriod)); //'2021-03-28' System.out.println(LocalTime.now().plus(thisPeriod)); //'Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Months' }
最後の例では時間の情報しか持たないLocalTime
オブジェクトに対して、【年・月・日】の足し算をしようとしているため、実行時例外となっている。
下記の場合でも同様である。
System.out.println(LocalTime.now().plus(Duration.ofHours(24))); //'20:48:26.033646' System.out.println(LocalTime.now().plus(Period.ofDays(1))); //'Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Days'
Durationクラス
Periodクラスが【年・月・日】の概念を持つのに対して、Durationでは【時間】の概念を扱う。Periodオブジェクトの標準出力が【P】で表されたのに対してDurationは【PT】となる。
ナノ秒の細かさまで保持することが出来、ofSeconds
メソッドでは第二引数でナノ秒を直接して出来る。
System.out.println(Duration.ofSeconds(9, 300000)); //'PT9.0003S' System.out.println(Duration.of(10, ChronoUnit.DAYS)); //'PT240H' System.out.println(Duration.of(10, ChronoUnit.HOURS)); //'PT10H' System.out.println(Duration.of(10, ChronoUnit.SECONDS)); //'PT10S'
Durationクラスも当然LocalDateTime
やLocalTime
オブジェクトに対する加算・減算に利用できる。但し、時刻情報を持たないLocalDate
オブジェクトに対しての加算・減算は実行時例外となる。
LocalDateTime nowTime = LocalDateTime.now(); System.out.println(nowTime.plus(Duration.of(10, ChronoUnit.DAYS))); //'2021-04-07T21:10:37.799255900' LocalDate noeDate = LocalDate.now(); System.out.println(noeDate.plus(Duration.of(10, ChronoUnit.DAYS))); //'Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds'
Instantクラス
LocalDateTimeは「ある一時点の時刻」を持てるが「どの場所での時刻か」は考慮されていない。同じ1999-11-20 14:20でもメルボルンの11-20 14:20と東京の11-20 14:20では2時間の差が生まれる。
ZonedLocalDateTimeも「ある時間ゾーンの一時点の時刻」を持てるが、下記の例のように「重複して発生する時間」が存在するため、一回目の2:59と二回目の2:59を見分けることは出来ない。
System.out.println(melbourneTimeEnd + " 1.0h 後 ->" + melbourneTimeEnd.plusHours(1)); //'2019-04-07T02:59+11:00[Australia/Melbourne] 1.0h 後 ->2019-04-07T02:59+10:00[Australia/Melbourne]'
そこで【UTCの1970年1月1日0:00】から何秒経過した時間か、という情報の持ち方をすることで【単一時点】を表現できるようにしたのがInstant
クラスである。
public static void epoch(){ System.out.println(Instant.now());//'2021-03-28T12:49:32.458080400Z' Arrays.asList(1,2,3,4,5).stream().forEach(System.out::println); System.out.println(Instant.now());//'2021-03-28T12:49:32.460081200Z' System.out.println(Instant.ofEpochSecond(1000000000));//'2001-09-09T01:46:40Z' }
エポック秒(とナノ秒)の2つの情報を持つオブジェクトだが、(そのまま表記されるとわかりにくいため)標準出力で表示されるのは「UTCのLocalDateTime」になっている。
ZonedDateTimeをInstantクラスのオブジェクトに変換することも出来る。
LocalDate endDate = LocalDate.of(2019,4,7); LocalTime endTime = LocalTime.of(2,59); ZonedDateTime melbourneTimeEnd = ZonedDateTime.of(endDate, endTime, ZoneId.of("Australia/Melbourne")); System.out.println(melbourneTimeEnd + " 1.0h 後 ->" + melbourneTimeEnd.plusHours(1)); System.out.println("Before the end of Daylight Saving Time: " + melbourneTimeEnd.toEpochSecond());//'1554566340' System.out.println("After the end of Daylight Saving Time: " + melbourneTimeEnd.plusHours(1).toEpochSecond());//'1554569940' System.out.println("Before the end of Daylight Saving Time: " + melbourneTimeEnd.toInstant());//'2019-04-06T15:59:00Z' System.out.println("After the end of Daylight Saving Time: " + melbourneTimeEnd.plusHours(1).toInstant());//'2019-04-06T16:59:00Z'
ZonedLocalDateTime
オブジェクトでは、夏時間終了時重複した時間を表現できなかったがInstantオブジェクトに変換することで時間の重複を含めた【一時点の表現】に成功している。
LocalDateTime
オブジェクトはゾーン情報や時差情報を持たないため、当然直接Instant
クラスのオブジェクトに変換することは出来ない。toInstant
メソッドを呼ぶときにゾーン情報(地域または時差)を指定して変換する必要がある。
LocalDateTime myTime = LocalDateTime.of(LocalDate.of(2020,12,20), LocalTime.of(10,20)); Instant myinstant = myTime.toInstant(ZoneOffset.of("+02:00")); System.out.println(myinstant.getEpochSecond());//'1608452400' System.out.println(myinstant);//'2020-12-20T08:20:00Z' ZonedDateTime myDarwinTime = ZonedDateTime.of(myTime,ZoneId.of("Australia/Darwin")); System.out.println(myDarwinTime.toInstant().getEpochSecond());//'1608425400' System.out.println(myDarwinTime.toInstant());//'2020-12-20T00:50:00Z'
instantオブジェクトに対する足し算にはDurationと同じChronoUnit
が利用できる。
ただしYEARS
はサポートされていない。