mike-neckのブログ

Java or Groovy or Swift or Golang

Java8のDate and Time API - 文字列から日付を取得してみる

こんにちわ、みけです。

前回、前々回に引き続き、Java8のDate and Time APIをさわります。

今日は、文字列から日付を取得する方法です。


DateTimeFormatterクラス

文字列とTemporalAccessorとを変換するクラスです。

DateTimeFormatter#format(TemporalAccessor)メソッドによって、

日付から文字列に変換することができます。

DateTimeFormatter#parse(String)メソッドによって、

文字列から日付に変換することができます。

また、このクラスは、必要に応じて何度でも利用できスレッドセーフです。

では、サンプルコードです。

@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 static final String DATE_TIME = "2014/10/24 14:09:20 +0900";

    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 文字列からDateTimeを取得する {

        private static final String CLASS = 文字列からDateTimeを取得する.class.getSimpleName();

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

        @Rule
        public TestName testName = new TestName();

        @Test
        public void 文字列からLocalDateTimeを取得する() {
            LocalDateTime parsed = LocalDateTime.parse(DATE_TIME, withZone);
            log(withoutZone.format(parsed));
        }

        @Test
        public void 文字列からZonedDateTimeを取得する() {
            ZonedDateTime parsed = ZonedDateTime.parse(DATE_TIME, withZone);
            log(withZone.format(parsed));
        }

        @Test(expected = DateTimeParseException.class)
        public void ゾーンなしの文字列からZonedDateTimeを取得しようとするとDateTimeParseException() {
            ZonedDateTime parsed = ZonedDateTime.parse("2014/10/24 14:09:20", withoutZone);
            toQueue(withZone.format(parsed));
        }

        @Test
        public void 文字列からTemporalAccessorを取得する() {
            TemporalAccessor parsed = withZone.parse(DATE_TIME);
            log(LocalDateTime.from(parsed).toString());
        }

        @Test(expected = DateTimeParseException.class)
        public void 起源なしのフォーマットをSTRICTモードで解釈させるとDateTimeParseException() {
            DateTimeFormatter strict = withZone.withResolverStyle(ResolverStyle.STRICT);
            ZonedDateTime.parse(DATE_TIME, strict);
        }

        @Test
        public void STRICTモードを使う場合はyearはuを用いる() {
            DateTimeFormatter strict = DateTimeFormatter.ofPattern("uuuu/MM/dd HH:mm:ss Z")
                    .withResolverStyle(ResolverStyle.STRICT);
            ZonedDateTime parsed = ZonedDateTime.parse(DATE_TIME, strict);
            toQueue(withZone.format(parsed));
        }

        @Test
        public void 起源ありのフォーマットをSTRICTモードで解釈させる() {
            String dateTime = "2014/10/24 AD 14:09:20 +0900";
            String pattern = "yyyy/MM/dd G HH:mm:ss Z";
            DateTimeFormatter strict = DateTimeFormatter.ofPattern(pattern)
                    .withLocale(Locale.US)
                    .withResolverStyle(ResolverStyle.STRICT);
            ZonedDateTime parsed = ZonedDateTime.parse(dateTime, strict);
            log(withZone.format(parsed));
        }

        private void log(String message) {
            QUEUE.offer(new Output(message, CLASS, testName));
        }

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

出力結果

文字列からDateTimeを取得する - 2014/10/24 14:09:20 <- 文字列からLocalDateTimeを取得する
文字列からDateTimeを取得する - 2014/10/24 14:09:20 +0900 <- 起源ありのフォーマットをSTRICTモードで解釈させる
文字列からDateTimeを取得する - java.time.format.Parsed <- 文字列からTemporalAccessorを取得する
文字列からDateTimeを取得する - {InstantSeconds=1414127360, OffsetSeconds=32400},ISO resolved to 2014-10-24T14:09:20 <- 文字列からTemporalAccessorを取得する
文字列からDateTimeを取得する - 2014/10/24 14:09:20 +0900 <- STRICTモードを使う場合はyearはuを用いる
文字列からDateTimeを取得する - 2014/10/24 14:09:20 +0900 <- 文字列からZonedDateTimeを取得する

最初のテスト文字列からLocalDateTimeを取得するでは、

書式"yyyy/MM/dd HH:mm:ss Z"の形式の文字列からLocalDateTimeを取得しています。

LocalDateTimeはゾーン情報を持たないので、パースした際に時差の情報はなくなります。


2つ目のテスト文字列からZonedDateTimeを取得するでは、

書式"yyyy/MM/dd HH:mm:ss Z"の形式の文字列からZonedDateTimeを取得しています。

ZonedDateTimeはゾーン情報をもつので、ゾーンありの形式で出力できます。


3つ目のテストゾーンなしの文字列からZonedDateTimeを取得しようとするとDateTimeParseExceptionでは、

ゾーンなしの"yyyy/MM/dd HH:mm:ss"の形式の文字列からZonedDateTimeを取得しようと

しますが、ゾーン情報がないためにDateTimeParseExceptionが発生します。


4つ目のテスト文字列からTemporalAccessorを取得するでは、最初に紹介した

DateTimeFormatter#parse(String)を使っています。

DateTimeFormatter#parse(String)で返される実装クラスはjava.time.format.Parsedというクラスです。

このクラスのjavadocを読むと、パースしている間にデータを保持するクラスであることがわかります。

まあ、たぶん、このクラスをそのまま使うことはほとんどないと思います。


5つ目のテスト起源なしのフォーマットをSTRICTモードで解釈させるとDateTimeParseExceptionでは、

これまで特に適当に使っていたDateTimeFormatterの書式を厳密に用いています。

このときに、書式"yyyy"は実際はyear-of-eraなので、

era(起源)の情報がないとDateTimeParseExceptionが発生します。

参考 : Java8 の Date & Time API ではまった。 - ゆっちのBlog


では、厳密にパースする場合どうするかというと、

一つ目は上記のブログで書いてあった解決方法で書式を"uuuu/MM/dd HH:mm:ss Z"を用いるという方法です。

これが6つ目のテストSTRICTモードを使う場合はyearはuを用いるです。


もう一つの方法は時刻を表す書式を"yyyy/MM/dd G HH:mm:ss Z"にするという方法です。

これが7つ目のテスト起源ありのフォーマットをSTRICTモードで解釈させるです。

なお、起源ありのフォーマットをパースする場合、

Localを設定していないとDateTimeParseExceptionが発生します。

参考 Java日付時刻APIメモ(Hishidama's Java8 Date and Time API Memo)

また、"SHOWA"とか試してみましたが、ダメでした(´・ω・`)


以上

次回はLocalDateTimeZonedDateTimeの相互変換について書きます。