0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Java] FastDateFormatでParseする

Posted at

FastDateFormatとは

FastDateFormat is a fast and thread-safe version of SimpleDateFormat.

FastDateFormatは、 SimpleDateFormatの早くてスレッドセーフ版。

This class can be used as a direct replacement to * {@code SimpleDateFormat} in most formatting and parsing situations. * This class is especially useful in multi-threaded server environments. * {@code SimpleDateFormat} is not thread-safe in any JDK version, * nor will it be as Sun have closed the bug/RFE.

「ほとんどのケースのformatとparseにおいて、SimpleDateFormatの代替となる」と書かれている。

基本的な使い方

Apache Commons Langをhttps://mvnrepository.com/artifact/org.apache.commons/commons-lang3/3.12.0 から追加する (今回は3.12.0を使用)

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.FastDateFormat;

public class FastDateFormatBasic {

  public static void main(String[] args) {
    FastDateFormat tsDatetimeFormat = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss z");
    try {
      Date parsedDate = tsDatetimeFormat.parse("2021/08/15 00:00:00 UTC");
      System.out.println(parsedDate);
    } catch (ParseException e1) {
    }
  }
}

実行結果:

Sun Aug 15 09:00:00 JST 2021

(実行環境のTimezoneで表示される。)

内部実装

fast-date-format.png

  1. FastDateFormatのInstanceは以下のいずれかの方法で取得する。 今回は、1つ目のgetInstanceをとりあげて見る

    1. getInstance(String, TimeZone, Locale)
    2. getDateInstance(int, TimeZone, Locale)
    3. getTimeInstance(int, TimeZone, Locale)
    4. getDateTimeInstance(int, int, TimeZone, Locale)
  2. getInstance(String pattern, TimeZone timezone, Locale locale)FormatCacheのgetInstanceをよんでいる。

    public static FastDateFormat getInstance(final String pattern) {
        return cache.getInstance(pattern, null, null);
    }
    
  3. cacheは、FormatCacheというClassで、 FastDateFormatのCacheを確保している

    private static final FormatCache<FastDateFormat> cache = new FormatCache<FastDateFormat>() {
        @Override
        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
            return new FastDateFormat(pattern, timeZone, locale);
        }
    };
    

    cacheのキーには、 pattern,timezone, localeの3つが使われておりこれらが同じであれば同じInstanceが返される。 (FormatCache.getInstance(final String pattern, TimeZone timeZone, Locale locale))

  4. FastDateFormatのConstructorでは、 FastDateParserFastDatePrinterを初期化する

    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
        printer = new FastDatePrinter(pattern, timeZone, locale);
        parser = new FastDateParser(pattern, timeZone, locale, centuryStart);
    }
    
    1. FastDateParserは、 FastDateFormat.parse(final String source)内でそのまま parser.parse(source)が呼ばれている。パースのロジックはすべてFastDateParserの中にある
    2. FastDatePrinterは、 patternで指定したフォーマットに整形して表示するClassで、 FastDateFormat.format()printer.formatを読んでいて表示部分のロジックは、 FastDatePrinter にある。
  5. FastDateParser.parse()の中身

    1. まずParsePositionを0として、 parse(final String source, final ParsePosition pos) を呼び返り値のDateを返す。

    2. parse(final String source, final ParsePosition pos)では、 Calendarインスタンスをセットし、 parse(source, pos, cal)を呼び結果のBooleanがTrueの場合は、 cal.getTime()を返し、それ以外の場合は、 nullを返す。

      public Date parse(final String source, final ParsePosition pos) {
          // timing tests indicate getting new instance is 19% faster than cloning
          final Calendar cal = Calendar.getInstance(timeZone, locale);
          cal.clear();
      
          return parse(source, pos, cal) ? cal.getTime() : null;
      }
      
    3. parse(source, pos, cal) では、 イテレータのpatternsに対するループで、 patternに対するStrategyを取得し、対応するStrategyでParseし、パースが失敗した時点でfalseを返す。すべてのParseが成功したときにtrueを返す。

          public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
              final ListIterator<StrategyAndWidth> lt = patterns.listIterator();
              while (lt.hasNext()) {
                  final StrategyAndWidth strategyAndWidth = lt.next();
                  final int maxWidth = strategyAndWidth.getMaxWidth(lt);
                  if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) {
                      return false;
                  }
              }
              return true;
          }
      
      1. patterns は constructorで呼ばれているinit()関数でセットされている。中では、 StrategyParserpattern (e.g. yyyy/MM/dd HH:mm:ss z) の文字一つずつに対してどのStrategyかとwidthをセットにした StrategyAndWidthを決定してセットしている
        1. strategyAndWidth.strategy.parseでは各Strategyのparse関数の中で、 setCalendar関数が呼ばれて、渡されているcalendarが更新される
    4. cal.getTime() は、以下のようにミリセカンドからDateを初期化して返している

      public final Date getTime() {
              return new Date(getTimeInMillis());
      }
      

