monotime 0.5.0 → 0.7.1

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: bbb84fdd4db4d629ca5d4cc847c47d9003f4553f010b575364f7ad562c16bb4d
4
- data.tar.gz: 40729463ba3970e77110c2ae92620f4d6ca08b6d9997776272a3d0a05a03b595
3
+ metadata.gz: 8a9395967e506d690912041251bc7b6afd1a373aa49d0b4425ec08ca611e5238
4
+ data.tar.gz: '09ec6a1e96c9ce434d4847d275d85f7514c4a9bb3d9c71d205b04ab0f06e6d5b'
5
5
  SHA512:
6
- metadata.gz: 2916513d7d5dada28d18855d7644055cf82ddd6d2ad66ebd27c7a73d9ae2c88cde94887e7769d007372768c7acbcac32d8210308f9bc2ba2256b150126f29d9b
7
- data.tar.gz: cff3a4bd7be443460f7dfc65432da3f3d30fef62433e5caa73c932497d490fdc5641b34659f18d8790bb0797449d93b9019f46678c0489b3aa9756c871222bd1
6
+ metadata.gz: a68a338484a8534a8953abf5bec0a98a6b7a2dc6a506cd1f5fb1e3b29e1b534fa0d41e4175aa81598273ee5d706d16caeb60ce1b89a92b5e8192f87a960265f3
7
+ data.tar.gz: f9f329d74339eccc1b4c50007d2cb93cc87132ce6a5841faa327d4db48dfb347df8faee3f9777551cb686e90001def65d72c8a77c6b6df4dfed1d847598ac685
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ fail-fast: false
8
+ matrix:
9
+ ruby: ['2.5', '2.6', '2.7', '3.0', jruby, truffleruby]
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: ${{ matrix.ruby }}
15
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
16
+ - run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,6 +1,18 @@
1
- Metrics/LineLength:
1
+ AllCops:
2
+ NewCops: enable
3
+ Exclude:
4
+ - "bin/*"
5
+ - "test/*"
6
+ - "*.gemspec"
7
+ - "Rakefile"
8
+ - "Gemfile"
9
+
10
+ Layout/LineLength:
2
11
  Max: 96
3
12
 
13
+ Metrics/ClassLength:
14
+ Max: 120
15
+
4
16
  Style/AsciiComments:
5
17
  Enabled: false
6
18
 
@@ -9,3 +21,18 @@ Style/AccessModifierDeclarations:
9
21
 
10
22
  Style/FormatStringToken:
11
23
  Enabled: false
