mike-neckのブログ

Java or Groovy or Swift or Golang

Java8のDate and Time API - LocalDateTimeを少しだけさわってみる

こんにちわ、みけです。

わけありで、LocalDateTimeとかZonedDateTime

触っててわからんところいっぱいあったので、

メモってみました。

Java8のData and Time Apiはこれだけではないけど、

はじめて触る人がハマるだろう箇所にとことんはまっています。


LocalDateTimeクラス

LocalDateTimeクラスは日時情報だけを持つクラスです。

文字列に出力するときDateTimeFormatterを用いますが、

タイムゾーンを表すフォーマッターが入ってると、

実行時例外が発生します。

package sample;

import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

@RunWith(Enclosed.class)
public class DateTimeTest {

    private static final Object LOCK = new Object();

    private static final DateTimeFormatter withoutZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

    private static final DateTimeFormatter withZone = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss Z");

    private static final ZoneId ASIA_TOKYO = ZoneId.of("Asia/Tokyo");

    private static final ZoneId GMT = ZoneId.of("GMT");

    private synchronized static void log(Queue<Output> outputs) {
        while (outputs.size() > 0) {
            synchronized (LOCK) {
                System.out.println(outputs.poll());
            }
        }
    }

    private static class Output {
        final String message;
        final String className;
        final String methodName;
        private Output(String message, String className, TestName testName) {
            this.message = message;
            this.className = className;
            this.methodName = testName.getMethodName();
        }
        @Override
        public String toString() {
            return className + " - " + message + " <- " + methodName;
        }
    }

    public static class LocalDateTimeTest {

        private static final String CLASS = LocalDateTimeTest.class.getSimpleName();

        private static final LocalDateTime NOW = LocalDateTime.now();

        private static final LocalDateTime NOW_AT_ASIA_TOKYO = LocalDateTime.now(ASIA_TOKYO);

        private static final LocalDateTime NOW_AT_GMT = LocalDateTime.now(GMT);

        private static final Queue<Output> QUEUE = new ConcurrentLinkedQueue<>();

        @Rule
        public TestName testName = new TestName();

        @Test
        public void localDateTimeは日時の情報だけを持つ() {
            QUEUE.offer(new Output(withoutZone.format(NOW), CLASS, testName));
        }

        @Test(expected = UnsupportedTemporalTypeException.class)
        public void localDateTimeは日時の情報だけを持っているのでゾーンを出力しようとすると例外() {
            withZone.format(NOW);
        }

        @Test
        public void ゾーン指定して取得したLocalDateTime() {
            QUEUE.offer(new Output(withoutZone.format(NOW_AT_ASIA_TOKYO), CLASS, testName));
        }

        @Test(expected = UnsupportedTemporalTypeException.class)
        public void ゾーン指定して取得したLocalDateTimeでもゾーンは持たない() {
            withZone.format(NOW_AT_ASIA_TOKYO);
        }

        @Test
        public void ゾーン指定して取得したLocalDateTimeは時間が異なる() {
            QUEUE.offer(new Output(withoutZone.format(NOW_AT_ASIA_TOKYO), CLASS, testName));
            QUEUE.offer(new Output(withoutZone.format(NOW_AT_GMT), CLASS, testName));
            assertThat(NOW_AT_GMT.isBefore(NOW_AT_ASIA_TOKYO), is(true));
        }

        @AfterClass
        public static void outputMessages() {
            log(QUEUE);
        }
    }
}

出力結果

LocalDateTimeTest - 2014/09/05 18:14:28 <- localDateTimeは日時の情報だけを持つ
LocalDateTimeTest - 2014/09/05 18:14:28 <- ゾーン指定して取得したLocalDateTimeは時間が異なる
LocalDateTimeTest - 2014/09/05 09:14:28 <- ゾーン指定して取得したLocalDateTimeは時間が異なる
LocalDateTimeTest - 2014/09/05 18:14:28 <- ゾーン指定して取得したLocalDateTime

本質的でないコードも多くありますが…

最初のメソッドlocalDateTimeは日時の情報だけを持つは、

単純にLocalDateTime.now()の結果を

ゾーン情報なしのフォーマットで出力します。

2つ目のメソッドlocalDateTimeは日時の情報だけを持っているのでゾーンを出力しようとすると例外は、

LocalDateTime.now()の結果をゾーン情報ありのフォーマットで出力しようと

しますが、ゾーン情報をLocalDateTimeは持たないので、

UnsupportedTemporalTypeExceptionを出して終了します。

また、4つ目のメソッドではLocalDateTime.nowに、

ZoneIdを渡してLocalDateTimeを取得したものを、

ゾーン情報ありのフォーマットで出力しようとしていますが、

これもUnsupportedTemporalTypeExceptionを出して終了します。

また、5つ目のメソッドではLocalDateTime.now

異なるZoneIdを渡した日時を比較しています。

標準出力の結果からもわかると思いますが、

GMTで取得した日時はAsia/Tokyoで取得した日時よりも、

9時間前の時間になるので、

gmt.isBefore(asiaTokyo)の結果はtrueになります。

その他は次回以降。