RailsでTimecop/TimeHelpersを使って時刻を変える方法について

この記事は、[Happy Elements Advent Calendar 2020](https://qiita.com/advent-calendar/2020/happyelements)の5日目です。
RailsでTimecop/TimeHelpersを使って時刻を変える方法についての記事です。

# はじめに

ソーシャルゲームでは、イベントが yyyy年mm月dd日に始まるなど、特定の時刻になると発動することが定番です。

例: 架空のソーシャルゲームのイベントカレンダー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/888217/316c01e8-fcd6-9737-a85c-cae606435dd0.png)

これらのようなイベントの動作を確認するために、サーバ側で時刻を変えることができると、動作テストがしやすくなります。
- 例1 12月2日 15:00開始のガチャAの動作確認をするために、サーバの時間を12/2 16時にセットする
- 例2 バグ報告があったので、1ヶ月前のイベントの時間にセットしてバグ調査をする　など

サーバ側で時刻を変える方法について、以下の３つの方法に絞って調査しました。
- Timecop
- ActiveSupport::Testing::TimeHelpers
- libfaketime

この記事では、それぞれがどのようなものか、をまとめました。

# Timecop

https://github.com/travisjeffery/timecop
こちらはRailsのgemです。
Time/Date/DateTimeクラスのオーバーライドとして実装されているものでした
https://github.com/travisjeffery/timecop/blob/master/lib/timecop/time_extensions.rb

調査を進めると、TimeHelpersという物がRailsに組み込まれていることがわかりました。
TimeHelpersは、Timecopより機能としては少なく、時刻を固定させるのみです。
Timecopは、固定の他に、過去や未来に設定した後に時刻が進むなども可能です。

TimecopとTimeHelperでは、Timecopの方がリッチな機能である、と言えると思います。

- 標準のTimeHelpersで十分である
- gemを入れたくない

などの場合は、TimeHelpersの利用が選択肢に上がってくると思います。
2020年現在もメンテナンスはされているようです。

# ActiveSupport::Testing::TimeHelpers

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

Rails4.1以降で標準のものです。
時刻を固定・解除するのみです。

# libfaketime

https://github.com/wolfcw/libfaketime

こちらはRailsの話ではなく、OSにインストールして動くサービスです。
システムコールを改変するものです

- メリット
    - プログラムを変えなくていい
- デメリット
    - 導入が少し手間


# システムコールとは？

libfaketimeでシステムコールの話が出てきたので、ここではシステムコールについて。

[strace](https://ja.wikipedia.org/wiki/Strace) というコマンドを使って、コマンドを打つと大量にログが出てきます。これがシステムコールです(fstat, close, read, ...etc)。
libfaketimeは、このシステムコールを書き換えて時刻を変えるので、TimecopやTimeHelpersよりも深い部分で動いていると言えると思います。

```
strace date

execve("/bin/date", ["date"], 0x7ffc3bc97870 /* 28 vars */) = 0
brk(NULL)                               = 0x55b92bdc6000
openat(AT_FDCWD, "/usr/local/lib/faketime/libfaketime.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0$\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=66512, ...}) = 0
...
read(4, "@2020-6-16 20:30:00\n", 4096)  = 20
close(4)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "Tue Jun 16 20:30:00 JST 2020\n", 29Tue Jun 16 20:30:00 JST 2020
) = 29
close(1)                                = 0
close(2)                                = 0
munmap(0x7f46f7935000, 8)               = 0
munmap(0x7f46f7936000, 32)              = 0
exit_group(0)                           = ?
+++ exited with 0 +++

```

# 余談

調査中に、もしTimecopとlibfaketimeを同時に使った場合どうなるのだろう、と思いました。
実験してみたところ、Timecopで設定した時刻がRailsでは得られました。

libfaketimeで改変された時間を、さらにTimecopが上書きするという順序になります。
イメージ図は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/888217/e4ee1068-60f8-5c3f-02a7-77ee90c22f7d.png)


# まとめ

自分が運営中のタイトルでは、開発者の手元では、TimeHelpersを使ってRailsの時刻を変えながらイベントの動作確認などに利用しています。libfaketimeも使えるようになっており、必要であればそちらも利用しています。
こちらの記事では、サーバ側で時刻を変える方法について、Timecop, TimeHelpers, libfaketimeを調査しました。
調査してわかりましたが、どれが優れているというものではありませんでした。
それぞれ、やりたいことの要件に合わせて選択すれば良いと思いました。
もしRailsで時刻を変えたいけど、どうしようと悩んで調べている方がいらっしゃいましたら、
参考にしていただけると幸いです。

(注) 記事の中の図は自分で用意しました

# 参考

https://andycroll.com/ruby/replace-timecop-with-rails-time-helpers-in-rspec/
https://techracho.bpsinc.jp/penguin10/2018_12_25/67780
https://qiita.com/ktrkmk/items/b1361dd43d22dcf5627e
https://qiita.com/Targityen/items/67682d6c80cdcbe1186c

# 終わりに

Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です！

もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
https://recruit.happyelements.co.jp/