timecop 0.9.4 → 0.9.5
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 +2 -1
- data/lib/timecop/time_extensions.rb +11 -2
- data/lib/timecop/time_stack_item.rb +110 -110
- data/lib/timecop/timecop.rb +12 -12
- data/lib/timecop/version.rb +1 -1
- data/test/timecop_test.rb +33 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 681f52d846b808b06ff833f3fbfc21e3c7c4c1bf426baa03f101c0a2c3b79ce8
|
4
|
+
data.tar.gz: b95b6d4a3c357e7f11f046d82dad3badd81d4903c96ef2b8e77762f241eeb66e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e62e48939fb924a78d59199222688ae7692a2f6649c16daac296375ac1e537fa1c85e6a34d1a5cbd64b209e643ffa08d180ce3d933905f91284ce4c819873c1e
|
7
|
+
data.tar.gz: f6f5ef32ee1bfadfd43a5a8899bb521873d61eafd2f242942df6e80a3ed11f80a170d074ada23a61eb3b92009eeaf3a1046b126c802dc9a1c014c77d32acb3ab
|
data/README.markdown
CHANGED
@@ -1,6 +1,7 @@
|
|
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
|
|
@@ -50,11 +50,16 @@ class Date #:nodoc:
|
|
50
50
|
"supports Date::ITALY for the start argument."
|
51
51
|
end
|
52
52
|
|
53
|
-
|
53
|
+
#If date is not valid the following line raises
|
54
|
+
Date.strptime_without_mock_date(str, fmt)
|
55
|
+
|
56
|
+
d = Date._strptime(str, fmt)
|
54
57
|
now = Time.now.to_date
|
55
58
|
year = d[:year] || now.year
|
56
59
|
mon = d[:mon] || now.mon
|
57
|
-
if d[:
|
60
|
+
if d.keys == [:year]
|
61
|
+
Date.new(year)
|
62
|
+
elsif d[:mday]
|
58
63
|
Date.new(year, mon, d[:mday])
|
59
64
|
elsif d[:wday]
|
60
65
|
Date.new(year, mon, now.mday) + (d[:wday] - now.wday)
|
@@ -85,6 +90,8 @@ class Date #:nodoc:
|
|
85
90
|
parsed_date
|
86
91
|
when date_hash[:mon] && date_hash[:mday]
|
87
92
|
Date.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
93
|
+
when date_hash[:mday]
|
94
|
+
Date.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday])
|
88
95
|
when date_hash[:wday]
|
89
96
|
closest_wday(date_hash[:wday])
|
90
97
|
else
|
@@ -133,6 +140,8 @@ class DateTime #:nodoc:
|
|
133
140
|
parsed_date
|
134
141
|
when date_hash[:mon] && date_hash[:mday]
|
135
142
|
DateTime.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
143
|
+
when date_hash[:mday]
|
144
|
+
DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday])
|
136
145
|
when date_hash[:wday]
|
137
146
|
Date.closest_wday(date_hash[:wday]).to_datetime
|
138
147
|
else
|
@@ -1,140 +1,140 @@
|
|
1
1
|
class Timecop
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def year
|
18
|
-
time.year
|
19
|
-
end
|
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
|
+
@time = parse_time(*args)
|
13
|
+
@time_was = Time.now_without_mock_time
|
14
|
+
@travel_offset = compute_travel_offset
|
15
|
+
end
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def year
|
18
|
+
time.year
|
19
|
+
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
def month
|
22
|
+
time.month
|
23
|
+
end
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
def day
|
26
|
+
time.day
|
27
|
+
end
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
def hour
|
30
|
+
time.hour
|
31
|
+
end
|
36
32
|
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
def min
|
34
|
+
time.min
|
35
|
+
end
|
40
36
|
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
def sec
|
38
|
+
time.sec
|
39
|
+
end
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
def utc_offset
|
42
|
+
time.utc_offset
|
43
|
+
end
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
def travel_offset
|
46
|
+
@travel_offset unless mock_type == :freeze
|
47
|
+
end
|
52
48
|
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
def travel_offset_days
|
50
|
+
(@travel_offset / 60 / 60 / 24).round
|
51
|
+
end
|
56
52
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
else
|
61
|
-
time = time_klass.at(@time)
|
62
|
-
end
|
53
|
+
def scaling_factor
|
54
|
+
@scaling_factor
|
55
|
+
end
|
63
56
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
time_klass.at(scaled_time)
|
70
|
-
end
|
57
|
+
def time(time_klass = Time) #:nodoc:
|
58
|
+
if @time.respond_to?(:in_time_zone)
|
59
|
+
time = time_klass.at(@time.dup.localtime)
|
60
|
+
else
|
61
|
+
time = time_klass.at(@time)
|
71
62
|
end
|
72
63
|
|
73
|
-
|
74
|
-
|
64
|
+
if travel_offset.nil?
|
65
|
+
time
|
66
|
+
elsif scaling_factor.nil?
|
67
|
+
time_klass.at(Time.now_without_mock_time + travel_offset)
|
68
|
+
else
|
69
|
+
time_klass.at(scaled_time)
|
75
70
|
end
|
71
|
+
end
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
def scaled_time
|
74
|
+
(@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f
|
75
|
+
end
|
80
76
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
77
|
+
def date(date_klass = Date)
|
78
|
+
date_klass.jd(time.__send__(:to_date).jd)
|
79
|
+
end
|
80
|
+
|
81
|
+
def datetime(datetime_klass = DateTime)
|
82
|
+
if Float.method_defined?(:to_r)
|
83
|
+
fractions_of_a_second = time.to_f % 1
|
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))
|
88
87
|
end
|
88
|
+
end
|
89
89
|
|
90
|
-
|
90
|
+
private
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
def rational_to_utc_offset(rational)
|
93
|
+
((24.0 / rational.denominator) * rational.numerator) * (60 * 60)
|
94
|
+
end
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
def utc_offset_to_rational(utc_offset)
|
97
|
+
Rational(utc_offset, 24 * 60 * 60)
|
98
|
+
end
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
100
|
+
def parse_time(*args)
|
101
|
+
arg = args.shift
|
102
|
+
if arg.is_a?(Time)
|
103
|
+
arg
|
104
|
+
elsif Object.const_defined?(:DateTime) && arg.is_a?(DateTime)
|
105
|
+
time_klass.at(arg.to_time.to_f).getlocal
|
106
|
+
elsif Object.const_defined?(:Date) && arg.is_a?(Date)
|
107
|
+
time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0)
|
108
|
+
elsif args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
|
109
|
+
time_klass.now + arg
|
110
|
+
elsif arg.nil?
|
111
|
+
time_klass.now
|
112
|
+
else
|
113
|
+
if arg.is_a?(String) && Time.respond_to?(:parse)
|
114
|
+
time_klass.parse(arg)
|
112
115
|
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
|
116
|
+
# we'll just assume it's a list of y/m/d/h/m/s
|
117
|
+
year = arg || 2000
|
118
|
+
month = args.shift || 1
|
119
|
+
day = args.shift || 1
|
120
|
+
hour = args.shift || 0
|
121
|
+
minute = args.shift || 0
|
122
|
+
second = args.shift || 0
|
123
|
+
time_klass.local(year, month, day, hour, minute, second)
|
125
124
|
end
|
126
125
|
end
|
126
|
+
end
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
|
128
|
+
def compute_travel_offset
|
129
|
+
time - Time.now_without_mock_time
|
130
|
+
end
|
131
131
|
|
132
|
-
|
133
|
-
|
134
|
-
|
132
|
+
def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds
|
133
|
+
(t1 - t2).abs < epsilon_in_seconds
|
134
|
+
end
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
end
|
136
|
+
def time_klass
|
137
|
+
Time.respond_to?(:zone) && Time.zone ? Time.zone : Time
|
139
138
|
end
|
139
|
+
end
|
140
140
|
end
|
data/lib/timecop/timecop.rb
CHANGED
@@ -13,6 +13,8 @@ class Timecop
|
|
13
13
|
include Singleton
|
14
14
|
|
15
15
|
class << self
|
16
|
+
private :instance
|
17
|
+
|
16
18
|
# Allows you to run a block of code and "fake" a time throughout the execution of that block.
|
17
19
|
# This is particularly useful for writing test methods where the passage of time is critical to the business
|
18
20
|
# logic being tested. For example:
|
@@ -74,11 +76,11 @@ class Timecop
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def baseline
|
77
|
-
instance.
|
79
|
+
instance.baseline
|
78
80
|
end
|
79
81
|
|
80
82
|
def baseline=(baseline)
|
81
|
-
instance.
|
83
|
+
instance.baseline = baseline
|
82
84
|
end
|
83
85
|
|
84
86
|
# Reverts back to system's Time.now, Date.today and DateTime.now (if it exists) permamently when
|
@@ -86,21 +88,21 @@ class Timecop
|
|
86
88
|
# the given block.
|
87
89
|
def return(&block)
|
88
90
|
if block_given?
|
89
|
-
instance.
|
91
|
+
instance.return(&block)
|
90
92
|
else
|
91
|
-
instance.
|
93
|
+
instance.unmock!
|
92
94
|
nil
|
93
95
|
end
|
94
96
|
end
|
95
97
|
alias :unfreeze :return
|
96
98
|
|
97
99
|
def return_to_baseline
|
98
|
-
instance.
|
100
|
+
instance.return_to_baseline
|
99
101
|
Time.now
|
100
102
|
end
|
101
103
|
|
102
104
|
def top_stack_item #:nodoc:
|
103
|
-
instance.
|
105
|
+
instance.stack.last
|
104
106
|
end
|
105
107
|
|
106
108
|
def safe_mode=(safe)
|
@@ -112,27 +114,25 @@ class Timecop
|
|
112
114
|
end
|
113
115
|
|
114
116
|
def thread_safe=(t)
|
115
|
-
instance.
|
117
|
+
instance.thread_safe = t
|
116
118
|
end
|
117
119
|
|
118
120
|
def thread_safe
|
119
|
-
instance.
|
121
|
+
instance.thread_safe
|
120
122
|
end
|
121
123
|
|
122
124
|
# Returns whether or not Timecop is currently frozen/travelled
|
123
125
|
def frozen?
|
124
|
-
!instance.
|
126
|
+
!instance.stack.empty?
|
125
127
|
end
|
126
128
|
|
127
129
|
private
|
128
130
|
def send_travel(mock_type, *args, &block)
|
129
|
-
val = instance.
|
131
|
+
val = instance.travel(mock_type, *args, &block)
|
130
132
|
block_given? ? val : Time.now
|
131
133
|
end
|
132
134
|
end
|
133
135
|
|
134
|
-
private
|
135
|
-
|
136
136
|
def baseline=(b)
|
137
137
|
set_baseline(b)
|
138
138
|
stack << TimeStackItem.new(:travel, b)
|
data/lib/timecop/version.rb
CHANGED
data/test/timecop_test.rb
CHANGED
@@ -289,6 +289,17 @@ class TestTimecop < Minitest::Test
|
|
289
289
|
t = Time.local(2008, 10, 10, 10, 10, 10)
|
290
290
|
assert_times_effectively_equal t, Timecop.scale(4, t)
|
291
291
|
end
|
292
|
+
|
293
|
+
def test_scaling_returns_now_if_nil_supplied
|
294
|
+
assert_times_effectively_equal Time.now, Timecop.scale(nil)
|
295
|
+
end
|
296
|
+
|
297
|
+
def test_scaling_raises_when_empty_string_supplied
|
298
|
+
err = assert_raises(TypeError) do
|
299
|
+
Timecop.scale("")
|
300
|
+
end
|
301
|
+
assert_match /String can't be coerced into Float/, err.message
|
302
|
+
end
|
292
303
|
|
293
304
|
def test_freeze_with_utc_time
|
294
305
|
each_timezone do
|
@@ -394,6 +405,10 @@ class TestTimecop < Minitest::Test
|
|
394
405
|
end
|
395
406
|
assert_times_effectively_equal(time_after_travel, Time.now)
|
396
407
|
end
|
408
|
+
|
409
|
+
def test_travel_returns_now_if_nil_supplied
|
410
|
+
assert_times_effectively_equal Time.now, Timecop.travel(nil)
|
411
|
+
end
|
397
412
|
|
398
413
|
def test_travel_time_with_block_returns_the_value_of_the_block
|
399
414
|
t_future = Time.local(2030, 10, 10, 10, 10, 10)
|
@@ -402,6 +417,13 @@ class TestTimecop < Minitest::Test
|
|
402
417
|
|
403
418
|
assert_equal expected, actual
|
404
419
|
end
|
420
|
+
|
421
|
+
def test_travel_raises_when_empty_string_supplied
|
422
|
+
err = assert_raises(ArgumentError) do
|
423
|
+
Timecop.travel("")
|
424
|
+
end
|
425
|
+
assert_match /no time information in \"\"/, err.message
|
426
|
+
end
|
405
427
|
|
406
428
|
def test_freeze_time_returns_now_if_no_block_given
|
407
429
|
t_future = Time.local(2030, 10, 10, 10, 10, 10)
|
@@ -428,6 +450,17 @@ class TestTimecop < Minitest::Test
|
|
428
450
|
end
|
429
451
|
end
|
430
452
|
end
|
453
|
+
|
454
|
+
def test_freeze_returns_now_if_nil_supplied
|
455
|
+
assert_times_effectively_equal Time.now, Timecop.freeze(nil)
|
456
|
+
end
|
457
|
+
|
458
|
+
def test_freeze_raises_when_empty_string_supplied
|
459
|
+
err = assert_raises(ArgumentError) do
|
460
|
+
Timecop.freeze("")
|
461
|
+
end
|
462
|
+
assert_match /no time information in \"\"/, err.message
|
463
|
+
end
|
431
464
|
|
432
465
|
def test_freeze_with_new_date
|
433
466
|
date = Date.new(2012, 6, 9)
|
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.5
|
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: 2022-03-07 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
|
@@ -54,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
requirements: []
|
57
|
-
rubygems_version: 3.
|
57
|
+
rubygems_version: 3.2.22
|
58
58
|
signing_key:
|
59
59
|
specification_version: 3
|
60
60
|
summary: A gem providing "time travel" and "time freezing" capabilities, making it
|