monotime 0.7.1 → 0.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a9395967e506d690912041251bc7b6afd1a373aa49d0b4425ec08ca611e5238
4
- data.tar.gz: '09ec6a1e96c9ce434d4847d275d85f7514c4a9bb3d9c71d205b04ab0f06e6d5b'
3
+ metadata.gz: 388a55af121b75e9a9ac06db8674e5a8a4b06ade0431a5cdd961f82426c87ade
4
+ data.tar.gz: 7d62a9b673252f01591769d8659c5a36b1026088724a2afc9694b41f633ca053
5
5
  SHA512:
6
- metadata.gz: a68a338484a8534a8953abf5bec0a98a6b7a2dc6a506cd1f5fb1e3b29e1b534fa0d41e4175aa81598273ee5d706d16caeb60ce1b89a92b5e8192f87a960265f3
7
- data.tar.gz: f9f329d74339eccc1b4c50007d2cb93cc87132ce6a5841faa327d4db48dfb347df8faee3f9777551cb686e90001def65d72c8a77c6b6df4dfed1d847598ac685
6
+ metadata.gz: 14c7ede1fc2daa4fef854d3be58392f57849ff07c4f815c3e995f32073e14893fa7ad95f08e074199457517d262fc8c2942316b8039260b1e66e4045409bb6cf
7
+ data.tar.gz: b2c907ce1b4f3f92185ba4cb238c3809b465d40be7e34eb1c4a225779a4f6c156ae1d78742ddf24a467d59bfd3437c15be54d4f8bc91a9a9d06fa42ee5e5544a
@@ -2,13 +2,14 @@ name: CI
2
2
  on: [push, pull_request]
3
3
  jobs:
4
4
  test:
5
- runs-on: ubuntu-latest
6
5
  strategy:
7
6
  fail-fast: false
8
7
  matrix:
9
- ruby: ['2.5', '2.6', '2.7', '3.0', jruby, truffleruby]
8
+ os: [ubuntu-latest, macos-latest]
9
+ ruby: ['2.7', '3.0', '3.1', '3.2', head, jruby, jruby-head, truffleruby, truffleruby-head]
10
+ runs-on: ${{ matrix.os }}
10
11
  steps:
11
- - uses: actions/checkout@v2
12
+ - uses: actions/checkout@v4
12
13
  - uses: ruby/setup-ruby@v1
13
14
  with:
14
15
  ruby-version: ${{ matrix.ruby }}
data/.rubocop.yml CHANGED
@@ -11,7 +11,10 @@ Layout/LineLength:
11
11
  Max: 96
12
12
 
13
13
  Metrics/ClassLength:
14
- Max: 120
14
+ Max: 140
15
+
16
+ Metrics/MethodLength:
17
+ Max: 20
15
18
 
16
19
  Style/AsciiComments:
17
20
  Enabled: false
@@ -36,3 +39,6 @@ Style/ExplicitBlockArgument:
36
39
 
37
40
  Style/MixinUsage:
38
41
  Enabled: false
42
+
43
+ Style/TrailingCommaInArrayLiteral:
44
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,100 +1,160 @@
1
1
  # Changelog
2
2
 
3
- ## 0.7.1 - 2021-10-22
3
+ ## [0.8.0] - 2023-09-17
4
+
4
5
  ### Added
5
- - `simplecov` introduced to test suite.
6
- - `monotime/include.rb` to auto-include types globally.
6
+
7
+ - Default precision for `Duration#to_s` can be set using
8
+ `Duration.default_to_s_precision=`.
9
+ - Default sleep function can be set using `Duration.sleep_function=`
10
+ - `Duration::ZERO` and `Duration.zero` for an easy, memory-efficient
11
+ zero-duration singleton.
12
+ - `Instant.clock_id` and `Instant.clock_id=` to control the default clock
13
+ source.
14
+ - `Instant.clock_getres` to get the minimum supported `Duration` from the
15
+ selected clock source.
16
+ - `Instant.monotonic_function=` to completely replace the default monotonic
17
+ function.
7
18
 
8
19
  ### Changed
