【Ruby】Date型が勝手にTime型になるメソッドを発見したのでメモ

Rails・Webシステム開発
Anja🤗#helpinghands #solidarity#stays healthy🙏によるPixabayからの画像
スポンサーリンク

0〜9時にrspecが落ちるからおかしいなと思ったら、タイムゾーン以前に型が勝手に変換されていました。

since(◯.days)やago(◯.days)はDate型でもTime型に変換されます。

その上で色々ややこしく勉強になったので、メモを残しておきます。



0〜9時にrspecが落ちる(経緯)

どうやら、バリデーションが正しく評価されていないみたい。

このvalueが、1週間後以降の場合はOK, 6日以内だった場合はNGにしたい

(byebug) value
Thu, 28 Apr 2022
(byebug) Date.current.since(6.days)
Thu, 28 Apr 2022 00:00:00.000000000 JST +09:00
(byebug) value > Date.current.since(6.days)
true

ファ!?? falseになってほしいんだが・・・



3つの盲点(原因)

(盲点1) Date型にsinceを使うとTime型に変換される

6日後の日付がほしかったのだが、

$ Date.current
=> Fri, 22 Apr 2022
$ Date.current.since(6.days)
=> Thu, 28 Apr 2022 00:00:00.000000000 JST +09:00

Time型になったのがおわかりいただけただろうか。

Date型に対して

  • since(◯.days)
  • ago(◯.days)

を使用すると、Time型になるようです。

しかも、時刻は0時0分0秒になっていることがわかります。

※ 最初から時刻(Time.current)で取得していれば、上記の現象は起きない。

$ Time.current.since(6.days)
=> Thu, 28 Apr 2022 07:54:01.082102900 JST +09:00

(ただ、今回は時刻が0時0分0秒になってしまうことは直接関係なかった。)

(盲点2) Date型にタイムゾーンの概念はない??

0~9時だけ落ちるというのは、だいたいタイムゾーンの問題ですよね。ということで、後日調べてみました。

<Date型の場合>
$ d = Date.current
=> Tue, 26 Apr 2022
$ d.zone
=> undefined method `zone' for Tue, 26 Apr 2022:Date (NoMethodError)
$ d.strftime("%Z")
=> "+00:00"

<Time型の場合>
$ t = Time.current
=> Tue, 26 Apr 2022 08:31:28.676181600 JST +09:00
$ t.zone
=> "JST"
$ t.strftime("%Z")
=> "JST"

・・・???

どうやら、Date型にタイムゾーンなる概念はないようです。

(盲点3) Date型とTime型を比較するときのタイムゾーン

にしても、4月28日 > 4月28日0時0分0秒がtrueなのはおかしくない?

$ d
=> Tue, 26 Apr 2022
$ t
=> Tue, 26 Apr 2022 08:31:28.676181600 JST +09:00
$ d > t
=> true
$ d == t
=> false
$ d < t
=> false

そう、d > t がtrueになってしまう(エラーの再現)。他もfaultなので明らかに d > tです。

ただし、型を揃えるときちんとfalseになります。

$ d > t
=> true
$ d.to_time > t
=> false
$ d > t.to_date
=> false

そこで、9時を回ったので、比較してみましょう。先程のd, tに加えて9時以降の時刻t_after9を定義します。

$ t_after9 = Time.current
=> Tue, 26 Apr 2022 09:07:47.713042400 JST +09:00
$ d > t_after9
=> false
$ d == t_after9
=> false
$ d < t_after9
=> true

先ほどと真逆の結果になりました! d < t_after9です。

つまり、t < d < t_after9なわけです。これに対して、2つの仮説が立ちました。

(仮説1) タイムゾーンが消滅して、全てUTCとして比較されている?

1つ目の仮説は、タイムゾーンが消滅して、全てUTCになったと考えると辻褄が合います。すなわち、タイムゾーンのない日付とタイムゾーン付の時刻を比較する際、

  • t: 4月25日(月) 23:31:28 (JSTからUTCに変換された)
  • d: 4月26日(火) 00:00:00 (タイムゾーンなし=UTC)
  • t_after9: 4月26日(火) 00:07:47(JSTからUTCに変換された)

として比較されたのだとしたら、t < d < t_after9になるのも納得がいきます。

(仮説2) 日付の方が、時刻のタイムゾーンJSTに変換されている?

2つ目の仮説は、タイムゾーンの概念のない日付の方が、タイムゾーンの設定されている時刻のタイムゾーンに合わせて自動補正されている可能性です。つまり、

d: 4月26日(火) 00:00:00 (UTC)→ 4月26日(火) 09:00:00 (JST)

として比較されたのだとしたら、t < d < t_after9になるのも納得がいきます。

型を揃えよう(解決策)

Date型(日付)とTime型(時刻)を比べると、上記のようにおかしなことになるので、そもそもDate型に統一して、型を揃えてから比べましょう。さすればタイムゾーンのややこしい話をせずに済みます。

(解決策1) to_dateまたはDate.parseして型を揃えよう

(byebug) value
Thu, 28 Apr 2022
(byebug) Date.current.since(6.days).to_date
Thu, 28 Apr 2022
(byebug) value > Date.current.since(6.days).to_date
false

OK!

文字列にしてパースしたもの、すなわち

Date.parse(Date.current.since(6.days).to_s)

でも同様の結果が得られます。

(解決策2) days_sinceやnext_dayなどの型が変わらないメソッドを使おう

そもそも、勝手にTime型になってしまうsenceメソッドを使うのをやめて、days_sinceやnext_dayなどの型が変わらないメソッドを使いましょう。

(byebug) Date.current.days_since(6)
Thu, 28 Apr 2022
(byebug) Date.current.next_day(6)
Thu, 28 Apr 2022

今回は、next_dayを採用しました。

(byebug) value
Thu, 28 Apr 2022
(byebug) Date.current.next_day(6)
Thu, 28 Apr 2022
(byebug) value > Date.current.next_day(6)
false

OK!見た目もスッキリして可読性も上がりました。

感想

比較するときは型を揃えようという、なんとも基本的なところで詰まってしまいました。これが動的型式言語のデメリットなのだと思います。それが経験できたのはとてもラッキー!

はるすと
はるすと

最後まで読んでくださってありがとうございました!

この記事を書いた人
こもれびエンジニア

自然と自由を愛するエンジニア。2021年1月に、大手製造業設計からプログラマ(Rails, AWS)へ転職。動物や自然との触れ合いや、汗を流すのが好き。

/HSP(繊細さん)/18デリケートな象/ストレングスファインダー(1分析思考/2親密性/3学習欲/4調和性/5収集心)、テニス、合気道、登山、あいだみつを、ジブリ、ワンピース、ドラゴンボール、AWS、Ruby on Rails、アイミング

twitterをフォローして、記事にならないちょっとした豆知識もチェック!
Rails・Webシステム開発
スポンサーリンク
SNSでシェア/コメントして、自分のアウトプット/発信力を高めるのにお使いください。 ↓ 各ページへジャンプ ↓
twitterをフォローして、記事にならないちょっとした豆知識もチェック!
スポンサーリンク
「そんなか」サイト

コメント

タイトルとURLをコピーしました