monotime 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d77cca7907b76e8276296abc382f10612e7879605f7472ab46e4b26c9594426d
4
- data.tar.gz: d4d1284f11de89438125a3e28e4686bdde6a78cf02a431d3eb57e861e7345613
3
+ metadata.gz: 54cec038f8a6e79cbfaf1364c22a2733b9c155d15562ac6c4181568e254ce3f2
4
+ data.tar.gz: f2fb3122d163d62cbfe6c148a547af7917efad19a30032695a34949bdf5758e4
5
5
  SHA512:
6
- metadata.gz: 4975756b4d469b02437d7ad45216eef58d5e20b3675f6c725c64d1b8d1da3518c5eec89fc98cbc1d9d0e6238665232e837415783726132b757cf2e19ab2ef4fc
7
- data.tar.gz: cfe8ffd03b80d308d512f5df277e5b9d82c6c88f47cb048390245dd8b34f598e03c41ad9286576add9f75dfdd1cbb2230101b60dc6bed844f85de9fab1247a76
6
+ metadata.gz: 708d7018fc1d90987b00074545ff351ab31ba60a37e3808092f85d48f0dce660380895c8fd0fa3e9f1038deb958a2274e730e2119d73f582377c72ef9aebe6a4
7
+ data.tar.gz: 0a5cd0b6f7ffb5a50f6d31efd21d4887e9130dec54551bb1e04642c7ada69532b12c4f4bf432b97ada9b4ba5be6d9e0f1ced6c307c6488df7dbb87199fec7853
data/.travis.yml CHANGED
@@ -4,4 +4,5 @@ language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
6
  - 2.5.1
7
+ - jruby
7
8
  before_install: gem install bundler -v 1.16.3
data/README.md CHANGED
@@ -1,3 +1,6 @@
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
+
1
4
  # Monotime
2
5
 
3
6
  A sensible interface to Ruby's monotonic clock, inspired by Rust.
@@ -62,6 +65,8 @@ And how to do basic maths on itself:
62
65
  ```ruby
63
66
  (Duration.from_millis(42) + Duration.from_secs(1)).to_s # => "1.042s"
64
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"
65
70
  ```
66
71
 
67
72
  `Instant` does some simple maths too:
@@ -77,6 +82,53 @@ And how to do basic maths on itself:
77
82
  `Duration` and `Instant` are also `Comparable` with other instances of their
78
83
  type, and support `#hash` for use in, er, hashes.
79
84
 
85
+ ## Sleeping
86
+
87
+ `Duration` can be used to sleep a thread, assuming it's positive (time travel
88
+ is not yet implemented):
89
+
90
+ ```ruby
91
+ # Equivalent
92
+ sleep(Duration.from_secs(1).to_secs) # => 1
93
+
94
+ Duration.from_secs(1).sleep # => 1
95
+ ```
96
+
97
+ 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:
100
+
101
+ ```ruby
102
+ poke_duration = Duration.from_secs(60)
103
+ 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)
108
+ end
109
+ ```
110
+
111
+ `Instant#sleep` returns a `Duration` which was slept, or a negative `Duration` if
112
+ the desired sleep period has passed.
113
+
114
+ ## Duration duck typing
115
+
116
+ Operations taking a `Duration` can also accept any type which implements
117
+ `#to_nanos`, returning an (Integer) number of nanoseconds the value represents.
118
+
119
+ For example, to treat built-in numeric types as second durations, you could do:
120
+
121
+ ```ruby
122
+ class Numeric
123
+ def to_nanos
124
+ Integer(self * 1_000_000_000)
125
+ end
126
+ end
127
+
128
+ (Duration.from_secs(1) + 41).to_s # => "42s"
129
+ (Instant.now - 42).to_s # => "42.000010545s"
130
+ ```
131
+
80
132
  ## Development
81
133
 
82
134
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Monotime
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/monotime.rb CHANGED
@@ -45,14 +45,54 @@ module Monotime
45
45
  duration_since(self.class.now)
46
46
  end
47
47
 