24
+
25
+ Style/HashEachMethods:
26
+ Enabled: true
27
+
28
+ Style/HashTransformKeys:
29
+ Enabled: true
30
+
31
+ Style/HashTransformValues:
32
+ Enabled: true
33
+
34
+ Style/ExplicitBlockArgument:
35
+ Enabled: false
36
+
37
+ Style/MixinUsage:
38
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,108 @@
1
+ # Changelog
2
+
3
+ ## 0.7.1 - 2021-10-22
4
+ ### Added
5
+ - `simplecov` introduced to test suite.
6
+ - `monotime/include.rb` to auto-include types globally.
7
+
8
+ ### Changed
9
+ - All `Instant` and `Duration` instances are now frozen.
10
+ - Migrate from Travis CI to Github Actions
11
+ - Update development dependency on `rake`.
12
+
13
+ ## [0.7.0] - 2019-04-24
14
+ ### Added
15
+ - `Duration.with_measure`, which yields and returns an array containing its
16
+ evaluated return value and its `Duration`.
17
+
18
+ ### 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).
24
+
25
+ ## [0.6.1] - 2018-10-26
26
+ ### Fixed
27
+ - Build gem from a clean git checkout, not my local development directory.
28
+ No functional changes.
29
+
30
+ ## [0.6.0] - 2018-10-26
31
+ ### 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?`.
38
+
39
+ ## [0.5.0] - 2018-10-13
40
+ ### 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?`
46
+
47
+ ### 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`.
53
+
54
+ ## [0.4.0] - 2018-10-09
55
+ ### 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.
61
+
62
+ ### Changed
63
+ - More `#to_nanos` `Duration` duck-typing.
64
+
65
+ ## [0.3.0] - 2018-10-04
66
+ ### Added
67
+ - `#to_nanos` is now used to duck-type `Duration` everywhere.
68
+
69
+ ### Changed
70
+ - Make `<=>` return nil on invalid types, rather than raising a `TypeError`.
71
+
72
+ ### Removed
73
+ - Dependency on `dry-equalizer`.
74
+
75
+ ## [0.2.0] - 2018-10-03
76
+ ### Added
77
+ - `Instant#to_s` as an alias for `#elapsed.to_s`
78
+ - `Duration#to_nanos`, with some limited duck-typing.
79
+
80
+ ### Changed
81
+ - Switch to microseconds internally.
82
+ - `Duration#to_{secs,millis,micros}` now return a `Float`.
83
+ - `Instant#ns` is now `protected`.
84
+
85
+ ### 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.
89
+
90
+ ### Removed
91
+ - `Instant` and `Duration` maths methods no longer support passing an `Integer`
92
+ number of nanoseconds.
93
+
94
+ ## [0.1.0] - 2018-10-02
95
+ ### Added
96
+ - Initial release
97
+
98
+
99
+ [0.1.0]: https://github.com/Freaky/monotime/commits/v0.1.0
100
+ [0.2.0]: https://github.com/Freaky/monotime/commits/v0.2.0
101
+ [0.3.0]: https://github.com/Freaky/monotime/commits/v0.3.0
102
+ [0.4.0]: https://github.com/Freaky/monotime/commits/v0.4.0
103
+ [0.5.0]: https://github.com/Freaky/monotime/commits/v0.5.0
104
+ [0.6.0]: https://github.com/Freaky/monotime/commits/v0.6.0
105
+ [0.6.1]: https://github.com/Freaky/monotime/commits/v0.6.1
106
+ [0.7.0]: https://github.com/Freaky/monotime/commits/v0.7.0
107
+ [issue #1]: https://github.com/Freaky/monotime/issues/1
108
+ [@celsworth]: https://github.com/celsworth
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
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)
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)
3
5
 
4
6
  # Monotime
5
7
 
@@ -21,66 +23,75 @@ Or install it yourself as:
21
23
 
22
24
  $ gem install monotime
23
25
 
24
- ## Usage
26
+ `Monotime` is tested on Ruby 2.5&mdash;3.0 and recent JRuby 9.x releases.
25
27
 
26
- The typical way everyone does "correct" elapsed-time measurements in Ruby is
27
- this pile of nonsense:
28
+ ## Usage
28
29
 
29
30
  ```ruby
30
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
31
- do_something
32
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
31
+ require 'monotime'
32
+ # or, to automatically include Monotime::* in the global scope,
33
+ # as used by these examples:
34
+ require 'monotime/include'
33
35
  ```
34
36
 
35
- Not only is it long-winded, it's imprecise, converting to floating point instead
36
- of working off precise timestamps.
37
+ `Monotime` offers a `Duration` type for describing spans of time, and an
38
+ `Instant` type for describing points in time. Both operate at nanosecond
39
+ resolution to the limits of whatever your Ruby implementation supports.
37
40
 
38
- `Monotime` offers this alternative:
41
+ For example, to measure an elapsed time, either create an `Instant` to mark the
42
+ start point, perform the action and then ask for the `Duration` that has elapsed
43
+ since:
39
44
 
40
45
  ```ruby
41
- include Monotime
42
-
43
46
  start = Instant.now
44
47
  do_something
45
48
  elapsed = start.elapsed
49
+ ```
46
50
 
47
- # or
51
+ Or use a convenience method:
52
+
53
+ ```ruby
48
54
  elapsed = Duration.measure { do_something }
55
+ # or
56
+ return_value, elapsed = Duration.with_measure { compute_something }
49
57
  ```
