timecop 0.9.1 → 0.9.11
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 +5 -5
- data/LICENSE +1 -1
- data/README.markdown +21 -11
- data/lib/timecop/time_extensions.rb +115 -8
- data/lib/timecop/time_stack_item.rb +136 -107
- data/lib/timecop/timecop.rb +42 -15
- data/lib/timecop/version.rb +1 -1
- metadata +9 -21
- data/test/test_helper.rb +0 -58
- data/test/time_stack_item_test.rb +0 -299
- data/test/timecop_test.rb +0 -585
- 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
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: '079905b60d7144434c07560dadc7a1dbd36d19ad27f25ecaf52e0143e0656d23'
|
|
4
|
+
data.tar.gz: f34eb8593ccdd28571a4ccbdd710f518e628da6113eeb0f904e2f04d91dc5bf1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd112c3657079e9c927d266725ba6d231a23658da5f385f1dca760011eff2021a30a1607cc187b26737b6d20244a0e9d54130a5a4c46b93e04c0ddebd3ba4fad
|
|
7
|
+
data.tar.gz: fa6dc270173399ff8c112624913703f612ea0d25720f13b01be67ee41781b07bd6661a502b0db1e1fdbc34fc91b4acd2966526f2ee1f1acd7463d8db26f40b8e
|
data/LICENSE
CHANGED
data/README.markdown
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# timecop
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/timecop)
|
|
4
|
+
[](https://github.com/travisjeffery/timecop/actions?query=workflow%3ACI)
|
|
4
5
|
|
|
5
6
|
## DESCRIPTION
|
|
6
7
|
|
|
7
|
-
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.
|
|
8
9
|
|
|
9
10
|
## INSTALL
|
|
10
11
|
|
|
11
|
-
`
|
|
12
|
+
`bundle add timecop`
|
|
12
13
|
|
|
13
14
|
## FEATURES
|
|
14
15
|
|
|
@@ -16,13 +17,13 @@ A gem providing "time travel" and "time freezing" capabilities, making it dead s
|
|
|
16
17
|
- Travel back to a specific point in time, but allow time to continue moving forward from there.
|
|
17
18
|
- Scale time by a given scaling factor that will cause time to move at an accelerated pace.
|
|
18
19
|
- No dependencies, can be used with _any_ ruby project
|
|
19
|
-
- 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:
|
|
20
21
|
- Time instance
|
|
21
22
|
- DateTime instance
|
|
22
23
|
- Date instance
|
|
23
24
|
- individual arguments (year, month, day, hour, minute, second)
|
|
24
|
-
- a single integer argument that is interpreted as an offset in seconds from Time.now
|
|
25
|
-
- 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.
|
|
26
27
|
- Works with regular Ruby projects, and Ruby on Rails projects
|
|
27
28
|
|
|
28
29
|
## USAGE
|
|
@@ -61,7 +62,7 @@ helpful if your whole application is time-sensitive. It allows you to build
|
|
|
61
62
|
your test data at a single point in time, and to move in/out of that time as
|
|
62
63
|
appropriate (within your tests)
|
|
63
64
|
|
|
64
|
-
in config/environments/test.rb
|
|
65
|
+
in `config/environments/test.rb`
|
|
65
66
|
|
|
66
67
|
```ruby
|
|
67
68
|
config.after_initialize do
|
|
@@ -73,10 +74,10 @@ end
|
|
|
73
74
|
|
|
74
75
|
### The difference between Timecop.freeze and Timecop.travel
|
|
75
76
|
|
|
76
|
-
freeze is used to statically mock the concept of now. As your program executes,
|
|
77
|
-
Time.now will not change unless you make subsequent calls into the Timecop API.
|
|
78
|
-
travel
|
|
79
|
-
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.
|
|
80
81
|
It uses this offset to simulate the passage of time. To demonstrate, consider
|
|
81
82
|
the following code snippets:
|
|
82
83
|
|
|
@@ -128,6 +129,15 @@ Timecop.freeze
|
|
|
128
129
|
# => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
|
|
129
130
|
```
|
|
130
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
|
+
|
|
131
141
|
### Rails v Ruby Date/Time libraries
|
|
132
142
|
|
|
133
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)
|
|
@@ -18,8 +18,14 @@ class Time #:nodoc:
|
|
|
18
18
|
|
|
19
19
|
alias_method :new_without_mock_time, :new
|
|
20
20
|
|
|
21
|
-
def new_with_mock_time(*args)
|
|
22
|
-
args.
|
|
21
|
+
def new_with_mock_time(*args, **kwargs)
|
|
22
|
+
if args.empty? && kwargs.empty?
|
|
23
|
+
now
|
|
24
|
+
elsif kwargs.any?
|
|
25
|
+
new_without_mock_time(*args, **kwargs)
|
|
26
|
+
else
|
|
27
|
+
new_without_mock_time(*args)
|
|
28
|
+
end
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
alias_method :new, :new_with_mock_time
|
|
@@ -43,12 +49,45 @@ class Date #:nodoc:
|
|
|
43
49
|
alias_method :strptime_without_mock_date, :strptime
|
|
44
50
|
|
|
45
51
|
def strptime_with_mock_date(str = '-4712-01-01', fmt = '%F', start = Date::ITALY)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
#If date is not valid the following line raises
|
|
53
|
+
Date.strptime_without_mock_date(str, fmt, start)
|
|
54
|
+
|
|
55
|
+
d = Date._strptime(str, fmt)
|
|
56
|
+
now = Time.now.to_date
|
|
57
|
+
|
|
58
|
+
# If "current" time falls near a year boundary, with a year-ambiguous str, we need to explicitly handle it
|
|
59
|
+
cwday = d[:cwday] && (now + (d[:cwday] - now.wday))
|
|
60
|
+
wday = d[:wday] && (now + (d[:wday] - now.wday))
|
|
61
|
+
|
|
62
|
+
year = d[:year] || d[:cwyear] || (cwday || wday || now).year
|
|
63
|
+
mon = d[:mon] || now.mon
|
|
64
|
+
if d.keys == [:year]
|
|
65
|
+
Date.new(year, 1, 1, start)
|
|
66
|
+
elsif d[:mday]
|
|
67
|
+
Date.new(year, mon, d[:mday], start)
|
|
68
|
+
elsif d[:yday]
|
|
69
|
+
Date.new(year, 1, 1, start).next_day(d[:yday] - 1)
|
|
70
|
+
elsif d[:cwyear] || d[:cweek] || d[:wnum0] || d[:wnum1] || d[:wday] || d[:cwday]
|
|
71
|
+
# When only a day (wday/cwday) is present, derive the week from the resolved date; otherwise fall back to now's week
|
|
72
|
+
week = d[:cweek] || d[:wnum1] || d[:wnum0] || (cwday || wday || now).strftime('%W').to_i
|
|
73
|
+
if d[:wnum0] #Week of year where week starts on sunday
|
|
74
|
+
if d[:cwday] #monday based day of week
|
|
75
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday]}", '%Y %U %u', start)
|
|
76
|
+
else
|
|
77
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:wday] || 0}", '%Y %U %w', start)
|
|
78
|
+
end
|
|
79
|
+
else #Week of year where week starts on monday
|
|
80
|
+
if d[:wday] #sunday based day of week
|
|
81
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:wday]}", '%Y %W %w', start)
|
|
82
|
+
else
|
|
83
|
+
Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday] || 1}", '%Y %W %u', start)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
elsif d[:seconds]
|
|
87
|
+
Time.at(d[:seconds]).to_date
|
|
88
|
+
else
|
|
89
|
+
Date.new(year, mon, 1, start)
|
|
49
90
|
end
|
|
50
|
-
|
|
51
|
-
Time.strptime(str, fmt).to_date
|
|
52
91
|
end
|
|
53
92
|
|
|
54
93
|
alias_method :strptime, :strptime_with_mock_date
|
|
@@ -63,6 +102,8 @@ class Date #:nodoc:
|
|
|
63
102
|
parsed_date
|
|
64
103
|
when date_hash[:mon] && date_hash[:mday]
|
|
65
104
|
Date.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
|
105
|
+
when date_hash[:mday]
|
|
106
|
+
Date.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday])
|
|
66
107
|
when date_hash[:wday]
|
|
67
108
|
closest_wday(date_hash[:wday])
|
|
68
109
|
else
|
|
@@ -101,7 +142,6 @@ class DateTime #:nodoc:
|
|
|
101
142
|
alias_method :now, :now_with_mock_time
|
|
102
143
|
|
|
103
144
|
def parse_with_mock_date(*args)
|
|
104
|
-
date_hash = Date._parse(*args)
|
|
105
145
|
parsed_date = parse_without_mock_date(*args)
|
|
106
146
|
return parsed_date unless mocked_time_stack_item
|
|
107
147
|
date_hash = DateTime._parse(*args)
|
|
@@ -111,8 +151,21 @@ class DateTime #:nodoc:
|
|
|
111
151
|
parsed_date
|
|
112
152
|
when date_hash[:mon] && date_hash[:mday]
|
|
113
153
|
DateTime.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
|
154
|
+
when date_hash[:mday]
|
|
155
|
+
DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday])
|
|
156
|
+
when date_hash[:wday] && date_hash[:hour] && date_hash[:min]
|
|
157
|
+
closest_date = Date.closest_wday(date_hash[:wday]).to_datetime
|
|
158
|
+
|
|
159
|
+
DateTime.new(
|
|
160
|
+
closest_date.year, closest_date.month, closest_date.day,
|
|
161
|
+
date_hash[:hour], date_hash[:min]
|
|
162
|
+
)
|
|
114
163
|
when date_hash[:wday]
|
|
115
164
|
Date.closest_wday(date_hash[:wday]).to_datetime
|
|
165
|
+
when date_hash[:hour] && date_hash[:min] && date_hash[:sec]
|
|
166
|
+
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])
|
|
167
|
+
when date_hash[:hour] && date_hash[:min]
|
|
168
|
+
DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, mocked_time_stack_item.day, date_hash[:hour], date_hash[:min], 0)
|
|
116
169
|
else
|
|
117
170
|
parsed_date + mocked_time_stack_item.travel_offset_days
|
|
118
171
|
end
|
|
@@ -126,3 +179,57 @@ class DateTime #:nodoc:
|
|
|
126
179
|
end
|
|
127
180
|
end
|
|
128
181
|
end
|
|
182
|
+
|
|
183
|
+
module Process #:nodoc:
|
|
184
|
+
class << self
|
|
185
|
+
alias_method :clock_gettime_without_mock, :clock_gettime
|
|
186
|
+
|
|
187
|
+
def clock_gettime_mock_time(clock_id, unit = :float_second)
|
|
188
|
+
mock_time = case clock_id
|
|
189
|
+
when Process::CLOCK_MONOTONIC
|
|
190
|
+
mock_time_monotonic
|
|
191
|
+
when Process::CLOCK_REALTIME
|
|
192
|
+
mock_time_realtime
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
return clock_gettime_without_mock(clock_id, unit) unless Timecop.mock_process_clock? && mock_time
|
|
196
|
+
|
|
197
|
+
divisor = case unit
|
|
198
|
+
when :float_second
|
|
199
|
+
1_000_000_000.0
|
|
200
|
+
when :second
|
|
201
|
+
1_000_000_000
|
|
202
|
+
when :float_millisecond
|
|
203
|
+
1_000_000.0
|
|
204
|
+
when :millisecond
|
|
205
|
+
1_000_000
|
|
206
|
+
when :float_microsecond
|
|
207
|
+
1000.0
|
|
208
|
+
when :microsecond
|
|
209
|
+
1000
|
|
210
|
+
when :nanosecond
|
|
211
|
+
1
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
(mock_time / divisor)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
alias_method :clock_gettime, :clock_gettime_mock_time
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def mock_time_monotonic
|
|
222
|
+
mocked_time_stack_item = Timecop.top_stack_item
|
|
223
|
+
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.monotonic
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def mock_time_realtime
|
|
227
|
+
mocked_time_stack_item = Timecop.top_stack_item
|
|
228
|
+
|
|
229
|
+
return nil if mocked_time_stack_item.nil?
|
|
230
|
+
|
|
231
|
+
t = mocked_time_stack_item.time
|
|
232
|
+
t.to_i * 1_000_000_000 + t.nsec
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -1,140 +1,169 @@
|
|
|
1
1
|
class Timecop
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
# A data class for carrying around "time movement" objects. Makes it easy to keep track of the time
|
|
3
|
+
# movements on a simple stack.
|
|
4
|
+
class TimeStackItem #:nodoc:
|
|
5
|
+
attr_reader :mock_type
|
|
6
|
+
|
|
7
|
+
def initialize(mock_type, *args)
|
|
8
|
+
raise "Unknown mock_type #{mock_type}" unless [:freeze, :travel, :scale].include?(mock_type)
|
|
9
|
+
@travel_offset = @scaling_factor = nil
|
|
10
|
+
@scaling_factor = args.shift if mock_type == :scale
|
|
11
|
+
@mock_type = mock_type
|
|
12
|
+
@monotonic = parse_monotonic_time(*args)
|
|
13
|
+
@time = parse_time(*args)
|
|
14
|
+
@time_was = Time.now_without_mock_time
|
|
15
|
+
@travel_offset = compute_travel_offset
|
|
16
|
+
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
def year
|
|
19
|
+
time.year
|
|
20
|
+
end
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
def month
|
|
23
|
+
time.month
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
def day
|
|
27
|
+
time.day
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
def hour
|
|
31
|
+
time.hour
|
|
32
|
+
end
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
def min
|
|
35
|
+
time.min
|
|
36
|
+
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
def sec
|
|
39
|
+
time.sec
|
|
40
|
+
end
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
def utc_offset
|
|
43
|
+
time.utc_offset
|
|
44
|
+
end
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
def travel_offset
|
|
47
|
+
@travel_offset unless mock_type == :freeze
|
|
48
|
+
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
def travel_offset_days
|
|
51
|
+
(@travel_offset / 60 / 60 / 24).round
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def scaling_factor
|
|
55
|
+
@scaling_factor
|
|
56
|
+
end
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
def monotonic
|
|
59
|
+
if travel_offset.nil?
|
|
60
|
+
@monotonic
|
|
61
|
+
elsif scaling_factor.nil?
|
|
62
|
+
current_monotonic + travel_offset * (10 ** 9)
|
|
63
|
+
else
|
|
64
|
+
(@monotonic + (current_monotonic - @monotonic) * scaling_factor).to_i
|
|
55
65
|
end
|
|
66
|
+
end
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
else
|
|
61
|
-
time = time_klass.at(@time)
|
|
62
|
-
end
|
|
68
|
+
def current_monotonic
|
|
69
|
+
Process.clock_gettime_without_mock(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
70
|
+
end
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
time_klass.at(Time.now_without_mock_time + travel_offset)
|
|
68
|
-
else
|
|
69
|
-
time_klass.at(scaled_time)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
+
def current_monotonic_with_mock
|
|
73
|
+
Process.clock_gettime_mock_time(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
74
|
+
end
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
def time(time_klass = Time) #:nodoc:
|
|
77
|
+
if @time.respond_to?(:in_time_zone)
|
|
78
|
+
time = time_klass.at(@time.dup.localtime)
|
|
79
|
+
else
|
|
80
|
+
time = time_klass.at(@time)
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
if travel_offset.nil?
|
|
84
|
+
time
|
|
85
|
+
elsif scaling_factor.nil?
|
|
86
|
+
time_klass.at(Time.now_without_mock_time + travel_offset)
|
|
87
|
+
else
|
|
88
|
+
time_klass.at(scaled_time)
|
|
79
89
|
end
|
|
90
|
+
end
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
datetime_klass.new(year, month, day, hour, min, (fractions_of_a_second + sec), utc_offset_to_rational(utc_offset))
|
|
85
|
-
else
|
|
86
|
-
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(utc_offset))
|
|
87
|
-
end
|
|
88
|
-
end
|
|
92
|
+
def scaled_time
|
|
93
|
+
(@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f
|
|
94
|
+
end
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
def date(date_klass = Date)
|
|
97
|
+
date_klass.jd(time.__send__(:to_date).jd)
|
|
98
|
+
end
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
def datetime(datetime_klass = DateTime)
|
|
101
|
+
if Float.method_defined?(:to_r)
|
|
102
|
+
fractions_of_a_second = time.to_f % 1
|
|
103
|
+
datetime_klass.new(year, month, day, hour, min, (fractions_of_a_second + sec), utc_offset_to_rational(utc_offset))
|
|
104
|
+
else
|
|
105
|
+
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(utc_offset))
|
|
94
106
|
end
|
|
107
|
+
end
|
|
95
108
|
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def rational_to_utc_offset(rational)
|
|
112
|
+
((24.0 / rational.denominator) * rational.numerator) * (60 * 60)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def utc_offset_to_rational(utc_offset)
|
|
116
|
+
Rational(utc_offset, 24 * 60 * 60)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_monotonic_time(*args)
|
|
120
|
+
arg = args.shift
|
|
121
|
+
offset_in_nanoseconds = if args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
|
|
122
|
+
arg * 1_000_000_000
|
|
123
|
+
else
|
|
124
|
+
0
|
|
98
125
|
end
|
|
126
|
+
current_monotonic_with_mock + offset_in_nanoseconds
|
|
127
|
+
end
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
def parse_time(*args)
|
|
130
|
+
arg = args.shift
|
|
131
|
+
if arg.is_a?(Time)
|
|
132
|
+
arg
|
|
133
|
+
elsif Object.const_defined?(:DateTime) && arg.is_a?(DateTime)
|
|
134
|
+
time_klass.at(arg.to_time.to_f).getlocal
|
|
135
|
+
elsif Object.const_defined?(:Date) && arg.is_a?(Date)
|
|
136
|
+
time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0)
|
|
137
|
+
elsif args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
|
|
138
|
+
time_klass.now + arg
|
|
139
|
+
elsif arg.nil?
|
|
140
|
+
time_klass.now
|
|
141
|
+
else
|
|
142
|
+
if arg.is_a?(String) && Time.respond_to?(:parse)
|
|
143
|
+
time_klass.parse(arg)
|
|
112
144
|
else
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
minute = args.shift || 0
|
|
122
|
-
second = args.shift || 0
|
|
123
|
-
time_klass.local(year, month, day, hour, minute, second)
|
|
124
|
-
end
|
|
145
|
+
# we'll just assume it's a list of y/m/d/h/m/s
|
|
146
|
+
year = arg || 2000
|
|
147
|
+
month = args.shift || 1
|
|
148
|
+
day = args.shift || 1
|
|
149
|
+
hour = args.shift || 0
|
|
150
|
+
minute = args.shift || 0
|
|
151
|
+
second = args.shift || 0
|
|
152
|
+
time_klass.local(year, month, day, hour, minute, second)
|
|
125
153
|
end
|
|
126
154
|
end
|
|
155
|
+
end
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
def compute_travel_offset
|
|
158
|
+
time - Time.now_without_mock_time
|
|
159
|
+
end
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds
|
|
162
|
+
(t1 - t2).abs < epsilon_in_seconds
|
|
163
|
+
end
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
165
|
+
def time_klass
|
|
166
|
+
Time.respond_to?(:zone) && Time.zone ? Time.zone : Time
|
|
139
167
|
end
|
|
168
|
+
end
|
|
140
169
|
end
|
data/lib/timecop/timecop.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
|
-
require File.join(File.dirname(__FILE__), "time_extensions")
|
|
3
2
|
require File.join(File.dirname(__FILE__), "time_stack_item")
|
|
4
3
|
|
|
5
4
|
# Timecop
|
|
@@ -14,6 +13,8 @@ class Timecop
|
|
|
14
13
|
include Singleton
|
|
15
14
|
|
|
16
15
|
class << self
|
|
16
|
+
private :instance
|
|
17
|
+
|
|
17
18
|
# Allows you to run a block of code and "fake" a time throughout the execution of that block.
|
|
18
19
|
# This is particularly useful for writing test methods where the passage of time is critical to the business
|
|
19
20
|
# logic being tested. For example:
|
|
@@ -37,6 +38,12 @@ class Timecop
|
|
|
37
38
|
# previous values after the block has finished executing. This allows us to nest multiple
|
|
38
39
|
# calls to Timecop.travel and have each block maintain it's concept of "now."
|
|
39
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
|
+
#
|
|
40
47
|
# * Note: Timecop.freeze will actually freeze time. This can cause unanticipated problems if
|
|
41
48
|
# benchmark or other timing calls are executed, which implicitly expect Time to actually move
|
|
42
49
|
# forward.
|
|
@@ -75,11 +82,11 @@ class Timecop
|
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
def baseline
|
|
78
|
-
instance.
|
|
85
|
+
instance.baseline
|
|
79
86
|
end
|
|
80
87
|
|
|
81
88
|
def baseline=(baseline)
|
|
82
|
-
instance.
|
|
89
|
+
instance.baseline = baseline
|
|
83
90
|
end
|
|
84
91
|
|
|
85
92
|
# Reverts back to system's Time.now, Date.today and DateTime.now (if it exists) permamently when
|
|
@@ -87,20 +94,21 @@ class Timecop
|
|
|
87
94
|
# the given block.
|
|
88
95
|
def return(&block)
|
|
89
96
|
if block_given?
|
|
90
|
-
instance.
|
|
97
|
+
instance.return(&block)
|
|
91
98
|
else
|
|
92
|
-
instance.
|
|
99
|
+
instance.unmock!
|
|
93
100
|
nil
|
|
94
101
|
end
|
|
95
102
|
end
|
|
103
|
+
alias :unfreeze :return
|
|
96
104
|
|
|
97
105
|
def return_to_baseline
|
|
98
|
-
instance.
|
|
106
|
+
instance.return_to_baseline
|
|
99
107
|
Time.now
|
|
100
108
|
end
|
|
101
109
|
|
|
102
110
|
def top_stack_item #:nodoc:
|
|
103
|
-
instance.
|
|
111
|
+
instance.stack.last
|
|
104
112
|
end
|
|
105
113
|
|
|
106
114
|
def safe_mode=(safe)
|
|
@@ -112,27 +120,43 @@ class Timecop
|
|
|
112
120
|
end
|
|
113
121
|
|
|
114
122
|
def thread_safe=(t)
|
|
115
|
-
instance.
|
|
123
|
+
instance.thread_safe = t
|
|
116
124
|
end
|
|
117
125
|
|
|
118
126
|
def thread_safe
|
|
119
|
-
instance.
|
|
127
|
+
instance.thread_safe
|
|
120
128
|
end
|
|
121
129
|
|
|
122
|
-
# Returns whether or not Timecop is currently frozen
|
|
130
|
+
# Returns whether or not Timecop is currently frozen
|
|
123
131
|
def frozen?
|
|
124
|
-
!instance.
|
|
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
|
|
125
151
|
end
|
|
126
152
|
|
|
127
153
|
private
|
|
128
154
|
def send_travel(mock_type, *args, &block)
|
|
129
|
-
val = instance.
|
|
155
|
+
val = instance.travel(mock_type, *args, &block)
|
|
130
156
|
block_given? ? val : Time.now
|
|
131
157
|
end
|
|
132
158
|
end
|
|
133
159
|
|
|
134
|
-
private
|
|
135
|
-
|
|
136
160
|
def baseline=(b)
|
|
137
161
|
set_baseline(b)
|
|
138
162
|
stack << TimeStackItem.new(:travel, b)
|
|
@@ -200,7 +224,7 @@ class Timecop
|
|
|
200
224
|
begin
|
|
201
225
|
yield stack_item.time
|
|
202
226
|
ensure
|
|
203
|
-
|
|
227
|
+
stack.replace stack_backup
|
|
204
228
|
@safe = safe_backup
|
|
205
229
|
end
|
|
206
230
|
end
|
|
@@ -235,3 +259,6 @@ class Timecop
|
|
|
235
259
|
end
|
|
236
260
|
end
|
|
237
261
|
end
|
|
262
|
+
|
|
263
|
+
# This must be done after TimeCop is available
|
|
264
|
+
require File.join(File.dirname(__FILE__), "time_extensions")
|
data/lib/timecop/version.rb
CHANGED