monotime 0.7.0 → 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: c88bca715126cabf9bf5ea1af377e16d43da5ecc06ee45f98dad584360eaa15b
4
- data.tar.gz: 8f741d6219cfe4b9050f892f2df905ed603f859e4a79f930a4eb954c213bf639
3
+ metadata.gz: 388a55af121b75e9a9ac06db8674e5a8a4b06ade0431a5cdd961f82426c87ade
4
+ data.tar.gz: 7d62a9b673252f01591769d8659c5a36b1026088724a2afc9694b41f633ca053
5
5
  SHA512:
6
- metadata.gz: 5ed67631407691f935ffdd30727c4054b3fa7f5964a9cdcb0db0f94e1a2aecddb83eac4b1e4a8e032cd3c57145152e6649ddf14093643329ff23b5d43348000f
7
- data.tar.gz: f38b77609652866fa4c1b7bfee164977a7f7895feeb0f76d8dd5e71a05da31b0cda24c680f5b2aef19c3f5c7787da237f580ee56ccb86e456dae576fc95d0e89
6
+ metadata.gz: 14c7ede1fc2daa4fef854d3be58392f57849ff07c4f815c3e995f32073e14893fa7ad95f08e074199457517d262fc8c2942316b8039260b1e66e4045409bb6cf
7
+ data.tar.gz: b2c907ce1b4f3f92185ba4cb238c3809b465d40be7e34eb1c4a225779a4f6c156ae1d78742ddf24a467d59bfd3437c15be54d4f8bc91a9a9d06fa42ee5e5544a
@@ -0,0 +1,17 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
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 }}
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: ${{ matrix.ruby }}
16
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
17
+ - run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ NewCops: enable
2
3
  Exclude:
3
4
  - "bin/*"
4
5
  - "test/*"
@@ -6,11 +7,14 @@ AllCops:
6
7
  - "Rakefile"
7
8
  - "Gemfile"
8
9
 
9
- Metrics/LineLength:
10
+ Layout/LineLength:
10
11
  Max: 96
11
12
 
12
13
  Metrics/ClassLength:
13
- Max: 120
14
+ Max: 140
15
+
16
+ Metrics/MethodLength:
17
+ Max: 20
14
18
 
15
19
  Style/AsciiComments:
16
20
  Enabled: false
@@ -20,3 +24,21 @@ Style/AccessModifierDeclarations:
20
24
 
21
25
  Style/FormatStringToken:
22
26
  Enabled: false
27
+
28
+ Style/HashEachMethods:
29
+ Enabled: true
30
+
31
+ Style/HashTransformKeys:
32
+ Enabled: true
33
+
34
+ Style/HashTransformValues:
35
+ Enabled: true
36
+
37
+ Style/ExplicitBlockArgument:
38
+ Enabled: false
39
+
40
+ Style/MixinUsage:
41
+ Enabled: false
42
+
43
+ Style/TrailingCommaInArrayLiteral:
44
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,90 +1,160 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0] - 2023-09-17
4
+
5
+ ### Added
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.
18
+
19
+ ### Changed
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`.
49
+
3
50
  ## [0.7.0] - 2019-04-24
51
+
4
52
  ### Added
5
- - `Duration.with_measure`, which yields and returns an array containing its
6
- 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`.
7
56
 
8
57
  ### Changed
9
- - Break `Duration` and `Instant` into their own files.
10
- - Rename `Monotime::VERSION` to `Monotime::MONOTIME_VERSION` to reduce
11
- potential for collision if the module is included.
12
- - Update to bundler 2.0.
13
- - 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).
14
64
 
15
65
  ## [0.6.1] - 2018-10-26
66
+
16
67
  ### Fixed
17
- - Build gem from a clean git checkout, not my local development directory.
18
- No functional changes.
68
+
69
+ - Build gem from a clean git checkout, not my local development directory.
70
+ No functional changes.
19
71
 
20
72
  ## [0.6.0] - 2018-10-26
73
+
21
74
  ### Added