50
58
 
51
- `elapsed` is not a dimensionless `Float`, but a `Duration` type, and internally
52
- both `Instant` and `Duration` operate in *nanoseconds* to most closely match
53
- the native timekeeping types used by most operating systems.
59
+ `Duration` offers formatting:
60
+
61
+ ```ruby
62
+ Duration.millis(42).to_s # => "42ms"
63
+ Duration.nanos(12345).to_s # => "12.345μs"
64
+ Duration.secs(1.12345).to_s(2) # => "1.12s"
65
+ ```
54
66
 
55
- `Duration` knows how to format itself:
67
+ Conversions:
56
68
 
57
69
  ```ruby
58
- Duration.from_millis(42).to_s # => "42ms"
59
- Duration.from_nanos(12345).to_s # => "12.345μs"
60
- Duration.from_secs(1.12345).to_s(2) # => "1.12s"
70
+ Duration.secs(10).millis # => 10000.0
71
+ Duration.micros(12345).secs # => 0.012345
61
72
  ```
62
73
 
63
- And how to do basic maths on itself:
74
+ And basic mathematical operations:
64
75
 
65
76
  ```ruby
66
- (Duration.from_millis(42) + Duration.from_secs(1)).to_s # => "1.042s"
67
- (Duration.from_millis(42) - Duration.from_secs(1)).to_s # => "-958ms"
68
- (Duration.from_secs(42) * 2).to_s # => "84s"
69
- (Duration.from_secs(42) / 2).to_s # => "21s"
77
+ (Duration.millis(42) + Duration.secs(1)).to_s # => "1.042s"
78
+ (Duration.millis(42) - Duration.secs(1)).to_s # => "-958ms"
79
+ (Duration.secs(42) * 2).to_s # => "84s"
80
+ (Duration.secs(42) / 2).to_s # => "21s"
70
81
  ```
71
82
 
72
83
  `Instant` does some simple maths too:
73
84
 
74
85
  ```ruby
75
86
  # Instant - Duration => Instant
76
- (Instant.now - Duration.from_secs(1)).elapsed.to_s # => "1.000014627s"
87
+ (Instant.now - Duration.secs(1)).elapsed.to_s # => "1.000014627s"
77
88
 
78
89
  # Instant - Instant => Duration
79
90
  (Instant.now - Instant.now).to_s # => "-5.585μs"
80
91
  ```
81
92
 
82
93
  `Duration` and `Instant` are also `Comparable` with other instances of their
83
- type, and support `#hash` for use in, er, hashes.
94
+ type, and can be used in hashes, sets, and similar structures.
84
95
 
85
96
  ## Sleeping
86
97
 
@@ -89,35 +100,38 @@ is not yet implemented):
89
100
 
90
101
  ```ruby
91
102
  # Equivalent
92
- sleep(Duration.from_secs(1).to_secs) # => 1
93
-
94
- Duration.from_secs(1).sleep # => 1
103
+ sleep(Duration.secs(1).secs) # => 1
104
+ Duration.secs(1).sleep # => 1
95
105
  ```
96
106
 
97
107
  So can `Instant`, taking a `Duration` and sleeping until the given `Duration`
98
- past the time the `Instant` was created, if any. This may be useful if you wish
99
- to maintain an approximate interval while performing work in between:
108
+ past the time the `Instant` was created, if any. This can be useful for
109
+ maintaining a precise candence between tasks:
100
110
 
101
111
  ```ruby
102
- poke_duration = Duration.from_secs(60)
112
+ interval = Duration.secs(60)
113
+ start = Instant.now
103
114
  loop do
104
- start = Instant.now
105
- poke_my_api(api_to_poke, what_to_poke_it_with)
106
- start.sleep(poke_duration) # sleeps 60 seconds minus how long poke_my_api took
107
- # alternative: start.sleep_secs(60)
115
+ do_stuff
116
+ start.sleep(interval)
117
+ start += interval
108
118
  end
109
119
  ```
