1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
class Timecop
# A data class for carrying around "time movement" objects. Makes it easy to keep track of the time
# movements on a simple stack.
class TimeStackItem #:nodoc:
attr_reader :mock_type
def initialize(mock_type, *args)
raise "Unknown mock_type #{mock_type}" unless [:freeze, :travel, :scale].include?(mock_type)
@travel_offset = @scaling_factor = nil
@scaling_factor = args.shift if mock_type == :scale
@mock_type = mock_type
@monotonic = parse_monotonic_time(*args) if RUBY_VERSION >= '2.1.0'
@time = parse_time(*args)
@time_was = Time.now_without_mock_time
@travel_offset = compute_travel_offset
end
def year
time.year
end
def month
time.month
end
def day
time.day
end
def hour
time.hour
end
def min
time.min
end
def sec
time.sec
end
def utc_offset
time.utc_offset
end
def travel_offset
@travel_offset unless mock_type == :freeze
end
def travel_offset_days
(@travel_offset / 60 / 60 / 24).round
end
def scaling_factor
@scaling_factor
end
if RUBY_VERSION >= '2.1.0'
def monotonic
if travel_offset.nil?
@monotonic
elsif scaling_factor.nil?
current_monotonic + travel_offset * (10 ** 9)
else
(@monotonic + (current_monotonic - @monotonic) * scaling_factor).to_i
end
end
def current_monotonic
Process.clock_gettime_without_mock(Process::CLOCK_MONOTONIC, :nanosecond)
end
def current_monotonic_with_mock
Process.clock_gettime_mock_time(Process::CLOCK_MONOTONIC, :nanosecond)
end
end
def time(time_klass = Time) #:nodoc:
if @time.respond_to?(:in_time_zone)
time = time_klass.at(@time.dup.localtime)
else
time = time_klass.at(@time)
end
if travel_offset.nil?
time
elsif scaling_factor.nil?
time_klass.at(Time.now_without_mock_time + travel_offset)
else
time_klass.at(scaled_time)
end
end
def scaled_time
(@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f
end
def date(date_klass = Date)
date_klass.jd(time.__send__(:to_date).jd)
end
def datetime(datetime_klass = DateTime)
if Float.method_defined?(:to_r)
fractions_of_a_second = time.to_f % 1
datetime_klass.new(year, month, day, hour, min, (fractions_of_a_second + sec), utc_offset_to_rational(utc_offset))
else
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(utc_offset))
end
end
private
def rational_to_utc_offset(rational)
((24.0 / rational.denominator) * rational.numerator) * (60 * 60)
end
def utc_offset_to_rational(utc_offset)
Rational(utc_offset, 24 * 60 * 60)
end
def parse_monotonic_time(*args)
arg = args.shift
offset_in_nanoseconds = if args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
arg * 1_000_000_000
else
0
end
current_monotonic_with_mock + offset_in_nanoseconds
end
def parse_time(*args)
arg = args.shift
if arg.is_a?(Time)
arg
elsif Object.const_defined?(:DateTime) && arg.is_a?(DateTime)
time_klass.at(arg.to_time.to_f).getlocal
elsif Object.const_defined?(:Date) && arg.is_a?(Date)
time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0)
elsif args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
time_klass.now + arg
elsif arg.nil?
time_klass.now
else
if arg.is_a?(String) && Time.respond_to?(:parse)
time_klass.parse(arg)
else
# we'll just assume it's a list of y/m/d/h/m/s
year = arg || 2000
month = args.shift || 1
day = args.shift || 1
hour = args.shift || 0
minute = args.shift || 0
second = args.shift || 0
time_klass.local(year, month, day, hour, minute, second)
end
end
end
def compute_travel_offset
time - Time.now_without_mock_time
end
def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds
(t1 - t2).abs < epsilon_in_seconds
end
def time_klass
Time.respond_to?(:zone) && Time.zone ? Time.zone : Time
end
end
end
|