monotime 0.7.0 → 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: 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"