Skip to content

Commit 4980a42

Browse files
committed
Add Astronoby::Duration
1 parent b264d73 commit 4980a42

8 files changed

Lines changed: 426 additions & 16 deletions

File tree

docs/lunar_eclipses.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ eclipse.shadow_axis_distance.km # => 2222.37
5050

5151
## Phases
5252

53-
An eclipse exposes its phases as `Astronoby::EclipsePhase` objects, each with a `#starting_instant`, an `#ending_instant`, and a `#duration` in seconds. The penumbral phase is always present. The partial phase is present for partial and total eclipses, and the total phase (totality) only for total eclipses. A phase that does not occur is `nil`.
53+
An eclipse exposes its phases as `Astronoby::EclipsePhase` objects, each with a `#starting_instant`, an `#ending_instant`, and a `#duration` (an `Astronoby::Duration`). The penumbral phase is always present. The partial phase is present for partial and total eclipses, and the total phase (totality) only for total eclipses. A phase that does not occur is `nil`.
5454

5555
```rb
5656
eclipse.penumbral.starting_instant.to_time # => 2025-03-14 03:57:29 UTC (P1)
@@ -60,7 +60,7 @@ eclipse.total.ending_instant.to_time # => 2025-03-14 07:31:32 UTC (U3)
6060
eclipse.partial.ending_instant.to_time # => 2025-03-14 08:47:55 UTC (U4)
6161
eclipse.penumbral.ending_instant.to_time # => 2025-03-14 10:00:08 UTC (P4)
6262

63-
eclipse.total.duration # => 3931 (seconds of totality)
63+
eclipse.total.duration.seconds # => 3931 (seconds of totality)
6464
```
6565

6666
For a penumbral eclipse, `#partial` and `#total` are `nil`. For a partial eclipse, `#total` is `nil`.

lib/astronoby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require "astronoby/constellations/data"
1313
require "astronoby/constellations/finder"
1414
require "astronoby/distance"
15+
require "astronoby/duration"
1516
require "astronoby/center"
1617
require "astronoby/body"
1718
require "astronoby/position"

lib/astronoby/bodies/sun.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def apparent_magnitude
9090
# Edition: 2nd edition
9191
# Chapter: 28 - Equation of Time
9292

