timecop 0.9.6 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.markdown +18 -9
- data/lib/timecop/time_extensions.rb +80 -9
- data/lib/timecop/time_stack_item.rb +31 -0
- data/lib/timecop/timecop.rb +26 -2
- data/lib/timecop/version.rb +1 -1
- metadata +5 -15
- data/test/test_helper.rb +0 -59
- data/test/time_stack_item_test.rb +0 -299
- data/test/timecop_test.rb +0 -622
- data/test/timecop_without_date_but_with_time_test.rb +0 -8
- data/test/timecop_without_date_test.rb +0 -118
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79ad5f132158245aadf9f2afbfbfaa2e0ed8298e69037270f51cb801f9794b88
|
4
|
+
data.tar.gz: ec1d3177b1ca8f8f03c2bef0316f97953a4fe021d250903ea9159cdca8079550
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82b29e41644233b3aa80205a40e1f72e8616fc45b0c4e10fb12cbe5abf1e13dba36423667ba8ab4ec0e592633056d30d6f760a462137333ed629cffaf4272729
|
7
|
+
data.tar.gz: 81f1c484e1254c6b72bd55bcac4dda7aa5b73d2af04125fdbb433ef69baaf796efe022b3384555ef9241261a799af495bbf49fac88d1ca90b3a497908a9f4df6
|
data/README.markdown
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
## DESCRIPTION
|
7
7
|
|
8
|
-
A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code.
|
8
|
+
A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock `Time.now`, `Date.today`, `DateTime.now`, and `Process.clock_gettime` in a single call.
|
9
9
|
|
10
10
|
## INSTALL
|
11
11
|
|
@@ -17,13 +17,13 @@ A gem providing "time travel" and "time freezing" capabilities, making it dead s
|
|
17
17
|
- Travel back to a specific point in time, but allow time to continue moving forward from there.
|
18
18
|
- Scale time by a given scaling factor that will cause time to move at an accelerated pace.
|
19
19
|
- No dependencies, can be used with _any_ ruby project
|
20
|
-
- Timecop api allows arguments to be passed into
|
20
|
+
- Timecop api allows arguments to be passed into `#freeze` and `#travel` as one of the following:
|
21
21
|
- Time instance
|
22
22
|
- DateTime instance
|
23
23
|
- Date instance
|
24
24
|
- individual arguments (year, month, day, hour, minute, second)
|
25
|
-
- a single integer argument that is interpreted as an offset in seconds from Time.now
|
26
|
-
- Nested calls to Timecop#travel and Timecop#freeze are supported -- each block will maintain its interpretation of now.
|
25
|
+
- a single integer argument that is interpreted as an offset in seconds from `Time.now`
|
26
|
+
- Nested calls to `Timecop#travel` and `Timecop#freeze` are supported -- each block will maintain its interpretation of now.
|
27
27
|
- Works with regular Ruby projects, and Ruby on Rails projects
|
28
28
|
|
29
29
|
## USAGE
|
@@ -62,7 +62,7 @@ helpful if your whole application is time-sensitive. It allows you to build
|
|
62
62
|
your test data at a single point in time, and to move in/out of that time as
|
63
63
|
appropriate (within your tests)
|
64
64
|
|
65
|
-
in config/environments/test.rb
|
65
|
+
in `config/environments/test.rb`
|
66
66
|
|
67
67
|
```ruby
|
68
68
|
config.after_initialize do
|
@@ -74,10 +74,10 @@ end
|
|
74
74
|
|
75
75
|
### The difference between Timecop.freeze and Timecop.travel
|
76
76
|
|
77
|
-
freeze is used to statically mock the concept of now. As your program executes,
|
78
|
-
Time.now will not change unless you make subsequent calls into the Timecop API.
|
79
|
-
travel
|
80
|
-
Time.now is (recall that we support nested traveling) and the time passed in.
|
77
|
+
`freeze` is used to statically mock the concept of now. As your program executes,
|
78
|
+
`Time.now` will not change unless you make subsequent calls into the Timecop API.
|
79
|
+
`travel`, on the other hand, computes an offset between what we currently think
|
80
|
+
`Time.now` is (recall that we support nested traveling) and the time passed in.
|
81
81
|
It uses this offset to simulate the passage of time. To demonstrate, consider
|
82
82
|
the following code snippets:
|
83
83
|
|
@@ -129,6 +129,15 @@ Timecop.freeze
|
|
129
129
|
# => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
|
130
130
|
```
|
131
131
|
|
132
|
+
### Configuring Mocking Process.clock_gettime
|
133
|
+
|
134
|
+
By default Timecop does not mock Process.clock_gettime. You must enable it like this:
|
135
|
+
|
136
|
+
``` ruby
|
137
|
+
# turn on
|
138
|
+
Timecop.mock_process_clock = true
|
139
|
+
```
|
140
|
+
|
132
141
|
### Rails v Ruby Date/Time libraries
|
133
142
|
|
134
143
|
Sometimes [Rails Date/Time methods don't play nicely with Ruby Date/Time methods.](https://rails.lighthouseapp.com/projects/8994/tickets/6410-dateyesterday-datetoday)
|
@@ -50,21 +50,28 @@ class Date #:nodoc:
|
|
50
50
|
|
51
51
|
d = Date._strptime(str, fmt)
|
52
52
|
now = Time.now.to_date
|
53
|
-
year = d[:year] || now.year
|
53
|
+
year = d[:year] || d[:cwyear] || now.year
|
54
54
|
mon = d[:mon] || now.mon
|
55
55
|
if d.keys == [:year]
|
56
56
|
Date.new(year, 1, 1, start)
|
57
57
|
elsif d[:mday]
|
58
58
|
Date.new(year, mon, d[:mday], start)
|
59
|
-
elsif d[:wday]
|
60
|
-
Date.new(year, mon, now.mday, start) + (d[:wday] - now.wday)
|
61
59
|
elsif d[:yday]
|
62
60
|
Date.new(year, 1, 1, start).next_day(d[:yday] - 1)
|
63
|
-
elsif d[:cwyear]
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
61
|
+
elsif d[:cwyear] || d[:cweek] || d[:wnum0] || d[:wnum1] || d[:wday] || d[:cwday]
|
62
|
+
week = d[:cweek] || d[:wnum1] || d[:wnum0] || now.strftime('%W').to_i
|
63
|
+
if d[:wnum0] #Week of year where week starts on sunday
|
64
|
+
if d[:cwday] #monday based day of week
|
65
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday]}", '%Y %U %u', start)
|
66
|
+
else
|
67
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:wday] || 0}", '%Y %U %w', start)
|
68
|
+
end
|
69
|
+
else #Week of year where week starts on monday
|
70
|
+
if d[:wday] #sunday based day of week
|
71
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:wday]}", '%Y %W %w', start)
|
72
|
+
else
|
73
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday] || 1}", '%Y %W %u', start)
|
74
|
+
end
|
68
75
|
end
|
69
76
|
elsif d[:seconds]
|
70
77
|
Time.at(d[:seconds]).to_date
|
@@ -125,7 +132,6 @@ class DateTime #:nodoc:
|
|
125
132
|
alias_method :now, :now_with_mock_time
|
126
133
|
|
127
134
|
def parse_with_mock_date(*args)
|
128
|
-
date_hash = Date._parse(*args)
|
129
135
|
parsed_date = parse_without_mock_date(*args)
|
130
136
|
return parsed_date unless mocked_time_stack_item
|
131
137
|
date_hash = DateTime._parse(*args)
|
@@ -137,8 +143,17 @@ class DateTime #:nodoc:
|
|
137
143
|
DateTime.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
138
144
|
when date_hash[:mday]
|
139
145
|
DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday])
|
146
|
+
when date_hash[:wday] && date_hash[:hour] && date_hash[:min]
|
147
|
+
closest_date = Date.closest_wday(date_hash[:wday]).to_datetime
|
148
|
+
|
149
|
+
DateTime.new(
|
150
|
+
closest_date.year, closest_date.month, closest_date.day,
|
151
|
+
date_hash[:hour], date_hash[:min]
|
152
|
+
)
|
140
153
|
when date_hash[:wday]
|
141
154
|
Date.closest_wday(date_hash[:wday]).to_datetime
|
155
|
+
when date_hash[:hour] && date_hash[:min] && date_hash[:sec]
|
156
|
+
DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, mocked_time_stack_item.day, date_hash[:hour], date_hash[:min], date_hash[:sec])
|
142
157
|
else
|
143
158
|
parsed_date + mocked_time_stack_item.travel_offset_days
|
144
159
|
end
|
@@ -152,3 +167,59 @@ class DateTime #:nodoc:
|
|
152
167
|
end
|
153
168
|
end
|
154
169
|
end
|
170
|
+
|
171
|
+
if RUBY_VERSION >= '2.1.0'
|
172
|
+
module Process #:nodoc:
|
173
|
+
class << self
|
174
|
+
alias_method :clock_gettime_without_mock, :clock_gettime
|
175
|
+
|
176
|
+
def clock_gettime_mock_time(clock_id, unit = :float_second)
|
177
|
+
mock_time = case clock_id
|
178
|
+
when Process::CLOCK_MONOTONIC
|
179
|
+
mock_time_monotonic
|
180
|
+
when Process::CLOCK_REALTIME
|
181
|
+
mock_time_realtime
|
182
|
+
end
|
183
|
+
|
184
|
+
return clock_gettime_without_mock(clock_id, unit) unless Timecop.mock_process_clock? && mock_time
|
185
|
+
|
186
|
+
divisor = case unit
|
187
|
+
when :float_second
|
188
|
+
1_000_000_000.0
|
189
|
+
when :second
|
190
|
+
1_000_000_000
|
191
|
+
when :float_millisecond
|
192
|
+
1_000_000.0
|
193
|
+
when :millisecond
|
194
|
+
1_000_000
|
195
|
+
when :float_microsecond
|
196
|
+
1000.0
|
197
|
+
when :microsecond
|
198
|
+
1000
|
199
|
+
when :nanosecond
|
200
|
+
1
|
201
|
+
end
|
202
|
+
|
203
|
+
(mock_time / divisor)
|
204
|
+
end
|
205
|
+
|
206
|
+
alias_method :clock_gettime, :clock_gettime_mock_time
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def mock_time_monotonic
|
211
|
+
mocked_time_stack_item = Timecop.top_stack_item
|
212
|
+
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.monotonic
|
213
|
+
end
|
214
|
+
|
215
|
+
def mock_time_realtime
|
216
|
+
mocked_time_stack_item = Timecop.top_stack_item
|
217
|
+
|
218
|
+
return nil if mocked_time_stack_item.nil?
|
219
|
+
|
220
|
+
t = mocked_time_stack_item.time
|
221
|
+
t.to_i * 1_000_000_000 + t.nsec
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -9,6 +9,7 @@ class Timecop
|
|
9
9
|
@travel_offset = @scaling_factor = nil
|
10
10
|
@scaling_factor = args.shift if mock_type == :scale
|
11
11
|
@mock_type = mock_type
|
12
|
+
@monotonic = parse_monotonic_time(*args) if RUBY_VERSION >= '2.1.0'
|
12
13
|
@time = parse_time(*args)
|
13
14
|
@time_was = Time.now_without_mock_time
|
14
15
|
@travel_offset = compute_travel_offset
|
@@ -54,6 +55,26 @@ class Timecop
|
|
54
55
|
@scaling_factor
|
55
56
|
end
|
56
57
|
|
58
|
+
if RUBY_VERSION >= '2.1.0'
|
59
|
+
def monotonic
|
60
|
+
if travel_offset.nil?
|
61
|
+
@monotonic
|
62
|
+
elsif scaling_factor.nil?
|
63
|
+
current_monotonic + travel_offset * (10 ** 9)
|
64
|
+
else
|
65
|
+
(@monotonic + (current_monotonic - @monotonic) * scaling_factor).to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def current_monotonic
|
70
|
+
Process.clock_gettime_without_mock(Process::CLOCK_MONOTONIC, :nanosecond)
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_monotonic_with_mock
|
74
|
+
Process.clock_gettime_mock_time(Process::CLOCK_MONOTONIC, :nanosecond)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
57
78
|
def time(time_klass = Time) #:nodoc:
|
58
79
|
if @time.respond_to?(:in_time_zone)
|
59
80
|
time = time_klass.at(@time.dup.localtime)
|
@@ -97,6 +118,16 @@ class Timecop
|
|
97
118
|
Rational(utc_offset, 24 * 60 * 60)
|
98
119
|
end
|
99
120
|
|
121
|
+
def parse_monotonic_time(*args)
|
122
|
+
arg = args.shift
|
123
|
+
offset_in_nanoseconds = if args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
|
124
|
+
arg * 1_000_000_000
|
125
|
+
else
|
126
|
+
0
|
127
|
+
end
|
128
|
+
current_monotonic_with_mock + offset_in_nanoseconds
|
129
|
+
end
|
130
|
+
|
100
131
|
def parse_time(*args)
|
101
132
|
arg = args.shift
|
102
133
|
if arg.is_a?(Time)
|
data/lib/timecop/timecop.rb
CHANGED
@@ -38,6 +38,12 @@ class Timecop
|
|
38
38
|
# previous values after the block has finished executing. This allows us to nest multiple
|
39
39
|
# calls to Timecop.travel and have each block maintain it's concept of "now."
|
40
40
|
#
|
41
|
+
# The Process.clock_gettime call mocks both CLOCK::MONOTIC and CLOCK::REALTIME
|
42
|
+
#
|
43
|
+
# CLOCK::MONOTONIC works slightly differently than other clocks. This clock cannot move to a
|
44
|
+
# particular date/time. So the only option that changes this clock is #4 which will move the
|
45
|
+
# clock the requested offset. Otherwise the clock is frozen to the current tick.
|
46
|
+
#
|
41
47
|
# * Note: Timecop.freeze will actually freeze time. This can cause unanticipated problems if
|
42
48
|
# benchmark or other timing calls are executed, which implicitly expect Time to actually move
|
43
49
|
# forward.
|
@@ -121,9 +127,27 @@ class Timecop
|
|
121
127
|
instance.thread_safe
|
122
128
|
end
|
123
129
|
|
124
|
-
# Returns whether or not Timecop is currently frozen
|
130
|
+
# Returns whether or not Timecop is currently frozen
|
125
131
|
def frozen?
|
126
|
-
!instance.stack.empty?
|
132
|
+
!instance.stack.empty? && instance.stack.last.mock_type == :freeze
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns whether or not Timecop is currently travelled
|
136
|
+
def travelled?
|
137
|
+
!instance.stack.empty? && instance.stack.last.mock_type == :travel
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns whether or not Timecop is currently scaled
|
141
|
+
def scaled?
|
142
|
+
!instance.stack.empty? && instance.stack.last.mock_type == :scale
|
143
|
+
end
|
144
|
+
|
145
|
+
def mock_process_clock=(mock)
|
146
|
+
@mock_process_clock = mock
|
147
|
+
end
|
148
|
+
|
149
|
+
def mock_process_clock?
|
150
|
+
@mock_process_clock ||= false
|
127
151
|
end
|
128
152
|
|
129
153
|
private
|
data/lib/timecop/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timecop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Travis Jeffery
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-06-14 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A gem providing "time travel" and "time freezing" capabilities, making
|
15
15
|
it dead simple to test time-dependent code. It provides a unified method to mock
|
@@ -29,11 +29,6 @@ files:
|
|
29
29
|
- lib/timecop/time_stack_item.rb
|
30
30
|
- lib/timecop/timecop.rb
|
31
31
|
- lib/timecop/version.rb
|
32
|
-
- test/test_helper.rb
|
33
|
-
- test/time_stack_item_test.rb
|
34
|
-
- test/timecop_test.rb
|
35
|
-
- test/timecop_without_date_but_with_time_test.rb
|
36
|
-
- test/timecop_without_date_test.rb
|
37
32
|
homepage: https://github.com/travisjeffery/timecop
|
38
33
|
licenses:
|
39
34
|
- MIT
|
@@ -54,15 +49,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
49
|
- !ruby/object:Gem::Version
|
55
50
|
version: '0'
|
56
51
|
requirements: []
|
57
|
-
rubygems_version: 3.2.
|
52
|
+
rubygems_version: 3.2.22
|
58
53
|
signing_key:
|
59
|
-
specification_version:
|
54
|
+
specification_version: 4
|
60
55
|
summary: A gem providing "time travel" and "time freezing" capabilities, making it
|
61
56
|
dead simple to test time-dependent code. It provides a unified method to mock Time.now,
|
62
57
|
Date.today, and DateTime.now in a single call.
|
63
|
-
test_files:
|
64
|
-
- test/test_helper.rb
|
65
|
-
- test/time_stack_item_test.rb
|
66
|
-
- test/timecop_test.rb
|
67
|
-
- test/timecop_without_date_test.rb
|
68
|
-
- test/timecop_without_date_but_with_time_test.rb
|
58
|
+
test_files: []
|
data/test/test_helper.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'minitest/rg'
|
4
|
-
require 'pry'
|
5
|
-
|
6
|
-
$VERBOSE = true # enable ruby warnings
|
7
|
-
|
8
|
-
require 'mocha/minitest'
|
9
|
-
|
10
|
-
class Minitest::Test
|
11
|
-
private
|
12
|
-
# Tests to see that two times are within the given distance,
|
13
|
-
# in seconds, from each other.
|
14
|
-
def times_effectively_equal(time1, time2, seconds_interval = 1)
|
15
|
-
(time1 - time2).abs <= seconds_interval
|
16
|
-
end
|
17
|
-
|
18
|
-
def assert_times_effectively_equal(time1, time2, seconds_interval = 1, msg = nil)
|
19
|
-
assert times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}"
|
20
|
-
end
|
21
|
-
|
22
|
-
def assert_times_effectively_not_equal(time1, time2, seconds_interval = 1, msg = nil)
|
23
|
-
assert !times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}"
|
24
|
-
end
|
25
|
-
|
26
|
-
# Gets the local offset (supplied by ENV['TZ'] or your computer's clock)
|
27
|
-
# At the given timestamp, or Time.now if not time is given.
|
28
|
-
def local_offset(time = Time.now)
|
29
|
-
Time.at(time.to_i).to_datetime.offset
|
30
|
-
end
|
31
|
-
|
32
|
-
TIMEZONES = ["Pacific/Midway", "Europe/Paris", "UTC", "America/Chicago"]
|
33
|
-
|
34
|
-
def each_timezone
|
35
|
-
old_tz = ENV["TZ"]
|
36
|
-
|
37
|
-
begin
|
38
|
-
TIMEZONES.each do |timezone|
|
39
|
-
ENV["TZ"] = timezone
|
40
|
-
yield
|
41
|
-
end
|
42
|
-
ensure
|
43
|
-
ENV["TZ"] = old_tz
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def a_time_stack_item
|
48
|
-
Timecop::TimeStackItem.new(:freeze, 2008, 1, 1, 0, 0, 0)
|
49
|
-
end
|
50
|
-
|
51
|
-
def assert_date_times_equal(dt1, dt2)
|
52
|
-
assert_in_delta dt1.to_time.to_f, dt2.to_time.to_f, 0.01, "Failed for timezone: #{ENV['TZ']}: #{dt1.to_s} not equal to #{dt2.to_s}"
|
53
|
-
end
|
54
|
-
|
55
|
-
def jruby?
|
56
|
-
RUBY_PLATFORM == "java"
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|