ISO週番号年境界で無効な week 値が生成されるバグを修正
<input type=\"week\"> が受け付ける文字列は ISO 8601 の 週番号年 (yyyy-Www) である必要があります。この PR は、Rails の WeekField が年末・年始の境界で誤った年を付与し、ブラウザに拒否される文字列を出力していた問題を修正します。
背景
WeekField の内部で format_datetime が strftime("%Y-W%V") を使用していたため、カレンダー年 (%Y) と ISO 週番号 (%V) が組み合わされました。ISO 週番号は前年または翌年に属することがあるため、例えば Date.new(2016, 1, 1) は "2016-W53" とフォーマットされ、実際には存在しない週文字列となります。この不整合はブラウザ側でバリデーションエラーを引き起こします。
既存の week_field テストは中間期の日付(%Y == %G)しかカバーしておらず、境界ケースが検出されていませんでした。問題の根本は年情報の指示子が不適切だったことにあります。
技術的な変更
WeekField#format_datetime の実装を strftime("%Y-W%V") から strftime("%G-W%V") に変更しました。%G は ISO 週番号年を返す指示子であり、%V と組み合わせることで常に正しい年・週の組み合わせが得られます。変更前後のコードは以下の通りです。
- value&.strftime("%Y-W%V")
+ value&.strftime("%G-W%V")
さらに、境界ケースを検証するテストが追加されました。DateTime.new(2016, 1, 1, 1, 2, 3) が "2015-W53" と正しくフォーマットされることを確認します。
+ def test_week_field_with_iso_week_year_boundary
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2015-W53" />}
+ @post.written_on = DateTime.new(2016, 1, 1, 1, 2, 3)
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ end
これにより、WeekField が全ての有効な日付に対して正しい ISO 週文字列を生成することが保証されます。
設計判断
WeekField は DatetimeField 系列のサブクラスであり、他のフィールド(DateField、MonthField、DatetimeLocalField)は既に指示子を一貫させています。今回の修正は、同様の設計方針に合わせて %Y を %G に置き換えるという最小限の変更です。既存の API(week_field の呼び出し方)は保持され、value が nil の場合も同様に安全です。境界外の日付に対する挙動は変わらず、正しい年・週の組み合わせが得られるようになりました。
まとめ
WeekField#format_datetime が ISO 週番号年境界で誤った文字列を生成していたバグを、%Y → %G の置換で解消し、境界テストを追加しました。これにより <input type=\"week\"> が常に有効な yyyy-Www 形式を出力し、ブラウザ側のバリデーションエラーを防ぎます。既存の挙動は保持され、コードベース全体の一貫性も高まりました。