ハマったポイント

以下のように、Stringからタイムゾーンを取得しようとして、 FastDateFormatを使ってみようとしたが、SimpleDateFormatではできるが、FastDateFormatではできなかった。Thread-safeにするために、calendarをFastDateFormatに持たせないようにした影響と思われれる。

例.

  • pattern: yyyy/MM/dd HH:mm:ss z
  • parseする対象: 様々なTimeZoneを持った文字列
    • 2021/07/10 00:00:00 UTC
    • 2021/07/10 00:00:00 PDT
  • やりたいこと: 各インプットで指定されたTimeZoneを取得

以下のように、

package com.example.time;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang3.time.FastDateFormat;

public class FastDateFormatCheck {

    public static void main(String[] args) {
        FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss z");
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z");

        String[] timeStamps = new String[] {
                "2021/07/10 00:00:00 UTC",
                "2021/07/10 00:00:00 PDT",
        };
        for (String timeStamp : timeStamps) {
            try {
                Date fastDate = fastDateFormat.parse(timeStamp);
                Date normalDate = dateFormat.parse(timeStamp);
                System.out.println("[FastDateFormat]\tsource: " + timeStamp + "\tparsed: " + fastDate + "\tgetTimeZone: " + fastDateFormat.getTimeZone().getID());
                System.out.println("[SimpleDateFormat]\tsource: " + timeStamp + "\tparsed: " + normalDate + "\tgetTimeZone: " + dateFormat.getTimeZone().getID());
            } catch (ParseException e1) {
            }
            System.out.println("------------------------------------");
        }
    }
}

結果:

[FastDateFormat]        source: 2021/07/10 00:00:00 UTC parsed: Sat Jul 10 09:00:00 JST 2021    getTimeZone: Asia/Tokyo
[SimpleDateFormat]      source: 2021/07/10 00:00:00 UTC parsed: Sat Jul 10 09:00:00 JST 2021    getTimeZone: UTC
------------------------------------
[FastDateFormat]        source: 2021/07/10 00:00:00 PDT parsed: Sat Jul 10 16:00:00 JST 2021    getTimeZone: Asia/Tokyo
[SimpleDateFormat]      source: 2021/07/10 00:00:00 PDT parsed: Sat Jul 10 16:00:00 JST 2021    getTimeZone: America/Los_Angeles
------------------------------------

内部実装からも分かる通り、FastDateFormatではTimeZoneの情報はCalendarに更新されるが、Calendarオブジェクトは FastDateParserparse関数内のローカル変数のため、FastDateFormatから取得することはできない。また、parseの結果も millisecondsからDateオブジェクトを初期化して返しているので、もともと文字列内にあったTimeZone情報は含まれていない。
SimpleDateFormatの場合は (内部ロジックを詳しく見ていないが) CalendarインスタンスがSimpleDateFormatに保持されていてparse時に更新されていて、getTimeZone()では、 return calendar.getTimeZone();と返しているためにTimeZoneをパースした文字列から取得できている。

まとめ

  • FastDateFormatは、SimpleDateFormatの高速版且つThread-safe版として代替に使われる
  • FastDateFormatは、getInstanceでのみインスタンスを取得できる
  • FastDateFormat内には、 FormatCacheFastDateFormatをキャッシュしていてgetInstancepattern, timezone, localeをキーにCacheを作成または既存のインスタンスを返す
  • FastDateFormatには、FastDatePrinterFastDateParserがあり、それぞれにFormatとParseのロジックが実装されている
  • FastDateParserparseでは、 Calendarを取得し、patternに対して一文字ずつParseStrategywidthを決め、対応するstrategyのparseの中で、calendarを更新し、最終的に、calendar.getTime()Dateオブジェクトを返す
  • calendarの返しかたから、 FastDateFormatでは、 parseしたStringの中にあったTimeZoneがどこだったかの情報は取得できない。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?