22
- - This `CHANGELOG.md` by request of [@celsworth].
23
- - Aliases for `Duration.from_*` and `Duration#to_*` without the prefix. e.g.
24
- `Duration.from_secs(42).to_secs == 42` can now be written as
25
- `Duration.secs(42).secs == 42`.
26
- - `Duration#nonzero?`.
27
- - `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?`.
28
82
 
29
83
  ## [0.5.0] - 2018-10-13
84
+
30
85
  ### Added
31
- - `Duration#abs` to make a `Duration` positive.
32
- - `Duration#-@` to invert the sign of a `Duration`.
33
- - `Duration#positive?`
34
- - `Duration#negative?`
35
- - `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?`
36
92
 
37
93
  ### Changed
38
- - `Instant#sleep` with no argument now sleeps until the `Instant`.
39
- - `Duration.from_*` no longer coerce their argument to `Float`.
40
- - `Duration#==` checks value via `#to_nanos`, not type.
41
- - `Duration#eql?` checks value and type.
42
- - `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`.
43
100
 
44
101
  ## [0.4.0] - 2018-10-09
102
+
45
103
  ### Added
46
- - `Instant#sleep` - sleep to a given `Duration` past an `Instant`.
47
- - `Instant#sleep_secs` and `Instant#sleep_millis` convenience methods.
48
- - `Duration#sleep` - sleep for the `Duration`.
49
- - `Duration#*` - multiply a `Duration` by a number.
50
- - `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.
51
110
 
52
111
  ### Changed
53
- - More `#to_nanos` `Duration` duck-typing.
112
+
113
+ More `#to_nanos` `Duration` duck-typing.
54
114
 
55
115
  ## [0.3.0] - 2018-10-04
116
+
56
117
  ### Added
57
- - `#to_nanos` is now used to duck-type `Duration` everywhere.
118
+
119
+ - `#to_nanos` is now used to duck-type `Duration` everywhere.
58
120
 
59
121
  ### Changed
60
- - Make `<=>` return nil on invalid types, rather than raising a `TypeError`.
122
+
123
+ - Make `<=>` return nil on invalid types, rather than raising a `TypeError`.
61
124
 
62
125
  ### Removed
63
- - Dependency on `dry-equalizer`.
126
+
127
+ - Dependency on `dry-equalizer`.
64
128
 
65
129
  ## [0.2.0] - 2018-10-03
130
+
66
131
  ### Added
67
- - `Instant#to_s` as an alias for `#elapsed.to_s`
68
- - `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.
69
135
 
70
136
  ### Changed
71
- - Switch to microseconds internally.
72
- - `Duration#to_{secs,millis,micros}` now return a `Float`.
73
- - `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`.
74
141
 
75
142
  ### Fixed
76
- - `Duration#to_s` zero-stripping with precision=0.
77
- - `Instant#-` argument ordering with other `Instant`.
78
- - `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.
79
147
 
80
148
  ### Removed
81
- - `Instant` and `Duration` maths methods no longer support passing an `Integer`
82
- number of nanoseconds.
149
+
150
+ - `Instant` and `Duration` maths methods no longer support passing an `Integer`
151
+ number of nanoseconds.
83
152
 
84
153
  ## [0.1.0] - 2018-10-02
154
+
85
155
  ### Added
86
- - Initial release
87
156
 
157
+ - Initial release
88
158
 
89
159
  [0.1.0]: https://github.com/Freaky/monotime/commits/v0.1.0
90
160
  [0.2.0]: https://github.com/Freaky/monotime/commits/v0.2.0
@@ -94,5 +164,9 @@
94
164
  [0.6.0]: https://github.com/Freaky/monotime/commits/v0.6.0
95
165
  [0.6.1]: https://github.com/Freaky/monotime/commits/v0.6.1
96
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
97
169
  [issue #1]: https://github.com/Freaky/monotime/issues/1
98
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://travis-ci.org/Freaky/monotime.svg?branch=master)](https://travis-ci.org/Freaky/monotime)
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,10 +24,17 @@ Or install it yourself as:
23
24
 
24
25
  $ gem install monotime
25
26
 
26
- `Monotime` is tested on Ruby 2.4&mdash;2.6 and recent JRuby 9.x releases.
27
+ `Monotime` is tested on Ruby 2.7+, TruffleRuby, and JRuby.
27
28
 
28
29
  ## Usage
29
30
 
31
+ ```ruby
32
+ require 'monotime'
33
+ # or, to automatically include Monotime::* in the global scope,
34
+ # as used by these examples:
35
+ require 'monotime/include'
36
+ ```
37
+
30
38
  `Monotime` offers a `Duration` type for describing spans of time, and an
31
39
  `Instant` type for describing points in time. Both operate at nanosecond
32
40
  resolution to the limits of whatever your Ruby implementation supports.
@@ -36,8 +44,6 @@ start point, perform the action and then ask for the `Duration` that has elapsed
36
44
  since:
37
45
 
38
46
  ```ruby