48
- # Sugar for +elapsed.to_s+.
48
+ # Sleep for the given `Duration` past this +Instant+, if any.
49
+ #
50
+ # @example Sleeps for a second
51
+ # start = Instant.now
52
+ # sleep 0.5 # do stuff for half a second
53
+ # start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
54
+ # start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)
55
+ #
56
+ # @param duration [Duration, #to_nanos]
57
+ # @return [Duration] the slept duration, if +#positive?+, else the overshot time
58
+ def sleep(duration)
59
+ remaining = duration - elapsed
60
+
61
+ return remaining unless remaining.positive?
62
+ remaining.tap(&:sleep)
63
+ end
64
+
65
+ # Sleep for the given number of seconds past this +Instant+, if any.
66
+ #
67
+ # Equivalent to +#sleep(Duration.from_secs(secs))+
68
+ #
69
+ # @param secs [Numeric] number of seconds to sleep past this +Instant+
70
+ # @return [Duration] the slept duration, if +#positive?+, else the overshot time
71
+ # @see #sleep
72
+ def sleep_secs(secs)
73
+ sleep(Duration.from_secs(secs))
74
+ end
75
+
76
+ # Sleep for the given number of milliseconds past this +Instant+, if any.
77
+ #
78
+ # Equivalent to +#sleep(Duration.from_millis(millis))+
79
+ #
80
+ # @param millis [Numeric] number of milliseconds to sleep past this +Instant+
81
+ # @return [Duration] the slept duration, if +#positive?+, else the overshot time
82
+ # @see #sleep
83
+ def sleep_millis(secs)
84
+ sleep(Duration.from_millis(secs))
85
+ end
86
+
87
+ # Sugar for +#elapsed.to_s+.
49
88
  #
50
89
  # @see Duration#to_s
51
90
  def to_s(*args)
52
91
  elapsed.to_s(*args)
53
92
  end
54
93
 
55
- # Add a +Duration+ to this +Instant+, returning a new +Instant+.
94
+ # Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
95
+ # a new +Instant+.
56
96
  #
57
97
  # @param other [Duration, #to_nanos]
58
98
  # @return [Instant]
@@ -63,7 +103,8 @@ module Monotime
63
103
  end
64
104
 
65
105
  # Subtract another +Instant+ to generate a +Duration+ between the two,
66
- # or a +Duration+, to generate an +Instant+ offset by it.
106
+ # or a +Duration+ or +#to_nanos+-coercible object, to generate an +Instant+
107
+ # offset by it.
67
108
  #
68
109
  # @param other [Instant, Duration, #to_nanos]
69
110
  # @return [Duration, Instant]
@@ -77,17 +118,28 @@ module Monotime
77
118
  end
78
119
  end
79
120
 
80
- # Compare this +Instant+ with another.
121
+ # Determine if the given +Instant+ is before, equal to or after this one.
122
+ # +nil+ if not passed an +Instant+.
123
+ #
124
+ # @return [-1, 0, 1, nil]
81
125
  def <=>(other)
82
126
  @ns <=> other.ns if other.is_a?(Instant)
83
127
  end
84
128
 
129
+ # Determine if +other+'s value equals that of this +Instant+.
130
+ # Use +eql?+ if type checks are desired for future compatibility.
131
+ #
132
+ # @return [Boolean]
133
+ # @see #eql?
85
134
  def ==(other)
86
135
  other.is_a?(Instant) && @ns == other.ns
87
136
  end
88
137
 
89
138
  alias eql? ==
90
139
 
140
+ # Generate a hash for this type and value.
141
+ #
142
+ # @return [Fixnum]
91
143
  def hash
92
144
  self.class.hash ^ @ns.hash
93
145
  end
@@ -146,7 +198,8 @@ module Monotime
146
198
  end
147
199
  end
148
200
 
149
- # Add another +Duration+ to this one, returning a new +Duration+.
201
+ # Add another +Duration+ or +#to_nanos+-coercible object to this one,
202
+ # returning a new +Duration+.
150
203
  #
151
204
  # @param [Duration, #to_nanos]
152
205
  #
@@ -157,7 +210,8 @@ module Monotime
157
210
  Duration.new(to_nanos + other.to_nanos)
158
211
  end
