🎯

timecopを使うのをやめてActiveSupport::Testing::TimeHelpersに切り替えた

warekiとtimecopのバグ

はじめまして。
駆け出しエンジニアのたけむーです。

今回は和暦とサーバーの時間を操作するgemの組み合わせで起きたバグとその対処法についてまとめてみました。

背景

管理画面ではユーザーの誕生日などを平成〇〇年〇〇月〇〇日のように和暦で表示したい場合がありwarekiを使っています。

また、開発環境やステージング環境でサーバーの時間を操作して動作を確認したい場合がありtimecopを使っています。

今回はこれらを組み合わせて使った時にバグが起きました。

発生したバグ

コード

Date.parse(@date)

@date には '2023-02-18' のように日付が入っています。

エラー

ArgumentError
wrong number of arguments (given 3, expected 1..2)

Date.parseを実行しようとしたが引数の数が合ってないというエラーです。

コードを追ってみました。

以下はwarekiのコードです。

alias _wareki__parse_orig _parse
def _parse(str, comp = true)
  di = Wareki::Date._parse(str)
  wdate = Wareki::Date.new(di[:era], di[:year], di[:month], di[:day], di[:is_leap])
rescue ArgumentError, Wareki::UnsupportedDateRange
  ::Date._wareki__parse_orig(str, comp)
else

ここの _parse の引数の数が合っていないのかなと推測できました。

def _parse(str, comp = true)

この _parse を呼び出している箇所を見てみます。

def parse_with_mock_date(*args)
  parsed_date = parse_without_mock_date(*args)
  return parsed_date unless mocked_time_stack_item
  date_hash = Date._parse(*args)

timecop内で呼ばれていました。

Date._parse(*args)*args が原因だと考えられます。

おそらくこれが値が3つ以上になってしまい _parse に渡された結果エラーが発生したと考えられます。

timecop自体はずっと使っていて問題なく動いていました。
今までは一つのレコードを使い回して時間を更新し、有効/無効を切り替える形でサーバーの時間を操作していました。
今回、サーバーの時間をリセットする(現在の時刻に戻す)機能を追加したタイミングで発生しました。
リセットといっても、レコードを削除するだけです。
時間を指定する際は新しくレコードをレコードを作るようにしました。
時間を指定している時と指定していない時の値は'2023-02-18'のようになっていて同じなのですがエラーが出るようになりました。

対処法

今回の対処法として以下のようなものが挙げられます。

  • ライブラリの修正を待つ
  • timecopのリポジトリをフォークして書き換えて使う
  • 他のライブラリを使う

一つ目の「ライブラリの修正を待つ」についてですが、リポジトリを見てみると既に修正のプルリクが上がっていましたが、ずっと放置されているようだったのでマージされる可能性は低いかと思います。

二つ目の「timecopのリポジトリをフォークして書き換えて使う」についてですが、自分たちが使いたいように書き換えることができるのがメリットです。

その反面、フォークしているとメンテナンスも自分たちしかできないことです。例えば、rubyやrailsのバージョンアップをした時などそれに合わせて更新する必要が出てきそうです。

今回はtimecop をやめてrailsに備わっている ActiveSupport::Testing::TimeHelpers を使うことにしました。

Time.current     # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
Time.current     # => Wed, 24 Nov 2004 01:04:44 EST -05:00
Date.current     # => Wed, 24 Nov 2004
DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500

このように日時を指定してあげれば、Time.currentなどが変更できます。

細かい使い方の説明は省略させていただきます。

ActiveSupport::Testing::TimeHelpersに切り替えて動作を確認したところ問題なく動きました。

まとめ

今回はtimecopが原因でwarekiがうまく動作しなくなったため、ActiveSupport::Testing::TimeHelpersに切り替えたという話でした。

参考

https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

みてねコールドクターテックブログ

Discussion