From f9abed9effb48441a7fefe7f2a76d73e94342238 Mon Sep 17 00:00:00 2001 From: Daniel George Holz Date: Tue, 14 May 2024 21:40:30 +0100 Subject: [PATCH 1/5] Calculate travel_offset to align with clock precision Subtracting two `Time`s returns a `Float`, which may not be accurate down to subsecond resolution. Because `Float`s are stored as double- precision values (IEEE 754), they can have resolutions much higher than the typical minimum clock precision of 10e-9 seconds. Which can result in two `Time` object not comparing as equal when they are the same down to the nanosecond, when one has had a travel_offset applied to it. --- lib/timecop/time_stack_item.rb | 2 +- test/time_stack_item_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/timecop/time_stack_item.rb b/lib/timecop/time_stack_item.rb index ef49150..82373d6 100644 --- a/lib/timecop/time_stack_item.rb +++ b/lib/timecop/time_stack_item.rb @@ -157,7 +157,7 @@ def parse_time(*args) end def compute_travel_offset - time - Time.now_without_mock_time + time.to_r - Time.now_without_mock_time.to_r end def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds diff --git a/test/time_stack_item_test.rb b/test/time_stack_item_test.rb index ed5ccf7..213488a 100644 --- a/test/time_stack_item_test.rb +++ b/test/time_stack_item_test.rb @@ -296,4 +296,11 @@ def test_datetime_timezones assert_equal dt, now, "#{dt.to_f}, #{now.to_f}" end end + + def test_travel_offset_aligns_to_nanoseconds + t = Time.now + stack_item = Timecop::TimeStackItem.new(:travel, t) + travel_offset_denom = stack_item.travel_offset.to_r.denominator + assert_equal 1_000_000_000.modulo(travel_offset_denom), 0 + end end From 14e0f8f787bb4ba043967d75f8764d5a3ef1321a Mon Sep 17 00:00:00 2001 From: Daniel George Holz Date: Wed, 15 May 2024 12:00:32 +0100 Subject: [PATCH 2/5] Use the realtime clock's resolution when testing travel_offset's alignment --- test/time_stack_item_test.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/time_stack_item_test.rb b/test/time_stack_item_test.rb index 213488a..3c3bc07 100644 --- a/test/time_stack_item_test.rb +++ b/test/time_stack_item_test.rb @@ -297,10 +297,11 @@ def test_datetime_timezones end end - def test_travel_offset_aligns_to_nanoseconds + def test_travel_offset_aligns_to_clock t = Time.now stack_item = Timecop::TimeStackItem.new(:travel, t) travel_offset_denom = stack_item.travel_offset.to_r.denominator - assert_equal 1_000_000_000.modulo(travel_offset_denom), 0 + clock_resolution = Process.clock_getres(:CLOCK_REALTIME, :hertz) + assert_equal clock_resolution.modulo(travel_offset_denom), 0 end end From 96b239e8957d98139888bed2fa260977e6963844 Mon Sep 17 00:00:00 2001 From: Daniel George Holz Date: Wed, 15 May 2024 12:09:25 +0100 Subject: [PATCH 3/5] Improve failure message for travel_offset alignment test --- test/time_stack_item_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/time_stack_item_test.rb b/test/time_stack_item_test.rb index 3c3bc07..29c3fdb 100644 --- a/test/time_stack_item_test.rb +++ b/test/time_stack_item_test.rb @@ -302,6 +302,7 @@ def test_travel_offset_aligns_to_clock stack_item = Timecop::TimeStackItem.new(:travel, t) travel_offset_denom = stack_item.travel_offset.to_r.denominator clock_resolution = Process.clock_getres(:CLOCK_REALTIME, :hertz) - assert_equal clock_resolution.modulo(travel_offset_denom), 0 + assert_equal 0, clock_resolution.modulo(travel_offset_denom), + "travel offset precision (#{travel_offset_denom}) does not align with clock resolution (#{clock_resolution})" end end From 8a5d52d6e31ace282a0dab6fe25e0aa35471d518 Mon Sep 17 00:00:00 2001 From: Daniel George Holz Date: Thu, 27 Jun 2024 12:40:38 +0100 Subject: [PATCH 4/5] Test that time_offset keeps precision of very high times passed to Timecop.travel --- test/time_stack_item_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/time_stack_item_test.rb b/test/time_stack_item_test.rb index 29c3fdb..e65bbeb 100644 --- a/test/time_stack_item_test.rb +++ b/test/time_stack_item_test.rb @@ -305,4 +305,13 @@ def test_travel_offset_aligns_to_clock assert_equal 0, clock_resolution.modulo(travel_offset_denom), "travel offset precision (#{travel_offset_denom}) does not align with clock resolution (#{clock_resolution})" end + + def test_travel_offset_aligns_to_travel_time + t = Time.now + 0.001_002_003_004 + stack_item = Timecop::TimeStackItem.new(:travel, t) + travel_offset_denom = stack_item.travel_offset.to_r.denominator + travel_time_denom = t.to_r.denominator + assert_equal 0, travel_time_denom.modulo(travel_offset_denom), + "travel offset precision (#{travel_offset_denom}) does not align with travel time precision (#{travel_time_denom})" + end end From c2bdfc75b7a197de59d8f6aa1c1c55c18db319be Mon Sep 17 00:00:00 2001 From: Daniel George Holz Date: Wed, 15 May 2024 12:43:50 +0100 Subject: [PATCH 5/5] Mention this change in History.md --- History.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/History.md b/History.md index 91442a9..4f2eaf2 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,8 @@ ## Unreleased +- Calculate travel_offset to align with the precision of argument to Timecop.travel ([#421](https://github.com/travisjeffery/timecop/pull/421)) + ## v0.9.10 - Make Process.clock_gettime configurable and turned off by default (for backwards compatability) ([#427](https://github.com/travisjeffery/timecop/pull/427))