RSpecで日付の値が有効ではないとHTMLから警告されて詰まる（一応は解決）

# 結論

https://aaronsaray.com/2015/error-validating-seconds-in-html5-time-input/

- HTML5では`input`要素の入力値に対してのバリデーションが働くため
    - 'email'など高頻度で使われるであろう形式にも機能

## 【編集時】
- Railsのフォームヘルパー `form.datetime_field` で生成されるのは `<input type="datetime-local">` 
- `YYYY-MM-DDThh:mm` という書式で値を扱っている点に注意
    - その関係で"文字列"であれば問題なくテストできた

```ruby:"編集"の場合
# 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`オブジェクトは非推奨なので基本使わない方が良い
```ruby:"新規登録"の場合
# 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. テスト箇所
- 「"作業日時"など作業についての情報を入力するフォーム」のテスト
    - このフォームで作業の新規登録・編集を行う

```ruby:app/views/tasks/_form.html.erb
<div class="field">
    <%= form.label :date %>
    <%= form.datetime_field :date %>
</div>

<div class="actions">
    <%= form.submit %>
</div>
```

### 2. テストの流れ
- 以下のようなシンプルな流れのテストを実施しようとした
    1. いくつかの入力値を入力（ここに主題の'日付値'を含む）
    1. 登録および編集完了ボタンを押下
    1. 詳細画面へ画面遷移

以下、『【編集】（日付入力 → ボタン押下）』のRSpecの内容
```ruby:spec/system/works_spec.rb
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
```

### 3. テスト結果
- テストを実施した所、「編集時に」どうやら 2. で止まっていることがスクリーンショット（画像１）から確認できた
    - （画像１：何かしらの警告が確認できるスクリーンショット）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/65e7130c-06ba-cc11-a034-2bbf5a600512.png)

#### [画像から分かる事]
- 値自体は入力されている
    - その上でどうやら「値が有効ではない」という警告がされている
    - 有効値も併せて提示されている

```:警告文
please enter a valid value. the two nearest valid values are ～, ～ and ～
```

### 解決策の模索
**1. 一旦値を空にしてから入力（失敗）** 
⇒ 同じ警告が表示され変化なし
```ruby:1. 一旦値を空にしてから入力
fill_in 'date', with: nil # Add
fill_in 'date', with: DataTime.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
```

**2. [`DataTime`が非推奨](https://docs.ruby-lang.org/ja/latest/class/DateTime.html)と分かったので`Time`に変更（失敗）**
⇒ 同じ警告が表示され変化なし
```ruby:2. Time オブジェクトに変更
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
```

<INS>A．やはり「入力している値が好ましくないのが原因」らしいと断定<INS>

# エラー２（新規登録時）
### 1. テスト箇所
- 先述の編集時と同様なので割愛

### 2. テストの流れ
- 先述の編集時と同様
- ただ、コードの統一感を持たせようと編集時のエラー１の解決策である`YYYY-MM-DDThh:mm`の書式をこちらにも記述した

以下、『【編集】（日付入力 → ボタン押下）』のRSpecの内容
```ruby:spec/system/works_spec.rb
# 変更前
# fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45)

