File: time_stack_item.rb

package info (click to toggle)
ruby-timecop 0.9.10-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 280 kB
  • sloc: ruby: 1,950; makefile: 17
file content (171 lines) | stat: -rw-r--r-- 4,489 bytes parent folder | download
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