monotime 0.1.0 → 0.2.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: d8f48959572be85d87513cfc071e48eea56af6c15e62d7c8d2b253fecf1b23d2
4
- data.tar.gz: 73f63a474ba0949c4ddc2e665e9102f89524ab9031c52476a3420b49cb3656e2
3
+ metadata.gz: 5a97344679471a970a324027b84b560faf9dfc9597c0937094de1486cc0da602
4
+ data.tar.gz: 35d55277a3181f0033939263122296b3a5328c5cc1605722644dd21aa9305ac1
5
5
  SHA512:
6
- metadata.gz: e03126c84a579e4096b28c56afd93eab7099bc782ea9c6ec0f2be8a9d8375e1ed90ce8efc3b22f59a93356c104b76c0945f18fd36bae0232cc0cf0f65fb68f98
7
- data.tar.gz: 1f74dd4465f686198e4e1fc098d04387528a7cc99d78ba850773cc137c99a3ab62e23b34c5424c9c6d03e7bb34ee9f383d2a6173c196a857eff31df25c62c108
6
+ metadata.gz: 29d7c0fb1aa092127f1a1a3036552466d9f6ea83d337fa3c9ffda0e88d3b5fb4ca996f81c828701a2a1bee4bf44d8c6392395298ac9f257034b3b71b8d0437b3
7
+ data.tar.gz: bd3bef852b6d5b25810091e0ee7452e60e03a5cd25cc5577e25760452804d3d201cfc05efd2dcf19d91680ff6bdacbd93b9428473def942ac3c024aea4675c9d
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Metrics/LineLength:
2
+ Max: 96
data/README.md CHANGED
@@ -71,7 +71,7 @@ And how to do basic maths on itself:
71
71
  (Instant.now - Duration.from_secs(1)).elapsed.to_s # => "1.000014627s"
72
72
 
73
73
  # Instant - Instant => Duration
