Class: Monotime::Instant

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/monotime/instant.rb

Overview

A measurement from the operating system’s monotonic clock, with up to nanosecond precision.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(nanos = self.class.monotonic_function.call) ⇒ Instant

Create a new Instant from an optional nanosecond measurement.

Users should generally not pass anything to this function.

Parameters:

  • nanos (Integer) (defaults to: self.class.monotonic_function.call)

See Also:

  • #now


127
128
129
130
# File 'lib/monotime/instant.rb', line 127

def initialize(nanos = self.class.monotonic_function.call)
  @ns = Integer(nanos)
  freeze
end

Class Attribute Details

.clock_idNumeric .clock_id=(id) ⇒ Object

Overloads:

  • .clock_idNumeric

    The Process.clock_gettime clock id used to create Instant instances by the default monotonic function.

    Returns:

    • (Numeric)
  • .clock_id=(id) ⇒ Object

    Override the default Process.clock_gettime clock id. Some potential choices include but are not limited to:

    • Process::CLOCK_MONOTONIC_RAW

    • Process::CLOCK_UPTIME_RAW

    • Process::CLOCK_UPTIME_PRECISE

    • Process::CLOCK_UPTIME_FAST

    • Process::CLOCK_UPTIME

    • Process::CLOCK_MONOTONIC_PRECISE

    • Process::CLOCK_MONOTONIC_FAST

    • Process::CLOCK_MONOTONIC

    • :MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC

    • :TIMES_BASED_CLOCK_MONOTONIC

    These are platform-dependant and may vary in resolution, accuracy, performance, and behaviour in light of system suspend/resume and NTP frequency skew. They should be selected carefully based on your specific needs and environment.

    It is possible to set non-monotonic clock sources here. You probably shouldn’t.

    Defaults to auto-selection from whatever is available from:

    • CLOCK_UPTIME_RAW (if running under macOS)

    • CLOCK_MONOTONIC

    • CLOCK_REALTIME (non-monotonic fallback, issues a run-time warning)

    Parameters:

    • id (Numeric, Symbol)


52
53
54
# File 'lib/monotime/instant.rb', line 52

def clock_id
  @clock_id
end

.monotonic_function=(function) ⇒ Object

The function used to create Instant instances.

This function must return a Numeric, monotonic count of nanoseconds since a fixed point in the past.

Defaults to -> { Process.clock_gettime(clock_id, :nanosecond) }.

Parameters:

  • function (#call)


63
64
65
# File 'lib/monotime/instant.rb', line 63

def monotonic_function
  @monotonic_function
end

Class Method Details

.clock_getres(clock = clock_id) ⇒ Object

Return the claimed resolution of the given clock id or the configured clock_id, as a Duration, or nil if invalid.

Note per Ruby issue #16740, the practical usability of this method is dubious and non-portable.

Parameters:

  • clock (Numeric, Symbol) (defaults to: clock_id)

    Optional clock id instead of default.



72
73
74
75
76
# File 'lib/monotime/instant.rb', line 72

def clock_getres(clock = clock_id)
  Duration.from_nanos(Integer(Process.clock_getres(clock, :nanosecond)))
rescue SystemCallError
  # suppress errors
end

.clock_nameSymbol?

The symbolic name of the currently-selected clock_id, if available.

Returns:

  • (Symbol, nil)


81
82
83
84
85
86
87
# File 'lib/monotime/instant.rb', line 81

def clock_name
  return clock_id if clock_id.is_a? Symbol

  Process.constants.find do |c|
    c.to_s.start_with?('CLOCK_') && Process.const_get(c) == clock_id
  end
end

.nowInstant

An alias to new, and generally preferred over it.

Returns:



135
136
137
# File 'lib/monotime/instant.rb', line 135

def self.now
  new
end

Instance Method Details

#+(other) ⇒ Instant

Add a Duration or #to_nanos-coercible object to this Instant, returning a new Instant.

Examples:

(Instant.now + Duration.from_secs(1)).to_s # => "-999.983976ms"

Parameters:

Returns:

Raises:

  • (TypeError)


242
243
244
245
246
# File 'lib/monotime/instant.rb', line 242

def +(other)
  raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)

  Instant.new(@ns + other.to_nanos)
end

#-(other) ⇒ Duration, Instant

Subtract another Instant to generate a Duration between the two, or a Duration or #to_nanos-coercible object, to generate an Instant offset by it.

Examples:

(Instant.now - Duration.from_secs(1)).to_s # => "1.000016597s"
(Instant.now - Instant.now).to_s           # => "-3.87μs"

Parameters:

Returns:



258
259
260
261
262
263
264
265
266
267
268
# File 'lib/monotime/instant.rb', line 258