110
120
 
111
- Or you can declare a future `Instant` and ask to sleep until it passes:
121
+ Or you can declare an `Instant` in the future and sleep to that point:
112
122
 
113
123
  ```ruby
114
- next_minute = Instant.now + Duration.from_secs(60)
115
- do_stuff
116
- next_minute.sleep # => sleeps any remaining seconds
124
+ interval = Duration.secs(60)
125
+ deadline = Instant.now + interval
126
+ loop do
127
+ do_stuff
128
+ deadline.sleep
129
+ deadline += interval
130
+ end
117
131
  ```
118
132
 
119
- `Instant#sleep` returns a `Duration` which was slept, or a negative `Duration` if
120
- the desired sleep period has passed.
133
+ `Instant#sleep` returns a `Duration` which was slept, or a negative `Duration`
134
+ indicating that the desired sleep point was in the past.
121
135
 
122
136
  ## Duration duck typing
123
137
 
@@ -133,8 +147,8 @@ class Numeric
133
147
  end
134
148
  end
135
149
 
136
- (Duration.from_secs(1) + 41).to_s # => "42s"
137
- (Instant.now - 42).to_s # => "42.000010545s"
150
+ (Duration.secs(1) + 41).to_s # => "42s"
151
+ (Instant.now - 42).to_s # => "42.000010545s"
138
152
  ```
139
153
 
140
154
  ## Development
@@ -150,3 +164,19 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Freaky
150
164
  ## License
151
165
 
152
166
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
167
+
168
+ ## See Also
169
+
170
+ ### Core Ruby
171
+
172
+ For a zero-dependency alternative, see
173
+ [`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.
176
+
177
+ ### Other Gems
178
+
179
+ [hitimes](https://rubygems.org/gems/hitimes) is a popular and mature alternative
180
+ which also includes a variety of features for gathering statistics about
181
+ measurements, and may offer higher precision on some platforms.
182
+
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Monotime
4
+ # A type representing a span of time in nanoseconds.
5
+ class Duration
6
+ include Comparable
7
+
8
+ # Create a new +Duration+ of a specified number of nanoseconds, zero by
9
+ # default.
10
+ #
11
+ # Users are strongly advised to use +#from_nanos+ instead.
12
+ #
13
+ # @param nanos [Integer]
14
+ # @see #from_nanos
15
+ def initialize(nanos = 0)
16
+ @ns = Integer(nanos)
17
+ freeze
18
+ end
19
+
20
+ class << self
21
+ # Generate a new +Duration+ measuring the given number of seconds.
22
+ #
23
+ # @param secs [Numeric]
24
+ # @return [Duration]
25
+ def from_secs(secs)
26
+ new(Integer(secs * 1_000_000_000))
27
+ end
28
+
29
+ alias secs from_secs
30
+
31
+ # Generate a new +Duration+ measuring the given number of milliseconds.
32
+ #
33
+ # @param millis [Numeric]
34
+ # @return [Duration]
35
+ def from_millis(millis)
36
+ new(Integer(millis * 1_000_000))
37
+ end
38
+
39
+ alias millis from_millis
40
+
41
+ # Generate a new +Duration+ measuring the given number of microseconds.
42
+ #
43
+ # @param micros [Numeric]
44
+ # @return [Duration]
45
+ def from_micros(micros)
46
+ new(Integer(micros * 1_000))
47
+ end
48
+
49
+ alias micros from_micros
50
+
51
+ # Generate a new +Duration+ measuring the given number of nanoseconds.
52
+ #
53
+ # @param nanos [Numeric]
54
+ # @return [Duration]
55
+ def from_nanos(nanos)
56
+ new(Integer(nanos))
57
+ end
58
+
59
+ alias nanos from_nanos
60
+
61
+ # Return a +Duration+ measuring the elapsed time of the yielded block.
62
+ #
63
+ # @example
64
+ # Duration.measure { sleep(0.5) }.to_s # => "512.226109ms"
65
+ #
66
+ # @return [Duration]
67
+ def measure
68
+ Instant.now.tap { yield }.elapsed
69
+ end
70
+
71
+ # Return the result of the yielded block alongside a +Duration+.
72
+ #
73
+ # @example
74
+ # Duration.with_measure { "bloop" } # => ["bloop", #<Monotime::Duration: ...>]
75
+ #
76
+ # @return [Object, Duration]
77
+ def with_measure
78
+ start = Instant.now
79
+ ret = yield
80
+ [ret, start.elapsed]
81
+ end
82
+ end
83
+
84
+ # Add another +Duration+ or +#to_nanos+-coercible object to this one,
85
+ # returning a new +Duration+.
86
+ #
87
+ # @example
88
+ # (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
89
+ #
90
+ # @param [Duration, #to_nanos]
91
+ # @return [Duration]
92
+ def +(other)
93
+ raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
94
+
95
+ Duration.new(to_nanos + other.to_nanos)
96
+ end
97
+
98
+ # Subtract another +Duration+ or +#to_nanos+-coercible object from this one,
99
+ # returning a new +Duration+.
100
+ #
101
+ # @example
102
+ # (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
103
+ #
104
+ # @param [Duration, #to_nanos]
105
+ # @return [Duration]
106
+ def -(other)
107
+ raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
108
+
109
+ Duration.new(to_nanos - other.to_nanos)
110
+ end
111
+
112
+ # Divide this duration by a +Numeric+.
113
+ #
114
+ # @example
115
+ # (Duration.from_secs(10) / 2).to_s # => "5s"
116
+ #
117
+ # @param [Numeric]
118
+ # @return [Duration]
119
+ def /(other)
120
+ Duration.new(to_nanos / other)
121
+ end
122
+
123
+ # Multiply this duration by a +Numeric+.
124
+ #
125
+ # @example
126
+ # (Duration.from_secs(10) * 2).to_s # => "20s"
127
+ #
128
+ # @param [Numeric]
129
+ # @return [Duration]
130
+ def *(other)
131
+ Duration.new(to_nanos * other)
132
+ end
133
+
134
+ # Unary minus: make a positive +Duration+ negative, and vice versa.
135
+ #
136
+ # @example
137
+ # -Duration.from_secs(-1).to_s # => "1s"
138
+ # -Duration.from_secs(1).to_s # => "-1s"
139
+ #
140
+ # @return [Duration]
141
+ def -@
142
+ Duration.new(-to_nanos)
143
+ end
144
+
145
+ # Return a +Duration+ that's absolute (positive).
146
+ #
147
+ # @example
148
+ # Duration.from_secs(-1).abs.to_s # => "1s"
149
+ # Duration.from_secs(1).abs.to_s # => "1s"
150
+ #
151
+ # @return [Duration]
152
+ def abs
153
+ return self if positive? || zero?
154
+
155
+ Duration.new(to_nanos.abs)
156
+ end
157
+
158
+ # Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
159
+ # object, or nil if not comparable.
160
+ #
161
+ # @param [Duration, #to_nanos, Object]
162
+ # @return [-1, 0, 1, nil]
163
+ def <=>(other)
164
+ to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
165
+ end
166
+
167
+ # Compare the equality of the *value* of this +Duration+ with another, or
168
+ # any +#to_nanos+-coercible object, or nil if not comparable.
169
+ #
170
+ # @param [Duration, #to_nanos, Object]
171
+ # @return [Boolean]
172
+ def ==(other)
173
+ other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
174
+ end
175
+
176
+ # Check equality of the value and type of this +Duration+ with another.
177
+ #
178
+ # @param [Duration, Object]
179
+ # @return [Boolean]
180
+ def eql?(other)
181
+ other.is_a?(Duration) && to_nanos == other.to_nanos
182
+ end
183
+
184
+ # Generate a hash for this type and value.
185
+ #
186
+ # @return [Integer]
187
+ def hash
188
+ self.class.hash ^ to_nanos.hash
189
+ end
190
+
191
+ # Return this +Duration+ in seconds.
192
+ #
193
+ # @return [Float]
194
+ def to_secs
195
+ to_nanos / 1_000_000_000.0
196
+ end
197
+
198
+ alias secs to_secs
199
+
200
+ # Return this +Duration+ in milliseconds.
201
+ #
202
+ # @return [Float]
203
+ def to_millis
204
+ to_nanos / 1_000_000.0
205
+ end
206
+
207
+ alias millis to_millis
208
+
209
+ # Return this +Duration+ in microseconds.
210
+ #
211
+ # @return [Float]
212
+ def to_micros
213
+ to_nanos / 1_000.0
214
+ end
215
+
216
+ alias micros to_micros
217
+
218
+ # Return this +Duration+ in nanoseconds.
219
+ #
220
+ # @return [Integer]
221
+ def to_nanos
222
+ @ns
223
+ end
224
+
225
+ alias nanos to_nanos
226
+
227
+ # Return true if this +Duration+ is positive.
228
+ #
229
+ # @return [Boolean]
230
+ def positive?
231
+ to_nanos.positive?
232
+ end
233
+
234
+ # Return true if this +Duration+ is negative.
235
+ #
236
+ # @return [Boolean]
237
+ def negative?
238
+ to_nanos.negative?
239
+ end
240
+
241
+ # Return true if this +Duration+ is zero.
242
+ #
243
+ # @return [Boolean]
244
+ def zero?
245
+ to_nanos.zero?
246
+ end
247
+
248
+ # Return true if this +Duration+ is non-zero.
249
+ #
250
+ # @return [Boolean]
251
+ def nonzero?
252
+ to_nanos.nonzero?
253
+ end
254
+
255
+ # Sleep for the duration of this +Duration+. Equivalent to
256
+ # +Kernel.sleep(duration.to_secs)+.
257
+ #
258
+ # @example
259
+ # Duration.from_secs(1).sleep # => 1
260
+ # Duration.from_secs(-1).sleep # => raises NotImplementedError
261
+ #
262
+ # @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
263
+ # @return [Integer]
264
+ # @see Instant#sleep
265
+ def sleep
266
+ raise NotImplementedError, 'time travel module missing' if negative?
267
+
268
+ Kernel.sleep(to_secs)
269
+ end
270
+
271
+ DIVISORS = [
272
+ [1_000_000_000.0, 's'],
273
+ [1_000_000.0, 'ms'],
274
+ [1_000.0, 'μs'],
275
+ [0, 'ns']
276
+ ].map(&:freeze).freeze
277
+
278
+ private_constant :DIVISORS
279
+
280
+ # Format this +Duration+ into a human-readable string, with a given number
281
+ # of decimal places.
282
+ #
283
+ # The exact format is subject to change, users with specific requirements
284
+ # are encouraged to use their own formatting methods.
285
+ #
286
+ # @example
287
+ # Duration.from_nanos(100).to_s # => "100ns"
288
+ # Duration.from_micros(100).to_s # => "100μs"
289
+ # Duration.from_millis(100).to_s # => "100ms"
290
+ # Duration.from_secs(100).to_s # => "100s"
291
+ # Duration.from_nanos(1234567).to_s # => "1.234567ms"
292
+ # Duration.from_nanos(1234567).to_s(2) # => "1.23ms"
293
+ #
294
+ # @param precision [Integer] the maximum number of decimal places
295
+ # @return [String]
296
+ def to_s(precision = 9)
297
+ precision = Integer(precision).abs
298
+ div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
299
+
300
+ if div.zero?
301
+ format('%d%s', to_nanos, unit)
302
+ else
303
+ format("%#.#{precision}f", to_nanos / div).sub(/\.?0*\z/, '') << unit
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monotime'
4
+
5
+ include Monotime