📊

Rubyでn営業日目を取得する方法と注意

2017/02/15に公開

概要

  • 営業日計算にはbusiness_timeを使うのが便利です。
  • しかしながら、タイムゾーン関連でハまったので、メモを残しておきます。

結論

  • 実行環境のタイムゾーンに依存しないように、日本時間で4営業日目の日付を取得したかったら以下のように書く必要があります。
4.business_days.after(Time.current.in_time_zone('Tokyo').midnight).to_date
  • 例えば、上記コマンドの実行日が土曜日か日曜日の場合、1営業日後を求めると火曜日の日付が返却されます。

試行錯誤した過程

以下に、意図した値とは違う値が返ってくる例を書いておきます。
一般的なユースケースでは使われないと思うコードなので単純にコピペしないように。

Time.zoneの値に依存した結果が返る

  • 公式のサンプルコードがこれです。
  • 当たり前ですが、ローカルタイムで計算されます。
  • 意図した日の前後が返ってくる場合があります。
  • 営業時間が考慮されてしまうので、そのタイムゾーンの、その日の営業時間が終わると+1日された値が返ってくる
4.business_days.from_now.to_date

タイムゾーンは考慮しているが、時間帯によって1日プラスされる

  • タイムゾーンは東京のタイムゾーンで計算されるので問題無いです。
  • しかしながら、その日の営業時間が終了したら、次の日と扱われるので日本時間で営業時間が終了すると1日意図した値より大きい値が返ってきます。
4.business_days.after(Time.current.in_time_zone('Tokyo')).to_date

番外編:休日から1営業日後は月曜日では無く、火曜日

  • 土曜日から換算した、1営業日後は月曜だと考えますが、営業日の定義によっては火曜が返ってくるのが正しいです。
  • 個人的にはモードで切り替えたいが。
  • 詳細な議論はこちら

rspec

  • 意図した時間を返却するかどうかは、以下のようなrspecを使って検証するのが良いと思います。
  describe 'check behavior of business_days gem' do
    let(:freeze) { Time.utc(2017, 2, 15) }
    let(:expected_date) { (freeze + 6.days).to_date }
    let(:timezone) { 'Asia/Tokyo' }

    shared_examples 'success' do
      it 'should be correct day' do
        Time.use_zone(timezone) do
          Timecop.freeze(freeze)
          expect(4.business_days.after(freeze.in_time_zone('Tokyo')).to_date).to eq expected_date
        end
      end
    end

    it_behaves_like 'success'

    context 'Different Timezone' do
      it_behaves_like 'success' do
        let(:timezone) { 'UTC' }
      end
      it_behaves_like 'success' do
        let(:timezone) { 'America/Los_Angeles' }
      end
      it_behaves_like 'success' do
        let(:timezone) { 'Samoa' }
      end
    end
  end

参考資料

株式会社マインディア テックブログ

Discussion