# 変更後
fill_in 'Deadline', with: '2000-01-02T03:45'
click_button 'Update' # 画面遷移
```

### 3. テスト結果
- テストを実施した所、「編集時に」どうやら 2. で止まっていることがスクリーンショット（画像２）から確認できた
    - （画像２：画像１の動揺に警告が確認できるスクリーンショット）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/74153e0a-0e90-c86c-f121-4c2f7c9baafa.png)


#### [画像から分かる事]
- 値自体は入力されているが、エラー１とは違い「入力内容が不完全だからちゃんと入力しろ」という指摘
- なにやら"AM・PM"が入っていないから不完全という指摘？

```:警告文
Please enter a valid value. The field is incomplete or has an invalid date.
```

### 解決策の模索
**1. 文字列ではなく`Time` オブジェクトで付与（成功）** 
⇒ これはオーソドックスな記述なので想定内
```ruby:1. 文字列ではなくオブジェクト
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Update' # 画面遷移
```

**2. 改めて書式を整えて文字列として付与（失敗）**
⇒ 同じ警告が表示され変化なし<br>　 値の過不足は依然として解消されず
```ruby:2. 改めて書式を整えて文字列として付与
fill_in 'Deadline', with: Time.new(2000, 1, 2, 3, 45).strftime("%Y-%m-%dT%T") # 日付入力
click_button 'Update' # 画面遷移
```

<INS>A．やはり「入力値の不足が原因」らしいと断定<INS>


# 調査
## 警告の存在について

- [HTML5 では `Form Validation` というフロント側のバリデーションが機能している](https://romantist.jp/blog/html5-form-validation-balloon/)<br>これにより警告が表示され、画面遷移も中止される
    - ただバックエンドで実装するようなバリデーション程に強力なバリデーションではない
（画像３：[こちらのサイト](https://www.htmq.com/htmls/input_type_datetime-local.shtml)で検証した結果の画像）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/5d2db081-1ffd-7c73-fee8-ee191bccadf9.png)

# 他気になる事項
- 原因を突き止めたものの原因が不明
- 解決策も何で正解なのかが分からない
1. "AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか
2. タイムゾーンなど直接入力値に含めていない情報も明示することが必要なのか


***

<details><summary>（最後に）</summary>
- 残念ながらはっきりとして原理が自分には分からなかった。原理を抑えられればもっと有益な記事にできたはず
    - 編集、新規登録時の違いで生じる差分が謎
        - 「入力値が既にあるかないか」の違いだけなのにこうも違いが出てくるのが意味不明
    - `datetime_local`のフォームに入力するのだから、どちらとも`YYYY-MM-DDThh:mm`の書式に倣って入力すれば丸く収まると思ったのに
- あとなぜ自分が扱っている題材には"AM""PM"表記があるのが分からない
    - これが悪さしている気もするし、この記事が参考にならない方が多そう
</details>





***
# 【追記】おそらく原因特定
あれから根気よくリサーチした結果、原因らしき特徴を発見

:::note info
`form.datetime_filed`（`<input type="datetime_local" />`）は『入力された値に応じてフォームの表示が変化する』のではないかという点
:::
- （きっかけとなった主な参考記事・サイト）
    1. https://www.aki--dev.com/entry/2022/04/10/004608
    1. https://github.com/dotnet/aspnetcore/issues/9727
    1. https://api.rubyonrails.org/v7.1/classes/ActionView/Helpers/FormHelper.html#method-i-datetime_field


## 「"AM""PM"表記があるのはブラウザによるものなのか、JS によるものなのか」に関して
<INS>A．どちらでもなかった。HTMLの `type="datetime_local"` の作用と思われる<INS>

### 原因・調査結果など
- そもそもなんで新規登録時と編集時で違いが出てしまうのかが謎だった
    - どちらも `datetime_local` のフォームに入力するの同じ要領で `Time.new(～)` で入力すれば問題ないのでは？
- JS も特に実装していないので JS が原因というのも考えづらい

⇒推論の通りだった 

#### 新規登録と編集の違いは "FacroryBot で作成したデータを参照しているか" の違いがあった
1. 新規登録時のテストにおいては `Time.new()` で日付を入力していた（この時点ではフォームは当然空欄）
1. `page.save_screenshot` で確認してみた所、編集時のテストにおいては「FacroryBot で生成したテストデータを参照してテスト」をしていた（初期値あり）
    - その FacroryBot では次のようにテストデータを作成していた
        - 画像４、画像５にてどのように日付が入力前後の値を確認したところ、"秒数（%S）"まで入力されていたためこれがどうも怪しいと考えた
```ruby:spec/factories/work.rb
FactoryBot.define do
  factory :work do

#（中略）

    date { 1.week.from_now }
  end