9
- - All `Instant` and `Duration` instances are now frozen.
10
- - Migrate from Travis CI to Github Actions
11
- - Update development dependency on `rake`.
20
+
21
+ - The default clock source is now chosen from a selection of options instead of
22
+ defaulting to `CLOCK_MONOTONIC``. Where possible options are used which are
23
+ unaffected by NTP frequency skew and which do not count time in system suspend.
24
+ - CI matrix drops Ruby 2.5 and 2.6 and adds 3.1, 3.2, head branches of Ruby,
25
+ JRuby, and TruffleRuby, and also tests under macOS.
26
+
27
+ ### Fixed
28
+
29
+ - CI on TruffleRuby has been fixed by disabling SimpleCov.
30
+ - Several fragile tests depending on relatively narrow sleep times have been fixed.
31
+
32
+ ### Thanks
33
+
34
+ - [@petergoldstein] for fixing CI on TruffleRuby and adding 3.1 and 3.2.
35
+ - [@fig] for fixing a README error.
36
+
37
+ ## [0.7.1] - 2021-10-22
38
+
39
+ ### Added
40
+
41
+ - `simplecov` introduced to test suite.
42
+ - `monotime/include.rb` to auto-include types globally.
43
+
44
+ ### Changed
45
+
46
+ - All `Instant` and `Duration` instances are now frozen.
47
+ - Migrate from Travis CI to Github Actions
48
+ - Update development dependency on `rake`.
12
49
 
13
50
  ## [0.7.0] - 2019-04-24
51
+
14
52
  ### Added
15
- - `Duration.with_measure`, which yields and returns an array containing its
16
- evaluated return value and its `Duration`.
53
+
54
+ - `Duration.with_measure`, which yields and returns an array containing its
55
+ evaluated return value and its `Duration`.
17
56
 
18
57
  ### Changed
19
- - Break `Duration` and `Instant` into their own files.
20
- - Rename `Monotime::VERSION` to `Monotime::MONOTIME_VERSION` to reduce
21
- potential for collision if the module is included.
22
- - Update to bundler 2.0.
23
- - Rework README.md. Includes fix for [issue #1] (added a "See Also" section).
58
+
59
+ - Break `Duration` and `Instant` into their own files.
60
+ - Rename `Monotime::VERSION` to `Monotime::MONOTIME_VERSION` to reduce
61
+ potential for collision if the module is included.
62
+ - Update to bundler 2.0.
63
+ - Rework README.md. Includes fix for [issue #1] (added a "See Also" section).
24
64
 
25
65
  ## [0.6.1] - 2018-10-26
66
+
26
67
  ### Fixed
27
- - Build gem from a clean git checkout, not my local development directory.
28
- No functional changes.
68
+
69
+ - Build gem from a clean git checkout, not my local development directory.
70
+ No functional changes.
29
71
 
30
72
  ## [0.6.0] - 2018-10-26
73
+
31
74
  ### Added
32
- - This `CHANGELOG.md` by request of [@celsworth].
33
- - Aliases for `Duration.from_*` and `Duration#to_*` without the prefix. e.g.
34
- `Duration.from_secs(42).to_secs == 42` can now be written as
35
- `Duration.secs(42).secs == 42`.
36
- - `Duration#nonzero?`.
37
- - `Instant#in_past?` and `Instant#in_future?`.
75
+
76
+ - This `CHANGELOG.md` by request of [@celsworth].
77
+ - Aliases for `Duration.from_*` and `Duration#to_*` without the prefix. e.g.
78
+ `Duration.from_secs(42).to_secs == 42` can now be written as
79
+ `Duration.secs(42).secs == 42`.
80
+ - `Duration#nonzero?`.
81
+ - `Instant#in_past?` and `Instant#in_future?`.
38
82
 
39
83
  ## [0.5.0] - 2018-10-13
84
+
40
85
  ### Added
41
- - `Duration#abs` to make a `Duration` positive.
42
- - `Duration#-@` to invert the sign of a `Duration`.
43
- - `Duration#positive?`
44
- - `Duration#negative?`
45
- - `Duration#zero?`
86
+
87
+ - `Duration#abs` to make a `Duration` positive.
88
+ - `Duration#-@` to invert the sign of a `Duration`.
89
+ - `Duration#positive?`
90
+ - `Duration#negative?`
91
+ - `Duration#zero?`
46
92
 
47
93
  ### Changed
