joda-timeで現在時間を差し替える

joda-timeのTipsをひとつ。

以前紹介したように、joda-timeではorg.joda.time.DateTime#now()で現在時間のインスタンスを生成できます。もちろんJVMが動作するマシンの時間に依存するため、ユニットテストを書いたり、現在時間を任意の時間に変更してアプリの動作確認をするような場合少し不都合。

org.joda.time.DateTime#now()を追っかけていけばわかりますが、実際に現在時間を算出している箇所はorg.joda.time.DateTimeUtils#currentTimeMillis にあります。ポイントを抜粋すると以下のような感じ。

DateTimeUtils

public class DateTimeUtils {

    /** The singleton instance of the system millisecond provider. */
    private static final SystemMillisProvider SYSTEM_MILLIS_PROVIDER = new SystemMillisProvider();
    /** The millisecond provider currently in use. */
    private static volatile MillisProvider cMillisProvider = SYSTEM_MILLIS_PROVIDER;

    // (中略)

    //-----------------------------------------------------------------------
    /**
     * Gets the current time in milliseconds.
     * <p>
     * By default this returns <code>System.currentTimeMillis()</code>.
     * This may be changed using other methods in this class.
     * 
     * @return the current time in milliseconds from 1970-01-01T00:00:00Z
     */
    public static final long currentTimeMillis() {
        return cMillisProvider.getMillis();
    }

    // (中略)
    //-----------------------------------------------------------------------
    /**
     * A millisecond provider, allowing control of the system clock.
     * 
     * @author Stephen Colebourne
     * @since 2.0 (previously private)
     */
    public static interface MillisProvider {
        /**
         * Gets the current time.
         * <p>
         * Implementations of this method must be thread-safe.
         * 
         * @return the current time in milliseconds
         */
        long getMillis();
    }

    /**
     * System millis provider.
     */
    static class SystemMillisProvider implements MillisProvider {
        /**
         * Gets the current time.
         * @return the current time in millis
         */
        public long getMillis() {
            return System.currentTimeMillis();
        }
    }

DateTimeUtils#currentTimeMillisが返す値はMillisProviderを実装したSystemMillisProvider#getMillisSystem.currentTimeMillisということになります。 つまり、MillisProviderを新たに実装したクラスを作り、cMillisProviderを差し替えれば良いです。

独自のMillisProvider

試しにこんなのを実装してみました。

public class FakeMillisProvider implements MillisProvider {
    
    private final long difference;
    
    public FakeMillisProvider(long fakeCurrentTimeMills) {
        long realCurrentTime = System.currentTimeMillis();
        difference = fakeCurrentTimeMills - realCurrentTime;
    }

    @Override
    public long getMillis() {
        return System.currentTimeMillis() + difference;
    }

}

コンストラクタで、任意の時間のミリ秒を設定します。その中で、実際の時間との差を取ってフィールドに持っておきます。 これによりgetMillisは実際の現在時間から差分を加算したミリ秒を返すようになります。

MillisProviderを差し替える

以下のように差し替えます。MillisProviderはDateTimeUtils内にstaticに持っているため、扱いには少し注意。

DateTime fakeTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").parseDateTime("2013-10-10 10:10:10");
DateTimeUtils.setCurrentMillisProvider(new FakeMillisProvider(fakeTime.getMillis()));

DateTime now = DateTime.now();

これでDateTime#nowは、2013-10-10 10:10:10をスタート時間とし、differenceを算出してから経過した時間を加算した時間を返すようになります。 日またぎの確認とかに有用ですね。

ユニットテストのときなんかは、モックのMillisProviderを実装して差し込めばよいですね。