end
```

```irb:date { 1.week.from_now } によって生成される値
irb(#<RSpec::___>):001:0> 1.week.from_now
=> Wed, 31 Jul 2024 05:18:47.310641803 UTC +00:00
```
（画像４：入力前フォーム）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/7e1e1826-7ebd-8c1e-ee76-b91f5ceeba7b.png)

（画像５：入力後フォーム）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/a57514c2-54ba-68b8-ac26-a0c9f207f729.png)

#### 初期値の有無が影響しているのではないか？
1. 書式としては `YYYY-MM-DD HH:MM:SS` な値が入力されたことになる
2. 先述の通り、"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](https://api.rubyonrails.org/v7.1/classes/ActionView/Helpers/FormHelper.html#method-i-datetime_field:~:text=The%20default%20value%20is%20generated%20by%20trying%20to%20call%20strftime%20with%20%E2%80%9C%25Y%2D%25m%2D%25dT%25T%E2%80%9D%20on%20the%20object%E2%80%99s%20value%2C%20which%20makes%20it%20behave%20as%20expected%20for%20instances%20of%20DateTime%20and%20ActiveSupport%3A%3ATimeWithZone.)


```ruby:デフォルト値に対して適応される'datetime_local'の書式のサンプルコード
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` と同義） となる仕様

<INS>Q．じゃあこの書式が変わってしまう場合はあるのか？<INS>

- デフォルト値である `value` 属性に書式を指定することで可能
    - [こちらの記事](https://www.aki--dev.com/entry/2022/04/10/004608)では、書式変更が適応されている

<INS>A．よって初期値 `value` での書式設定が "datetime_field" の表示に関係してくると思われる</INS>
　⇒この `value` での書式設定をしているというのは先述のドキュメントがやっている事と同じでは…

- よって具体的には以下２点が関係しているのではと推測
    1. 「`strftime`メソッドが呼ばれるため返り値が"String"」
    1. 「書式が`%Y-%m-%dT%T`に設定されている」


・1. については fill_in に「入力したい値」を指定するのにデータ型が関係しているとは思えなかった
・2. については書式に沿った適した「入力したい値」を指定しているかを改めて調査した

```ruby:出力結果の違いについてのサンプル
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 での検証の際は読み込んでいます。](https://docs.ruby-lang.org/ja/latest/class/Time.html#:~:text=time%20%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AB%E3%82%88%E3%81%A3%E3%81%A6%E3%80%81Time.parse%2C%20Time.rfc2822%2C%20Time.httpdate%2C%20Time.iso8601%20%E7%AD%89%E3%81%8C%E6%8B%A1%E5%BC%B5%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82)

```ruby:ナノ秒を含まない Time 型の値
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` が使えた。おそらく事前にライブラリを読み込んでくれている。
```ruby:spec/factories/work.rb
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
```

```ruby:spec/system/works_spec.rb（新規作成時のテスト）
fill_in 'date', with: Time.new(2001, 2, 3, 4, 50) # 日付入力
click_button 'Create' # 画面遷移
```

```ruby:spec/system/works_spec.rb（編集時のテスト）
fill_in 'date', with: Time.new(2000, 1, 2, 3, 45)
click_button 'Update' # 画面遷移
```

**【結果】本記事で問題提起した「新規作成時・編集時の付与する入力値の違い」は解消された**

## 追記分のまとめ
1. 新規登録時はそもそも初期値である `value` が Rails によって付与されないため、`fill_in` で付与する入力値はオブジェクトでも問題ない
    - ただし `value`に設定される`%Y-%m-%dT%T`書式に合わせて入力すると余計な "T" が含まれてしまうため今回のような入力値として不適切という警告が表示される
        ```ruby:例1
        # NGパターン
        Time.now.strftime("%Y-%m-%dT%T") # => "2024-07-24T13:04:47"
        ```
2. 編集時は初期値が `strftime("%Y-%m-%dT%T")` の書式で付与されるため、その書式に合った入力値が求められる
    - なので先述の 例1 はこちらではOKパターンになる
    - NGパターンとして「ナノ秒（%N）を含む」「"-"、"T"を含まないなど書式に沿わない」場合が該当
        ```ruby:例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
        ```
3. `Time.new()` と `Time.now()` は Time 型インスタンスを生成する点で同じだが、後者は「ナノ秒が含まれる」点に注意が必要
    - 前者は引数で任意に決められる
    - 後者でナノ秒を含めない場合は `strftime` を使用して書式を強制変更するしかない
4. ビューヘルパーである `datetime_field` の作用として『初期値があれば `strftime("%Y-%m-%dT%T")` を施して `value` に設定』される
    - この記事にある通り書式は任意に変更可能
    - ただし、書式を特に設定しない場合は本記事の問題になったように「入力値もデフォルトの書式に強制される」ため注意が必要
    （画像６：初期値が無い場合）
    ![create_non_value.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/fccb6a9e-3c1a-19c7-8172-153cb545a545.png)<br>
    （画像７：初期値がある場合）
    ![editing_with_value.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518714/7b1952d9-2d0e-5e02-c25d-c67b9e529688.png)

***
# 参考サイト・参考記事など

以下、参考にさせていただいたサイト群になります。数が多いため折り畳みにさせていただいております。ご了承ください。

<details><summary>【参考サイト】</summary>

1. https://docs.ruby-lang.org/ja/latest/class/DateTime.html
1. https://romantist.jp/blog/html5-form-validation-balloon/
1. https://www.htmq.com/htmls/input_type_datetime-local.shtml
1. https://aaronsaray.com/2015/error-validating-seconds-in-html5-time-input/
1. https://stackoverflow.com/questions/linked/19011861?lq=1
1. https://stackoverflow.com/questions/19284193/invalid-value-when-setting-default-value-in-html5-datetime-local-input
1. https://stackoverflow.com/questions/22332520/how-can-i-use-datetime-local-without-am-pm
1. https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html
1. https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/date_format.html
1. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local
1. https://phrase.com/blog/posts/date-time-localization/
1. https://qiita.com/nakanaka444/items/a699393da1e197333243
1. https://teratail.com/questions/301872
1. 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
1. https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
1. https://www.aki--dev.com/entry/2022/04/10/004608
1. https://stackoverflow.com/questions/49148017/how-to-fill-in-a-datetime-field-with-capybara

[後日更新時の関連サイト]
1. https://www.aki--dev.com/entry/2022/04/10/004608
1. https://github.com/dotnet/aspnetcore/issues/9727

</details>