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 +4 -4
- data/.github/workflows/ci.yml +16 -0
- data/.rubocop.yml +28 -1
- data/CHANGELOG.md +108 -0
- data/README.md +75 -45
- data/lib/monotime/duration.rb +307 -0
- data/lib/monotime/include.rb +5 -0
- data/lib/monotime/instant.rb +179 -0
- data/lib/monotime/version.rb +2 -1
- data/lib/monotime.rb +3 -467
- data/monotime.gemspec +4 -3
- metadata +30 -13
- data/.travis.yml +0 -8
@@ -0,0 +1,179 @@
|
|
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
|
+
freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
# An alias to +new+, and generally preferred over it.
|
26
|
+
#
|
27
|
+
# @return [Instant]
|
28
|
+
def self.now
|
29
|
+
new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return a +Duration+ between this +Instant+ and another.
|
33
|
+
#
|
34
|
+
# @param earlier [Instant]
|
35
|
+
# @return [Duration]
|
36
|
+
def duration_since(earlier)
|
37
|
+
raise TypeError, 'Not an Instant' unless earlier.is_a?(Instant)
|
38
|
+
|
39
|
+
earlier - self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return a +Duration+ since this +Instant+ and now.
|
43
|
+
#
|
44
|
+
# @return [Duration]
|
45
|
+
def elapsed
|
46
|
+
duration_since(self.class.now)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return whether this +Instant+ is in the past.
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
def in_past?
|
53
|
+
elapsed.positive?
|
54
|
+
end
|
55
|
+
|
56
|
+
alias past? in_past?
|
57
|
+
|
58
|
+
# Return whether this +Instant+ is in the future.
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
def in_future?
|
62
|
+
elapsed.negative?
|
63
|
+
end
|
64
|
+
|
65
|
+
alias future? in_future?
|
66
|
+
|
67
|
+
# Sleep until this +Instant+, plus an optional +Duration+, returning a +Duration+
|
68
|
+
# that's either positive if any time was slept, or negative if sleeping would
|
69
|
+
# require time travel.
|
70
|
+
#
|
71
|
+
# @example Sleeps for a second
|
72
|
+
# start = Instant.now
|
73
|
+
# sleep 0.5 # do stuff for half a second
|
74
|
+
# start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
|
75
|
+
# start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)
|
76
|
+
#
|
77
|
+
# @example Also sleeps for a second.
|
78
|
+
# one_second_in_the_future = Instant.now + Duration.from_secs(1)
|
79
|
+
# one_second_in_the_future.sleep.to_s # => "985.592712ms" (slept)
|
80
|
+
# one_second_in_the_future.sleep.to_s # => "-4.71217ms" (did not sleep)
|
81
|
+
#
|
82
|
+
# @param duration [nil, Duration, #to_nanos]
|
83
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
84
|
+
def sleep(duration = nil)
|
85
|
+
remaining = duration ? duration - elapsed : -elapsed
|
86
|
+
|
87
|
+
remaining.tap { |rem| rem.sleep if rem.positive? }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sleep for the given number of seconds past this +Instant+, if any.
|
91
|
+
#
|
92
|
+
# Equivalent to +#sleep(Duration.from_secs(secs))+
|
93
|
+
#
|
94
|
+
# @param secs [Numeric] number of seconds to sleep past this +Instant+
|
95
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
96
|
+
# @see #sleep
|
97
|
+
def sleep_secs(secs)
|
98
|
+
sleep(Duration.from_secs(secs))
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sleep for the given number of milliseconds past this +Instant+, if any.
|
102
|
+
#
|
103
|
+
# Equivalent to +#sleep(Duration.from_millis(millis))+
|
104
|
+
#
|
105
|
+
# @param millis [Numeric] number of milliseconds to sleep past this +Instant+
|
106
|
+
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
107
|
+
# @see #sleep
|
108
|
+
def sleep_millis(millis)
|
109
|
+
sleep(Duration.from_millis(millis))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sugar for +#elapsed.to_s+.
|
113
|
+
#
|
114
|
+
# @see Duration#to_s
|
115
|
+
def to_s(*args)
|
116
|
+
elapsed.to_s(*args)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
|
120
|
+
# a new +Instant+.
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# (Instant.now + Duration.from_secs(1)).to_s # => "-999.983976ms"
|
124
|
+
#
|
125
|
+
# @param other [Duration, #to_nanos]
|
126
|
+
# @return [Instant]
|
127
|
+
def +(other)
|
128
|
+
return TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
129
|
+
|
130
|
+
Instant.new(@ns + other.to_nanos)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Subtract another +Instant+ to generate a +Duration+ between the two,
|
134
|
+
# or a +Duration+ or +#to_nanos+-coercible object, to generate an +Instant+
|
135
|
+
# offset by it.
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# (Instant.now - Duration.from_secs(1)).to_s # => "1.000016597s"
|
139
|
+
# (Instant.now - Instant.now).to_s # => "-3.87μs"
|
140
|
+
#
|
141
|
+
# @param other [Instant, Duration, #to_nanos]
|
142
|
+
# @return [Duration, Instant]
|
143
|
+
def -(other)
|
144
|
+
if other.is_a?(Instant)
|
145
|
+
Duration.new(@ns - other.ns)
|
146
|
+
elsif other.respond_to?(:to_nanos)
|
147
|
+
Instant.new(@ns - other.to_nanos)
|
148
|
+
else
|
149
|
+
raise TypeError, 'Not one of: [Instant, Duration, #to_nanos]'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Determine if the given +Instant+ is before, equal to or after this one.
|
154
|
+
# +nil+ if not passed an +Instant+.
|
155
|
+
#
|
156
|
+
# @return [-1, 0, 1, nil]
|
157
|
+
def <=>(other)
|
158
|
+
@ns <=> other.ns if other.is_a?(Instant)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Determine if +other+'s value equals that of this +Instant+.
|
162
|
+
# Use +eql?+ if type checks are desired for future compatibility.
|
163
|
+
#
|
164
|
+
# @return [Boolean]
|
165
|
+
# @see #eql?
|
166
|
+
def ==(other)
|
167
|
+
other.is_a?(Instant) && @ns == other.ns
|
168
|
+
end
|
169
|
+
|
170
|
+
alias eql? ==
|
171
|
+
|
172
|
+
# Generate a hash for this type and value.
|
173
|
+
#
|
174
|
+
# @return [Integer]
|
175
|
+
def hash
|
176
|
+
self.class.hash ^ @ns.hash
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/monotime/version.rb
CHANGED
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
|
-
# Sleep until this +Instant+, plus an optional +Duration+, returning a +Duration+
|
51
|
-
# that's either positive if any time was slept, or negative if sleeping would
|
52
|
-
# require time travel.
|
53
|
-
#
|
54
|
-
# @example Sleeps for a second
|
55
|
-
# start = Instant.now
|
56
|
-
# sleep 0.5 # do stuff for half a second
|
57
|
-
# start.sleep(Duration.from_secs(1)).to_s # => "490.088706ms" (slept)
|
58
|
-
# start.sleep(Duration.from_secs(1)).to_s # => "-12.963502ms" (did not sleep)
|
59
|
-
#
|
60
|
-
# @example Also sleeps for a second.
|
61
|
-
# one_second_in_the_future = Instant.now + Duration.from_secs(1)
|
62
|
-
# one_second_in_the_future.sleep.to_s # => "985.592712ms" (slept)
|
63
|
-
# one_second_in_the_future.sleep.to_s # => "-4.71217ms" (did not sleep)
|
64
|
-
#
|
65
|
-
# @param duration [nil, Duration, #to_nanos]
|
66
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
67
|
-
def sleep(duration = nil)
|
68
|
-
remaining = duration ? duration - elapsed : -elapsed
|
69
|
-
|
70
|
-
remaining.tap { |rem| rem.sleep if rem.positive? }
|
71
|
-
end
|
72
|
-
|
73
|
-
# Sleep for the given number of seconds past this +Instant+, if any.
|
74
|
-
#
|
75
|
-
# Equivalent to +#sleep(Duration.from_secs(secs))+
|
76
|
-
#
|
77
|
-
# @param secs [Numeric] number of seconds to sleep past this +Instant+
|
78
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
79
|
-
# @see #sleep
|
80
|
-
def sleep_secs(secs)
|
81
|
-
sleep(Duration.from_secs(secs))
|
82
|
-
end
|
83
|
-
|
84
|
-
# Sleep for the given number of milliseconds past this +Instant+, if any.
|
85
|
-
#
|
86
|
-
# Equivalent to +#sleep(Duration.from_millis(millis))+
|
87
|
-
#
|
88
|
-
# @param millis [Numeric] number of milliseconds to sleep past this +Instant+
|
89
|
-
# @return [Duration] the slept duration, if +#positive?+, else the overshot time
|
90
|
-
# @see #sleep
|
91
|
-
def sleep_millis(millis)
|
92
|
-
sleep(Duration.from_millis(millis))
|
93
|
-
end
|
94
|
-
|
95
|
-
# Sugar for +#elapsed.to_s+.
|
96
|
-
#
|
97
|
-
# @see Duration#to_s
|
98
|
-
def to_s(*args)
|
99
|
-
elapsed.to_s(*args)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Add a +Duration+ or +#to_nanos+-coercible object to this +Instant+, returning
|
103
|
-
# a new +Instant+.
|
104
|
-
#
|
105
|
-
# @example
|
106
|
-
# (Instant.now + Duration.from_secs(1)).to_s # => "-999.983976ms"
|
107
|
-
#
|
108
|
-
# @param other [Duration, #to_nanos]
|
109
|
-
# @return [Instant]
|
110
|
-
def +(other)
|
111
|
-
return TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
112
|
-
|
113
|
-
Instant.new(@ns + other.to_nanos)
|
114
|
-
end
|
115
|
-
|
116
|
-
# Subtract another +Instant+ to generate a +Duration+ between the two,
|
117
|
-
# or a +Duration+ or +#to_nanos+-coercible object, to generate an +Instant+
|
118
|
-
# offset by it.
|
119
|
-
#
|
120
|
-
# @example
|
121
|
-
# (Instant.now - Duration.from_secs(1)).to_s # => "1.000016597s"
|
122
|
-
# (Instant.now - Instant.now).to_s # => "-3.87μs"
|
123
|
-
#
|
124
|
-
# @param other [Instant, Duration, #to_nanos]
|
125
|
-
# @return [Duration, Instant]
|
126
|
-
def -(other)
|
127
|
-
if other.is_a?(Instant)
|
128
|
-
Duration.new(@ns - other.ns)
|
129
|
-
elsif other.respond_to?(:to_nanos)
|
130
|
-
Instant.new(@ns - other.to_nanos)
|
131
|
-
else
|
132
|
-
raise TypeError, 'Not one of: [Instant, Duration, #to_nanos]'
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Determine if the given +Instant+ is before, equal to or after this one.
|
137
|
-
# +nil+ if not passed an +Instant+.
|
138
|
-
#
|
139
|
-
# @return [-1, 0, 1, nil]
|
140
|
-
def <=>(other)
|
141
|
-
@ns <=> other.ns if other.is_a?(Instant)
|
142
|
-
end
|
143
|
-
|
144
|
-
# Determine if +other+'s value equals that of this +Instant+.
|
145
|
-
# Use +eql?+ if type checks are desired for future compatibility.
|
146
|
-
#
|
147
|
-
# @return [Boolean]
|
148
|
-
# @see #eql?
|
149
|
-
def ==(other)
|
150
|
-
other.is_a?(Instant) && @ns == other.ns
|
151
|
-
end
|
152
|
-
|
153
|
-
alias eql? ==
|
154
|
-
|
155
|
-
# Generate a hash for this type and value.
|
156
|
-
#
|
157
|
-
# @return [Fixnum]
|
158
|
-
def hash
|
159
|
-
self.class.hash ^ @ns.hash
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# A type representing a span of time in nanoseconds.
|
164
|
-
class Duration
|
165
|
-
include Comparable
|
166
|
-
|
167
|
-
# Create a new +Duration+ of a specified number of nanoseconds, zero by
|
168
|
-
# default.
|
169
|
-
#
|
170
|
-
# Users are strongly advised to use +#from_nanos+ instead.
|
171
|
-
#
|
172
|
-
# @param nanos [Integer]
|
173
|
-
# @see #from_nanos
|
174
|
-
def initialize(nanos = 0)
|
175
|
-
@ns = Integer(nanos)
|
176
|
-
end
|
177
|
-
|
178
|
-
class << self
|
179
|
-
# Generate a new +Duration+ measuring the given number of seconds.
|
180
|
-
#
|
181
|
-
# @param secs [Numeric]
|
182
|
-
# @return [Duration]
|
183
|
-
def from_secs(secs)
|
184
|
-
new(Integer(secs * 1_000_000_000))
|
185
|
-
end
|
186
|
-
|
187
|
-
# Generate a new +Duration+ measuring the given number of milliseconds.
|
188
|
-
#
|
189
|
-
# @param millis [Numeric]
|
190
|
-
# @return [Duration]
|
191
|
-
def from_millis(millis)
|
192
|
-
new(Integer(millis * 1_000_000))
|
193
|
-
end
|
194
|
-
|
195
|
-
# Generate a new +Duration+ measuring the given number of microseconds.
|
196
|
-
#
|
197
|
-
# @param micros [Numeric]
|
198
|
-
# @return [Duration]
|
199
|
-
def from_micros(micros)
|
200
|
-
new(Integer(micros * 1_000))
|
201
|
-
end
|
202
|
-
|
203
|
-
# Generate a new +Duration+ measuring the given number of nanoseconds.
|
204
|
-
#
|
205
|
-
# @param nanos [Numeric]
|
206
|
-
# @return [Duration]
|
207
|
-
def from_nanos(nanos)
|
208
|
-
new(Integer(nanos))
|
209
|
-
end
|
210
|
-
|
211
|
-
# Return a +Duration+ measuring the elapsed time of the yielded block.
|
212
|
-
#
|
213
|
-
# @example
|
214
|
-
# Duration.measure { sleep(0.5) }.to_s # => "512.226109ms"
|
215
|
-
#
|
216
|
-
# @return [Duration]
|
217
|
-
def measure
|
218
|
-
Instant.now.tap { yield }.elapsed
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Add another +Duration+ or +#to_nanos+-coercible object to this one,
|
223
|
-
# returning a new +Duration+.
|
224
|
-
#
|
225
|
-
# @example
|
226
|
-
# (Duration.from_secs(10) + Duration.from_secs(5)).to_s # => "15s"
|
227
|
-
#
|
228
|
-
# @param [Duration, #to_nanos]
|
229
|
-
# @return [Duration]
|
230
|
-
def +(other)
|
231
|
-
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
232
|
-
|
233
|
-
Duration.new(to_nanos + other.to_nanos)
|
234
|
-
end
|
235
|
-
|
236
|
-
# Subtract another +Duration+ or +#to_nanos+-coercible object from this one,
|
237
|
-
# returning a new +Duration+.
|
238
|
-
#
|
239
|
-
# @example
|
240
|
-
# (Duration.from_secs(10) - Duration.from_secs(5)).to_s # => "5s"
|
241
|
-
#
|
242
|
-
# @param [Duration, #to_nanos]
|
243
|
-
# @return [Duration]
|
244
|
-
def -(other)
|
245
|
-
raise TypeError, 'Not one of: [Duration, #to_nanos]' unless other.respond_to?(:to_nanos)
|
246
|
-
|
247
|
-
Duration.new(to_nanos - other.to_nanos)
|
248
|
-
end
|
249
|
-
|
250
|
-
# Divide this duration by a +Numeric+.
|
251
|
-
#
|
252
|
-
# @example
|
253
|
-
# (Duration.from_secs(10) / 2).to_s # => "5s"
|
254
|
-
#
|
255
|
-
# @param [Numeric]
|
256
|
-
# @return [Duration]
|
257
|
-
def /(other)
|
258
|
-
Duration.new(to_nanos / other)
|
259
|
-
end
|
260
|
-
|
261
|
-
# Multiply this duration by a +Numeric+.
|
262
|
-
#
|
263
|
-
# @example
|
264
|
-
# (Duration.from_secs(10) * 2).to_s # => "20s"
|
265
|
-
#
|
266
|
-
# @param [Numeric]
|
267
|
-
# @return [Duration]
|
268
|
-
def *(other)
|
269
|
-
Duration.new(to_nanos * other)
|
270
|
-
end
|
271
|
-
|
272
|
-
# Unary minus: make a positive +Duration+ negative, and vice versa.
|
273
|
-
#
|
274
|
-
# @example
|
275
|
-
# -Duration.from_secs(-1).to_s # => "1s"
|
276
|
-
# -Duration.from_secs(1).to_s # => "-1s"
|
277
|
-
#
|
278
|
-
# @return [Duration]
|
279
|
-
def -@
|
280
|
-
Duration.new(-to_nanos)
|
281
|
-
end
|
282
|
-
|
283
|
-
# Return a +Duration+ that's absolute (positive).
|
284
|
-
#
|
285
|
-
# @example
|
286
|
-
# Duration.from_secs(-1).abs.to_s # => "1s"
|
287
|
-
# Duration.from_secs(1).abs.to_s # => "1s"
|
288
|
-
#
|
289
|
-
# @return [Duration]
|
290
|
-
def abs
|
291
|
-
return self if positive? || zero?
|
292
|
-
Duration.new(to_nanos.abs)
|
293
|
-
end
|
294
|
-
|
295
|
-
# Compare the *value* of this +Duration+ with another, or any +#to_nanos+-coercible
|
296
|
-
# object, or nil if not comparable.
|
297
|
-
#
|
298
|
-
# @param [Duration, #to_nanos, Object]
|
299
|
-
# @return [-1, 0, 1, nil]
|
300
|
-
def <=>(other)
|
301
|
-
to_nanos <=> other.to_nanos if other.respond_to?(:to_nanos)
|
302
|
-
end
|
303
|
-
|
304
|
-
# Compare the equality of the *value* of this +Duration+ with another, or
|
305
|
-
# any +#to_nanos+-coercible object, or nil if not comparable.
|
306
|
-
#
|
307
|
-
# @param [Duration, #to_nanos, Object]
|
308
|
-
# @return [Boolean]
|
309
|
-
def ==(other)
|
310
|
-
other.respond_to?(:to_nanos) && to_nanos == other.to_nanos
|
311
|
-
end
|
312
|
-
|
313
|
-
# Check equality of the value and type of this +Duration+ with another.
|
314
|
-
#
|
315
|
-
# @param [Duration, Object]
|
316
|
-
# @return [Boolean]
|
317
|
-
def eql?(other)
|
318
|
-
other.is_a?(Duration) && to_nanos == other.to_nanos
|
319
|
-
end
|
320
|
-
|
321
|
-
# Generate a hash for this type and value.
|
322
|
-
#
|
323
|
-
# @return [Fixnum]
|
324
|
-
def hash
|
325
|
-
self.class.hash ^ to_nanos.hash
|
326
|
-
end
|
327
|
-
|
328
|
-
# Return this +Duration+ in seconds.
|
329
|
-
#
|
330
|
-
# @return [Float]
|
331
|
-
def to_secs
|
332
|
-
to_nanos / 1_000_000_000.0
|
333
|
-
end
|
334
|
-
|
335
|
-
# Return this +Duration+ in milliseconds.
|
336
|
-
#
|
337
|
-
# @return [Float]
|
338
|
-
def to_millis
|
339
|
-
to_nanos / 1_000_000.0
|
340
|
-
end
|
341
|
-
|
342
|
-
# Return this +Duration+ in microseconds.
|
343
|
-
#
|
344
|
-
# @return [Float]
|
345
|
-
def to_micros
|
346
|
-
to_nanos / 1_000.0
|
347
|
-
end
|
348
|
-
|
349
|
-
# Return this +Duration+ in nanoseconds.
|
350
|
-
#
|
351
|
-
# @return [Integer]
|
352
|
-
def to_nanos
|
353
|
-
@ns
|
354
|
-
end
|
355
|
-
|
356
|
-
# Return true if this +Duration+ is positive.
|
357
|
-
#
|
358
|
-
# @return [Boolean]
|
359
|
-
def positive?
|
360
|
-
to_nanos.positive?
|
361
|
-
end
|
362
|
-
|
363
|
-
# Return true if this +Duration+ is negative.
|
364
|
-
#
|
365
|
-
# @return [Boolean]
|
366
|
-
def negative?
|
367
|
-
to_nanos.negative?
|
368
|
-
end
|
369
|
-
|
370
|
-
# Return true if this +Duration+ is zero.
|
371
|
-
#
|
372
|
-
# @return [Boolean]
|
373
|
-
def zero?
|
374
|
-
to_nanos.zero?
|
375
|
-
end
|
376
|
-
|
377
|
-
# Sleep for the duration of this +Duration+. Equivalent to
|
378
|
-
# +Kernel.sleep(duration.to_secs)+.
|
379
|
-
#
|
380
|
-
# @example
|
381
|
-
# Duration.from_secs(1).sleep # => 1
|
382
|
-
# Duration.from_secs(-1).sleep # => raises NotImplementedError
|
383
|
-
#
|
384
|
-
# @raise [NotImplementedError] negative +Duration+ sleeps are not yet supported.
|
385
|
-
# @return [Integer]
|
386
|
-
# @see Instant#sleep
|
387
|
-
def sleep
|
388
|
-
raise NotImplementedError, 'time travel module missing' if negative?
|
389
|
-
Kernel.sleep(to_secs)
|
390
|
-
end
|
391
|
-
|
392
|
-
DIVISORS = [
|
393
|
-
[1_000_000_000.0, 's'],
|
394
|
-
[1_000_000.0, 'ms'],
|
395
|
-
[1_000.0, 'μs'],
|
396
|
-
[0, 'ns']
|
397
|
-
].map(&:freeze).freeze
|
398
|
-
|
399
|
-
private_constant :DIVISORS
|
400
|
-
|
401
|
-
# Format this +Duration+ into a human-readable string, with a given number
|
402
|
-
# of decimal places.
|
403
|
-
#
|
404
|
-
# The exact format is subject to change, users with specific requirements
|
405
|
-
# are encouraged to use their own formatting methods.
|
406
|
-
#
|
407
|
-
# @example
|
408
|
-
# Duration.from_nanos(100).to_s # => "100ns"
|
409
|
-
# Duration.from_micros(100).to_s # => "100μs"
|
410
|
-
# Duration.from_millis(100).to_s # => "100ms"
|
411
|
-
# Duration.from_secs(100).to_s # => "100s"
|
412
|
-
# Duration.from_nanos(1234567).to_s # => "1.234567ms"
|
413
|
-
# Duration.from_nanos(1234567).to_s(2) # => "1.23ms"
|
414
|
-
#
|
415
|
-
# @param precision [Integer] the maximum number of decimal places
|
416
|
-
# @return [String]
|
417
|
-
def to_s(precision = 9)
|
418
|
-
precision = Integer(precision).abs
|
419
|
-
div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
|
420
|
-
|
421
|
-
if div.zero?
|
422
|
-
format('%d%s', to_nanos, unit)
|
423
|
-
else
|
424
|
-
format("%#.#{precision}f", to_nanos / div).sub(/\.?0*\z/, '') << unit
|
425
|
-
end
|
426
|
-
end
|
427
|
-
|
428
|
-
# def to_s_bigdecimal(precision = 9)
|
429
|
-
# require 'bigdecimal'
|
430
|
-
# precision = Integer(precision).abs
|
431
|
-
# div, unit = DIVISORS.find { |d, _| to_nanos.abs >= d }
|
432
|
-
|
433
|
-
# if div.zero?
|
434
|
-
# format('%d%s', to_nanos, unit)
|
435
|
-
# else
|
436
|
-
# num = (BigDecimal(to_nanos) / div.to_i)
|
437
|
-
# .round(precision, :banker).to_s('F').sub(/\.?0*$/, '')
|
438
|
-
# format('%s%s', num, unit)
|
439
|
-
# end
|
440
|
-
# end
|
441
|
-
|
442
|
-
# def to_s_divmod(precision = 9)
|
443
|
-
# precision = Integer(precision).abs
|
444
|
-
|
445
|
-
# ns = to_nanos.abs
|
446
|
-
# div, unit = DIVISORS.find { |d, _| ns >= d }
|
447
|
-
|
448
|
-
# return format('%dns', to_nanos) if div.zero?
|
449
|
-
|
450
|
-
# whole, frac = to_nanos.divmod(div.to_i)
|
451
|
-
|
452
|
-
# if precision.zero? || frac.zero?
|
453
|
-
# whole += 1 if frac > div / 2
|
454
|
-
# return format('%d%s', whole, unit)
|
455
|
-
# end
|
456
|
-
|
457
|
-
# # XXX: still need to round: Duration.from_nanos(99999999999).to_s_divmod 7 # => 99.1s
|
458
|
-
# p frac
|
459
|
-
# frac = ((frac / div.to_f) * (10 ** precision)).round
|
460
|
-
# if frac.to_s =~ /\A10*\z/ # erm...
|
461
|
-
# whole += 1
|
462
|
-
# frac = 0
|
463
|
-
# end
|
464
|
-
# num = format('%d.%d', whole, frac)
|
465
|
-
# num.sub!(/\.?0*\z/, '') if precision.nonzero?
|
466
|
-
# num << unit
|
467
|
-
# end
|
468
|
-
end
|
469
|
-
end
|
3
|
+
require_relative 'monotime/version'
|
4
|
+
require_relative 'monotime/duration'
|
5
|
+
require_relative 'monotime/instant'
|
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,8 @@ 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", "~>
|
35
|
-
spec.add_development_dependency "rake", "~>
|
34
|
+
spec.add_development_dependency "bundler", "~> 2"
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
36
36
|
spec.add_development_dependency "minitest", "~> 5.0"
|
37
|
+
spec.add_development_dependency "simplecov", "~> 0.21"
|
37
38
|
end
|