48
- - `Instant#sleep` with no argument now sleeps until the `Instant`.
49
- - `Duration.from_*` no longer coerce their argument to `Float`.
50
- - `Duration#==` checks value via `#to_nanos`, not type.
51
- - `Duration#eql?` checks value and type.
52
- - `Duration#<=>` compares value via `#to_nanos`.
94
+
95
+ - `Instant#sleep` with no argument now sleeps until the `Instant`.
96
+ - `Duration.from_*` no longer coerce their argument to `Float`.
97
+ - `Duration#==` checks value via `#to_nanos`, not type.
98
+ - `Duration#eql?` checks value and type.
99
+ - `Duration#<=>` compares value via `#to_nanos`.
53
100
 
54
101
  ## [0.4.0] - 2018-10-09
102
+
55
103
  ### Added
56
- - `Instant#sleep` - sleep to a given `Duration` past an `Instant`.
57
- - `Instant#sleep_secs` and `Instant#sleep_millis` convenience methods.
58
- - `Duration#sleep` - sleep for the `Duration`.
59
- - `Duration#*` - multiply a `Duration` by a number.
60
- - `Duration#/` - divide a `Duration` by a number.
104
+
105
+ - `Instant#sleep` - sleep to a given `Duration` past an `Instant`.
106
+ - `Instant#sleep_secs` and `Instant#sleep_millis` convenience methods.
107
+ - `Duration#sleep` - sleep for the `Duration`.
108
+ - `Duration#*` - multiply a `Duration` by a number.
109
+ - `Duration#/` - divide a `Duration` by a number.
61
110
 
62
111
  ### Changed
63
- - More `#to_nanos` `Duration` duck-typing.
112
+
113
+ More `#to_nanos` `Duration` duck-typing.
64
114
 
65
115
  ## [0.3.0] - 2018-10-04
116
+
66
117
  ### Added
67
- - `#to_nanos` is now used to duck-type `Duration` everywhere.
118
+
119
+ - `#to_nanos` is now used to duck-type `Duration` everywhere.
68
120
 
69
121
  ### Changed
70
- - Make `<=>` return nil on invalid types, rather than raising a `TypeError`.
122
+
123
+ - Make `<=>` return nil on invalid types, rather than raising a `TypeError`.
71
124
 
72
125
  ### Removed
73
- - Dependency on `dry-equalizer`.
126
+
127
+ - Dependency on `dry-equalizer`.
74
128
 
75
129
  ## [0.2.0] - 2018-10-03
130
+
76
131
  ### Added
77
- - `Instant#to_s` as an alias for `#elapsed.to_s`
78
- - `Duration#to_nanos`, with some limited duck-typing.
132
+
133
+ - `Instant#to_s` as an alias for `#elapsed.to_s`
134
+ - `Duration#to_nanos`, with some limited duck-typing.
79
135
 
80
136
  ### Changed
81
- - Switch to microseconds internally.
82
- - `Duration#to_{secs,millis,micros}` now return a `Float`.
83
- - `Instant#ns` is now `protected`.
137
+
138
+ - Switch to microseconds internally.
139
+ - `Duration#to_{secs,millis,micros}` now return a `Float`.
140
+ - `Instant#ns` is now `protected`.
84
141
 
85
142
  ### Fixed
86
- - `Duration#to_s` zero-stripping with precision=0.
87
- - `Instant#-` argument ordering with other `Instant`.
88
- - `Duration#to_micros` returns microseconds, not picoseconds.
143
+
144
+ - `Duration#to_s` zero-stripping with precision=0.
145
+ - `Instant#-` argument ordering with other `Instant`.
146
+ - `Duration#to_micros` returns microseconds, not picoseconds.
89
147
 
90
148
  ### Removed
91
- - `Instant` and `Duration` maths methods no longer support passing an `Integer`
92
- number of nanoseconds.
149
+
150
+ - `Instant` and `Duration` maths methods no longer support passing an `Integer`
151
+ number of nanoseconds.
93
152
 
94
153
  ## [0.1.0] - 2018-10-02
154
+
95
155
  ### Added
96
- - Initial release
97
156
 
157
+ - Initial release
98
158
 
99
159
  [0.1.0]: https://github.com/Freaky/monotime/commits/v0.1.0
