monotime 0.5.0 → 0.7.1

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: 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