monotime 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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