TimeZone#strptime がサブ秒を保持できるように修正
ActiveSupport::TimeZone#strptime がエポック秒フォーマットにサブ秒が含まれる場合に小数部を失っていたバグを修正し、Ruby 標準の Time.strptime と同等の精度を提供するようになりました。
背景
ActiveSupport::TimeZone#strptime は文字列を指定フォーマットで解析し Time オブジェクトを生成しますが、"%s.%N" や "%s.%L" のようにエポック秒にサブ秒が付随するケースでサブ秒が常に 0 になる問題がありました。具体例として zone.strptime("1577836800.123456789", "%s.%N").nsec が 0 になる一方、Time.strptime では正しく 123456789 が返ります。この不一致は parts_to_time の実装が :sec_fraction を無視したことが根本原因です。
技術的な変更
修正は activesupport/lib/active_support/values/time_zone.rb の parts_to_time メソッドの :seconds ブランチに sec_fraction を加算 するだけのシンプルな差分です。変更前は time = Time.at(parts[:seconds]) で秒のみを渡していましたが、変更後は time = Time.at(parts[:seconds] + parts.fetch(:sec_fraction, 0)) とし、parts[:sec_fraction] が存在すれば秒に加算して時間を生成します。これにより %s.%N 形式の文字列でもナノ秒単位が保持され、既存の整数秒形式 (%s) やミリ秒形式 (%Q) に影響はありません。
修正の検証として activesupport/test/time_zone_test.rb に新しいテスト test_strptime_with_timestamp_seconds_and_fractional_seconds が追加されました。テストでは東部標準時 (US/Eastern) 環境下で "1470272280.123456789" を %s.%N でパースし、time.nsec が 123456789 になることと、Ruby 標準の Time.strptime と結果が一致することを assert しています。既存のテストスイート 566 件すべてが緑になることが確認され、回帰リスクは無いことが示されました。
設計判断
この修正は 既存コードへの影響を最小限に抑える ことを最優先にした設計判断の表れです。parts_to_time の :seconds ブランチに 1 行の加算ロジックを追加しただけで、他の分岐や呼び出し側のインターフェースは変更していません。また、parts.fetch(:sec_fraction, 0) を用いることで sec_fraction が無い場合でも既存の挙動と同等になります。従って、アップグレード時にアプリケーション側で特別な対応は不要です。
まとめ
ActiveSupport::TimeZone#strptime がエポック秒に付随するサブ秒を正しく扱えるようになり、Rails が提供する日時パースと Ruby 標準ライブラリの挙動が一致しました。今回の変更はバグ修正に留まりつつ、コードサイズは最小で後方互換性を維持した点が評価できます。