100
160
  [0.2.0]: https://github.com/Freaky/monotime/commits/v0.2.0
@@ -104,5 +164,9 @@
104
164
  [0.6.0]: https://github.com/Freaky/monotime/commits/v0.6.0
105
165
  [0.6.1]: https://github.com/Freaky/monotime/commits/v0.6.1
106
166
  [0.7.0]: https://github.com/Freaky/monotime/commits/v0.7.0
167
+ [0.7.1]: https://github.com/Freaky/monotime/commits/v0.7.0
168
+ [0.8.0]: https://github.com/Freaky/monotime/commits/v0.7.0
107
169
  [issue #1]: https://github.com/Freaky/monotime/issues/1
108
170
  [@celsworth]: https://github.com/celsworth
171
+ [@petergoldstein]: https://github.com/petergoldstein
172
+ [@fig]: https://github.com/fig
data/README.md CHANGED
@@ -1,12 +1,13 @@
1
- [![Gem Version](https://badge.fury.io/rb/monotime.svg)](https://badge.fury.io/rb/monotime)
2
- ![Build Status](https://github.com/Freaky/monotime/actions/workflows/ci.yml/badge.svg)
3
- [![Inline docs](http://inch-ci.org/github/Freaky/monotime.svg?branch=master)](http://inch-ci.org/github/Freaky/monotime)
4
- [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/monotime)
5
1
 
6
2
  # Monotime
7
3
 
8
4
  A sensible interface to Ruby's monotonic clock, inspired by Rust.
9
5
 
6
+ [![Gem Version](https://badge.fury.io/rb/monotime.svg)](https://badge.fury.io/rb/monotime)
7
+ [![Build Status](https://github.com/Freaky/monotime/actions/workflows/ci.yml/badge.svg)](https://github.com/Freaky/monotime/actions)
8
+ [![Inline docs](http://inch-ci.org/github/Freaky/monotime.svg?branch=master)](http://inch-ci.org/github/Freaky/monotime)
9
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/monotime)
10
+
10
11
  ## Installation
11
12
 
12
13
  Add this line to your application's Gemfile:
@@ -23,7 +24,7 @@ Or install it yourself as:
23
24
 
24
25
  $ gem install monotime
25
26
 
26
- `Monotime` is tested on Ruby 2.5&mdash;3.0 and recent JRuby 9.x releases.
27
+ `Monotime` is tested on Ruby 2.7+, TruffleRuby, and JRuby.
27
28
 
28
29
  ## Usage
29
30
 
@@ -106,7 +107,7 @@ Duration.secs(1).sleep # => 1
106
107
 
107
108
  So can `Instant`, taking a `Duration` and sleeping until the given `Duration`
108
109
  past the time the `Instant` was created, if any. This can be useful for
109
- maintaining a precise candence between tasks:
110
+ maintaining a precise cadence between tasks:
110
111
 
111
112
  ```ruby
112
113
  interval = Duration.secs(60)
@@ -159,7 +160,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
159
160
 
160
161
  ## Contributing
161
162
 
162
- Bug reports and pull requests are welcome on GitHub at https://github.com/Freaky/monotime.
163
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/Freaky/monotime>.
163
164
 
164
165
  ## License
165
166
 
@@ -169,14 +170,19 @@ The gem is available as open source under the terms of the [MIT License](https:/
169
170
 
170
171
  ### Core Ruby
171
172
 
172
- For a zero-dependency alternative, see
173
+ For a zero-dependency alternative upon which `monotime` is based, see
173
174
  [`Process.clock_gettime`](https://ruby-doc.org/core-2.6.3/Process.html#method-c-clock_gettime).
174
- `monotime` currently only uses `Process::CLOCK_MONOTONIC`, but others may offer higher precision
175
- depending on platform.
175
+
176
+ `Process::CLOCK_MONOTONIC` is a safe default, but other options may offer better
177
+ behaviour in face of NTP frequency skew or suspend/resume and should be evaluated
178
+ carefully.
176
179
 
177
180
  ### Other Gems
178
181
 
179
182
  [hitimes](https://rubygems.org/gems/hitimes) is a popular and mature alternative
180
183
  which also includes a variety of features for gathering statistics about
181
- measurements, and may offer higher precision on some platforms.
184
+ measurements.
182
185
 
186
+ [concurrent-ruby](https://rubygems.org/gems/concurrent-ruby) includes
187
+ `Concurrent.monotonic_time`, which is at the time of writing a trivial proxy to
188
+ the aforementioned `Process::clock_gettime` with `Process::CLOCK_MONOTONIC`.
@@ -8,16 +8,57 @@ module Monotime
8
8
  # Create a new +Duration+ of a specified number of nanoseconds, zero by
9
9
  # default.
10
10
  #
11
- # Users are strongly advised to use +#from_nanos+ instead.
11
+ # Users are strongly advised to use +Duration.from_nanos+ instead.
12
12
  #
13
13
  # @param nanos [Integer]
14
- # @see #from_nanos
14
+ # @see from_nanos
15
15
  def initialize(nanos = 0)
16
16
  @ns = Integer(nanos)
17
17
  freeze
18
18
  end
19
19
 
20
+ # A static instance for zero durations
21
+ ZERO = allocate.tap { |d| d.__send__(:initialize, 0) }
22
+
23
+ class << self
24
+ # The sleep function used by all +Monotime+ sleep functions.
25
+ #
26
+ # This function must accept a positive +Float+ number of seconds and return
27
+ # the +Float+ time slept.
28
+ #
29
+ # Defaults to +Kernel.method(:sleep)+
30
+ #
31
+ # @overload sleep_function=(function)
32
+ # @param function [#call]
33
+ attr_accessor :sleep_function
34
+
35
+ # Precision for +Duration#to_s+ if not otherwise specified
36
+ #
37
+ # Defaults to 9.
38
+ #
39
+ # @overload default_to_s_precision=(precision)
40
+ # @param precision [Numeric]
41
+ attr_accessor :default_to_s_precision
42
+ end
43
+
44
+ self.sleep_function = Kernel.method(:sleep)
45
+ self.default_to_s_precision = 9
46
+
20
47
  class << self
48
+ # @!visibility private
49
+ def new(nanos = 0)
50
+ return ZERO if nanos.zero?
51
+
52
+ super
53
+ end
54
+
55
+ # Return a zero +Duration+.
56
+ #
57
+ # @return [Duration]
58
+ def zero
59
+ ZERO
60
+ end
61
+
21
62
  # Generate a new +Duration+ measuring the given number of seconds.
22
63
  #
23
64
  # @param secs [Numeric]
@@ -87,7 +128,7 @@ module Monotime
87
128
  # @example
88
129
  # (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
89
130
  #
90
- # @param [Duration, #to_nanos]
131
+ # @param other [Duration, #to_nanos]
91
132
  # @return [Duration]
92
133
  def +(other)
93
134
  raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
@@ -101,7 +142,7 @@ module Monotime
101
142
  # @example
102
143
  # (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
103
144
  #
104
- # @param [Duration, #to_nanos]
145
+ # @param other [Duration, #to_nanos]
105
146
  # @return [Duration]
106
147
  def -(other)
107
148
  raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
@@ -114,7 +155,7 @@ module Monotime
114
155
  # @example
115
156
  # (Duration.from_secs(10) / 2).to_s # => "5s"
116
157
  #
117
- # @param [Numeric]
158
+ # @param other [Numeric]
118
159
  # @return [Duration]
119
160
  def /(other)
120
161
  Duration.new(to_nanos / other)
@@ -125,7 +166,7 @@ module Monotime
125
166
  # @example
126
167
  # (Duration.from_secs(10) * 2).to_s # => "20s"
127
168
  #
128
- # @param [Numeric]
169
+ # @param other [Numeric]
129
170
  # @return [Duration]
130
171
  def *(other)
131
172
  Duration.new(to_nanos * other)
@@ -158,7 +199,7 @@ module Monotime
158
199
  # Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
159
200
  # object, or nil if not comparable.
160
201
  #
161
- # @param [Duration, #to_nanos, Object]
202
+ # @param other [Duration, #to_nanos, Object]
162
203
  # @return [-1, 0, 1, nil]
163
204
  def <=>(other)
164
205
  to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
@@ -167,7 +208,7 @@ module Monotime
167
208
  # Compare the equality of the *value* of this +Duration+ with another, or
168
209
  # any +#to_nanos+-coercible object, or nil if not comparable.
169
210
  #
170
- # @param [Duration, #to_nanos, Object]
211
+ # @param other [Duration, #to_nanos, Object]
171
212
  # @return [Boolean]
172
213
  def ==(other)
173
214
  other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
@@ -175,7 +216,7 @@ module Monotime
175
216
 
176
217
  # Check equality of the value and type of this +Duration+ with another.
177
218
  #
178
- # @param [Duration, Object]
219
+ # @param other [Duration, Object]
179
220
  # @return [Boolean]
180
221
  def eql?(other)
181
222
  other.is_a?(Duration) && to_nanos == other.to_nanos
@@ -185,7 +226,7 @@ module Monotime
185
226
  #
186
227
  # @return [Integer]
187
228
  def hash
188
- self.class.hash ^ to_nanos.hash
229
+ [self.class, to_nanos].hash
189
230
  end
190
231
 
191
232
  # Return this +Duration+ in seconds.
@@ -255,6 +296,8 @@ module Monotime
255
296
  # Sleep for the duration of this +Duration+. Equivalent to
256
297
  # +Kernel.sleep(duration.to_secs)+.
257
298
  #
299
+ # The sleep function may be overridden globally using +Duration.sleep_function=+
300
+ #
258
301
  # @example
259
302
  # Duration.from_secs(1).sleep # => 1
260
303
  # Duration.from_secs(-1).sleep # => raises NotImplementedError
@@ -262,10 +305,11 @@ module Monotime
262
305
  # @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
263
306
  # @return [Integer]
264
307
  # @see Instant#sleep
308
+ # @see sleep_function=
265
309
  def sleep
266
310
  raise NotImplementedError, 'time travel module missing' if negative?
267
311
 
268
- Kernel.sleep(to_secs)
312
+ self.class.sleep_function.call(to_secs)
269
313
  end
270
314
 
271
315
  DIVISORS = [
@@ -280,6 +324,8 @@ module Monotime
280
324
  # Format this +Duration+ into a human-readable string, with a given number
281
325
  # of decimal places.
282
326
  #
327
+ # The default precision may be set globally using +Duration.default_to_s_precision=+
328
+ #
283
329
  # The exact format is subject to change, users with specific requirements
284
330
  # are encouraged to use their own formatting methods.
285
331
  #
@@ -293,7 +339,8 @@ module Monotime
293
339
  #
294
340
  # @param precision [Integer] the maximum number of decimal places
295
341
  # @return [String]
296
- def to_s(precision = 9)
342
+ # @see default_to_s_precision=
343
+ def to_s(precision = self.class.default_to_s_precision)
297
344
  precision = Integer(precision).abs
298
345
  div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
299
346
 
@@ -11,13 +11,106 @@ module Monotime
11
11
 
12
12
  include Comparable
13
13
 
14
+ class << self
15
+ attr_writer :clock_id
16
+
17
+ # The symbolic name of the automatically-selected +Process.clock_gettime+
18
+ # clock id, if available. This will *not* reflect a manually-set +clock_id+.
19
+ #
20
+ # @return [Symbol, nil]
21
+ attr_reader :clock_name
22
+
23
+ # The function used to create +Instant+ instances.
24
+ #
25
+ # This function must return a +Numeric+, monotonic count of nanoseconds
26
+ # since a fixed point in the past.
27
+ #
28
+ # Defaults to `lambda { Process.clock_gettime(clock_id, :nanosecond) }`.
29
+ #
30
+ # @overload monotonic_function=(function)
31
+ # @param function [#call]
32
+ attr_accessor :monotonic_function
33
+
34
+ # @overload clock_id
35
+ # The configured or detected +Process.clock_getime+ clock identifier.
36
+ #
37
+ # Raises +NotImplementedError+ if a suitable monotonic clock source cannot
38
+ # be found and one has not been specified manually.
39
+ #
40
+ # @return [Numeric]
41
+ #
42
+ # @overload clock_id=(id)
43
+ # The +Process.clock_gettime+ clock id used to create +Instant+ instances
44
+ # by the default monotonic function.
45
+ #
46
+ # Suggested options include but are not limited to:
47
+ #
48
+ # * +Process::CLOCK_MONOTONIC_RAW+
49
+ # * +Process::CLOCK_UPTIME_RAW+
50
+ # * +Process::CLOCK_UPTIME_PRECISE+
51
+ # * +Process::CLOCK_UPTIME_FAST+
52
+ # * +Process::CLOCK_UPTIME+
53
+ # * +Process::CLOCK_MONOTONIC_PRECISE+
54
+ # * +Process::CLOCK_MONOTONIC_FAST+
55
+ # * +Process::CLOCK_MONOTONIC+
56
+ #
57
+ # These are platform-dependant and may vary in resolution, performance,
58
+ # and behaviour from NTP frequency skew and system suspend/resume.
59
+ #
60
+ # It is possible to set non-monotonic clock sources here. You probably
61
+ # shouldn't.
62
+ #
63
+ # Defaults to auto-detect.
64
+ #
65
+ # @param id [Numeric]
66
+ def clock_id
67
+ @clock_id ||= detect_clock_id
68
+ end
69
+
70
+ # Return the claimed resolution of the given clock id or the configured
71
+ # +clock_id+, as a +Duration+, or +nil+ if invalid.
72
+ #
73
+ # @param clock [Numeric] Optional clock id instead of default.
74
+ def clock_getres(clock = @clock_id)
75
+ Duration.from_nanos(Process.clock_getres(clock, :nanosecond))
76
+ rescue SystemCallError
77
+ # suppress errors
78
+ end
79
+
80
+ private
81
+
82
+ def detect_clock_id
83
+ name, id, =
84
+ [
85
+ :CLOCK_MONOTONIC_RAW, # Linux, not affected by NTP frequency adjustments
86
+ :CLOCK_UPTIME_RAW, # macOS, not affected by NTP frequency adjustments
87
+ :CLOCK_UPTIME_PRECISE, # FreeBSD, increments while system is running
88
+ :CLOCK_UPTIME, # OpenBSD, increments while system is running
89
+ :CLOCK_MONOTONIC_PRECISE, # FreeBSD, precise monotonic clock
90
+ :CLOCK_MONOTONIC, # Standard cross-platform monotonic clock
91
+ ]
92
+ .each_with_index # Used to force a stable sort in min_by
93
+ .filter { |name, _| Process.const_defined?(name) }
94
+ .map { |name, index| [name, Process.const_get(name), index] }
95
+ .filter_map { |clock| clock.insert(2, clock_getres(clock[1])) }
96
+ .min_by { |clock| clock[2..] } # find smallest resolution and index
97
+ .tap { |clock| raise NotImplementedError, 'No usable clock' unless clock }
98
+
99
+ @clock_name = name
100
+ id
101
+ end
102
+ end
103
+
104
+ self.monotonic_function = -> { Process.clock_gettime(clock_id, :nanosecond) }
105
+ clock_id # detect our clock_id early
106
+
14
107
  # Create a new +Instant+ from an optional nanosecond measurement.
15
108
  #
16
109
  # Users should generally *not* pass anything to this function.
17
110
  #
18
111
  # @param nanos [Integer]
19
112
  # @see #now
20
- def initialize(nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))
113
+ def initialize(nanos = self.class.monotonic_function.call)
21
114
  @ns = Integer(nanos)
22
115
  freeze
23
116
  end
@@ -112,8 +205,8 @@ module Monotime
112
205
  # Sugar for +#elapsed.to_s+.
113
206
  #
114
207
  # @see Duration#to_s
115
- def to_s(*args)
116
- elapsed.to_s(*args)
208
+ def to_s(...)
209
+ elapsed.to_s(...)
117
210
  end
118
211
 
119
212
  # Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
@@ -173,7 +266,7 @@ module Monotime
173
266
  #
174
267
  # @return [Integer]
175
268
  def hash
176
- self.class.hash ^ @ns.hash
269
+ [self.class, @ns].hash
177
270
  end
178
271
  end
179
272
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Monotime
4
- # Try to avoid blatting existing VERSION constants when we're included.
5
- MONOTIME_VERSION = '0.7.1'
4
+ # Version of the `monotime` gem
5
+ MONOTIME_VERSION = '0.8.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monotime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Hurst
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-22 00:00:00.000000000 Z
11
+ date: 2023-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 3.2.22
112
+ rubygems_version: 3.4.19
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: A sensible interface to the monotonic clock