74
- (Instant.now - Instant.now).to_s # => "3.439μs"
74
+ (Instant.now - Instant.now).to_s # => "-5.585μs"
75
75
  ```
76
76
 
77
77
  `Duration` and `Instant` are also `Comparable` with other instances of their
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "monotime"
3
+ require 'bundler/setup'
4
+ require 'monotime'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
10
+ # require 'pry'
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Monotime
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/monotime.rb CHANGED
@@ -5,147 +5,167 @@ require 'monotime/version'
5
5
  require 'dry-equalizer'
6
6
 
7
7
  module Monotime
8
+ # A measurement from the operating system's monotonic clock, with up to
9
+ # nanosecond precision.
8
10
  class Instant
9
- attr_reader :ns
11
+ # A measurement, in nanoseconds. Should be considered opaque and
12
+ # non-portable outside the process that created it.
13
+ protected def ns() @ns end
10
14
 
11
15
  include Dry::Equalizer(:ns)
12
16
  include Comparable
13
17
 
14
- def initialize(ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))
15
- raise TypeError, 'Not an Integer' unless ns.is_a? Integer
16
-
17
- @ns = ns
18
+ # Create a new +Instant+ from a given nanosecond measurement, defaulting to
19
+ # that given by +Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))+.
20
+ #
21
+ # Users should generally *not* pass anything to this function.
22
+ def initialize(nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond))
23
+ @ns = Integer(nanos)
18
24
  end
19
25
 
26
+ # An alias to +new+, and generally preferred over it.
20
27
  def self.now
21
28
  new
22
29
  end
23
30
 
31
+ # Return a +Duration+ between this +Instant+ and another.
24
32
  def duration_since(earlier)
25
33
  case earlier
26
- when Instant then self - earlier
34
+ when Instant then earlier - self
27
35
  else raise TypeError, 'Not an Instant'
28
36
  end
29
37
  end
30
38
 
39
+ # Return a +Duration+ since this +Instant+ and now.
31
40
  def elapsed
32
41
  duration_since(self.class.now)
33
42
  end
34
43
 
44
+ # Sugar for +elapsed.to_s+.
45
+ def to_s(*args)
46
+ elapsed.to_s(*args)
47
+ end
48
+
49
+ # Add a +Duration+ to this +Instant+, returning a new +Instant+.
35
50
  def +(other)
36
51
  case other
37
- when Duration then Instant.new(self.ns + other.ns)
38
- when Integer then Instant.new(self.ns + other)
39
- else raise TypeError, 'Not a Duration or Integer'
52
+ when Duration then Instant.new(@ns + other.to_nanos)
53
+ else raise TypeError, 'Not a Duration'
40
54
  end
41
55
  end
42
56
 
57
+ # Subtract another +Instant+ to generate a +Duration+ between the two,
58
+ # or a +Duration+, to generate an +Instant+ offset by it.
43
59
  def -(other)
44
60
  case other
45
- when Instant then Duration.new(other.ns - self.ns)
46
- when Duration then Instant.new(self.ns - other.ns)
47
- when Integer then Instant.new(self.ns - other)
48
- else raise TypeError, 'Not an Instant, Duration or Integer'
61
+ when Instant then Duration.new(@ns - other.ns)
62
+ when Duration then Instant.new(@ns - other.to_nanos)
63
+ else raise TypeError, 'Not an Instant or Duration'
49
64
  end
50
65
  end
51
66
 
67
+ # Compare this +Instant+ with another.
52
68
  def <=>(other)
53
69
  case other
54
- when self.class then self.ns <=> other.ns
55
- else raise TypeError, 'Not a #{self.class}'
70
+ when Instant then @ns <=> other.ns
71
+ else raise TypeError, 'Not an Instant'
56
72
  end
57
73
  end
58
74
  end
59
75
 
76
+ # A type representing a span of time in nanoseconds.
60
77
  class Duration
61
- attr_reader :ns
62
-
63
- include Dry::Equalizer(:ns)
78
+ include Dry::Equalizer(:to_nanos)
64
79
  include Comparable
65
80
 
66
- def initialize(ns = 0)
67
- raise TypeError, 'Not an Integer' unless ns.is_a? Integer
68
-
69
- @ns = ns
81
+ # Create a new +Duration+ of a specified number of nanoseconds, zero by
82
+ # default.
83
+ def initialize(nanos = 0)
84
+ @ns = Integer(nanos)
70
85
  end
71
86
 
72
87
  class << self
88
+ # Generate a new +Duration+ measuring the given number of seconds.
73
89
  def from_secs(secs)
74
90
  new(Integer(Float(secs) * 1_000_000_000))
75
91
  end
76
92
 
93
+ # Generate a new +Duration+ measuring the given number of milliseconds.
77
94
  def from_millis(millis)
78
95
  new(Integer(Float(millis) * 1_000_000))
79
96
  end
80
97
 
98
+ # Generate a new +Duration+ measuring the given number of microseconds.
81
99
  def from_micros(micros)
82
100
  new(Integer(Float(micros) * 1_000))
83
101
  end
84
102
 
103
+ # Generate a new +Duration+ measuring the given number of nanoseconds.
85
104
  def from_nanos(nanos)
86
105
  new(Integer(nanos))
87
106
  end
88
107
 
108
+ # Return a +Duration+ measuring the elapsed time of the yielded block.
89
109
  def measure
90
110
  Instant.now.tap { yield }.elapsed
91
111
  end
92
112
  end
93
113
 
114
+ # Add another +Duration+ to this one, returning a new +Duration+.
94
115
  def +(other)
95
- case other
96
- when Duration then Duration.new(self.ns + other.ns)
97
- when Numeric then Duration.new(self.ns + other)
98
- else raise TypeError, 'Not a Duration or Numeric'
99
- end
116
+ Duration.new(to_nanos + other.to_nanos)
100
117
  end
101
118
 
119
+ # Subtract another +Duration+ from this one, returning a new +Duration+.
102
120
  def -(other)
103
- case other
104
- when Duration then Duration.new(self.ns - other.ns)
105
- when Numeric then Duration.new(self.ns - other)
106
- else raise TypeError, 'Not a Duration or Numeric'
107
- end
121
+ Duration.new(to_nanos - other.to_nanos)
108
122
  end
109
123
 
124
+ # Compare this +Duration+ with another.
110
125
  def <=>(other)
111
- case other
112
- when self.class then self.ns <=> other.ns
113
- else raise TypeError, "Not a #{self.class}"
114
- end
126
+ to_nanos <=> other.to_nanos
115
127
  end
116
128
 
129
+ # Return this +Duration+ in seconds.
117
130
  def to_secs
118
- @ns / 1_000_000_000
131
+ to_nanos / 1_000_000_000.0
119
132
  end
120
133
 
134
+ # Return this +Duration+ in milliseconds.
121
135
  def to_millis
122
- @ns / 1_000_000
136
+ to_nanos / 1_000_000.0
123
137
  end
124
138
 
139
+ # Return this +Duration+ in microseconds.
125
140
  def to_micros
126
- @ns * 1_000
141
+ to_nanos / 1_000.0
127
142
  end
128
143
 
144
+ # Return this +Duration+ in nanoseconds.
129
145
  def to_nanos
130
146
  @ns
131
147
  end
132
148
 
149
+ DIVISORS = [
150
+ [1_000_000_000.0, 's'],
151
+ [1_000_000.0, 'ms'],
152
+ [1_000.0, 'μs'],
153
+ [0, 'ns']
154
+ ].map(&:freeze).freeze
155
+
156
+ # Format this +Duration+ into a human-readable string, with a given number
157
+ # of decimal places.
158
+ #
159
+ # The exact format is subject to change, users with specific requirements
160
+ # are encouraged to use their own formatting methods.
133
161
  def to_s(precision = 9)
134
- postfix = 's'
135
- ns = self.ns.abs
136
- num = "#{'-' if self.ns < 0}%.#{precision}f" % if ns >= 1_000_000_000
137
- ns / 1_000_000_000.0
138
- elsif ns >= 1_000_000
139
- postfix = 'ms'
140
- ns / 1_000_000.0
141
- elsif ns >= 1_000
142
- postfix = 'μs'
143
- ns / 1_000.0
144
- else
145
- postfix = 'ns'
146
- ns
147
- end
148
- num.sub(/\.?0*$/, '') << postfix
162
+ precision = Integer(precision).abs
163
+ ns = to_nanos.abs
164
+ div, unit = DIVISORS.find { |d, _| ns >= d }
165
+ ns /= div if div.nonzero?
166
+ num = format("#{'-' if to_nanos.negative?}%.#{precision}f", ns)
167
+ num.sub!(/\.?0*$/, '') if precision.nonzero?
168
+ num << unit
149
169
  end
150
170
  end
151
171
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monotime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Hurst
@@ -74,9 +74,9 @@ extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
+ - ".rubocop.yml"
77
78
  - ".travis.yml"
78
79
  - Gemfile
79
- - Gemfile.lock
80
80
  - LICENSE.txt
81
81
  - README.md
82
82
  - Rakefile
data/Gemfile.lock DELETED
@@ -1,24 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- monotime (0.1.0)
5
- dry-equalizer
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- dry-equalizer (0.2.1)
11
- minitest (5.11.3)
12
- rake (10.5.0)
13
-
14
- PLATFORMS
15
- ruby
16
-
17
- DEPENDENCIES
18
- bundler (~> 1.16)
19
- minitest (~> 5.0)
20
- monotime!
21
- rake (~> 10.0)
22
-
23
- BUNDLED WITH
24
- 1.16.3