無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

JavaScript Dateのタイムゾーン指定できない問題とその対策

JavaScriptのDate型では、タイムゾーンを外部から指定できない

  • JavaScriptのDate型は、タイムゾーンの情報を持ってはいますが、APIで外から変更することはできません。
    • getTimezoneOffset() はあるけど、setTimezoneOffset(timezone) はありません。
    • getTimezoneOffset() の値は実行環境のタイムゾーンになります。

  • getHours()やgetMinutes()はローカルのタイムゾーンにおける時刻表現を返すので、実行環境によって結果が異なり、問題になる場合があります。
    • Dateを表示用の文字列に変換する処理のテストを書いていたら、CI環境(CircleCI)で動かしたときに結果が違ってエラーに。
    • 変換APIの仕様としては、「実行環境に合わせた時刻表現を返す」のが期待の動作なので、動きとしては間違っていないのですが、テストでは困る・・
    • CI環境のタイムゾーンを変更する道もあるけど、タイムゾーンの設定によって影響が出る範囲を把握するためにテストケースはいろいろな環境で動かすようにした方がいいかな、ということで、環境はいじらない方向で対策を検討。
const date = new Date( '2015-05-10T12:00:00.000Z' );
expect( date.getHours() ).toEqual(21); 
// ローカルタイムゾーンがJSTの場合は動作するが、他の環境では違う結果になる。

対策

以下のようにしてみました。

  • Dateの代わりに、 date-with-offset を使う。
    • Dateにタイムゾーン指定機能を追加したライブラリ。DateのAPIと互換性があるのでそのまま置き換えられます。
  • DateWithOffsetをnewするときに、static変数からタイムゾーンを読み込んで使う。
    • static変数で指定されていればそちら、指定がなければローカルのタイムゾーンを使います。
    • これにより、ローカルタイムゾーンの影響を受けるテストの実行時にのみ、指定したタイムゾーンでテストを実行きるようになります。
    • 指定がない場合は普通のDateと同じ動きになるので、プロダクション環境では「実行環境に合わせた時刻表現を返す」動作となります。

具体的にはこんなユーティリティを作成。

import DateWithOffset from "date-with-offset"

const defaultTimezoneOffset = new Date().getTimezoneOffset()*-1;

export default class Dates {

  static date(iso8601String) {
    return new DateWithOffset(
      iso8601String, this.getTimezoneOffset() );
  }

  static getTimezoneOffset() {
    return this.timezoneOffset != null
        ? this.timezoneOffset
        : defaultTimezoneOffset;
  }
  static setTimezoneOffset(timezoneOffset) {
    this.timezoneOffset = timezoneOffset;
  }
  static resetTimezoneOffset() {
    this.timezoneOffset = null;
  }
}

ローカルタイムゾーンに依存するテストが動作するところは、以下のようにすることで、どの環境でも同じ結果が返るようになります。

beforeEach( ()=> Dates.setTimezoneOffset(540) );
afterEach(  ()=> Dates.resetTimezoneOffset() );

it("ローカルタイムゾーンに依存するテスト", () => {
  // このテスト内では、ローカルタイムゾーンがJSTになる。
  expect( Dates.date("2015-05-10T12:00:00.000Z").getHours() ).toEqual(21);
});