93-
# @return [Integer] Equation of time in seconds
93+
# @return [Astronoby::Duration] Equation of time
9494
def equation_of_time
9595
right_ascension = apparent.equatorial.right_ascension
9696
t = (@instant.julian_date - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
@@ -103,7 +103,7 @@ def equation_of_time
103103
nutation = Nutation.new(instant: instant).nutation_in_longitude
104104
obliquity = TrueObliquity.at(@instant)
105105

106-
(
106+
seconds = (
107107
Angle
108108
.from_degrees(
109109
l0 -
@@ -112,6 +112,7 @@ def equation_of_time
112112
nutation.degrees * obliquity.cos
113113
).hours * Constants::SECONDS_PER_HOUR
114114
).round
115+
Duration.from_seconds(seconds)
115116
end
116117

117118
# @return [nil] the Sun has no phase angle as seen from Earth

lib/astronoby/duration.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
module Astronoby
4+
class Duration
5+
include Comparable
6+
7+
class << self
8+
# @return [Astronoby::Duration] a zero duration
9+
def zero
10+
new(0)
11+
end
12+
13+
# @param seconds [Numeric] the duration in seconds
14+
# @return [Astronoby::Duration] a new Duration
15+
def from_seconds(seconds)
16+
new(seconds)
17+
end
18+
19+
# @param minutes [Numeric] the duration in minutes
20+
# @return [Astronoby::Duration] a new Duration
21+
def from_minutes(minutes)
22+
seconds = minutes * Constants::SECONDS_PER_MINUTE
23+
from_seconds(seconds)
24+
end
25+
26+
# @param hours [Numeric] the duration in hours
27+
# @return [Astronoby::Duration] a new Duration
28+
def from_hours(hours)
29+
seconds = hours * Constants::SECONDS_PER_HOUR
30+
from_seconds(seconds)
31+
end
32+
33+
# @param days [Numeric] the duration in days
34+
# @return [Astronoby::Duration] a new Duration
35+
def from_days(days)
36+
seconds = days * Constants::SECONDS_PER_DAY
37+
from_seconds(seconds)
38+
end
39+
end
40+
41+
# @return [Numeric] the duration in seconds
42+
attr_reader :seconds
43+
44+
# @param seconds [Numeric] the duration in seconds
45+
def initialize(seconds)
46+
@seconds = seconds
47+
freeze
48+
end
49+
50+
# @return [Float] the duration in minutes
51+
def minutes
52+
@seconds / Constants::SECONDS_PER_MINUTE
53+
end
54+
55+
# @return [Float] the duration in hours
56+
def hours
57+
@seconds / Constants::SECONDS_PER_HOUR
58+
end
59+
60+
# @return [Float] the duration in days
61+
def days
62+
@seconds / Constants::SECONDS_PER_DAY
63+
end
64+
65+
# @param other [Astronoby::Duration] duration to add
66+
# @return [Astronoby::Duration] the sum
67+
def +(other)
68+
self.class.from_seconds(@seconds + other.seconds)
69+
end
70+
71+
# @param other [Astronoby::Duration] duration to subtract
72+
# @return [Astronoby::Duration] the difference
73+
def -(other)
74+
self.class.from_seconds(@seconds - other.seconds)
75+
end
76+
77+
# @return [Astronoby::Duration] the negated duration
78+
def -@
79+
self.class.from_seconds(-@seconds)
80+
end
81+
82+
# @return [Astronoby::Duration] the absolute duration
83+
def abs
84+
self.class.from_seconds(@seconds.abs)
85+
end
86+
87+
# @return [Boolean] true if the duration is positive
88+
def positive?
89+
@seconds > 0
90+
end
91+
92+
# @return [Boolean] true if the duration is negative
93+
def negative?
94+
@seconds < 0
95+
end
96+
97+
# @return [Boolean] true if the duration is zero
98+
def zero?
99+
@seconds.zero?
100+
end
101+
102+
# @return [Integer] hash value
103+
def hash
104+
[@seconds, self.class].hash
105+
end
106+
107+
# @param other [Astronoby::Duration] duration to compare with
108+
# @return [Integer, nil] -1, 0, or 1; nil if not comparable
109+
def <=>(other)
110+
return unless other.is_a?(self.class)
111+
112+
seconds <=> other.seconds
113+
end
114+
alias_method :eql?, :==
115+
end
116+
end

lib/astronoby/events/eclipse_phase.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ def initialize(starting_instant:, ending_instant:)
1717
freeze
1818
end
1919

20-
# @return [Integer] phase duration in seconds
20+
# @return [Astronoby::Duration] phase duration
2121
def duration
22-
(@ending_instant.to_time - @starting_instant.to_time).round
22+
Duration.from_seconds(
23+
(@ending_instant.to_time - @starting_instant.to_time).round
24+
)
2325
end
2426
end
2527
end

spec/astronoby/bodies/sun_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -503,15 +503,15 @@
503503
end
504504

505505
describe "#equation_of_time" do
506-
it "returns an Integer" do
506+
it "returns a Duration" do
507507
time = Time.new
508508
instant = Astronoby::Instant.from_time(time)
509509
ephem = test_ephem_sun
510510
sun = described_class.new(instant: instant, ephem: ephem)
511511

512512
equation_of_time = sun.equation_of_time
513513

514-
expect(equation_of_time).to be_an Integer
514+
expect(equation_of_time).to be_a Astronoby::Duration
515515
end
516516

517517
# Source:
@@ -527,7 +527,7 @@
527527

528528
equation_of_time = sun.equation_of_time
529529

530-
expect(equation_of_time).to eq(-392)
530+
expect(equation_of_time.seconds).to eq(-392)
531531
# Value from Practical Astronomy: 392
532532
end
533533

@@ -544,7 +544,7 @@
544544

545545
equation_of_time = sun.equation_of_time
546546

547-
expect(equation_of_time).to eq(199)
547+
expect(equation_of_time.seconds).to eq(199)
548548
# Value from Celestial Calculations: 199
549549
end
550550

@@ -561,7 +561,7 @@
561561

562562
equation_of_time = sun.equation_of_time
563563

564-
expect(equation_of_time).to eq(-336)
564+
expect(equation_of_time.seconds).to eq(-336)
565565
# Value from Celestial Calculations: 337
566566
end
567567

@@ -578,7 +578,7 @@
578578

579579
equation_of_time = sun.equation_of_time
580580

581-
expect(equation_of_time).to eq(202)
581+
expect(equation_of_time.seconds).to eq(202)
582582
# Value from Celestial Calculations: 201
583583
end
584584

@@ -595,7 +595,7 @@
595595

596596
equation_of_time = sun.equation_of_time
597597

598-
expect(equation_of_time).to eq(-185)
598+
expect(equation_of_time.seconds).to eq(-185)
599599
# Value from Celestial Calculations: 187
600600
end
601601
end

0 commit comments

Comments
 (0)