39
- include Monotime
40
-
41
47
  start = Instant.now
42
48
  do_something
43
49
  elapsed = start.elapsed
@@ -47,9 +53,7 @@ Or use a convenience method:
47
53
 
48
54
  ```ruby
49
55
  elapsed = Duration.measure { do_something }
50
-
51
56
  # or
52
-
53
57
  return_value, elapsed = Duration.with_measure { compute_something }
54
58
  ```
55
59
 
@@ -98,34 +102,37 @@ is not yet implemented):
98
102
  ```ruby
99
103
  # Equivalent
100
104
  sleep(Duration.secs(1).secs) # => 1
101
-
102
- Duration.secs(1).sleep # => 1
105
+ Duration.secs(1).sleep # => 1
103
106
  ```
104
107
 
105
108
  So can `Instant`, taking a `Duration` and sleeping until the given `Duration`
106
- past the time the `Instant` was created, if any. This may be useful if you wish
107
- to maintain an approximate interval while performing work in between:
109
+ past the time the `Instant` was created, if any. This can be useful for
110
+ maintaining a precise cadence between tasks:
108
111
 
109
112
  ```ruby
110
- poke_duration = Duration.secs(60)
113
+ interval = Duration.secs(60)
114
+ start = Instant.now
111
115
  loop do
112
- start = Instant.now
113
- poke_my_api(api_to_poke, what_to_poke_it_with)
114
- start.sleep(poke_duration) # sleeps 60 seconds minus how long poke_my_api took
115
- # alternative: start.sleep_secs(60)
116
+ do_stuff
117
+ start.sleep(interval)
118
+ start += interval
116
119
  end
117
120
  ```
118
121
 
119
- Or you can declare a future `Instant` and ask to sleep until it passes:
122
+ Or you can declare an `Instant` in the future and sleep to that point:
120
123
 
121
124
  ```ruby
122
- next_minute = Instant.now + Duration.secs(60)
123
- do_stuff
124
- next_minute.sleep # => sleeps any remaining seconds
125
+ interval = Duration.secs(60)
126
+ deadline = Instant.now + interval
127
+ loop do
128
+ do_stuff
129
+ deadline.sleep
130
+ deadline += interval
131
+ end
125
132
  ```
126
133
 
127
134
  `Instant#sleep` returns a `Duration` which was slept, or a negative `Duration`
128
- if the desired sleep period has passed.
135
+ indicating that the desired sleep point was in the past.
129
136
 
130
137
  ## Duration duck typing
131
138
 
@@ -153,7 +160,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
153
160
 
154
161
  ## Contributing
155
162
 
156
- 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>.
157
164
 
158
165
  ## License
159
166
 
