結論
- HTML5では
input
要素の入力値に対してのバリデーションが働くため- 'email'など高頻度で使われるであろう形式にも機能
【編集時】
- Railsのフォームヘルパー
form.datetime_field
で生成されるのは<input type="datetime-local">
-
YYYY-MM-DDThh:mm
という書式で値を扱っている点に注意- その関係で"文字列"であれば問題なくテストできた
# OKパターン
fill_in 'Deadline', with: '2001-02-03T04:50'
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).strftime("%m%d%Y\t%I%M%P")
# ERRORパターン
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50)
fill_in 'Deadline', with: DateTime.new(2001, 2, 3, 4, 50)
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).to_s
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50).strftime("%Y-%m-%dT%T")
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50)
fill_in 'Deadline', with: Time.new(2001, 2, 3, 4, 50, 0, "+09:00")
【新規登録時】
- オブジェクト生成で入力したい日付を指定する際には ナノ秒(%N)を含めない ように注意する必要がある
- この際"秒(%S)"は問題ない
- 現時点で
DateTime
オブジェクトは非推奨なので基本使わない方が良い
# OKパターン
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45)
fill_in 'Deadline', with: DateTime.new(2000, 1, 2, 3, 45) # 注)DateTime が非推奨
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%m%d%Y\t%I%M%P")
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45, 0, "+09:00")
# ERRORパターン
fill_in 'Deadline', with: '2000-01-02T03:45'
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).to_s
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%Y-%m-%dT%T")
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45, 0, "+09:00")
本件の状況・詳細について
エラー1(編集時)
1. テスト箇所
- 「"作業日時"など作業についての情報を入力するフォーム」のテスト
- このフォームで作業の新規登録・編集を行う
<div class="field">
<%= form.label :date %>
<%= form.datetime_field :date %>
</div>
<div class="actions">
<%= form.submit %>
</div>
2. テストの流れ
- 以下のようなシンプルな流れのテストを実施しようとした
- いくつかの入力値を入力(ここに主題の'日付値'を含む)
- 登録および編集完了ボタンを押下
- 詳細画面へ画面遷移
以下、『【編集】(日付入力 → ボタン押下)』のRSpecの内容
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
3. テスト結果
[画像から分かる事]
- 値自体は入力されている
- その上でどうやら「値が有効ではない」という警告がされている
- 有効値も併せて提示されている
please enter a valid value. the two nearest valid values are ~, ~ and ~
解決策の模索
1. 一旦値を空にしてから入力(失敗)
⇒ 同じ警告が表示され変化なし
fill_in 'date', with: nil # Add
fill_in 'date', with: DataTime.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
2. DataTime
が非推奨と分かったのでTime
に変更(失敗)
⇒ 同じ警告が表示され変化なし
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
A.やはり「入力している値が好ましくないのが原因」らしいと断定
エラー2(新規登録時)
1. テスト箇所
- 先述の編集時と同様なので割愛
2. テストの流れ
- 先述の編集時と同様
- ただ、コードの統一感を持たせようと編集時のエラー1の解決策である
YYYY-MM-DDThh:mm
の書式をこちらにも記述した
以下、『【編集】(日付入力 → ボタン押下)』のRSpecの内容
# 変更前
# fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45)
# 変更後
fill_in 'Deadline', with: '2000-01-02T03:45'
click_button 'Update' # 画面遷移
3. テスト結果
[画像から分かる事]
- 値自体は入力されているが、エラー1とは違い「入力内容が不完全だからちゃんと入力しろ」という指摘
- なにやら"AM・PM"が入っていないから不完全という指摘?
Please enter a valid value. The field is incomplete or has an invalid date.
解決策の模索
1. 文字列ではなくTime
オブジェクトで付与(成功)
⇒ これはオーソドックスな記述なので想定内
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Update' # 画面遷移
2. 改めて書式を整えて文字列として付与(失敗)
⇒ 同じ警告が表示され変化なし
値の過不足は依然として解消されず
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%Y-%m-%dT%T") # 日付入力
click_button 'Update' # 画面遷移
A.やはり「入力値の不足が原因」らしいと断定
調査
警告の存在について
-
HTML5 では
Form Validation
というフロント側のバリデーションが機能している
これにより警告が表示され、画面遷移も中止される- ただバックエンドで実装するようなバリデーション程に強力なバリデーションではない
(画像3:こちらのサイトで検証した結果の画像)
- ただバックエンドで実装するようなバリデーション程に強力なバリデーションではない
他気になる事項
- 原因を突き止めたものの原因が不明
- 解決策も何で正解なのかが分からない
- "AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか
- タイムゾーンなど直接入力値に含めていない情報も明示することが必要なのか
(最後に)
- 残念ながらはっきりとして原理が自分には分からなかった。原理を抑えられればもっと有益な記事にできたはず - 編集、新規登録時の違いで生じる差分が謎 - 「入力値が既にあるかないか」の違いだけなのにこうも違いが出てくるのが意味不明 - `datetime_local`のフォームに入力するのだから、どちらとも`YYYY-MM-DDThh:mm`の書式に倣って入力すれば丸く収まると思ったのに - あとなぜ自分が扱っている題材には"AM""PM"表記があるのが分からない - これが悪さしている気もするし、この記事が参考にならない方が多そう【追記】おそらく原因特定
あれから根気よくリサーチした結果、原因らしき特徴を発見
form.datetime_filed
(<input type="datetime_local" />
)は『入力された値に応じてフォームの表示が変化する』のではないかという点
- (きっかけとなった主な参考記事・サイト)
「"AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか」に関して
A.どちらでもなかった。HTMLの type="datetime_local"
の作用と思われる
原因・調査結果など
- そもそもなんで新規登録時と編集時で違いが出てしまうのかが謎だった
- どちらも
datetime_local
のフォームに入力するの同じ要領でTime.new(~)
で入力すれば問題ないのでは?
- どちらも
- JS も特に実装していないので JS が原因というのも考えづらい
⇒推論の通りだった
新規登録と編集の違いは "FacroryBot で作成したデータを参照しているか" の違いがあった
- 新規登録時のテストにおいては
Time.new()
で日付を入力していた(この時点ではフォームは当然空欄) -
page.save_screenshot
で確認してみた所、編集時のテストにおいては「FacroryBot で生成したテストデータを参照してテスト」をしていた(初期値あり)- その FacroryBot では次のようにテストデータを作成していた
- 画像4、画像5にてどのように日付が入力前後の値を確認したところ、"秒数(%S)"まで入力されていたためこれがどうも怪しいと考えた
- その FacroryBot では次のようにテストデータを作成していた
FactoryBot.define do
factory :work do
#(中略)
date { 1.week.from_now }
end
end
irb(#<RSpec::___>):001:0> 1.week.from_now
=> Wed, 31 Jul 2024 05:18:47.310641803 UTC +00:00
初期値の有無が影響しているのではないか?
- 書式としては
YYYY-MM-DD HH:MM:SS
な値が入力されたことになる - 先述の通り、"datetime_local" の書式は
%Y-%m-%dT%T
(YYYY-MM-DDThh:mm
) である
- しかし下記ドキュメントより、デフォルト値は書式を『
strftime
メソッドを呼んで%Y-%m-%dT%T
』としている
The default value is generated by trying to call strftime with “%Y-%m-%dT%T” on the object’s value, which makes it behave as expected for instances of DateTime and ActiveSupport::TimeWithZone.
訳)デフォルト値は、オブジェクトの値に「%Y-%m-%dT%T」を指定して strftime を呼び出すことによって生成されます。これにより、DateTime および ActiveSupport::TimeWithZone のインスタンスに対して期待どおりに動作します。
引用 : api.rubyonrails.org
t = Time.new(2001, 2, 3, 4, 50)
p t.strftime("%Y-%m-%dT%T") # => "2001-02-03T04:50:00"
- ドキュメントの指す "デフォルト値" というのは "value" と思われる
- 特に指定しない場合は
%Y-%m-%dT%T
(%Y-%m-%dT%H:%M:%S
と同義) となる仕様
Q.じゃあこの書式が変わってしまう場合はあるのか?
- デフォルト値である
value
属性に書式を指定することで可能- こちらの記事では、書式変更が適応されている
A.よって初期値 value
での書式設定が "datetime_field" の表示に関係してくると思われる
⇒この value
での書式設定をしているというのは先述のドキュメントがやっている事と同じでは…
- よって具体的には以下2点が関係しているのではと推測
- 「
strftime
メソッドが呼ばれるため返り値が"String"」 - 「書式が
%Y-%m-%dT%T
に設定されている」
- 「
・1. については fill_in に「入力したい値」を指定するのにデータ型が関係しているとは思えなかった
・2. については書式に沿った適した「入力したい値」を指定しているかを改めて調査した
t1 = Time.now
p t1 # => 2024-07-24 10:58:45.694187938 +0000
p t1.class # => Time
t2 = Time.now.strftime("%Y-%m-%dT%T")
p t2 # => "2024-07-24T10:58:45"
p t2.class # => String
# Railsの`1.week.from_now`と同義
t3 = Time.now + (7 * 24 * 60 * 60)
p t3 # => 2024-07-24 10:58:45.694187938 +0000
p t3.class # => Time
このサンプルから「Time インスタンスだと 'ナノ秒'までも渡している のが良くないのでは?」と考えて修正を試みた
修正
- 私の場合は FacroryBot で「秒数を含む Time 型」である
1.week.from_now
で入力されたテストデータを作成していたため、「ナノ秒を含まない Time 型の値」を生成する必要があった
強引にナノ秒を含まない書式に変更 → Time 型にキャスト
-
strftime
の対となるメソッドparse
を発見したので、これがあればTime.now
で生成されるナノ秒を排除できると考え次のように実装した
※time
ライブラリにより parse
メソッドが拡張されるので、paiza.io での検証の際は読み込んでいます。
require 'time' # paiza.ioでは必須
Time.parse(Time.now.strftime("%Y-%m-%d %H:%m"))
# => 2024-07-24 12:07:00 +0000
◎ナノ秒を含まない Time 型の値を取得することに成功した
RSpecでの実証
ここで試しに次のように実装し RSpec に用いた
※Rails においては require 'time'
がなくとも parse
が使えた。おそらく事前にライブラリを読み込んでくれている。
FactoryBot.define do
factory :work do
# ---(中略)---
# date { 1.week.from_now } # 修正前
# 修正後
# `1.week.from_now` と `Time.now + (7 * 24 * 60 * 60)` は同義
date { Time.parse((Time.now + (7 * 24 * 60 * 60)).strftime("%Y-%m-%d %H:%m")) }
end
end
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
fill_in 'date', with: Time.new(2000, 1, 2, 3, 45)
click_button 'Update' # 画面遷移
【結果】本記事で問題提起した「新規作成時・編集時の付与する入力値の違い」は解消された
追記分のまとめ
- 新規登録時はそもそも初期値である
value
が Rails によって付与されないため、fill_in
で付与する入力値はオブジェクトでも問題ない- ただし
value
に設定される%Y-%m-%dT%T
書式に合わせて入力すると余計な "T" が含まれてしまうため今回のような入力値として不適切という警告が表示される例1# NGパターン Time.now.strftime("%Y-%m-%dT%T") # => "2024-07-24T13:04:47"
- ただし
- 編集時は初期値が
strftime("%Y-%m-%dT%T")
の書式で付与されるため、その書式に合った入力値が求められる- なので先述の 例1 はこちらではOKパターンになる
- NGパターンとして「ナノ秒(%N)を含む」「"-"、"T"を含まないなど書式に沿わない」場合が該当
例1
# NGパターン Time.now # => "2024-07-24T13:04:47" # => 2024-07-24 13:15:14.993104276 +0000 Time.new(2001, 2, 3, 4, 50) # => 2001-02-03 04:50:00 +0000
-
Time.new()
とTime.now()
は Time 型インスタンスを生成する点で同じだが、後者は「ナノ秒が含まれる」点に注意が必要- 前者は引数で任意に決められる
- 後者でナノ秒を含めない場合は
strftime
を使用して書式を強制変更するしかない
- ビューヘルパーである
datetime_field
の作用として『初期値があればstrftime("%Y-%m-%dT%T")
を施してvalue
に設定』される
参考サイト・参考記事など
以下、参考にさせていただいたサイト群になります。数が多いため折り畳みにさせていただいております。ご了承ください。
【参考サイト】
- https://docs.ruby-lang.org/ja/latest/class/DateTime.html
- https://romantist.jp/blog/html5-form-validation-balloon/
- https://www.htmq.com/htmls/input_type_datetime-local.shtml
- https://aaronsaray.com/2015/error-validating-seconds-in-html5-time-input/
- https://stackoverflow.com/questions/linked/19011861?lq=1
- https://stackoverflow.com/questions/19284193/invalid-value-when-setting-default-value-in-html5-datetime-local-input
- https://stackoverflow.com/questions/22332520/how-can-i-use-datetime-local-without-am-pm
- https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html
- https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/date_format.html
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local
- https://phrase.com/blog/posts/date-time-localization/
- https://qiita.com/nakanaka444/items/a699393da1e197333243
- https://teratail.com/questions/301872
- https://github.com/teamcapybara/capybara/blob/master/History.md#changed-4:~:text=Selenium%20driver%20supports%20Date/Time%20when%20filling%20in%20date/time/datetime%2Dlocal%20inputs
- https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
- https://www.aki--dev.com/entry/2022/04/10/004608
- https://stackoverflow.com/questions/49148017/how-to-fill-in-a-datetime-field-with-capybara
[後日更新時の関連サイト]