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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a9395967e506d690912041251bc7b6afd1a373aa49d0b4425ec08ca611e5238
|
4
|
+
data.tar.gz: '09ec6a1e96c9ce434d4847d275d85f7514c4a9bb3d9c71d205b04ab0f06e6d5b'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
26
|
+
`Monotime` is tested on Ruby 2.5—3.0 and recent JRuby 9.x releases.
|
25
27
|
|
26
|
-
|
27
|
-
this pile of nonsense:
|
28
|
+
## Usage
|
28
29
|
|
29
30
|
```ruby
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
36
|
-
|
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
|
-
`
|
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
|
-
|
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
|
-
`
|
52
|
-
|
53
|
-
|
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
|
-
|
67
|
+
Conversions:
|
56
68
|
|
57
69
|
```ruby
|
58
|
-
Duration.
|
59
|
-
Duration.
|
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
|
74
|
+
And basic mathematical operations:
|
64
75
|
|
65
76
|
```ruby
|
66
|
-
(Duration.
|
67
|
-
(Duration.
|
68
|
-
(Duration.
|
69
|
-
(Duration.
|
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.
|
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
|
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.
|
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.
|
99
|
-
|
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
|
-
|
112
|
+
interval = Duration.secs(60)
|
113
|
+
start = Instant.now
|
103
114
|
loop do
|
104
|
-
|
105
|
-
|
106
|
-
start
|
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
|
121
|
+
Or you can declare an `Instant` in the future and sleep to that point:
|
112
122
|
|
113
123
|
```ruby
|
114
|
-
|
115
|
-
|
116
|
-
|
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`
|
120
|
-
the desired sleep
|
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.
|
137
|
-
(Instant.now - 42).to_s
|
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
|