duration.rb 0.3.6 → 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: d4947aa87a27f048e0c81a3a6b7a9ce86e6d60b82d0f76db4667694865bf1887
4
- data.tar.gz: 104cc227d961d1e48925b1e8118588f02ba9b88289fdf9e8e3dd06753ac4ced7
3
+ metadata.gz: 9f7d0f0552f3ce6cb16fd75947bad208ccf403bd8e572b23ae262d4b6aada58f
4
+ data.tar.gz: 7423385510fa2bb3769521892430705fb036eb12b1a40c276a9b4b5445b7bc94
5
5
  SHA512:
6
- metadata.gz: 0feb8f48532ed5bc8484f9ff897b294c34b88cfdd5a9c44f77cf4deefba5d7bc4cab774c693fa63fdabc1f6b89cad4a9500b7858257e53131a774b28905d3706
7
- data.tar.gz: 82d00f722d7255f803b4c8632d0cf9a788c1cdf22d38f9755ef274281604a22ec74b052c0d5e9f3ecb165eed4abed4bd8f584f213731649cfba1248c6f891848
6
+ metadata.gz: be48eabdcaddff69c28452886f068623646af55255ea06f17809e38b2fafd0c343ccc7592478952bb1a7879ef60b10433ad074fbbfa0d47ee0c54405b0066723
7
+ data.tar.gz: 852569a9d5a66c7e576c9c33310c549e6846e369bf934da1eae13d9c54a343a9c6bc06e0dcdbb5da12768fc24f0feabf9c20a1c1b0d3c405b6a40b7f299d9af3
data/CHANGELOG CHANGED
@@ -1,5 +1,29 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 20260718
4
+
5
+ 0.4.0: + Nanoseconds and Microseconds, arithmetic and comparison, exact conversions without a base, and the Numeric monkeypatch made opt-in.
6
+
7
+ 1. + Duration::Nanoseconds
8
+ 2. + Duration::Microseconds: This and Nanoseconds are the two units the clock actually speaks, Process.clock_gettime taking :nanosecond and :microsecond directly. Milliseconds was the floor, which left nothing beneath it.
9
+ 3. ~ Duration::Common: + #+, #-, and Comparable via #<=>. Arithmetic and comparison happen in the receiver's own unit, so 5.minutes + 30.seconds is 5.5 minutes rather than any number of seconds, and no unit is privileged. Combining with a non-duration raises TypeError, since a bare number has no unit. Comparing with a non-duration returns nil rather than raising, so that == is false.
10
+ 4. ~ lib/Duration/*.rb: - the coercion to Float in initialize. The value is held as given, so an Integer stays an Integer and a Rational stays a Rational. A Float stays a Float: the library neither manufactures precision nor discards it.
11
+ 5. ~ lib/Duration/*.rb: Conversions divide by a Rational literal (60r, 1000r) rather than an Integer, so a conversion is exact for an exact input. 5.minutes.to_hours holds 1/12, not 0.0833..., and (1.hour - 50.minutes).to_minutes.to_f is 10.0 rather than 9.999999999999998. There is no base and no Float floor in the interior; to_f is the edge where a Float is asked for.
12
+ 6. + Duration::Relative#ago, #hence, moved here from Common. Every unit includes it, Months among them, so 1.month.ago remains: it displaces by the mean month, approximate rather than calendar-correct (see 0.3.5's note), as 1.month.to_days is a mean.
13
+ 7. + lib/Duration/*.rb: A private #unit per class, naming the unit as a symbol, which Common uses to reach the quantity and to convert the other operand. Explicit rather than reflected from the class name.
14
+ 8. ~ lib/Duration.rb: + require the two new classes
15
+ 9. ~ lib/Duration.rb: - require Duration/Numeric. BREAKING: require 'Duration' no longer monkeypatches Numeric, so 30.minutes is not available until Duration/Numeric is required. A duration library used directly may want the sugar, but a gem depending on this one should not have Numeric extended from under it.
16
+ 10. ~ lib/Duration/Numeric.rb: + require the classes, so require 'Duration/Numeric' stands alone, + nanoseconds and microseconds with their singular aliases.
17
+ 11. + test/Duration/Nanoseconds_test.rb, test/Duration/Microseconds_test.rb, test/Duration/Common_test.rb: The new units, and the arithmetic, comparison and exactness.
18
+ 12. ~ test/Duration/Numeric_test.rb: + require Duration/Numeric, since it is no longer loaded by default, + the nanosecond and microsecond extensions.
19
+ 13. ~ test/Duration/Seconds_test.rb: + that an Integer and a Rational are held without coercion.
20
+ 14. ~ lib/Duration.rb --> lib/duration.rb: The one file required from outside the gem, lowercased to match the gem name so require 'duration' resolves on a case-sensitive filesystem and not only on a case-insensitive one. The Duration/ subtree stays PascalCase, reached by exact-case require_relative.
21
+ 15. ~ lib/Duration/Numeric.rb: /require_relative '../Duration'/require_relative '../duration'/. The one interior reference reaching back up to the load file, which would otherwise have failed on a case-sensitive filesystem after the rename.
22
+ 16. + lib/duration-numeric.rb: A lowercase top-level shim that require_relatives Duration/Numeric, so the sugar opts in via require 'duration-numeric' rather than reaching into the PascalCase namespace with require 'Duration/Numeric'. A file rather than a duration/ directory, so it does not collide with Duration/ where the filesystem is case-insensitive.
23
+ 17. ~ test/test_helper.rb: /require_relative '../lib/Duration'/require_relative '../lib/duration'/, following the rename.
24
+ 18. ~ README.md: + Philosophy and Loading sections, + the new units, arithmetic and comparison, and the exactness through Rational.
25
+ 19. ~ README.md: /require 'Duration'/require 'duration'/, and /require 'Duration/Numeric'/require 'duration-numeric'/ in the Loading section.
26
+ 20. ~ Duration::VERSION: /0.3.6/0.4.0/
3
27
 
4
28
  ## 20260717
5
29
 
@@ -11,7 +35,8 @@
11
35
  4. ~ duration.rb.gemspec: + Gem::Specification#development_dependencies=
12
36
  5. ~ duration.rb.gemspec: + development_dependencies
13
37
  6. - test/test_all.rb
14
- 9. ~ Duration::VERSION: /0.3.5/0.3.6/
38
+ 7. ~ Duration::VERSION: /0.3.5/0.3.6/
39
+
15
40
 
16
41
  ## 20260717
17
42
 
data/README.md CHANGED
@@ -21,90 +21,111 @@ Or install directly:
21
21
  $ gem install duration.rb
22
22
  ```
23
23
 
24
+ ## Philosophy
25
+
26
+ This is the anti-`ActiveSupport::Duration`. There, every duration is really a
27
+ number of seconds and the unit is decoration: `1.hour == 3600`. Here the unit
28
+ *is* the class. A `Minutes` holds minutes — not seconds it renders as minutes —
29
+ so `30.minutes.to_i` is `30`, not `1800`. No unit is privileged; there is no
30
+ base. Conversion is a behaviour (`to_hours`), not a representation.
31
+
32
+ Because nothing is normalised at construction, exactness is yours to keep: hand
33
+ a constructor an `Integer` or a `Rational` and it stays exact all the way
34
+ through, since conversions divide by exact ratios rather than through a float.
35
+ Hand it a `Float` and it stays a `Float` — the library never manufactures
36
+ precision and never silently discards it.
37
+
38
+ ```ruby
39
+ Duration::Minutes.new(5).to_hours.to_f # => 0.08333333333333333
40
+ Duration::Minutes.new(5).to_hours # holds exactly 1/12, not 0.0833...
41
+ (Duration::Hours.new(1) - Duration::Minutes.new(50)).to_minutes.to_f
42
+ # => 10.0 exactly (no float drift)
43
+ ```
44
+
45
+ ## Loading
46
+
47
+ ```ruby
48
+ require 'duration' # the duration classes only
49
+
50
+ require 'duration-numeric' # opt in to the Numeric sugar (5.minutes, etc.),
51
+ # which monkeypatches Numeric
52
+ ```
53
+
54
+ The examples below use the `Numeric` sugar, so they assume
55
+ `require 'duration-numeric'`. Without it, construct durations directly with
56
+ `Duration::Minutes.new(5)` and friends.
57
+
24
58
  ## Usage
25
59
 
26
60
  ### Basic Duration Creation
27
61
  ```ruby
28
62
  # Create durations using convenience methods
29
- 200.milliseconds # => #<Milliseconds: @milliseconds=200>
30
- 1.second # => #<Seconds: @seconds=1>
31
- 30.minutes # => #<Minutes: @minutes=30>
32
- 2.hours # => #<Hours: @hours=2>
33
- 7.days # => #<Days: @days=7>
34
- 4.weeks # => #<Weeks: @weeks=4>
35
- 6.months # => #<Months: @months=6>
63
+ 500.nanoseconds # => #<Nanoseconds @nanoseconds=500>
64
+ 500.microseconds # => #<Microseconds @microseconds=500>
65
+ 200.milliseconds # => #<Milliseconds @milliseconds=200>
66
+ 1.second # => #<Seconds @seconds=1>
67
+ 30.minutes # => #<Minutes @minutes=30>
68
+ 2.hours # => #<Hours @hours=2>
69
+ 7.days # => #<Days @days=7>
70
+ 4.weeks # => #<Weeks @weeks=4>
71
+ 6.months # => #<Months @months=6>
36
72
  ```
37
73
 
38
74
  ### Conversions
39
75
  ```ruby
40
- # Convert to integers
41
- 200.milliseconds.to_i # => 200
42
- 1.second.to_i # => 1
43
- 30.minutes.to_i # => 30
44
- 2.hours.to_i # => 2
45
- 7.days.to_i # => 7
46
- 4.weeks.to_i # => 4
47
- 6.months.to_i # => 6
48
-
49
- # Convert to floats
50
- 200.milliseconds.to_f # => 200.0
51
- 1.second.to_f # => 1.0
76
+ # The number is in the unit's own terms, not a base:
77
+ 30.minutes.to_i # => 30 (not 1800)
52
78
  30.minutes.to_f # => 30.0
53
- 2.hours.to_f # => 2.0
54
- 7.days.to_f # => 7.0
55
- 4.weeks.to_f # => 4.0
56
- 6.months.to_f # => 6.0
57
-
58
- # Convert between units
59
- 90.minutes.to_hours # => #<Hours: @hours=1.5>
60
- 1.5.hours.to_minutes # => #<Minutes: @minutes=90>
61
- 1.day.to_seconds # => #<Seconds: @seconds=86400>
62
79
 
63
- # Chain conversions
80
+ # Convert between units. The result is exact (a Rational internally) when the
81
+ # input is exact; call to_f at the edge for a float.
82
+ 90.minutes.to_hours # => #<Hours @hours=(3/2)>
64
83
  90.minutes.to_hours.to_f # => 1.5
65
84
  1.5.hours.to_minutes.to_i # => 90
66
85
  1.day.to_seconds.to_i # => 86400
86
+ 5.minutes.to_hours.to_f # => 0.08333333333333333
87
+ ```
88
+
89
+ ### Arithmetic and Comparison
90
+ ```ruby
91
+ # Durations add and subtract, with the left operand's unit deciding the result:
92
+ 5.minutes + 30.seconds # => #<Minutes @minutes=(11/2)> (5.5 minutes)
93
+ 5.minutes - 30.seconds # => #<Minutes @minutes=(9/2)> (4.5 minutes)
94
+ 5.minutes + 30 # => TypeError (a bare number has no unit)
95
+
96
+ # Durations are Comparable, across units and exactly:
97
+ 5.minutes == 300.seconds # => true
98
+ 90.seconds > 1.minute # => true
99
+ [1.hour, 30.seconds, 5.minutes].sort
100
+ # => [30.seconds, 5.minutes, 1.hour]
67
101
  ```
68
102
 
69
103
  ### Time Calculations
70
104
  ```ruby
71
105
  # The past
72
- 1.millisecond.ago # => #<Time:> (now - 1.millisecond.to_seconds.to_f)
73
106
  2.seconds.ago # => #<Time:> (now - 2.seconds.to_seconds.to_f)
74
107
  3.minutes.ago # => #<Time:> (now - 3.minutes.to_seconds.to_f)
75
108
  4.hours.ago # => #<Time:> (now - 4.hours.to_seconds.to_f)
76
109
  5.days.ago # => #<Time:> (now - 5.days.to_seconds.to_f)
77
110
  6.weeks.ago # => #<Time:> (now - 6.weeks.to_seconds.to_f)
78
- 7.months.ago # => #<Time:> (now - 7.months.to_seconds.to_f)
79
111
 
80
112
  # The future
81
- 1.millisecond.hence # => #<Time:> (now + 1.millisecond.to_seconds.to_f)
82
113
  2.seconds.hence # => #<Time:> (now + 2.seconds.to_seconds.to_f)
83
114
  3.minutes.hence # => #<Time:> (now + 3.minutes.to_seconds.to_f)
84
115
  4.hours.hence # => #<Time:> (now + 4.hours.to_seconds.to_f)
85
116
  5.days.hence # => #<Time:> (now + 5.days.to_seconds.to_f)
86
117
  6.weeks.hence # => #<Time:> (now + 6.weeks.to_seconds.to_f)
87
- 7.months.hence # => #<Time:> (now + 7.months.to_seconds.to_f)
88
118
  ```
89
119
 
90
120
  ### A Note on Months
91
-
92
121
  `Months` is the odd one out. Every other unit is a fixed quantity with an exact
93
- ratio to every other, but a month is a calendar operation whose length depends
94
- on which month. The conversions use the Gregorian mean month (2629746 seconds),
95
- so they are honest only as a statistical average:
96
-
97
- ```ruby
98
- 12.months.to_days # => 365.2425, the Gregorian mean year
99
- ```
100
-
101
- Two actual calendar months are anywhere from 59 to 62 days, while the mean gives
102
- about 60.87 — no particular pair of months. No single constant can be right.
103
-
104
- For the same reason `ago` and `hence` are not calendar-correct on `Months`: one
105
- month before the 15th of March is the 15th of February to a calendar, but the
106
- mean lands on the 12th. Where the calendar matters, reach for a date-anchored
107
- type such as month.rb.
122
+ ratio to every other; a month is a calendar operation whose length depends on
123
+ the calendar. Its conversions use the Gregorian mean month (2629746 seconds),
124
+ so they are honest only as a statistical average, never as calendar-correct
125
+ arithmetic. Its `ago`/`hence` inherit this — they displace by the mean month,
126
+ so they are approximate rather than calendar-correct; reach for a
127
+ calendar-anchored type such as `month.rb` when that matters. `Months` is slated
128
+ for removal in 0.5.0.
108
129
 
109
130
  ## Contributing
110
131
 
@@ -3,12 +3,34 @@
3
3
 
4
4
  module Duration
5
5
  module Common
6
- def ago
7
- Time.now - self.to_seconds.to_f
6
+ include Comparable
7
+
8
+ def +(other)
9
+ combine(:+, other)
10
+ end
11
+
12
+ def -(other)
13
+ combine(:-, other)
14
+ end
15
+
16
+ def <=>(other)
17
+ return nil unless other.is_a?(Duration::Common)
18
+ quantity <=> other.send(:"to_#{unit}").quantity
8
19
  end
9
20
 
10
- def hence
11
- Time.now + self.to_seconds.to_f
21
+ protected
22
+
23
+ def quantity
24
+ instance_variable_get(:"@#{unit}")
25
+ end
26
+
27
+ private
28
+
29
+ def combine(operator, other)
30
+ unless other.is_a?(Duration::Common)
31
+ raise TypeError, "can't combine #{self.class} with #{other.class}"
32
+ end
33
+ self.class.new(quantity.send(operator, other.send(:"to_#{unit}").quantity))
12
34
  end
13
35
  end
14
36
  end
data/lib/Duration/Days.rb CHANGED
@@ -2,12 +2,22 @@
2
2
  # Duration::Days
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Hours'
6
7
  require_relative './Weeks'
7
8
 
8
9
  module Duration
9
10
  class Days
10
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_hours.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ to_hours.to_microseconds
20
+ end
11
21
 
12
22
  def to_milliseconds
13
23
  to_hours.to_milliseconds
@@ -30,7 +40,7 @@ module Duration
30
40
  end
31
41
 
32
42
  def to_weeks
33
- Duration::Weeks.new(@days/7)
43
+ Duration::Weeks.new(@days / 7r)
34
44
  end
35
45
 
36
46
  def to_months
@@ -42,13 +52,17 @@ module Duration
42
52
  end
43
53
 
44
54
  def to_f
45
- @days
55
+ @days.to_f
46
56
  end
47
57
 
48
58
  private
49
59
 
50
60
  def initialize(days = nil)
51
- @days = days.to_f
61
+ @days = days
62
+ end
63
+
64
+ def unit
65
+ :days
52
66
  end
53
67
  end
54
68
  end
@@ -2,12 +2,22 @@
2
2
  # Duration::Hours
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Minutes'
6
7
  require_relative './Days'
7
8
 
8
9
  module Duration
9
10
  class Hours
10
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_minutes.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ to_minutes.to_microseconds
20
+ end
11
21
 
12
22
  def to_milliseconds
13
23
  to_minutes.to_milliseconds
@@ -26,7 +36,7 @@ module Duration
26
36
  end
27
37
 
28
38
  def to_days
29
- Duration::Days.new(@hours/24)
39
+ Duration::Days.new(@hours / 24r)
30
40
  end
31
41
 
32
42
  def to_weeks
@@ -42,13 +52,17 @@ module Duration
42
52
  end
43
53
 
44
54
  def to_f
45
- @hours
55
+ @hours.to_f
46
56
  end
47
57
 
48
58
  private
49
59
 
50
60
  def initialize(hours = nil)
51
- @hours = hours.to_f
61
+ @hours = hours
62
+ end
63
+
64
+ def unit
65
+ :hours
52
66
  end
53
67
  end
54
68
  end
@@ -0,0 +1,68 @@
1
+ # Duration/Microseconds.rb
2
+ # Duration::Microseconds
3
+
4
+ require_relative './Common'
5
+ require_relative './Relative'
6
+ require_relative './Nanoseconds'
7
+ require_relative './Milliseconds'
8
+
9
+ module Duration
10
+ class Microseconds
11
+ include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ Duration::Nanoseconds.new(@microseconds * 1000)
16
+ end
17
+
18
+ def to_microseconds
19
+ self
20
+ end
21
+
22
+ def to_milliseconds
23
+ Duration::Milliseconds.new(@microseconds / 1000r)
24
+ end
25
+
26
+ def to_seconds
27
+ to_milliseconds.to_seconds
28
+ end
29
+
30
+ def to_minutes
31
+ to_milliseconds.to_minutes
32
+ end
33
+
34
+ def to_hours
35
+ to_milliseconds.to_hours
36
+ end
37
+
38
+ def to_days
39
+ to_milliseconds.to_days
40
+ end
41
+
42
+ def to_weeks
43
+ to_milliseconds.to_weeks
44
+ end
45
+
46
+ def to_months
47
+ to_milliseconds.to_months
48
+ end
49
+
50
+ def to_i
51
+ @microseconds.to_i
52
+ end
53
+
54
+ def to_f
55
+ @microseconds.to_f
56
+ end
57
+
58
+ private
59
+
60
+ def initialize(microseconds = nil)
61
+ @microseconds = microseconds
62
+ end
63
+
64
+ def unit
65
+ :microseconds
66
+ end
67
+ end
68
+ end
@@ -2,18 +2,29 @@
2
2
  # Duration::Milliseconds
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
6
+ require_relative './Microseconds'
5
7
  require_relative './Seconds'
6
8
 
7
9
  module Duration
8
10
  class Milliseconds
9
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_microseconds.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ Duration::Microseconds.new(@milliseconds * 1000)
20
+ end
10
21
 
11
22
  def to_milliseconds
12
23
  self
13
24
  end
14
25
 
15
26
  def to_seconds
16
- Duration::Seconds.new(@milliseconds/1000)
27
+ Duration::Seconds.new(@milliseconds / 1000r)
17
28
  end
18
29
 
19
30
  def to_minutes
@@ -41,13 +52,17 @@ module Duration
41
52
  end
42
53
 
43
54
  def to_f
44
- @milliseconds
55
+ @milliseconds.to_f
45
56
  end
46
57
 
47
58
  private
48
59
 
49
60
  def initialize(milliseconds = nil)
50
- @milliseconds = milliseconds.to_f
61
+ @milliseconds = milliseconds
62
+ end
63
+
64
+ def unit
65
+ :milliseconds
51
66
  end
52
67
  end
53
68
  end
@@ -2,12 +2,22 @@
2
2
  # Duration::Minutes
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Seconds'
6
7
  require_relative './Hours'
7
8
 
8
9
  module Duration
9
10
  class Minutes
10
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_seconds.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ to_seconds.to_microseconds
20
+ end
11
21
 
12
22
  def to_milliseconds
13
23
  to_seconds.to_milliseconds
@@ -22,7 +32,7 @@ module Duration
22
32
  end
23
33
 
24
34
  def to_hours
25
- Duration::Hours.new(@minutes/60)
35
+ Duration::Hours.new(@minutes / 60r)
26
36
  end
27
37
 
28
38
  def to_days
@@ -42,13 +52,17 @@ module Duration
42
52
  end
43
53
 
44
54
  def to_f
45
- @minutes
55
+ @minutes.to_f
46
56
  end
47
57
 
48
58
  private
49
59
 
50
60
  def initialize(minutes = nil)
51
- @minutes = minutes.to_f
61
+ @minutes = minutes
62
+ end
63
+
64
+ def unit
65
+ :minutes
52
66
  end
53
67
  end
54
68
  end
@@ -2,11 +2,29 @@
2
2
  # Duration::Months
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Weeks'
6
7
 
7
8
  module Duration
9
+ # A month is not a fixed quantity: its length depends on the calendar. The
10
+ # conversions below use the Gregorian mean month (2629746 seconds), so they
11
+ # are honest only as a statistical average, never as calendar-correct
12
+ # arithmetic. For that, reach for a calendar-anchored type such as month.rb.
13
+ #
14
+ # ago and hence (via Relative) inherit the same caveat: they displace by the
15
+ # mean month rather than an actual calendar month, so they are approximate,
16
+ # not calendar-correct. Months is slated for removal in 0.5.0.
8
17
  class Months
9
18
  include Common
19
+ include Relative
20
+
21
+ def to_nanoseconds
22
+ to_weeks.to_nanoseconds
23
+ end
24
+
25
+ def to_microseconds
26
+ to_weeks.to_microseconds
27
+ end
10
28
 
11
29
  def to_milliseconds
12
30
  to_weeks.to_milliseconds
@@ -30,7 +48,7 @@ module Duration
30
48
 
31
49
  def to_weeks
32
50
  # A month is the Gregorian mean: 2629746 seconds against 604800 per week.
33
- Duration::Weeks.new(@months * 2629746 / 604800.0)
51
+ Duration::Weeks.new(@months * 2629746 / 604800r)
34
52
  end
35
53
 
36
54
  def to_months
@@ -42,13 +60,17 @@ module Duration
42
60
  end
43
61
 
44
62
  def to_f
45
- @months
63
+ @months.to_f
46
64
  end
47
65
 
48
66
  private
49
67
 
50
68
  def initialize(months = nil)
51
- @months = months.to_f
69
+ @months = months
70
+ end
71
+
72
+ def unit
73
+ :months
52
74
  end
53
75
  end
54
76
  end
@@ -0,0 +1,67 @@
1
+ # Duration/Nanoseconds.rb
2
+ # Duration::Nanoseconds
3
+
4
+ require_relative './Common'
5
+ require_relative './Relative'
6
+ require_relative './Microseconds'
7
+
8
+ module Duration
9
+ class Nanoseconds
10
+ include Common
11
+ include Relative
12
+
13
+ def to_nanoseconds
14
+ self
15
+ end
16
+
17
+ def to_microseconds
18
+ Duration::Microseconds.new(@nanoseconds / 1000r)
19
+ end
20
+
21
+ def to_milliseconds
22
+ to_microseconds.to_milliseconds
23
+ end
24
+
25
+ def to_seconds
26
+ to_microseconds.to_seconds
27
+ end
28
+
29
+ def to_minutes
30
+ to_microseconds.to_minutes
31
+ end
32
+
33
+ def to_hours
34
+ to_microseconds.to_hours
35
+ end
36
+
37
+ def to_days
38
+ to_microseconds.to_days
39
+ end
40
+
41
+ def to_weeks
42
+ to_microseconds.to_weeks
43
+ end
44
+
45
+ def to_months
46
+ to_microseconds.to_months
47
+ end
48
+
49
+ def to_i
50
+ @nanoseconds.to_i
51
+ end
52
+
53
+ def to_f
54
+ @nanoseconds.to_f
55
+ end
56
+
57
+ private
58
+
59
+ def initialize(nanoseconds = nil)
60
+ @nanoseconds = nanoseconds
61
+ end
62
+
63
+ def unit
64
+ :nanoseconds
65
+ end
66
+ end
67
+ end
@@ -1,8 +1,20 @@
1
1
  # Duration/Numeric.rb
2
2
  # Duration::Numeric
3
3
 
4
+ require_relative '../duration'
5
+
4
6
  module Duration
5
7
  module Numeric
8
+ def nanoseconds
9
+ Duration::Nanoseconds.new(self)
10
+ end
11
+ alias_method :nanosecond, :nanoseconds
12
+
13
+ def microseconds
14
+ Duration::Microseconds.new(self)
15
+ end
16
+ alias_method :microsecond, :microseconds
17
+
6
18
  def milliseconds
7
19
  Duration::Milliseconds.new(self)
8
20
  end
@@ -0,0 +1,14 @@
1
+ # Duration/Relative.rb
2
+ # Duration::Relative
3
+
4
+ module Duration
5
+ module Relative
6
+ def ago
7
+ Time.now - self.to_seconds.to_f
8
+ end
9
+
10
+ def hence
11
+ Time.now + self.to_seconds.to_f
12
+ end
13
+ end
14
+ end
@@ -2,12 +2,22 @@
2
2
  # Duration::Seconds
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Milliseconds'
6
7
  require_relative './Minutes'
7
8
 
8
9
  module Duration
9
10
  class Seconds
10
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_milliseconds.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ to_milliseconds.to_microseconds
20
+ end
11
21
 
12
22
  def to_milliseconds
13
23
  Duration::Milliseconds.new(@seconds * 1000)
@@ -18,7 +28,7 @@ module Duration
18
28
  end
19
29
 
20
30
  def to_minutes
21
- Duration::Minutes.new(@seconds/60)
31
+ Duration::Minutes.new(@seconds / 60r)
22
32
  end
23
33
 
24
34
  def to_hours
@@ -42,13 +52,17 @@ module Duration
42
52
  end
43
53
 
44
54
  def to_f
45
- @seconds
55
+ @seconds.to_f
46
56
  end
47
57
 
48
58
  private
49
59
 
50
60
  def initialize(seconds = nil)
51
- @seconds = seconds.to_f
61
+ @seconds = seconds
62
+ end
63
+
64
+ def unit
65
+ :seconds
52
66
  end
53
67
  end
54
68
  end
@@ -1,3 +1,3 @@
1
1
  module Duration
2
- VERSION = '0.3.6'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -2,12 +2,22 @@
2
2
  # Duration::Weeks
3
3
 
4
4
  require_relative './Common'
5
+ require_relative './Relative'
5
6
  require_relative './Days'
6
7
  require_relative './Months'
7
8
 
8
9
  module Duration
9
10
  class Weeks
10
11
  include Common
12
+ include Relative
13
+
14
+ def to_nanoseconds
15
+ to_days.to_nanoseconds
16
+ end
17
+
18
+ def to_microseconds
19
+ to_days.to_microseconds
20
+ end
11
21
 
12
22
  def to_milliseconds
13
23
  to_days.to_milliseconds
@@ -35,7 +45,7 @@ module Duration
35
45
 
36
46
  def to_months
37
47
  # A month is the Gregorian mean: 2629746 seconds against 604800 per week.
38
- Duration::Months.new(@weeks * 604800 / 2629746.0)
48
+ Duration::Months.new(@weeks * 604800 / 2629746r)
39
49
  end
40
50
 
41
51
  def to_i
@@ -43,13 +53,17 @@ module Duration
43
53
  end
44
54
 
45
55
  def to_f
46
- @weeks
56
+ @weeks.to_f
47
57
  end
48
58
 
49
59
  private
50
60
 
51
61
  def initialize(weeks = nil)
52
- @weeks = weeks.to_f
62
+ @weeks = weeks
63
+ end
64
+
65
+ def unit
66
+ :weeks
53
67
  end
54
68
  end
55
69
  end
@@ -0,0 +1,4 @@
1
+ # duration-numeric.rb
2
+ # Duration::Numeric
3
+
4
+ require_relative 'Duration/Numeric'
@@ -3,6 +3,8 @@
3
3
 
4
4
  module Duration; end
5
5
 
6
+ require_relative 'Duration/Nanoseconds'
7
+ require_relative 'Duration/Microseconds'
6
8
  require_relative 'Duration/Milliseconds'
7
9
  require_relative 'Duration/Seconds'
8
10
  require_relative 'Duration/Minutes'
@@ -10,5 +12,4 @@ require_relative 'Duration/Hours'
10
12
  require_relative 'Duration/Days'
11
13
  require_relative 'Duration/Weeks'
12
14
  require_relative 'Duration/Months'
13
- require_relative 'Duration/Numeric'
14
15
  require_relative 'Duration/VERSION'
@@ -0,0 +1,61 @@
1
+ # test/duration/common_test.rb
2
+
3
+ require_relative '../test_helper'
4
+ require_relative '../../lib/Duration/Numeric'
5
+
6
+ describe Duration::Common do
7
+ describe "addition" do
8
+ it "adds two durations of the same unit" do
9
+ result = 5.minutes + 3.minutes
10
+ _(result).must_be_instance_of Duration::Minutes
11
+ _(result.to_i).must_equal 8
12
+ end
13
+
14
+ it "adds across units in the left operand's unit" do
15
+ result = 5.minutes + 30.seconds
16
+ _(result).must_be_instance_of Duration::Minutes
17
+ _(result.to_f).must_equal 5.5
18
+ end
19
+
20
+ it "raises when combined with a non-duration" do
21
+ _{5.minutes + 30}.must_raise TypeError
22
+ end
23
+ end
24
+
25
+ describe "subtraction" do
26
+ it "subtracts across units in the left operand's unit" do
27
+ result = 5.minutes - 30.seconds
28
+ _(result).must_be_instance_of Duration::Minutes
29
+ _(result.to_f).must_equal 4.5
30
+ end
31
+ end
32
+
33
+ describe "comparison" do
34
+ it "compares durations of different units exactly" do
35
+ _(5.minutes).must_equal 300.seconds
36
+ _(90.seconds).must_be :>, 1.minute
37
+ _(1.hour).must_be :<, 2.hours
38
+ end
39
+
40
+ it "sorts a mixed collection" do
41
+ sorted = [1.hour, 30.seconds, 5.minutes].sort
42
+ _(sorted).must_equal [30.seconds, 5.minutes, 1.hour]
43
+ end
44
+
45
+ it "returns nil against a non-duration rather than raising" do
46
+ _(5.minutes <=> 30).must_be_nil
47
+ end
48
+
49
+ it "is not equal to a non-duration" do
50
+ _(5.minutes == 300).must_equal false
51
+ end
52
+ end
53
+
54
+ describe "exactness" do
55
+ it "converts without float drift" do
56
+ # 1/6 of an hour is exactly 10 minutes; a float round-trip would not
57
+ # land on 10.0.
58
+ _((1.hour - 50.minutes).to_minutes.to_f).must_equal 10.0
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,68 @@
1
+ # test/duration/microseconds_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Duration::Microseconds do
6
+ describe "initialization" do
7
+ it "holds an integer without coercing to a float" do
8
+ us = Duration::Microseconds.new(1500)
9
+ _(us.instance_variable_get(:@microseconds)).must_equal 1500
10
+ _(us.instance_variable_get(:@microseconds)).must_be_kind_of Integer
11
+ end
12
+ end
13
+
14
+ describe "conversions" do
15
+ subject do
16
+ Duration::Microseconds.new(2_000_000)
17
+ end
18
+
19
+ it "converts to nanoseconds" do
20
+ ns = subject.to_nanoseconds
21
+ _(ns).must_be_instance_of Duration::Nanoseconds
22
+ _(ns.to_i).must_equal 2_000_000_000
23
+ end
24
+
25
+ it "converts to milliseconds" do
26
+ ms = subject.to_milliseconds
27
+ _(ms).must_be_instance_of Duration::Milliseconds
28
+ _(ms.to_f).must_equal 2000.0
29
+ end
30
+
31
+ it "converts to seconds" do
32
+ secs = subject.to_seconds
33
+ _(secs).must_be_instance_of Duration::Seconds
34
+ _(secs.to_f).must_equal 2.0
35
+ end
36
+
37
+ it "converts to minutes" do
38
+ mins = subject.to_minutes
39
+ _(mins).must_be_instance_of Duration::Minutes
40
+ _(mins.to_f).must_be_close_to 0.0333, 0.0001
41
+ end
42
+
43
+ it "converts to milliseconds without truncating sub-millisecond precision" do
44
+ ms = Duration::Microseconds.new(1500).to_milliseconds
45
+ _(ms.to_f).must_equal 1.5
46
+ end
47
+
48
+ it "returns itself for to_microseconds" do
49
+ _((subject.to_microseconds).object_id).must_equal subject.object_id
50
+ end
51
+ end
52
+
53
+ describe "time calculations" do
54
+ it "calculates ago from current time" do
55
+ us = Duration::Microseconds.new(1_000_000)
56
+ time_ago = us.ago
57
+ _(time_ago).must_be_instance_of Time
58
+ _(Time.now - time_ago).must_be_close_to 1.0, 1.0
59
+ end
60
+
61
+ it "calculates hence from current time" do
62
+ us = Duration::Microseconds.new(1_000_000)
63
+ time_hence = us.hence
64
+ _(time_hence).must_be_instance_of Time
65
+ _(time_hence - Time.now).must_be_close_to 1.0, 1.0
66
+ end
67
+ end
68
+ end
@@ -19,13 +19,13 @@ describe Duration::Months do
19
19
  it "converts to milliseconds" do
20
20
  ms = subject.to_milliseconds
21
21
  _(ms).must_be_instance_of Duration::Milliseconds
22
- _(ms.to_f).must_be_close_to 5259492000.0, 0.001
22
+ _(ms.to_f).must_equal 5259492000.0
23
23
  end
24
24
 
25
25
  it "converts to seconds" do
26
26
  secs = subject.to_seconds
27
27
  _(secs).must_be_instance_of Duration::Seconds
28
- _(secs.to_f).must_be_close_to 5259492.0, 0.001
28
+ _(secs.to_f).must_equal 5259492.0
29
29
  end
30
30
 
31
31
  it "converts to minutes" do
@@ -58,20 +58,20 @@ describe Duration::Months do
58
58
  end
59
59
 
60
60
  describe "time calculations" do
61
+ # ago and hence displace by the mean month, so they are approximate rather
62
+ # than calendar-correct. See month.rb for calendar-anchored work.
61
63
  it "calculates ago from current time" do
62
64
  months = Duration::Months.new(1)
63
65
  time_ago = months.ago
64
66
  _(time_ago).must_be_instance_of Time
65
- # Approximately 30 days worth of seconds
66
- _(Time.now - time_ago).must_be :>, 2500000.0
67
+ _(Time.now - time_ago).must_be_close_to 2629746.0, 1.0
67
68
  end
68
69
 
69
70
  it "calculates hence from current time" do
70
71
  months = Duration::Months.new(1)
71
72
  time_hence = months.hence
72
73
  _(time_hence).must_be_instance_of Time
73
- # Approximately 30 days worth of seconds
74
- _(time_hence - Time.now).must_be :>, 2500000.0
74
+ _(time_hence - Time.now).must_be_close_to 2629746.0, 1.0
75
75
  end
76
76
  end
77
77
  end
@@ -0,0 +1,68 @@
1
+ # test/duration/nanoseconds_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Duration::Nanoseconds do
6
+ describe "initialization" do
7
+ it "holds an integer without coercing to a float" do
8
+ ns = Duration::Nanoseconds.new(1500)
9
+ _(ns.instance_variable_get(:@nanoseconds)).must_equal 1500
10
+ _(ns.instance_variable_get(:@nanoseconds)).must_be_kind_of Integer
11
+ end
12
+ end
13
+
14
+ describe "conversions" do
15
+ subject do
16
+ Duration::Nanoseconds.new(2_000_000_000)
17
+ end
18
+
19
+ it "converts to microseconds" do
20
+ us = subject.to_microseconds
21
+ _(us).must_be_instance_of Duration::Microseconds
22
+ _(us.to_f).must_equal 2_000_000.0
23
+ end
24
+
25
+ it "converts to milliseconds" do
26
+ ms = subject.to_milliseconds
27
+ _(ms).must_be_instance_of Duration::Milliseconds
28
+ _(ms.to_f).must_equal 2000.0
29
+ end
30
+
31
+ it "converts to seconds" do
32
+ secs = subject.to_seconds
33
+ _(secs).must_be_instance_of Duration::Seconds
34
+ _(secs.to_f).must_equal 2.0
35
+ end
36
+
37
+ it "converts to minutes" do
38
+ mins = subject.to_minutes
39
+ _(mins).must_be_instance_of Duration::Minutes
40
+ _(mins.to_f).must_be_close_to 0.0333, 0.0001
41
+ end
42
+
43
+ it "converts to microseconds without truncating sub-microsecond precision" do
44
+ us = Duration::Nanoseconds.new(1_500_500).to_microseconds
45
+ _(us.to_f).must_equal 1500.5
46
+ end
47
+
48
+ it "returns itself for to_nanoseconds" do
49
+ _((subject.to_nanoseconds).object_id).must_equal subject.object_id
50
+ end
51
+ end
52
+
53
+ describe "time calculations" do
54
+ it "calculates ago from current time" do
55
+ ns = Duration::Nanoseconds.new(1_000_000_000)
56
+ time_ago = ns.ago
57
+ _(time_ago).must_be_instance_of Time
58
+ _(Time.now - time_ago).must_be_close_to 1.0, 1.0
59
+ end
60
+
61
+ it "calculates hence from current time" do
62
+ ns = Duration::Nanoseconds.new(1_000_000_000)
63
+ time_hence = ns.hence
64
+ _(time_hence).must_be_instance_of Time
65
+ _(time_hence - Time.now).must_be_close_to 1.0, 1.0
66
+ end
67
+ end
68
+ end
@@ -1,8 +1,37 @@
1
1
  # test/duration/numeric_test.rb
2
2
 
3
3
  require_relative '../test_helper'
4
+ require_relative '../../lib/Duration/Numeric'
4
5
 
5
6
  describe Duration::Numeric do
7
+ describe "nanosecond extensions" do
8
+ it "creates Nanoseconds from integer" do
9
+ ns = 1000.nanoseconds
10
+ _(ns).must_be_instance_of Duration::Nanoseconds
11
+ _(ns.to_i).must_equal 1000
12
+ end
13
+
14
+ it "supports singular alias" do
15
+ ns = 1.nanosecond
16
+ _(ns).must_be_instance_of Duration::Nanoseconds
17
+ _(ns.to_i).must_equal 1
18
+ end
19
+ end
20
+
21
+ describe "microsecond extensions" do
22
+ it "creates Microseconds from integer" do
23
+ us = 1000.microseconds
24
+ _(us).must_be_instance_of Duration::Microseconds
25
+ _(us.to_i).must_equal 1000
26
+ end
27
+
28
+ it "supports singular alias" do
29
+ us = 1.microsecond
30
+ _(us).must_be_instance_of Duration::Microseconds
31
+ _(us.to_i).must_equal 1
32
+ end
33
+ end
34
+
6
35
  describe "millisecond extensions" do
7
36
  it "creates Milliseconds from integer" do
8
37
  ms = 1000.milliseconds
@@ -9,6 +9,18 @@ describe Duration::Seconds do
9
9
  _(secs.instance_variable_get(:@seconds)).must_equal 60.5
10
10
  _(secs.instance_variable_get(:@seconds)).must_be_kind_of Float
11
11
  end
12
+
13
+ it "holds an integer without coercing to a float" do
14
+ secs = Duration::Seconds.new(90)
15
+ _(secs.instance_variable_get(:@seconds)).must_equal 90
16
+ _(secs.instance_variable_get(:@seconds)).must_be_kind_of Integer
17
+ end
18
+
19
+ it "holds a rational without coercing" do
20
+ secs = Duration::Seconds.new(Rational(3, 2))
21
+ _(secs.instance_variable_get(:@seconds)).must_equal Rational(3, 2)
22
+ _(secs.instance_variable_get(:@seconds)).must_be_kind_of Rational
23
+ end
12
24
  end
13
25
 
14
26
  describe "conversions" do
data/test/test_helper.rb CHANGED
@@ -3,4 +3,4 @@
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/spec'
5
5
 
6
- require_relative '../lib/Duration'
6
+ require_relative '../lib/duration'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: duration.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -62,22 +62,29 @@ files:
62
62
  - README.md
63
63
  - Rakefile
64
64
  - duration.rb.gemspec
65
- - lib/Duration.rb
66
65
  - lib/Duration/Common.rb
67
66
  - lib/Duration/Days.rb
68
67
  - lib/Duration/Hours.rb
68
+ - lib/Duration/Microseconds.rb
69
69
  - lib/Duration/Milliseconds.rb
70
70
  - lib/Duration/Minutes.rb
71
71
  - lib/Duration/Months.rb
72
+ - lib/Duration/Nanoseconds.rb
72
73
  - lib/Duration/Numeric.rb
74
+ - lib/Duration/Relative.rb
73
75
  - lib/Duration/Seconds.rb
74
76
  - lib/Duration/VERSION.rb
75
77
  - lib/Duration/Weeks.rb
78
+ - lib/duration-numeric.rb
79
+ - lib/duration.rb
80
+ - test/Duration/Common_test.rb
76
81
  - test/Duration/Days_test.rb
77
82
  - test/Duration/Hours_test.rb
83
+ - test/Duration/Microseconds_test.rb
78
84
  - test/Duration/Milliseconds_test.rb
79
85
  - test/Duration/Minutes_test.rb
80
86
  - test/Duration/Months_test.rb
87
+ - test/Duration/Nanoseconds_test.rb
81
88
  - test/Duration/Numeric_test.rb
82
89
  - test/Duration/Seconds_test.rb
83
90
  - test/Duration/Weeks_test.rb