monotime 0.6.1 → 0.7.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 +4 -4
- data/.rubocop.yml +11 -0
- data/.travis.yml +5 -3
- data/CHANGELOG.md +15 -1
- data/README.md +61 -35
- data/lib/monotime.rb +3 -467
- data/lib/monotime/duration.rb +306 -0
- data/lib/monotime/instant.rb +178 -0
- data/lib/monotime/version.rb +2 -1
- data/monotime.gemspec +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c88bca715126cabf9bf5ea1af377e16d43da5ecc06ee45f98dad584360eaa15b
|
4
|
+
data.tar.gz: 8f741d6219cfe4b9050f892f2df905ed603f859e4a79f930a4eb954c213bf639
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ed67631407691f935ffdd30727c4054b3fa7f5964a9cdcb0db0f94e1a2aecddb83eac4b1e4a8e032cd3c57145152e6649ddf14093643329ff23b5d43348000f
|
7
|
+
data.tar.gz: f38b77609652866fa4c1b7bfee164977a7f7895feeb0f76d8dd5e71a05da31b0cda24c680f5b2aef19c3f5c7787da237f580ee56ccb86e456dae576fc95d0e89
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.7.0] - 2019-04-24
|
4
|
+
### Added
|
5
|
+
- `Duration.with_measure`, which yields and returns an array containing its
|
6
|
+
evaluated return value and its `Duration`.
|
7
|
+
|
8
|
+
### 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).
|
14
|
+
|
15
|
+
## [0.6.1] - 2018-10-26
|
4
16
|
### Fixed
|
5
17
|
- Build gem from a clean git checkout, not my local development directory.
|
6
18
|
No functional changes.
|
@@ -81,4 +93,6 @@
|
|
81
93
|
[0.5.0]: https://github.com/Freaky/monotime/commits/v0.5.0
|
82
94
|
[0.6.0]: https://github.com/Freaky/monotime/commits/v0.6.0
|
83
95
|
[0.6.1]: https://github.com/Freaky/monotime/commits/v0.6.1
|
96
|
+
[0.7.0]: https://github.com/Freaky/monotime/commits/v0.7.0
|
97
|
+
[issue #1]: https://github.com/Freaky/monotime/issues/1
|
84
98
|
[@celsworth]: https://github.com/celsworth
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
[](https://badge.fury.io/rb/monotime)
|
2
2
|
[](https://travis-ci.org/Freaky/monotime)
|
3
|
+
[](http://inch-ci.org/github/Freaky/monotime)
|
4
|
+
[](https://www.rubydoc.info/gems/monotime)
|
3
5
|
|
4
6
|
# Monotime
|
5
7
|
|
@@ -21,21 +23,17 @@ Or install it yourself as:
|
|
21
23
|
|
22
24
|
$ gem install monotime
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
The typical way everyone does "correct" elapsed-time measurements in Ruby is
|
27
|
-
this pile of nonsense:
|
26
|
+
`Monotime` is tested on Ruby 2.4—2.6 and recent JRuby 9.x releases.
|
28
27
|
|
29
|
-
|
30
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
31
|
-
do_something
|
32
|
-
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
33
|
-
```
|
28
|
+
## Usage
|
34
29
|
|
35
|
-
|
36
|
-
|
30
|
+
`Monotime` offers a `Duration` type for describing spans of time, and an
|
31
|
+
`Instant` type for describing points in time. Both operate at nanosecond
|
32
|
+
resolution to the limits of whatever your Ruby implementation supports.
|
37
33
|
|
38
|
-
`
|
34
|
+
For example, to measure an elapsed time, either create an `Instant` to mark the
|
35
|
+
start point, perform the action and then ask for the `Duration` that has elapsed
|
36
|
+
since:
|
39
37
|
|
40
38
|
```ruby
|
41
39
|
include Monotime
|
@@ -43,44 +41,54 @@ include Monotime
|
|
43
41
|
start = Instant.now
|
44
42
|
do_something
|
45
43
|
elapsed = start.elapsed
|
44
|
+
```
|
46
45
|
|
47
|
-
|
46
|
+
Or use a convenience method:
|
47
|
+
|
48
|
+
```ruby
|
48
49
|
elapsed = Duration.measure { do_something }
|
50
|
+
|
51
|
+
# or
|
52
|
+
|
53
|
+
return_value, elapsed = Duration.with_measure { compute_something }
|
49
54
|
```
|
50
55
|
|
51
|
-
`
|
52
|
-
|
53
|
-
|
56
|
+
`Duration` offers formatting:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Duration.millis(42).to_s # => "42ms"
|
60
|
+
Duration.nanos(12345).to_s # => "12.345μs"
|
61
|
+
Duration.secs(1.12345).to_s(2) # => "1.12s"
|
62
|
+
```
|
54
63
|
|
55
|
-
|
64
|
+
Conversions:
|
56
65
|
|
57
66
|
```ruby
|
58
|
-
Duration.
|
59
|
-
Duration.
|
60
|
-
Duration.from_secs(1.12345).to_s(2) # => "1.12s"
|
67
|
+
Duration.secs(10).millis # => 10000.0
|
68
|
+
Duration.micros(12345).secs # => 0.012345
|
61
69
|
```
|
62
70
|
|
63
|
-
And
|
71
|
+
And basic mathematical operations:
|
64
72
|
|
65
73
|
```ruby
|
66
|
-
(Duration.
|
67
|
-
(Duration.
|
68
|
-
(Duration.
|
69
|
-
(Duration.
|
74
|
+
(Duration.millis(42) + Duration.secs(1)).to_s # => "1.042s"
|
75
|
+
(Duration.millis(42) - Duration.secs(1)).to_s # => "-958ms"
|
76
|
+
(Duration.secs(42) * 2).to_s # => "84s"
|
77
|
+
(Duration.secs(42) / 2).to_s # => "21s"
|
70
78
|
```
|
71
79
|
|
72
80
|
`Instant` does some simple maths too:
|
73
81
|
|
74
82
|
```ruby
|
75
83
|
# Instant - Duration => Instant
|
76
|
-
(Instant.now - Duration.
|
84
|
+
(Instant.now - Duration.secs(1)).elapsed.to_s # => "1.000014627s"
|
77
85
|
|
78
86
|
# Instant - Instant => Duration
|
79
87
|
(Instant.now - Instant.now).to_s # => "-5.585μs"
|
80
88
|
```
|
81
89
|
|
82
90
|
`Duration` and `Instant` are also `Comparable` with other instances of their
|
83
|
-
type, and
|
91
|
+
type, and can be used in hashes, sets, and similar structures.
|
84
92
|
|
85
93
|
## Sleeping
|
86
94
|
|
@@ -89,9 +97,9 @@ is not yet implemented):
|
|
89
97
|
|
90
98
|
```ruby
|
91
99
|
# Equivalent
|
92
|
-
sleep(Duration.
|
100
|
+
sleep(Duration.secs(1).secs) # => 1
|
93
101
|
|
94
|
-
Duration.
|
102
|
+
Duration.secs(1).sleep # => 1
|
95
103
|
```
|
96
104
|
|
97
105
|
So can `Instant`, taking a `Duration` and sleeping until the given `Duration`
|
@@ -99,7 +107,7 @@ past the time the `Instant` was created, if any. This may be useful if you wish
|
|
99
107
|
to maintain an approximate interval while performing work in between:
|
100
108
|
|
101
109
|
```ruby
|
102
|
-
poke_duration = Duration.
|
110
|
+
poke_duration = Duration.secs(60)
|
103
111
|
loop do
|
104
112
|
start = Instant.now
|
105
113
|
poke_my_api(api_to_poke, what_to_poke_it_with)
|
@@ -111,13 +119,13 @@ end
|
|
111
119
|
Or you can declare a future `Instant` and ask to sleep until it passes:
|
112
120
|
|
113
121
|
```ruby
|
114
|
-
next_minute = Instant.now + Duration.
|
122
|
+
next_minute = Instant.now + Duration.secs(60)
|
115
123
|
do_stuff
|
116
124
|
next_minute.sleep # => sleeps any remaining seconds
|
117
125
|
```
|
118
126
|
|
119
|
-
`Instant#sleep` returns a `Duration` which was slept, or a negative `Duration`
|
120
|
-
the desired sleep period has passed.
|
127
|
+
`Instant#sleep` returns a `Duration` which was slept, or a negative `Duration`
|
128
|
+
if the desired sleep period has passed.
|
121
129
|
|
122
130
|
## Duration duck typing
|
123
131
|
|
@@ -133,8 +141,8 @@ class Numeric
|
|
133
141
|
end
|
134
142
|
end
|
135
143
|
|
136
|
-
(Duration.
|
137
|
-
(Instant.now - 42).to_s
|
144
|
+
(Duration.secs(1) + 41).to_s # => "42s"
|
145
|
+
(Instant.now - 42).to_s # => "42.000010545s"
|
138
146
|
```
|
139
147
|
|
140
148
|
## Development
|
@@ -150,3 +158,21 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Freaky
|
|
150
158
|
## License
|
151
159
|
|
152
160
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
161
|
+
|
162
|
+
## See Also
|
163
|
+
|
164
|
+
### Core Ruby
|
165
|
+
|
166
|
+
For a zero-dependency alternative, see
|
167
|
+
[`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.
|
170
|
+
|
171
|
+
### Other Gems
|
172
|
+
|
173
|
+
[hitimes](https://rubygems.org/gems/hitimes) is a popular and mature alternative
|
174
|
+
which also includes a variety of features for gathering statistics about
|
175
|
+
measurements, and may offer higher precision on some platforms.
|
176
|
+
|
177
|
+
Note until [#73](https://github.com/copiousfreetime/hitimes/pull/73) is closed it
|
178
|
+
depends on compiled C/Java extensions.
|
data/lib/monotime.rb
CHANGED
@@ -1,469 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# A measurement from the operating system's monotonic clock, with up to
|
7
|
-
# nanosecond precision.
|
8
|
-
class Instant
|
9
|
-
# A measurement, in nanoseconds. Should be considered opaque and
|
10
|
-
# non-portable outside the process that created it.
|
11
|
-
attr_reader :ns
|
12
|
-
protected :ns
|
13
|
-
|
14
|
-
include Comparable
|
15
|
-
|
16
|
-
# Create a new +Instant+ from an optional nanosecond measurement.
|
17
|
-
#
|
18
|
-
# Users should generally *not* pass anything to this function.
|
19
|
-
#
|
20
|
-
# @param nanos [Integer]
|
21
|
-
# @see #now
|
22
|
-
def initialize(nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))
|
23
|
-
@ns = Integer(nanos)
|
24
|
-
end
|
25
|
-
|
26
|
-
# An alias to +new+, and generally preferred over it.
|
27
|
-
#
|
28
|
-
# @return [Instant]
|
29
|
-
def self.now
|
30
|
-
new
|
31
|
-
end
|
32
|
-
|
33
|
-
# Return a +Duration+ between this +Instant+ and another.
|
34
|
-
#
|
35
|
-
# @param earlier [Instant]
|
36
|
-
# @return [Duration]
|
37
|
-
def duration_since(earlier)
|
38
|
-
raise TypeError, 'Not an Instant' unless earlier.is_a?(Instant)
|
39
|
-
|
40
|
-
earlier - self
|
41
|
-
end
|
42
|
-
|
43
|
-
# Return a +Duration+ since this +Instant+ and now.
|
44
|
-
#
|
45
|
-
# @return [Duration]
|
46
|
-
def elapsed
|
47
|
-
duration_since(self.class.now)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Return whether this +Instant+ is in the past.
|
51
|
-
#
|
52
|
-
# @return [Boolean]
|
53
|
-
def in_past?
|
54
|
-
elapsed.positive?
|
55
|
-
end
|
56
|
-
|
57
|
-
alias past? in_past?
|
58
|
-
|
59
|
-
# Return whether this +Instant+ is in the future.
|
60
|
-
#
|
61
|
-
# @return [Boolean]
|
62
|
-
def in_future?
|
63
|
-
elapsed.negative?
|
64
|
-
end
|
65
|
-
|
66
|
-
alias future? in_future?
|
67
|
-
|
68
|
-
# Sleep until this +Instant+, plus an optional +Duration+, returning a +Duration+
|
69
|
-
# that's either positive if any time was slept, or negative if sleeping would
|
70
|
-
# require time travel.
|
71
|
-
#
|
72
|
-
# @example Sleeps for a second
|
73
|
-
# start = Instant.now
|
74
|
-
# sleep 0.5 # do stuff for half a second
|
75
|
-
# start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
|
76
|
-
# start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)
|
77
|
-
#
|
78
|
-
# @example Also sleeps for a second.
|
79
|
-
# one_second_in_the_future = Instant.now + Duration.from_secs(1)
|
80
|
-
# one_second_in_the_future.sleep.to_s # => "985.592712ms" (slept)
|
81
|
-
# one_second_in_the_future.sleep.to_s # => "-4.71217ms" (did not sleep)
|
82
|
-
#
|
83
|
-
# @param duration [nil, Duration, #to_nanos]
|
84
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
85
|
-
def sleep(duration = nil)
|
86
|
-
remaining = duration ? duration - elapsed : -elapsed
|
87
|
-
|
88
|
-
remaining.tap { |rem| rem.sleep if rem.positive? }
|
89
|
-
end
|
90
|
-
|
91
|
-
# Sleep for the given number of seconds past this +Instant+, if any.
|
92
|
-
#
|
93
|
-
# Equivalent to +#sleep(Duration.from_secs(secs))+
|
94
|
-
#
|
95
|
-
# @param secs [Numeric] number of seconds to sleep past this +Instant+
|
96
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
97
|
-
# @see #sleep
|
98
|
-
def sleep_secs(secs)
|
99
|
-
sleep(Duration.from_secs(secs))
|
100
|
-
end
|
101
|
-
|
102
|
-
# Sleep for the given number of milliseconds past this +Instant+, if any.
|
103
|
-
#
|
104
|
-
# Equivalent to +#sleep(Duration.from_millis(millis))+
|
105
|
-
#
|
106
|
-
# @param millis [Numeric] number of milliseconds to sleep past this +Instant+
|
107
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
108
|
-
# @see #sleep
|
109
|
-
def sleep_millis(millis)
|
110
|
-
sleep(Duration.from_millis(millis))
|
111
|
-
end
|
112
|
-
|
113
|
-
# Sugar for +#elapsed.to_s+.
|
114
|
-
#
|
115
|
-
# @see Duration#to_s
|
116
|
-
def to_s(*args)
|
117
|
-
elapsed.to_s(*args)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
|
121
|
-
# a new +Instant+.
|
122
|
-
#
|
123
|
-
# @example
|
124
|
-
# (Instant.now + Duration.from_secs(1)).to_s # => "-999.983976ms"
|
125
|
-
#
|
126
|
-
# @param other [Duration, #to_nanos]
|
127
|
-
# @return [Instant]
|
128
|
-
def +(other)
|
129
|
-
return TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
130
|
-
|
131
|
-
Instant.new(@ns + other.to_nanos)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Subtract another +Instant+ to generate a +Duration+ between the two,
|
135
|
-
# or a +Duration+ or +#to_nanos+-coercible object, to generate an +Instant+
|
136
|
-
# offset by it.
|
137
|
-
#
|
138
|
-
# @example
|
139
|
-
# (Instant.now - Duration.from_secs(1)).to_s # => "1.000016597s"
|
140
|
-
# (Instant.now - Instant.now).to_s # => "-3.87μs"
|
141
|
-
#
|
142
|
-
# @param other [Instant, Duration, #to_nanos]
|
143
|
-
# @return [Duration, Instant]
|
144
|
-
def -(other)
|
145
|
-
if other.is_a?(Instant)
|
146
|
-
Duration.new(@ns - other.ns)
|
147
|
-
elsif other.respond_to?(:to_nanos)
|
148
|
-
Instant.new(@ns - other.to_nanos)
|
149
|
-
else
|
150
|
-
raise TypeError, 'Not one of: [Instant, Duration, #to_nanos]'
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Determine if the given +Instant+ is before, equal to or after this one.
|
155
|
-
# +nil+ if not passed an +Instant+.
|
156
|
-
#
|
157
|
-
# @return [-1, 0, 1, nil]
|
158
|
-
def <=>(other)
|
159
|
-
@ns <=> other.ns if other.is_a?(Instant)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Determine if +other+'s value equals that of this +Instant+.
|
163
|
-
# Use +eql?+ if type checks are desired for future compatibility.
|
164
|
-
#
|
165
|
-
# @return [Boolean]
|
166
|
-
# @see #eql?
|
167
|
-
def ==(other)
|
168
|
-
other.is_a?(Instant) && @ns == other.ns
|
169
|
-
end
|
170
|
-
|
171
|
-
alias eql? ==
|
172
|
-
|
173
|
-
# Generate a hash for this type and value.
|
174
|
-
#
|
175
|
-
# @return [Integer]
|
176
|
-
def hash
|
177
|
-
self.class.hash ^ @ns.hash
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
# A type representing a span of time in nanoseconds.
|
182
|
-
class Duration
|
183
|
-
include Comparable
|
184
|
-
|
185
|
-
# Create a new +Duration+ of a specified number of nanoseconds, zero by
|
186
|
-
# default.
|
187
|
-
#
|
188
|
-
# Users are strongly advised to use +#from_nanos+ instead.
|
189
|
-
#
|
190
|
-
# @param nanos [Integer]
|
191
|
-
# @see #from_nanos
|
192
|
-
def initialize(nanos = 0)
|
193
|
-
@ns = Integer(nanos)
|
194
|
-
end
|
195
|
-
|
196
|
-
class << self
|
197
|
-
# Generate a new +Duration+ measuring the given number of seconds.
|
198
|
-
#
|
199
|
-
# @param secs [Numeric]
|
200
|
-
# @return [Duration]
|
201
|
-
def from_secs(secs)
|
202
|
-
new(Integer(secs * 1_000_000_000))
|
203
|
-
end
|
204
|
-
|
205
|
-
alias secs from_secs
|
206
|
-
|
207
|
-
# Generate a new +Duration+ measuring the given number of milliseconds.
|
208
|
-
#
|
209
|
-
# @param millis [Numeric]
|
210
|
-
# @return [Duration]
|
211
|
-
def from_millis(millis)
|
212
|
-
new(Integer(millis * 1_000_000))
|
213
|
-
end
|
214
|
-
|
215
|
-
alias millis from_millis
|
216
|
-
|
217
|
-
# Generate a new +Duration+ measuring the given number of microseconds.
|
218
|
-
#
|
219
|
-
# @param micros [Numeric]
|
220
|
-
# @return [Duration]
|
221
|
-
def from_micros(micros)
|
222
|
-
new(Integer(micros * 1_000))
|
223
|
-
end
|
224
|
-
|
225
|
-
alias micros from_micros
|
226
|
-
|
227
|
-
# Generate a new +Duration+ measuring the given number of nanoseconds.
|
228
|
-
#
|
229
|
-
# @param nanos [Numeric]
|
230
|
-
# @return [Duration]
|
231
|
-
def from_nanos(nanos)
|
232
|
-
new(Integer(nanos))
|
233
|
-
end
|
234
|
-
|
235
|
-
alias nanos from_nanos
|
236
|
-
|
237
|
-
# Return a +Duration+ measuring the elapsed time of the yielded block.
|
238
|
-
#
|
239
|
-
# @example
|
240
|
-
# Duration.measure { sleep(0.5) }.to_s # => "512.226109ms"
|
241
|
-
#
|
242
|
-
# @return [Duration]
|
243
|
-
def measure
|
244
|
-
Instant.now.tap { yield }.elapsed
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# Add another +Duration+ or +#to_nanos+-coercible object to this one,
|
249
|
-
# returning a new +Duration+.
|
250
|
-
#
|
251
|
-
# @example
|
252
|
-
# (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
|
253
|
-
#
|
254
|
-
# @param [Duration, #to_nanos]
|
255
|
-
# @return [Duration]
|
256
|
-
def +(other)
|
257
|
-
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
258
|
-
|
259
|
-
Duration.new(to_nanos + other.to_nanos)
|
260
|
-
end
|
261
|
-
|
262
|
-
# Subtract another +Duration+ or +#to_nanos+-coercible object from this one,
|
263
|
-
# returning a new +Duration+.
|
264
|
-
#
|
265
|
-
# @example
|
266
|
-
# (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
|
267
|
-
#
|
268
|
-
# @param [Duration, #to_nanos]
|
269
|
-
# @return [Duration]
|
270
|
-
def -(other)
|
271
|
-
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
272
|
-
|
273
|
-
Duration.new(to_nanos - other.to_nanos)
|
274
|
-
end
|
275
|
-
|
276
|
-
# Divide this duration by a +Numeric+.
|
277
|
-
#
|
278
|
-
# @example
|
279
|
-
# (Duration.from_secs(10) / 2).to_s # => "5s"
|
280
|
-
#
|
281
|
-
# @param [Numeric]
|
282
|
-
# @return [Duration]
|
283
|
-
def /(other)
|
284
|
-
Duration.new(to_nanos / other)
|
285
|
-
end
|
286
|
-
|
287
|
-
# Multiply this duration by a +Numeric+.
|
288
|
-
#
|
289
|
-
# @example
|
290
|
-
# (Duration.from_secs(10) * 2).to_s # => "20s"
|
291
|
-
#
|
292
|
-
# @param [Numeric]
|
293
|
-
# @return [Duration]
|
294
|
-
def *(other)
|
295
|
-
Duration.new(to_nanos * other)
|
296
|
-
end
|
297
|
-
|
298
|
-
# Unary minus: make a positive +Duration+ negative, and vice versa.
|
299
|
-
#
|
300
|
-
# @example
|
301
|
-
# -Duration.from_secs(-1).to_s # => "1s"
|
302
|
-
# -Duration.from_secs(1).to_s # => "-1s"
|
303
|
-
#
|
304
|
-
# @return [Duration]
|
305
|
-
def -@
|
306
|
-
Duration.new(-to_nanos)
|
307
|
-
end
|
308
|
-
|
309
|
-
# Return a +Duration+ that's absolute (positive).
|
310
|
-
#
|
311
|
-
# @example
|
312
|
-
# Duration.from_secs(-1).abs.to_s # => "1s"
|
313
|
-
# Duration.from_secs(1).abs.to_s # => "1s"
|
314
|
-
#
|
315
|
-
# @return [Duration]
|
316
|
-
def abs
|
317
|
-
return self if positive? || zero?
|
318
|
-
Duration.new(to_nanos.abs)
|
319
|
-
end
|
320
|
-
|
321
|
-
# Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
|
322
|
-
# object, or nil if not comparable.
|
323
|
-
#
|
324
|
-
# @param [Duration, #to_nanos, Object]
|
325
|
-
# @return [-1, 0, 1, nil]
|
326
|
-
def <=>(other)
|
327
|
-
to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
|
328
|
-
end
|
329
|
-
|
330
|
-
# Compare the equality of the *value* of this +Duration+ with another, or
|
331
|
-
# any +#to_nanos+-coercible object, or nil if not comparable.
|
332
|
-
#
|
333
|
-
# @param [Duration, #to_nanos, Object]
|
334
|
-
# @return [Boolean]
|
335
|
-
def ==(other)
|
336
|
-
other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
|
337
|
-
end
|
338
|
-
|
339
|
-
# Check equality of the value and type of this +Duration+ with another.
|
340
|
-
#
|
341
|
-
# @param [Duration, Object]
|
342
|
-
# @return [Boolean]
|
343
|
-
def eql?(other)
|
344
|
-
other.is_a?(Duration) && to_nanos == other.to_nanos
|
345
|
-
end
|
346
|
-
|
347
|
-
# Generate a hash for this type and value.
|
348
|
-
#
|
349
|
-
# @return [Integer]
|
350
|
-
def hash
|
351
|
-
self.class.hash ^ to_nanos.hash
|
352
|
-
end
|
353
|
-
|
354
|
-
# Return this +Duration+ in seconds.
|
355
|
-
#
|
356
|
-
# @return [Float]
|
357
|
-
def to_secs
|
358
|
-
to_nanos / 1_000_000_000.0
|
359
|
-
end
|
360
|
-
|
361
|
-
alias secs to_secs
|
362
|
-
|
363
|
-
# Return this +Duration+ in milliseconds.
|
364
|
-
#
|
365
|
-
# @return [Float]
|
366
|
-
def to_millis
|
367
|
-
to_nanos / 1_000_000.0
|
368
|
-
end
|
369
|
-
|
370
|
-
alias millis to_millis
|
371
|
-
|
372
|
-
# Return this +Duration+ in microseconds.
|
373
|
-
#
|
374
|
-
# @return [Float]
|
375
|
-
def to_micros
|
376
|
-
to_nanos / 1_000.0
|
377
|
-
end
|
378
|
-
|
379
|
-
alias micros to_micros
|
380
|
-
|
381
|
-
# Return this +Duration+ in nanoseconds.
|
382
|
-
#
|
383
|
-
# @return [Integer]
|
384
|
-
def to_nanos
|
385
|
-
@ns
|
386
|
-
end
|
387
|
-
|
388
|
-
alias nanos to_nanos
|
389
|
-
|
390
|
-
# Return true if this +Duration+ is positive.
|
391
|
-
#
|
392
|
-
# @return [Boolean]
|
393
|
-
def positive?
|
394
|
-
to_nanos.positive?
|
395
|
-
end
|
396
|
-
|
397
|
-
# Return true if this +Duration+ is negative.
|
398
|
-
#
|
399
|
-
# @return [Boolean]
|
400
|
-
def negative?
|
401
|
-
to_nanos.negative?
|
402
|
-
end
|
403
|
-
|
404
|
-
# Return true if this +Duration+ is zero.
|
405
|
-
#
|
406
|
-
# @return [Boolean]
|
407
|
-
def zero?
|
408
|
-
to_nanos.zero?
|
409
|
-
end
|
410
|
-
|
411
|
-
# Return true if this +Duration+ is non-zero.
|
412
|
-
#
|
413
|
-
# @return [Boolean]
|
414
|
-
def nonzero?
|
415
|
-
to_nanos.nonzero?
|
416
|
-
end
|
417
|
-
|
418
|
-
# Sleep for the duration of this +Duration+. Equivalent to
|
419
|
-
# +Kernel.sleep(duration.to_secs)+.
|
420
|
-
#
|
421
|
-
# @example
|
422
|
-
# Duration.from_secs(1).sleep # => 1
|
423
|
-
# Duration.from_secs(-1).sleep # => raises NotImplementedError
|
424
|
-
#
|
425
|
-
# @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
|
426
|
-
# @return [Integer]
|
427
|
-
# @see Instant#sleep
|
428
|
-
def sleep
|
429
|
-
raise NotImplementedError, 'time travel module missing' if negative?
|
430
|
-
Kernel.sleep(to_secs)
|
431
|
-
end
|
432
|
-
|
433
|
-
DIVISORS = [
|
434
|
-
[1_000_000_000.0, 's'],
|
435
|
-
[1_000_000.0, 'ms'],
|
436
|
-
[1_000.0, 'μs'],
|
437
|
-
[0, 'ns']
|
438
|
-
].map(&:freeze).freeze
|
439
|
-
|
440
|
-
private_constant :DIVISORS
|
441
|
-
|
442
|
-
# Format this +Duration+ into a human-readable string, with a given number
|
443
|
-
# of decimal places.
|
444
|
-
#
|
445
|
-
# The exact format is subject to change, users with specific requirements
|
446
|
-
# are encouraged to use their own formatting methods.
|
447
|
-
#
|
448
|
-
# @example
|
449
|
-
# Duration.from_nanos(100).to_s # => "100ns"
|
450
|
-
# Duration.from_micros(100).to_s # => "100μs"
|
451
|
-
# Duration.from_millis(100).to_s # => "100ms"
|
452
|
-
# Duration.from_secs(100).to_s # => "100s"
|
453
|
-
# Duration.from_nanos(1234567).to_s # => "1.234567ms"
|
454
|
-
# Duration.from_nanos(1234567).to_s(2) # => "1.23ms"
|
455
|
-
#
|
456
|
-
# @param precision [Integer] the maximum number of decimal places
|
457
|
-
# @return [String]
|
458
|
-
def to_s(precision = 9)
|
459
|
-
precision = Integer(precision).abs
|
460
|
-
div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
|
461
|
-
|
462
|
-
if div.zero?
|
463
|
-
format('%d%s', to_nanos, unit)
|
464
|
-
else
|
465
|
-
format("%#.#{precision}f", to_nanos / div).sub(/\.?0*\z/, '') << unit
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
end
|
3
|
+
require_relative 'monotime/version'
|
4
|
+
require_relative 'monotime/duration'
|
5
|
+
require_relative 'monotime/instant'
|
@@ -0,0 +1,306 @@
|
|
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
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Generate a new +Duration+ measuring the given number of seconds.
|
21
|
+
#
|
22
|
+
# @param secs [Numeric]
|
23
|
+
# @return [Duration]
|
24
|
+
def from_secs(secs)
|
25
|
+
new(Integer(secs * 1_000_000_000))
|
26
|
+
end
|
27
|
+
|
28
|
+
alias secs from_secs
|
29
|
+
|
30
|
+
# Generate a new +Duration+ measuring the given number of milliseconds.
|
31
|
+
#
|
32
|
+
# @param millis [Numeric]
|
33
|
+
# @return [Duration]
|
34
|
+
def from_millis(millis)
|
35
|
+
new(Integer(millis * 1_000_000))
|
36
|
+
end
|
37
|
+
|
38
|
+
alias millis from_millis
|
39
|
+
|
40
|
+
# Generate a new +Duration+ measuring the given number of microseconds.
|
41
|
+
#
|
42
|
+
# @param micros [Numeric]
|
43
|
+
# @return [Duration]
|
44
|
+
def from_micros(micros)
|
45
|
+
new(Integer(micros * 1_000))
|
46
|
+
end
|
47
|
+
|
48
|
+
alias micros from_micros
|
49
|
+
|
50
|
+
# Generate a new +Duration+ measuring the given number of nanoseconds.
|
51
|
+
#
|
52
|
+
# @param nanos [Numeric]
|
53
|
+
# @return [Duration]
|
54
|
+
def from_nanos(nanos)
|
55
|
+
new(Integer(nanos))
|
56
|
+
end
|
57
|
+
|
58
|
+
alias nanos from_nanos
|
59
|
+
|
60
|
+
# Return a +Duration+ measuring the elapsed time of the yielded block.
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# Duration.measure { sleep(0.5) }.to_s # => "512.226109ms"
|
64
|
+
#
|
65
|
+
# @return [Duration]
|
66
|
+
def measure
|
67
|
+
Instant.now.tap { yield }.elapsed
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return the result of the yielded block alongside a +Duration+.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# Duration.with_measure { "bloop" } # => ["bloop", #<Monotime::Duration: ...>]
|
74
|
+
#
|
75
|
+
# @return [Object, Duration]
|
76
|
+
def with_measure
|
77
|
+
start = Instant.now
|
78
|
+
ret = yield
|
79
|
+
[ret, start.elapsed]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Add another +Duration+ or +#to_nanos+-coercible object to this one,
|
84
|
+
# returning a new +Duration+.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
|
88
|
+
#
|
89
|
+
# @param [Duration, #to_nanos]
|
90
|
+
# @return [Duration]
|
91
|
+
def +(other)
|
92
|
+
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
93
|
+
|
94
|
+
Duration.new(to_nanos + other.to_nanos)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Subtract another +Duration+ or +#to_nanos+-coercible object from this one,
|
98
|
+
# returning a new +Duration+.
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
|
102
|
+
#
|
103
|
+
# @param [Duration, #to_nanos]
|
104
|
+
# @return [Duration]
|
105
|
+
def -(other)
|
106
|
+
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
107
|
+
|
108
|
+
Duration.new(to_nanos - other.to_nanos)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Divide this duration by a +Numeric+.
|
112
|
+
#
|
113
|
+
# @example
|
114
|
+
# (Duration.from_secs(10) / 2).to_s # => "5s"
|
115
|
+
#
|
116
|
+
# @param [Numeric]
|
117
|
+
# @return [Duration]
|
118
|
+
def /(other)
|
119
|
+
Duration.new(to_nanos / other)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Multiply this duration by a +Numeric+.
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# (Duration.from_secs(10) * 2).to_s # => "20s"
|
126
|
+
#
|
127
|
+
# @param [Numeric]
|
128
|
+
# @return [Duration]
|
129
|
+
def *(other)
|
130
|
+
Duration.new(to_nanos * other)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Unary minus: make a positive +Duration+ negative, and vice versa.
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# -Duration.from_secs(-1).to_s # => "1s"
|
137
|
+
# -Duration.from_secs(1).to_s # => "-1s"
|
138
|
+
#
|
139
|
+
# @return [Duration]
|
140
|
+
def -@
|
141
|
+
Duration.new(-to_nanos)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return a +Duration+ that's absolute (positive).
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# Duration.from_secs(-1).abs.to_s # => "1s"
|
148
|
+
# Duration.from_secs(1).abs.to_s # => "1s"
|
149
|
+
#
|
150
|
+
# @return [Duration]
|
151
|
+
def abs
|
152
|
+
return self if positive? || zero?
|
153
|
+
|
154
|
+
Duration.new(to_nanos.abs)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
|
158
|
+
# object, or nil if not comparable.
|
159
|
+
#
|
160
|
+
# @param [Duration, #to_nanos, Object]
|
161
|
+
# @return [-1, 0, 1, nil]
|
162
|
+
def <=>(other)
|
163
|
+
to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Compare the equality of the *value* of this +Duration+ with another, or
|
167
|
+
# any +#to_nanos+-coercible object, or nil if not comparable.
|
168
|
+
#
|
169
|
+
# @param [Duration, #to_nanos, Object]
|
170
|
+
# @return [Boolean]
|
171
|
+
def ==(other)
|
172
|
+
other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
|
173
|
+
end
|
174
|
+
|
175
|
+
# Check equality of the value and type of this +Duration+ with another.
|
176
|
+
#
|
177
|
+
# @param [Duration, Object]
|
178
|
+
# @return [Boolean]
|
179
|
+
def eql?(other)
|
180
|
+
other.is_a?(Duration) && to_nanos == other.to_nanos
|
181
|
+
end
|
182
|
+
|
183
|
+
# Generate a hash for this type and value.
|
184
|
+
#
|
185
|
+
# @return [Integer]
|
186
|
+
def hash
|
187
|
+
self.class.hash ^ to_nanos.hash
|
188
|
+
end
|
189
|
+
|
190
|
+
# Return this +Duration+ in seconds.
|
191
|
+
#
|
192
|
+
# @return [Float]
|
193
|
+
def to_secs
|
194
|
+
to_nanos / 1_000_000_000.0
|
195
|
+
end
|
196
|
+
|
197
|
+
alias secs to_secs
|
198
|
+
|
199
|
+
# Return this +Duration+ in milliseconds.
|
200
|
+
#
|
201
|
+
# @return [Float]
|
202
|
+
def to_millis
|
203
|
+
to_nanos / 1_000_000.0
|
204
|
+
end
|
205
|
+
|
206
|
+
alias millis to_millis
|
207
|
+
|
208
|
+
# Return this +Duration+ in microseconds.
|
209
|
+
#
|
210
|
+
# @return [Float]
|
211
|
+
def to_micros
|
212
|
+
to_nanos / 1_000.0
|
213
|
+
end
|
214
|
+
|
215
|
+
alias micros to_micros
|
216
|
+
|
217
|
+
# Return this +Duration+ in nanoseconds.
|
218
|
+
#
|
219
|
+
# @return [Integer]
|
220
|
+
def to_nanos
|
221
|
+
@ns
|
222
|
+
end
|
223
|
+
|
224
|
+
alias nanos to_nanos
|
225
|
+
|
226
|
+
# Return true if this +Duration+ is positive.
|
227
|
+
#
|
228
|
+
# @return [Boolean]
|
229
|
+
def positive?
|
230
|
+
to_nanos.positive?
|
231
|
+
end
|
232
|
+
|
233
|
+
# Return true if this +Duration+ is negative.
|
234
|
+
#
|
235
|
+
# @return [Boolean]
|
236
|
+
def negative?
|
237
|
+
to_nanos.negative?
|
238
|
+
end
|
239
|
+
|
240
|
+
# Return true if this +Duration+ is zero.
|
241
|
+
#
|
242
|
+
# @return [Boolean]
|
243
|
+
def zero?
|
244
|
+
to_nanos.zero?
|
245
|
+
end
|
246
|
+
|
247
|
+
# Return true if this +Duration+ is non-zero.
|
248
|
+
#
|
249
|
+
# @return [Boolean]
|
250
|
+
def nonzero?
|
251
|
+
to_nanos.nonzero?
|
252
|
+
end
|
253
|
+
|
254
|
+
# Sleep for the duration of this +Duration+. Equivalent to
|
255
|
+
# +Kernel.sleep(duration.to_secs)+.
|
256
|
+
#
|
257
|
+
# @example
|
258
|
+
# Duration.from_secs(1).sleep # => 1
|
259
|
+
# Duration.from_secs(-1).sleep # => raises NotImplementedError
|
260
|
+
#
|
261
|
+
# @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
|
262
|
+
# @return [Integer]
|
263
|
+
# @see Instant#sleep
|
264
|
+
def sleep
|
265
|
+
raise NotImplementedError, 'time travel module missing' if negative?
|
266
|
+
|
267
|
+
Kernel.sleep(to_secs)
|
268
|
+
end
|
269
|
+
|
270
|
+
DIVISORS = [
|
271
|
+
[1_000_000_000.0, 's'],
|
272
|
+
[1_000_000.0, 'ms'],
|
273
|
+
[1_000.0, 'μs'],
|
274
|
+
[0, 'ns']
|
275
|
+
].map(&:freeze).freeze
|
276
|
+
|
277
|
+
private_constant :DIVISORS
|
278
|
+
|
279
|
+
# Format this +Duration+ into a human-readable string, with a given number
|
280
|
+
# of decimal places.
|
281
|
+
#
|
282
|
+
# The exact format is subject to change, users with specific requirements
|
283
|
+
# are encouraged to use their own formatting methods.
|
284
|
+
#
|
285
|
+
# @example
|
286
|
+
# Duration.from_nanos(100).to_s # => "100ns"
|
287
|
+
# Duration.from_micros(100).to_s # => "100μs"
|
288
|
+
# Duration.from_millis(100).to_s # => "100ms"
|
289
|
+
# Duration.from_secs(100).to_s # => "100s"
|
290
|
+
# Duration.from_nanos(1234567).to_s # => "1.234567ms"
|
291
|
+
# Duration.from_nanos(1234567).to_s(2) # => "1.23ms"
|
292
|
+
#
|
293
|
+
# @param precision [Integer] the maximum number of decimal places
|
294
|
+
# @return [String]
|
295
|
+
def to_s(precision = 9)
|
296
|
+
precision = Integer(precision).abs
|
297
|
+
div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
|
298
|
+
|
299
|
+
if div.zero?
|
300
|
+
format('%d%s', to_nanos, unit)
|
301
|
+
else
|
302
|
+
format("%#.#{precision}f", to_nanos / div).sub(/\.?0*\z/, '') << unit
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monotime
|
4
|
+
# A measurement from the operating system's monotonic clock, with up to
|
5
|
+
# nanosecond precision.
|
6
|
+
class Instant
|
7
|
+
# A measurement, in nanoseconds. Should be considered opaque and
|
8
|
+
# non-portable outside the process that created it.
|
9
|
+
attr_reader :ns
|
10
|
+
protected :ns
|
11
|
+
|
12
|
+
include Comparable
|
13
|
+
|
14
|
+
# Create a new +Instant+ from an optional nanosecond measurement.
|
15
|
+
#
|
16
|
+
# Users should generally *not* pass anything to this function.
|
17
|
+
#
|
18
|
+
# @param nanos [Integer]
|
19
|
+
# @see #now
|
20
|
+
def initialize(nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))
|
21
|
+
@ns = Integer(nanos)
|
22
|
+
end
|
23
|
+
|
24
|
+
# An alias to +new+, and generally preferred over it.
|
25
|
+
#
|
26
|
+
# @return [Instant]
|
27
|
+
def self.now
|
28
|
+
new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return a +Duration+ between this +Instant+ and another.
|
32
|
+
#
|
33
|
+
# @param earlier [Instant]
|
34
|
+
# @return [Duration]
|
35
|
+
def duration_since(earlier)
|
36
|
+
raise TypeError, 'Not an Instant' unless earlier.is_a?(Instant)
|
37
|
+
|
38
|
+
earlier - self
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a +Duration+ since this +Instant+ and now.
|
42
|
+
#
|
43
|
+
# @return [Duration]
|
44
|
+
def elapsed
|
45
|
+
duration_since(self.class.now)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return whether this +Instant+ is in the past.
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
def in_past?
|
52
|
+
elapsed.positive?
|
53
|
+
end
|
54
|
+
|
55
|
+
alias past? in_past?
|
56
|
+
|
57
|
+
# Return whether this +Instant+ is in the future.
|
58
|
+
#
|
59
|
+
# @return [Boolean]
|
60
|
+
def in_future?
|
61
|
+
elapsed.negative?
|
62
|
+
end
|
63
|
+
|
64
|
+
alias future? in_future?
|
65
|
+
|
66
|
+
# Sleep until this +Instant+, plus an optional +Duration+, returning a +Duration+
|
67
|
+
# that's either positive if any time was slept, or negative if sleeping would
|
68
|
+
# require time travel.
|
69
|
+
#
|
70
|
+
# @example Sleeps for a second
|
71
|
+
# start = Instant.now
|
72
|
+
# sleep 0.5 # do stuff for half a second
|
73
|
+
# start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
|
74
|
+
# start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)
|
75
|
+
#
|
76
|
+
# @example Also sleeps for a second.
|
77
|
+
# one_second_in_the_future = Instant.now + Duration.from_secs(1)
|
78
|
+
# one_second_in_the_future.sleep.to_s # => "985.592712ms" (slept)
|
79
|
+
# one_second_in_the_future.sleep.to_s # => "-4.71217ms" (did not sleep)
|
80
|
+
#
|
81
|
+
# @param duration [nil, Duration, #to_nanos]
|
82
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
83
|
+
def sleep(duration = nil)
|
84
|
+
remaining = duration ? duration - elapsed : -elapsed
|
85
|
+
|
86
|
+
remaining.tap { |rem| rem.sleep if rem.positive? }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Sleep for the given number of seconds past this +Instant+, if any.
|
90
|
+
#
|
91
|
+
# Equivalent to +#sleep(Duration.from_secs(secs))+
|
92
|
+
#
|
93
|
+
# @param secs [Numeric] number of seconds to sleep past this +Instant+
|
94
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
95
|
+
# @see #sleep
|
96
|
+
def sleep_secs(secs)
|
97
|
+
sleep(Duration.from_secs(secs))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Sleep for the given number of milliseconds past this +Instant+, if any.
|
101
|
+
#
|
102
|
+
# Equivalent to +#sleep(Duration.from_millis(millis))+
|
103
|
+
#
|
104
|
+
# @param millis [Numeric] number of milliseconds to sleep past this +Instant+
|
105
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
106
|
+
# @see #sleep
|
107
|
+
def sleep_millis(millis)
|
108
|
+
sleep(Duration.from_millis(millis))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sugar for +#elapsed.to_s+.
|
112
|
+
#
|
113
|
+
# @see Duration#to_s
|
114
|
+
def to_s(*args)
|
115
|
+
elapsed.to_s(*args)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
|
119
|
+
# a new +Instant+.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# (Instant.now + Duration.from_secs(1)).to_s # => "-999.983976ms"
|
123
|
+
#
|
124
|
+
# @param other [Duration, #to_nanos]
|
125
|
+
# @return [Instant]
|
126
|
+
def +(other)
|
127
|
+
return TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
128
|
+
|
129
|
+
Instant.new(@ns + other.to_nanos)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Subtract another +Instant+ to generate a +Duration+ between the two,
|
133
|
+
# or a +Duration+ or +#to_nanos+-coercible object, to generate an +Instant+
|
134
|
+
# offset by it.
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# (Instant.now - Duration.from_secs(1)).to_s # => "1.000016597s"
|
138
|
+
# (Instant.now - Instant.now).to_s # => "-3.87μs"
|
139
|
+
#
|
140
|
+
# @param other [Instant, Duration, #to_nanos]
|
141
|
+
# @return [Duration, Instant]
|
142
|
+
def -(other)
|
143
|
+
if other.is_a?(Instant)
|
144
|
+
Duration.new(@ns - other.ns)
|
145
|
+
elsif other.respond_to?(:to_nanos)
|
146
|
+
Instant.new(@ns - other.to_nanos)
|
147
|
+
else
|
148
|
+
raise TypeError, 'Not one of: [Instant, Duration, #to_nanos]'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Determine if the given +Instant+ is before, equal to or after this one.
|
153
|
+
# +nil+ if not passed an +Instant+.
|
154
|
+
#
|
155
|
+
# @return [-1, 0, 1, nil]
|
156
|
+
def <=>(other)
|
157
|
+
@ns <=> other.ns if other.is_a?(Instant)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Determine if +other+'s value equals that of this +Instant+.
|
161
|
+
# Use +eql?+ if type checks are desired for future compatibility.
|
162
|
+
#
|
163
|
+
# @return [Boolean]
|
164
|
+
# @see #eql?
|
165
|
+
def ==(other)
|
166
|
+
other.is_a?(Instant) && @ns == other.ns
|
167
|
+
end
|
168
|
+
|
169
|
+
alias eql? ==
|
170
|
+
|
171
|
+
# Generate a hash for this type and value.
|
172
|
+
#
|
173
|
+
# @return [Integer]
|
174
|
+
def hash
|
175
|
+
self.class.hash ^ @ns.hash
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/monotime/version.rb
CHANGED
data/monotime.gemspec
CHANGED
@@ -5,7 +5,7 @@ require "monotime/version"
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "monotime"
|
8
|
-
spec.version = Monotime::
|
8
|
+
spec.version = Monotime::MONOTIME_VERSION
|
9
9
|
spec.authors = ["Thomas Hurst"]
|
10
10
|
spec.email = ["tom@hur.st"]
|
11
11
|
|
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
spec.add_development_dependency "bundler", "~>
|
34
|
+
spec.add_development_dependency "bundler", "~> 2"
|
35
35
|
spec.add_development_dependency "rake", "~> 10.0"
|
36
36
|
spec.add_development_dependency "minitest", "~> 5.0"
|
37
37
|
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.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Hurst
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,6 +70,8 @@ files:
|
|
70
70
|
- bin/console
|
71
71
|
- bin/setup
|
72
72
|
- lib/monotime.rb
|
73
|
+
- lib/monotime/duration.rb
|
74
|
+
- lib/monotime/instant.rb
|
73
75
|
- lib/monotime/version.rb
|
74
76
|
- monotime.gemspec
|
75
77
|
homepage: https://github.com/Freaky/monotime
|