159
212
 
160
- # Subtract another +Duration+ from this one, returning a new +Duration+.
213
+ # Subtract another +Duration+ or +#to_nanos+-coercible object from this one,
214
+ # returning a new +Duration+.
161
215
  #
162
216
  # @param [Duration, #to_nanos]
163
217
  # @return [Duration]
@@ -167,17 +221,51 @@ module Monotime
167
221
  Duration.new(to_nanos - other.to_nanos)
168
222
  end
169
223
 
170
- # Compare this +Duration+ with another.
224
+ # Divide this duration by a +Numeric+.
225
+ #
226
+ # @param [Numeric]
227
+ # @return [Duration]
228
+ def /(other)
229
+ Duration.new(to_nanos / other)
230
+ end
231
+
232
+ # Multiply this duration by a +Numeric+.
233
+ #
234
+ # @param [Numeric]
235
+ # @return [Duration]
236
+ def *(other)
237
+ Duration.new(to_nanos * other)
238
+ end
239
+
240
+ # Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
241
+ # object, or nil if not comparable.
242
+ #
243
+ # @param [Duration, #to_nanos, Object]
244
+ # @return [-1, 0, 1, nil]
171
245
  def <=>(other)
172
- to_nanos <=> other.to_nanos if other.is_a? Duration
246
+ to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
173
247
  end
174
248
 
249
+ # Compare the equality of the *value* of this +Duration+ with another, or
250
+ # any +#to_nanos+-coercible object, or nil if not comparable.
251
+ #
252
+ # @param [Duration, #to_nanos, Object]
253
+ # @return [Boolean]
175
254
  def ==(other)
176
- other.is_a?(Duration) && to_nanos == other.to_nanos
255
+ other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
177
256
  end
178
257
 
179
- alias eql? ==
258
+ # Check equality of the value and type of this +Duration+ with another.
259
+ #
260
+ # @param [Duration, Object]
261
+ # @return [Boolean]
262
+ def eql?(other)
263
+ other.is_a?(Duration) && to_nanos == other.to_nanos
264
+ end
180
265
 
266
+ # Generate a hash for this type and value.
267
+ #
268
+ # @return [Fixnum]
181
269
  def hash
182
270
  self.class.hash ^ to_nanos.hash
183
271
  end
@@ -210,6 +298,38 @@ module Monotime
210
298
  @ns
211
299
  end
212
300
 
301
+ # Return true if this +Duration+ is positive.
302
+ #
303
+ # @return [Boolean]
304
+ def positive?
305
+ to_nanos.positive?
306
+ end
307
+
308
+ # Return true if this +Duration+ is negative.
309
+ #
310
+ # @return [Boolean]
311
+ def negative?
312
+ to_nanos.negative?
313
+ end
314
+
315
+ # Return true if this +Duration+ is zero.
316
+ #
317
+ # @return [Boolean]
318
+ def zero?
319
+ to_nanos.zero?
320
+ end
321
+
322
+ # Sleep for the duration of this +Duration+. Equivalent to
323
+ # +Kernel.sleep(duration.to_secs)+.
324
+ #
325
+ # @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
326
+ # @return [Integer]
327
+ # @see Instant#sleep
328
+ def sleep
329
+ raise NotImplementedError, 'time travel module missing' if negative?
330
+ Kernel.sleep(to_secs)
331
+ end
332
+
213
333
  DIVISORS = [
214
334
  [1_000_000_000.0, 's'],
215
335
  [1_000_000.0, 'ms'],
@@ -232,7 +352,7 @@ module Monotime
232
352
  ns = to_nanos.abs
233
353
  div, unit = DIVISORS.find { |d, _| ns >= d }
234
354
  ns /= div if div.nonzero?
235
- num = format("#{'-' if to_nanos.negative?}%.#{precision}f", ns)
355
+ num = format("#{'-' if negative?}%.#{precision}f", ns)
236
356
  num.sub!(/\.?0*$/, '') if precision.nonzero?
237
357
  num << unit
238
358
  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.3.0
4
+ version: 0.4.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: 2018-10-04 00:00:00.000000000 Z
11
+ date: 2018-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler