From 25c1aadffbfe142d62c08355e22e75941409a355 Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Sun, 12 Apr 2026 03:49:39 +0900 Subject: [PATCH 1/2] Fix Time.new with keyword arguments on JRuby 10 and Ruby 3.1+ Replace `ruby2_keywords` approach with explicit `**kwargs` in `new_with_mock_time`. On JRuby 10 (Ruby 3.4 compat), the `ruby2_keywords` flag doesn't properly forward keyword arguments through `new_without_mock_time`, causing `TypeError: no implicit conversion of Hash into Integer` when ActiveModel deserializes datetime columns. The fix uses explicit `**kwargs` which works correctly across all Ruby versions and implementations. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/timecop/time_extensions.rb | 12 ++++++++---- test/timecop_test.rb | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/timecop/time_extensions.rb b/lib/timecop/time_extensions.rb index 04632c8..15fd9e1 100644 --- a/lib/timecop/time_extensions.rb +++ b/lib/timecop/time_extensions.rb @@ -18,12 +18,16 @@ def now_with_mock_time alias_method :new_without_mock_time, :new - def new_with_mock_time(*args) - args.size <= 0 ? now : new_without_mock_time(*args) + def new_with_mock_time(*args, **kwargs) + if args.empty? && kwargs.empty? + now + elsif kwargs.any? + new_without_mock_time(*args, **kwargs) + else + new_without_mock_time(*args) + end end - ruby2_keywords :new_with_mock_time if Module.private_method_defined?(:ruby2_keywords) - alias_method :new, :new_with_mock_time end end diff --git a/test/timecop_test.rb b/test/timecop_test.rb index d860b26..12dec6e 100644 --- a/test/timecop_test.rb +++ b/test/timecop_test.rb @@ -519,6 +519,26 @@ def test_mock_time_new_same_as_now assert_equal date, Time.new end + def test_time_new_with_keyword_arguments + Timecop.freeze(2011, 1, 2) do + # Time.new with keyword args (Ruby 3.1+) + t = Time.new(2020, 1, 1, 0, 0, 0, in: "+05:00") + assert_equal 2020, t.year + assert_equal 18000, t.utc_offset + end + rescue ArgumentError + # Ruby < 3.1 doesn't support `in:` keyword — skip gracefully + end + + def test_time_new_with_positional_args_still_works + Timecop.freeze(2011, 1, 2) do + t = Time.new(2020, 6, 15, 12, 30, 0) + assert_equal 2020, t.year + assert_equal 6, t.month + assert_equal 15, t.day + end + end + def test_not_callable_send_travel assert_raises NoMethodError do Timecop.send_travel(:travel, Time.now - 100) From 6742f036622d80b0d4c987027b2a01769fcab28a Mon Sep 17 00:00:00 2001 From: Josh Cronemeyer Date: Sun, 12 Apr 2026 08:17:59 -0700 Subject: [PATCH 2/2] replaced rescue ArgumentError with skip + version guard. This ensures a TypeError (or any other error) on older Ruby won't silently mask a failure. Also new test covering kwargs only, no positional args), which exercises the args.empty? && kwargs.any? branch --- test/timecop_test.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/timecop_test.rb b/test/timecop_test.rb index 12dec6e..902e80e 100644 --- a/test/timecop_test.rb +++ b/test/timecop_test.rb @@ -520,14 +520,20 @@ def test_mock_time_new_same_as_now end def test_time_new_with_keyword_arguments + skip "Time.new with in: keyword requires Ruby 3.1+" if RUBY_VERSION < "3.1" Timecop.freeze(2011, 1, 2) do - # Time.new with keyword args (Ruby 3.1+) t = Time.new(2020, 1, 1, 0, 0, 0, in: "+05:00") assert_equal 2020, t.year assert_equal 18000, t.utc_offset end - rescue ArgumentError - # Ruby < 3.1 doesn't support `in:` keyword — skip gracefully + end + + def test_time_new_with_only_keyword_arguments + skip "Time.new with in: keyword requires Ruby 3.1+" if RUBY_VERSION < "3.1" + Timecop.freeze(2011, 1, 2) do + t = Time.new(in: "+05:00") + assert_equal 18000, t.utc_offset + end end def test_time_new_with_positional_args_still_works