def -(other)
  if other.is_a?(Instant)
    # @type var other: Instant
    Duration.new(@ns - other.ns)
  elsif other.respond_to?(:to_nanos)
    # @type var other: Duration | _ToNanos
    Instant.new(@ns - other.to_nanos)
  else
    raise TypeError, 'Not one of: [Instant, Duration, #to_nanos]'
  end
end

#<=>(other) ⇒ -1, ...

Determine if the given Instant is before, equal to or after this one. nil if not passed an Instant.

Returns:

  • (-1, 0, 1, nil)


274
275
276
# File 'lib/monotime/instant.rb', line 274

def <=>(other)
  @ns <=> other.ns if other.is_a?(Instant)
end

#==(other) ⇒ Boolean Also known as: eql?

Determine if other‘s value equals that of this Instant. Use eql? if type checks are desired for future compatibility.

Returns:

  • (Boolean)

See Also:



283
284
285
# File 'lib/monotime/instant.rb', line 283

def ==(other)
  other.is_a?(Instant) && @ns == other.ns
end

#duration_since(earlier) ⇒ Duration

Return a Duration between this Instant and another.

Parameters:

Returns:

Raises:

  • (TypeError)


143
144
145
146
147
148
149
150
151
# File 'lib/monotime/instant.rb', line 143

def duration_since(earlier)
  raise TypeError, 'Not an Instant' unless earlier.is_a?(Instant)

  # `earlier - self` is cleaner, but upsets type checks and duplicates our
  # type checks.

  # @type var earlier: Instant
  Duration.new(earlier.ns - @ns)
end

#elapsedDuration

Return a Duration since this Instant and now.

Returns:



156
157
158
# File 'lib/monotime/instant.rb', line 156

def elapsed
  duration_since(self.class.now)
end

#hashInteger

Generate a hash for this type and value.

Returns:

  • (Integer)


292
293
294
# File 'lib/monotime/instant.rb', line 292

def hash
  [self.class, @ns].hash
end

#in_future?Boolean Also known as: future?

Return whether this Instant is in the future.

Returns:

  • (Boolean)


172
173
174
# File 'lib/monotime/instant.rb', line 172

def in_future?
  elapsed.negative?
end

#in_past?Boolean Also known as: past?

Return whether this Instant is in the past.

Returns:

  • (Boolean)


163
164
165
# File 'lib/monotime/instant.rb', line 163

def in_past?
  elapsed.positive?
end

#sleep(duration = nil) ⇒ Duration

Sleep until this Instant, plus an optional Duration, returning a Duration that’s either positive if any time was slept, or negative if sleeping would require time travel.

Examples:

Sleeps for a second

start = Instant.now
sleep 0.5 # do stuff for half a second
start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)

Also sleeps for a second.

one_second_in_the_future = Instant.now + Duration.from_secs(1)
one_second_in_the_future.sleep.to_s     # => "985.592712ms" (slept)
one_second_in_the_future.sleep.to_s     # => "-4.71217ms" (did not sleep)

Parameters:

  • duration (nil, Duration, #to_nanos) (defaults to: nil)

Returns:

  • (Duration)

    the slept duration, if #positive?, else the overshot time



195
196
197
198
199
200
201
202
203
# File 'lib/monotime/instant.rb', line 195

def sleep(duration = nil)
  remaining = if duration
                Duration.from_nanos(duration.to_nanos - elapsed.to_nanos)
              else
                -elapsed
              end

  remaining.tap { |rem| rem.sleep if rem.positive? }
end

#sleep_millis(millis) ⇒ Duration

Sleep for the given number of milliseconds past this Instant, if any.

Equivalent to #sleep(Duration.from_millis(millis))

Parameters:

  • millis (Numeric)

    number of milliseconds to sleep past this Instant

Returns:

  • (Duration)

    the slept duration, if #positive?, else the overshot time

See Also:



223
224
225
# File 'lib/monotime/instant.rb', line 223

def sleep_millis(millis)
  sleep(Duration.from_millis(millis))
end

#sleep_secs(secs) ⇒ Duration

Sleep for the given number of seconds past this Instant, if any.

Equivalent to #sleep(Duration.from_secs(secs))

Parameters:

  • secs (Numeric)

    number of seconds to sleep past this Instant

Returns:

  • (Duration)

    the slept duration, if #positive?, else the overshot time

See Also:



212
213
214
# File 'lib/monotime/instant.rb', line 212

def sleep_secs(secs)
  sleep(Duration.from_secs(secs))
end

#to_sObject

Sugar for #elapsed.to_s.

See Also:



230
231
232
# File 'lib/monotime/instant.rb', line 230

def to_s(...)
  elapsed.to_s(...)
end