@@ -163,16 +170,19 @@ The gem is available as open source under the terms of the [MIT License](https:/
163
170
 
164
171
  ### Core Ruby
165
172
 
166
- For a zero-dependency alternative, see
173
+ For a zero-dependency alternative upon which `monotime` is based, see
167
174
  [`Process.clock_gettime`](https://ruby-doc.org/core-2.6.3/Process.html#method-c-clock_gettime).
168
- `monotime` currently only uses `Process::CLOCK_MONOTONIC`, but others may offer higher precision
169
- 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.
170
179
 
171
180
  ### Other Gems
172
181
 
173
182
  [hitimes](https://rubygems.org/gems/hitimes) is a popular and mature alternative
174
183
  which also includes a variety of features for gathering statistics about
175
- measurements, and may offer higher precision on some platforms.
184
+ measurements.
176
185
 
177
- Note until [#73](https://github.com/copiousfreetime/hitimes/pull/73) is closed it
178
- depends on compiled C/Java extensions.
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,15 +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
+ freeze
17
18
  end
18
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
+
19
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
+
20
62
  # Generate a new +Duration+ measuring the given number of seconds.
21
63
  #
22
64
  # @param secs [Numeric]
@@ -86,7 +128,7 @@ module Monotime
86
128
  # @example
87
129
  # (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
88
130
  #
89
- # @param [Duration, #to_nanos]
131
+ # @param other [Duration, #to_nanos]
90
132
  # @return [Duration]
91
133
  def +(other)
92
134
  raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
@@ -100,7 +142,7 @@ module Monotime
100
142
  # @example
101
143
  # (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
102
144
  #
103
- # @param [Duration, #to_nanos]
145
+ # @param other [Duration, #to_nanos]
104
146
  # @return [Duration]
105
147
  def -(other)
106
148
  raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
@@ -113,7 +155,7 @@ module Monotime
113
155
  # @example
114
156
  # (Duration.from_secs(10) / 2).to_s # => "5s"
115
157
  #
116
- # @param [Numeric]
158
+ # @param other [Numeric]
117
159
  # @return [Duration]
118
160
  def /(other)
119
161
  Duration.new(to_nanos / other)
@@ -124,7 +166,7 @@ module Monotime
124
166
  # @example
125
167
  # (Duration.from_secs(10) * 2).to_s # => "20s"
126
168
  #
127
- # @param [Numeric]
169
+ # @param other [Numeric]
128
170
  # @return [Duration]
129
171
  def *(other)
130
172
  Duration.new(to_nanos * other)
@@ -157,7 +199,7 @@ module Monotime
157
199
  # Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
158
200
  # object, or nil if not comparable.
159
201
  #
160
- # @param [Duration, #to_nanos, Object]
202
+ # @param other [Duration, #to_nanos, Object]
161
203
  # @return [-1, 0, 1, nil]
162
204
  def <=>(other)
163
205
  to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
@@ -166,7 +208,7 @@ module Monotime
166
208
  # Compare the equality of the *value* of this +Duration+ with another, or
167
209
  # any +#to_nanos+-coercible object, or nil if not comparable.
168
210
  #
169
- # @param [Duration, #to_nanos, Object]
211
+ # @param other [Duration, #to_nanos, Object]
170
212
  # @return [Boolean]
171
213
  def ==(other)
172
214
  other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
@@ -174,7 +216,7 @@ module Monotime
174
216
 
175
217
  # Check equality of the value and type of this +Duration+ with another.
176
218
  #
177
- # @param [Duration, Object]
219
+ # @param other [Duration, Object]
178
220
  # @return [Boolean]
179
221
  def eql?(other)
180
222
  other.is_a?(Duration) && to_nanos == other.to_nanos
@@ -184,7 +226,7 @@ module Monotime
184
226
  #
185
227
  # @return [Integer]
186
228
  def hash
187
- self.class.hash ^ to_nanos.hash
229
+ [self.class, to_nanos].hash
188
230
  end
189
231
 
190
232
  # Return this +Duration+ in seconds.
@@ -254,6 +296,8 @@ module Monotime
254
296
  # Sleep for the duration of this +Duration+. Equivalent to
255
297
  # +Kernel.sleep(duration.to_secs)+.
256
298
  #
299
+ # The sleep function may be overridden globally using +Duration.sleep_function=+
300
+ #
257
301
  # @example
258
302
  # Duration.from_secs(1).sleep # => 1
259
303
  # Duration.from_secs(-1).sleep # => raises NotImplementedError
@@ -261,10 +305,11 @@ module Monotime
261
305
  # @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
262
306
  # @return [Integer]
263
307
  # @see Instant#sleep
308
+ # @see sleep_function=
264
309
  def sleep
265
310
  raise NotImplementedError, 'time travel module missing' if negative?
266
311
 
267
- Kernel.sleep(to_secs)
312
+ self.class.sleep_function.call(to_secs)
268
313
  end
269
314
 
270
315
  DIVISORS = [
@@ -279,6 +324,8 @@ module Monotime
279
324
  # Format this +Duration+ into a human-readable string, with a given number
280
325
  # of decimal places.
281
326
  #
327
+ # The default precision may be set globally using +Duration.default_to_s_precision=+
328
+ #
282
329
  # The exact format is subject to change, users with specific requirements
283
330
  # are encouraged to use their own formatting methods.
284
331
  #
@@ -292,7 +339,8 @@ module Monotime
292
339
  #
293
340
  # @param precision [Integer] the maximum number of decimal places
294
341
  # @return [String]
295
- def to_s(precision = 9)
342
+ # @see default_to_s_precision=
343
+ def to_s(precision = self.class.default_to_s_precision)
296
344
  precision = Integer(precision).abs
297
345
  div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
298
346
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monotime'
4
+
5
+ include Monotime
@@ -11,14 +11,108 @@ 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)
115
+ freeze
22
116
  end
23
117
 
24
118
  # An alias to +new+, and generally preferred over it.
@@ -111,8 +205,8 @@ module Monotime
111
205
  # Sugar for +#elapsed.to_s+.
112
206
  #
113
207
  # @see Duration#to_s
114
- def to_s(*args)
115
- elapsed.to_s(*args)
208
+ def to_s(...)
209
+ elapsed.to_s(...)
116
210
  end
117
211
 
118
212
  # Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
@@ -172,7 +266,7 @@ module Monotime
172
266
  #
173
267
  # @return [Integer]
174
268
  def hash
175
- self.class.hash ^ @ns.hash
269
+ [self.class, @ns].hash
176
270
  end
177
271
  end
178
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.0'
4
+ # Version of the `monotime` gem
5
+ MONOTIME_VERSION = '0.8.0'
6
6
  end
data/monotime.gemspec CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.require_paths = ["lib"]
33
33
 
34
34
  spec.add_development_dependency "bundler", "~> 2"
35
- spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "rake", "~> 13.0"
36
36
  spec.add_development_dependency "minitest", "~> 5.0"
37
+ spec.add_development_dependency "simplecov", "~> 0.21"
37
38
  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.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Hurst
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-24 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
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,16 +52,30 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
- description:
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.21'
69
+ description:
56
70
  email:
57
71
  - tom@hur.st
58
72
  executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
76
+ - ".github/workflows/ci.yml"
62
77
  - ".gitignore"
63
78
  - ".rubocop.yml"
64
- - ".travis.yml"
65
79
  - CHANGELOG.md
66
80
  - Gemfile
67
81
  - LICENSE.txt
@@ -71,6 +85,7 @@ files:
71
85
  - bin/setup
72
86
  - lib/monotime.rb
73
87
  - lib/monotime/duration.rb
88
+ - lib/monotime/include.rb
74
89
  - lib/monotime/instant.rb
75
90
  - lib/monotime/version.rb
76
91
  - monotime.gemspec
@@ -79,7 +94,7 @@ licenses:
79
94
  - MIT
80
95
  metadata:
81
96
  allowed_push_host: https://rubygems.org
82
- post_install_message:
97
+ post_install_message:
83
98
  rdoc_options: []
84
99
  require_paths:
85
100
  - lib
@@ -94,9 +109,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
109
  - !ruby/object:Gem::Version
95
110
  version: '0'
96
111
  requirements: []
97
- rubyforge_project:
98
- rubygems_version: 2.7.6
99
- signing_key:
112
+ rubygems_version: 3.4.19
113
+ signing_key:
100
114
  specification_version: 4
101
115
  summary: A sensible interface to the monotonic clock
102
116
  test_files: []
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.4.6
7
- - 2.5.5
8
- - 2.6.3
9
- - jruby-9.2.7.0
10
- before_install: gem install bundler -v "~> 2"