monotime 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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