timecop 0.5.2 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -1
- data/README.markdown +48 -13
- data/Rakefile +10 -4
- data/lib/timecop/time_extensions.rb +128 -54
- data/lib/timecop/time_stack_item.rb +78 -83
- data/lib/timecop/timecop.rb +142 -49
- data/lib/timecop/version.rb +2 -2
- data/lib/timecop.rb +2 -2
- data/test/test_helper.rb +26 -17
- data/test/time_stack_item_test.rb +153 -73
- data/test/timecop_test.rb +247 -41
- data/test/timecop_without_date_but_with_time_test.rb +4 -8
- data/test/timecop_without_date_test.rb +22 -23
- metadata +16 -23
- data/History.rdoc +0 -137
- data/test/run_tests.sh +0 -6
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 681f52d846b808b06ff833f3fbfc21e3c7c4c1bf426baa03f101c0a2c3b79ce8
|
4
|
+
data.tar.gz: b95b6d4a3c357e7f11f046d82dad3badd81d4903c96ef2b8e77762f241eeb66e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e62e48939fb924a78d59199222688ae7692a2f6649c16daac296375ac1e537fa1c85e6a34d1a5cbd64b209e643ffa08d180ce3d933905f91284ce4c819873c1e
|
7
|
+
data.tar.gz: f6f5ef32ee1bfadfd43a5a8899bb521873d61eafd2f242942df6e80a3ed11f80a170d074ada23a61eb3b92009eeaf3a1046b126c802dc9a1c014c77d32acb3ab
|
data/LICENSE
CHANGED
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# timecop
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/timecop.svg)](https://rubygems.org/gems/timecop)
|
4
|
+
[![Build Status](https://github.com/travisjeffery/timecop/workflows/CI/badge.svg)](https://github.com/travisjeffery/timecop/actions?query=workflow%3ACI)
|
5
5
|
|
6
6
|
## DESCRIPTION
|
7
7
|
|
@@ -9,7 +9,7 @@ A gem providing "time travel" and "time freezing" capabilities, making it dead s
|
|
9
9
|
|
10
10
|
## INSTALL
|
11
11
|
|
12
|
-
|
12
|
+
`gem install timecop`
|
13
13
|
|
14
14
|
## FEATURES
|
15
15
|
|
@@ -40,11 +40,28 @@ Timecop.freeze(Date.today + 30) do
|
|
40
40
|
end
|
41
41
|
```
|
42
42
|
|
43
|
+
You can mock the time for a set of tests easily via setup/teardown methods
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
describe "some set of tests to mock" do
|
47
|
+
before do
|
48
|
+
Timecop.freeze(Time.local(1990))
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
Timecop.return
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should do blah blah blah" do
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
43
60
|
Set the time for the test environment of a rails app -- this is particularly
|
44
61
|
helpful if your whole application is time-sensitive. It allows you to build
|
45
62
|
your test data at a single point in time, and to move in/out of that time as
|
46
63
|
appropriate (within your tests)
|
47
|
-
|
64
|
+
|
48
65
|
in config/environments/test.rb
|
49
66
|
|
50
67
|
```ruby
|
@@ -85,25 +102,43 @@ being able to simulate activity via subsequent calls to your application.
|
|
85
102
|
|
86
103
|
```ruby
|
87
104
|
# seconds will now seem like hours
|
88
|
-
Timecop.
|
105
|
+
Timecop.scale(3600)
|
89
106
|
Time.now
|
90
107
|
# => 2012-09-20 21:23:25 -0500
|
91
|
-
# seconds later, hours have
|
108
|
+
# seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning
|
92
109
|
Time.now
|
93
110
|
# => 2012-09-21 06:22:59 -0500
|
94
111
|
```
|
95
112
|
|
96
|
-
|
113
|
+
See [#42](https://github.com/travisjeffery/timecop/pull/42) for more information, thanks to Ken Mayer, David Holcomb, and Pivotal Labs.
|
114
|
+
|
115
|
+
### Timecop.safe_mode
|
116
|
+
|
117
|
+
Safe mode forces you to use Timecop with the block syntax since it always puts time back the way it was. If you are running in safe mode and use Timecop without the block syntax `Timecop::SafeModeException` will be raised to tell the user they are not being safe.
|
118
|
+
|
119
|
+
``` ruby
|
120
|
+
# turn on safe mode
|
121
|
+
Timecop.safe_mode = true
|
122
|
+
|
123
|
+
# check if you are in safe mode
|
124
|
+
Timecop.safe_mode?
|
125
|
+
# => true
|
126
|
+
|
127
|
+
# using method without block
|
128
|
+
Timecop.freeze
|
129
|
+
# => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
|
130
|
+
```
|
131
|
+
|
132
|
+
### Rails v Ruby Date/Time libraries
|
133
|
+
|
134
|
+
Sometimes [Rails Date/Time methods don't play nicely with Ruby Date/Time methods.](https://rails.lighthouseapp.com/projects/8994/tickets/6410-dateyesterday-datetoday)
|
97
135
|
|
98
|
-
|
99
|
-
* {0.3.0 release}[http://blog.smartlogicsolutions.com/2009/09/20/timecop-0-3-0-released/]
|
100
|
-
* {0.2.0 release}[http://blog.smartlogicsolutions.com/2008/12/24/timecop-2-released-freeze-and-rebase-time-ruby/]
|
101
|
-
* {0.1.0 release}[http://blog.smartlogicsolutions.com/2008/11/19/timecop-freeze-time-in-ruby-for-better-testing/]
|
136
|
+
Be careful mixing Ruby `Date.today` with Rails `Date.tomorrow` / `Date.yesterday` as things might break.
|
102
137
|
|
103
138
|
## Contribute
|
104
139
|
|
105
|
-
timecop is maintained by [travisjeffery](http://github.com/travisjeffery),
|
106
|
-
|
140
|
+
timecop is maintained by [travisjeffery](http://github.com/travisjeffery), and
|
141
|
+
was created by [jtrupiano](https://github.com/jtrupiano).
|
107
142
|
|
108
143
|
Here's the most direct way to get your work merged into the project.
|
109
144
|
|
data/Rakefile
CHANGED
@@ -3,8 +3,6 @@ require 'bundler/gem_tasks'
|
|
3
3
|
require 'rake/testtask'
|
4
4
|
require 'rdoc/task'
|
5
5
|
|
6
|
-
$LOAD_PATH.unshift("lib")
|
7
|
-
|
8
6
|
Rake::RDocTask.new do |rdoc|
|
9
7
|
if File.exist?('VERSION')
|
10
8
|
version = File.read('VERSION')
|
@@ -20,8 +18,16 @@ Rake::RDocTask.new do |rdoc|
|
|
20
18
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
19
|
end
|
22
20
|
|
23
|
-
task :test do
|
24
|
-
|
21
|
+
task :test do
|
22
|
+
failed = Dir["test/*_test.rb"].map do |test|
|
23
|
+
command = "ruby #{test}"
|
24
|
+
puts
|
25
|
+
puts command
|
26
|
+
command unless system(command)
|
27
|
+
end.compact
|
28
|
+
if failed.any?
|
29
|
+
abort "#{failed.count} Tests failed\n#{failed.join("\n")}"
|
30
|
+
end
|
25
31
|
end
|
26
32
|
|
27
33
|
desc 'Default: run tests'
|
@@ -1,85 +1,159 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'date'
|
1
3
|
|
2
4
|
class Time #:nodoc:
|
3
5
|
class << self
|
4
|
-
# Time we are behaving as
|
5
6
|
def mock_time
|
6
7
|
mocked_time_stack_item = Timecop.top_stack_item
|
7
8
|
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.time(self)
|
8
9
|
end
|
9
|
-
|
10
|
-
# Alias the original now
|
10
|
+
|
11
11
|
alias_method :now_without_mock_time, :now
|
12
12
|
|
13
|
-
# Define now_with_mock_time
|
14
13
|
def now_with_mock_time
|
15
14
|
mock_time || now_without_mock_time
|
16
15
|
end
|
17
|
-
|
18
|
-
# Alias now to now_with_mock_time
|
16
|
+
|
19
17
|
alias_method :now, :now_with_mock_time
|
20
18
|
|
21
19
|
alias_method :new_without_mock_time, :new
|
22
20
|
|
23
|
-
def new_with_mock_time
|
24
|
-
|
25
|
-
raise ArgumentError.new if args.size <= 0
|
26
|
-
new_without_mock_time *args
|
27
|
-
rescue ArgumentError
|
28
|
-
now
|
29
|
-
end
|
21
|
+
def new_with_mock_time(*args)
|
22
|
+
args.size <= 0 ? now : new_without_mock_time(*args)
|
30
23
|
end
|
31
24
|
|
25
|
+
ruby2_keywords :new_with_mock_time if Module.private_method_defined?(:ruby2_keywords)
|
26
|
+
|
32
27
|
alias_method :new, :new_with_mock_time
|
33
28
|
end
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
class
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
class Date #:nodoc:
|
32
|
+
class << self
|
33
|
+
def mock_date
|
34
|
+
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.date(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :today_without_mock_date, :today
|
38
|
+
|
39
|
+
def today_with_mock_date
|
40
|
+
mock_date || today_without_mock_date
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :today, :today_with_mock_date
|
44
|
+
|
45
|
+
alias_method :strptime_without_mock_date, :strptime
|
46
|
+
|
47
|
+
def strptime_with_mock_date(str = '-4712-01-01', fmt = '%F', start = Date::ITALY)
|
48
|
+
unless start == Date::ITALY
|
49
|
+
raise ArgumentError, "Timecop's #{self}::#{__method__} only " +
|
50
|
+
"supports Date::ITALY for the start argument."
|
43
51
|
end
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
|
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)
|
57
|
+
now = Time.now.to_date
|
58
|
+
year = d[:year] || now.year
|
59
|
+
mon = d[:mon] || now.mon
|
60
|
+
if d.keys == [:year]
|
61
|
+
Date.new(year)
|
62
|
+
elsif d[:mday]
|
63
|
+
Date.new(year, mon, d[:mday])
|
64
|
+
elsif d[:wday]
|
65
|
+
Date.new(year, mon, now.mday) + (d[:wday] - now.wday)
|
66
|
+
elsif d[:yday]
|
67
|
+
Date.new(year).next_day(d[:yday] - 1)
|
68
|
+
elsif d[:cwyear] && d[:cweek]
|
69
|
+
if d[:cwday]
|
70
|
+
Date.commercial(d[:cwyear], d[:cweek], d[:cwday])
|
71
|
+
else
|
72
|
+
Date.commercial(d[:cwyear], d[:cweek])
|
73
|
+
end
|
74
|
+
elsif d[:seconds]
|
75
|
+
Time.at(d[:seconds]).to_date
|
76
|
+
else
|
77
|
+
Date.new(year, mon)
|
51
78
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
79
|
+
end
|
80
|
+
|
81
|
+
alias_method :strptime, :strptime_with_mock_date
|
82
|
+
|
83
|
+
def parse_with_mock_date(*args)
|
84
|
+
parsed_date = parse_without_mock_date(*args)
|
85
|
+
return parsed_date unless mocked_time_stack_item
|
86
|
+
date_hash = Date._parse(*args)
|
87
|
+
|
88
|
+
case
|
89
|
+
when date_hash[:year] && date_hash[:mon]
|
90
|
+
parsed_date
|
91
|
+
when date_hash[:mon] && date_hash[:mday]
|
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])
|
95
|
+
when date_hash[:wday]
|
96
|
+
closest_wday(date_hash[:wday])
|
97
|
+
else
|
98
|
+
parsed_date + mocked_time_stack_item.travel_offset_days
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
alias_method :parse_without_mock_date, :parse
|
103
|
+
alias_method :parse, :parse_with_mock_date
|
104
|
+
|
105
|
+
def mocked_time_stack_item
|
106
|
+
Timecop.top_stack_item
|
107
|
+
end
|
108
|
+
|
109
|
+
def closest_wday(wday)
|
110
|
+
today = Date.today
|
111
|
+
result = today - today.wday
|
112
|
+
result += 1 until wday == result.wday
|
113
|
+
result
|
55
114
|
end
|
56
115
|
end
|
57
116
|
end
|
58
117
|
|
59
|
-
|
60
|
-
class
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
118
|
+
class DateTime #:nodoc:
|
119
|
+
class << self
|
120
|
+
def mock_time
|
121
|
+
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.datetime(self)
|
122
|
+
end
|
123
|
+
|
124
|
+
def now_with_mock_time
|
125
|
+
mock_time || now_without_mock_time
|
126
|
+
end
|
127
|
+
|
128
|
+
alias_method :now_without_mock_time, :now
|
129
|
+
|
130
|
+
alias_method :now, :now_with_mock_time
|
131
|
+
|
132
|
+
def parse_with_mock_date(*args)
|
133
|
+
date_hash = Date._parse(*args)
|
134
|
+
parsed_date = parse_without_mock_date(*args)
|
135
|
+
return parsed_date unless mocked_time_stack_item
|
136
|
+
date_hash = DateTime._parse(*args)
|
137
|
+
|
138
|
+
case
|
139
|
+
when date_hash[:year] && date_hash[:mon]
|
140
|
+
parsed_date
|
141
|
+
when date_hash[:mon] && date_hash[:mday]
|
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])
|
145
|
+
when date_hash[:wday]
|
146
|
+
Date.closest_wday(date_hash[:wday]).to_datetime
|
147
|
+
else
|
148
|
+
parsed_date + mocked_time_stack_item.travel_offset_days
|
79
149
|
end
|
150
|
+
end
|
151
|
+
|
152
|
+
alias_method :parse_without_mock_date, :parse
|
153
|
+
alias_method :parse, :parse_with_mock_date
|
80
154
|
|
81
|
-
|
82
|
-
|
155
|
+
def mocked_time_stack_item
|
156
|
+
Timecop.top_stack_item
|
83
157
|
end
|
84
158
|
end
|
85
159
|
end
|
@@ -1,145 +1,140 @@
|
|
1
|
-
|
2
1
|
class Timecop
|
3
2
|
# A data class for carrying around "time movement" objects. Makes it easy to keep track of the time
|
4
3
|
# movements on a simple stack.
|
5
4
|
class TimeStackItem #:nodoc:
|
6
|
-
|
7
5
|
attr_reader :mock_type
|
6
|
+
|
8
7
|
def initialize(mock_type, *args)
|
9
8
|
raise "Unknown mock_type #{mock_type}" unless [:freeze, :travel, :scale].include?(mock_type)
|
9
|
+
@travel_offset = @scaling_factor = nil
|
10
10
|
@scaling_factor = args.shift if mock_type == :scale
|
11
11
|
@mock_type = mock_type
|
12
12
|
@time = parse_time(*args)
|
13
13
|
@time_was = Time.now_without_mock_time
|
14
14
|
@travel_offset = compute_travel_offset
|
15
|
-
@dst_adjustment = compute_dst_adjustment(@time)
|
16
15
|
end
|
17
|
-
|
16
|
+
|
18
17
|
def year
|
19
18
|
time.year
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
def month
|
23
22
|
time.month
|
24
23
|
end
|
25
|
-
|
24
|
+
|
26
25
|
def day
|
27
26
|
time.day
|
28
27
|
end
|
29
|
-
|
28
|
+
|
30
29
|
def hour
|
31
30
|
time.hour
|
32
31
|
end
|
33
|
-
|
32
|
+
|
34
33
|
def min
|
35
34
|
time.min
|
36
35
|
end
|
37
|
-
|
36
|
+
|
38
37
|
def sec
|
39
38
|
time.sec
|
40
39
|
end
|
41
|
-
|
40
|
+
|
42
41
|
def utc_offset
|
43
42
|
time.utc_offset
|
44
43
|
end
|
45
|
-
|
44
|
+
|
46
45
|
def travel_offset
|
47
|
-
@travel_offset
|
46
|
+
@travel_offset unless mock_type == :freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def travel_offset_days
|
50
|
+
(@travel_offset / 60 / 60 / 24).round
|
48
51
|
end
|
49
52
|
|
50
53
|
def scaling_factor
|
51
54
|
@scaling_factor
|
52
55
|
end
|
53
|
-
|
54
|
-
def time(time_klass=Time) #:nodoc:
|
56
|
+
|
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)
|
62
|
+
end
|
63
|
+
|
55
64
|
if travel_offset.nil?
|
56
|
-
|
65
|
+
time
|
57
66
|
elsif scaling_factor.nil?
|
58
|
-
time_klass.at(
|
67
|
+
time_klass.at(Time.now_without_mock_time + travel_offset)
|
59
68
|
else
|
60
|
-
time_klass.at(
|
69
|
+
time_klass.at(scaled_time)
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
73
|
def scaled_time
|
65
74
|
(@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f
|
66
75
|
end
|
67
|
-
|
68
|
-
def date(date_klass=Date)
|
76
|
+
|
77
|
+
def date(date_klass = Date)
|
69
78
|
date_klass.jd(time.__send__(:to_date).jd)
|
70
79
|
end
|
71
|
-
|
72
|
-
def datetime(datetime_klass=DateTime)
|
73
|
-
# DateTime doesn't know about DST, so let's remove its presence
|
74
|
-
our_offset = utc_offset + dst_adjustment
|
80
|
+
|
81
|
+
def datetime(datetime_klass = DateTime)
|
75
82
|
if Float.method_defined?(:to_r)
|
76
83
|
fractions_of_a_second = time.to_f % 1
|
77
|
-
datetime_klass.new(year, month, day, hour, min,
|
78
|
-
utc_offset_to_rational(our_offset))
|
84
|
+
datetime_klass.new(year, month, day, hour, min, (fractions_of_a_second + sec), utc_offset_to_rational(utc_offset))
|
79
85
|
else
|
80
|
-
|
81
|
-
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(our_offset))
|
86
|
+
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(utc_offset))
|
82
87
|
end
|
83
88
|
end
|
84
|
-
|
85
|
-
def dst_adjustment
|
86
|
-
@dst_adjustment
|
87
|
-
end
|
88
|
-
|
89
|
+
|
89
90
|
private
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
Time.now + arg
|
115
|
-
elsif arg.nil?
|
116
|
-
Time.now
|
91
|
+
|
92
|
+
def rational_to_utc_offset(rational)
|
93
|
+
((24.0 / rational.denominator) * rational.numerator) * (60 * 60)
|
94
|
+
end
|
95
|
+
|
96
|
+
def utc_offset_to_rational(utc_offset)
|
97
|
+
Rational(utc_offset, 24 * 60 * 60)
|
98
|
+
end
|
99
|
+
|
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)
|
117
115
|
else
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
minute = args.shift || 0
|
127
|
-
second = args.shift || 0
|
128
|
-
time_klass.local(year, month, day, hour, minute, second)
|
129
|
-
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)
|
130
124
|
end
|
131
125
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
126
|
+
end
|
127
|
+
|
128
|
+
def compute_travel_offset
|
129
|
+
time - Time.now_without_mock_time
|
130
|
+
end
|
131
|
+
|
132
|
+
def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds
|
133
|
+
(t1 - t2).abs < epsilon_in_seconds
|
134
|
+
end
|
135
|
+
|
136
|
+
def time_klass
|
137
|
+
Time.respond_to?(:zone) && Time.zone ? Time.zone : Time
|
138
|
+
end
|
144
139
|
end
|
145
140
|
end
|