astronoby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d5ac807296bab40ecf868557f3a6a7a1022360f78860b46332e87e5a2fd2697
4
- data.tar.gz: 26682e9092af30150eff0b47edb5a1476341f0d133c39ced8754148e6a4b0927
3
+ metadata.gz: 1c5bcfe7077b3772243f90a9c8a9ac8b0b0d90fdce2c777d6f05ab88062bc94c
4
+ data.tar.gz: ecd3bbbbcf699c1bde1b2fd6a92ccd7dc45ed1839cb3e6e6549ddb2d52002e48
5
5
  SHA512:
6
- metadata.gz: 5ae6f829d9c23476edd3c24411426a2c8c1aa6e432c19ee999889b01d9b7843476ea14c163bd083659e768a12a6b772cf46488dfebfa5c5c8f7449a2580bc420
7
- data.tar.gz: 54e3f4ce4090c120b3156d4429fb3fbc6c2830ec816355931d8b416b734210a4d82af3ef5c058a4795b27e753b748f524a0419c2f3696603c5b05ee2b60a71cd
6
+ metadata.gz: a97c4ee597dbcd5b3adbb9da8402dd95d413d120e51c263675a8019e839496168d4a893c473b6dbf1687463eb8e2f4749b13a9acab1205488944ae49f160de83
7
+ data.tar.gz: 70b0f12a52a668e1f3948a20fb5fe38b59e42ed0531381b8a5a03d130d1e64b2e7ba2c56806982d1f0e667dc47776a54609d7a35a5b067594b78295a440cf020
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.2.2
data/CHANGELOG.md CHANGED
@@ -1,6 +1,28 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.0.1] - 2022-04-20
3
+ ## 0.1.0 - 2024-02-28
4
4
 
5
- - Add `Astronoby::Angle`
6
- - Support angles in degrees and radians
5
+ ### Features
6
+
7
+ * Support angles in hours
8
+ * Support coordinates
9
+ * `Astronoby::Coordinates::Horizontal`
10
+ * `Astronoby::Coordinates::Equatorial`
11
+ * `Astronoby::Coordinates::Ecliptic`
12
+ * Add new body `Astronoby::Sun`
13
+ * Add `Astronoby::Aberration`
14
+ * Add `Astronoby::Epoch`
15
+ * Add `Astronoby::MeanObliquity`
16
+ * Add `Astronoby::TrueObliquity`
17
+ * Add `Astronoby::Nutation`
18
+ * Add `Astronoby::Precession`
19
+ * Add `Astronoby::Refraction`
20
+ * Add utils
21
+ * `Astronoby::Util::Astrodynamics`
22
+ * `Astronoby::Util::Time`
23
+ * `Astronoby::Util::Trigonometry`
24
+
25
+ ## 0.0.1 - 2022-04-20
26
+
27
+ * Add `Astronoby::Angle`
28
+ * Support angles in degrees and radians
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,86 @@
1
+ # Contributing to Astronoby
2
+
3
+ By participating in this project, you agree to follow the [code of conduct].
4
+
5
+ [code of conduct]: https://github.com/rhannequin/astronoby/blob/main/CODE_OF_CONDUCT.md
6
+
7
+ Here are some ways *you* can contribute:
8
+
9
+ * by reporting bugs
10
+ * by suggesting new features
11
+ * by writing or editing documentation
12
+ * by writing specifications
13
+ * by writing code (**no patch is too small** : fix typos, add comments, etc.)
14
+ * by refactoring code
15
+ * by closing [issues]
16
+ * by reviewing patches
17
+
18
+ [issues]: https://github.com/rhannequin/astronoby/issues
19
+
20
+ ## Submitting an Issue
21
+
22
+ * We use the [GitHub issue tracker][issues] to track bugs and features.
23
+ * Before submitting a bug report or feature request, check to make sure it hasn't
24
+ already been submitted.
25
+ * When submitting a bug report, please include any other details that may be
26
+ necessary to reproduce the bug, including your gem version, Ruby version, and
27
+ operating system.
28
+
29
+ ## Cleaning up issues
30
+
31
+ * Issues that have no response from the submitter will be closed after 30 days.
32
+ * Issues will be closed once they're assumed to be fixed or answered. If the
33
+ maintainer is wrong, it can be opened again.
34
+ * If your issue is closed by mistake, please understand and explain the issue.
35
+ We will happily reopen the issue.
36
+
37
+ ## Submitting a Pull Request
38
+
39
+ 1. [Fork][fork] the [official repository][repo].
40
+ 1. [Create a topic branch.][branch]
41
+ 1. Implement your feature or bug fix.
42
+ 1. Add, commit, and push your changes.
43
+ 1. [Submit a pull request.][pr]
44
+
45
+ ### Notes
46
+
47
+ * Please add tests if you changed code. Contributions without tests won't be accepted.
48
+ * If you don't know how to add tests, please put in a PR and leave a comment
49
+ asking for help. We love helping!
50
+ * Please don't update the Gem version.
51
+
52
+ ## Setting up
53
+
54
+ ```sh
55
+ bin/setup
56
+ ```
57
+
58
+ ## Running the test suite
59
+
60
+ The default rake task will run the full test suite:
61
+
62
+ ```sh
63
+ bundle exec rake
64
+ ```
65
+
66
+ To run an individual rspec test, you can provide a path and line number:
67
+
68
+ ```sh
69
+ bundle exec rspec spec/path/to/spec.rb:123
70
+ ```
71
+
72
+ ## Formatting
73
+
74
+ Use [standard] to automatically format your code:
75
+
76
+ ```sh
77
+ bundle exec standardrb --fix
78
+ ```
79
+
80
+ [repo]: https://github.com/rhannequin/astronoby/tree/main
81
+ [fork]: https://help.github.com/articles/fork-a-repo/
82
+ [branch]: https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/
83
+ [pr]: https://help.github.com/articles/using-pull-requests/
84
+ [standard]: https://github.com/testdouble/standard
85
+
86
+ Inspired by https://github.com/thoughtbot/factory_bot/blob/main/CONTRIBUTING.md
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- astronoby (0.0.1)
4
+ astronoby (0.1.0)
5
+ matrix (~> 0.4.2)
5
6
  rake (~> 13.0)
6
7
  rspec (~> 3.0)
7
8
 
@@ -10,53 +11,67 @@ GEM
10
11
  specs:
11
12
  ast (2.4.2)
12
13
  diff-lcs (1.5.0)
13
- parallel (1.22.1)
14
- parser (3.1.2.0)
14
+ json (2.6.3)
15
+ language_server-protocol (3.17.0.3)
16
+ lint_roller (1.0.0)
17
+ matrix (0.4.2)
18
+ parallel (1.23.0)
19
+ parser (3.2.2.3)
15
20
  ast (~> 2.4.1)
16
- prettier (2.1.0)
21
+ racc
22
+ racc (1.7.0)
17
23
  rainbow (3.1.1)
18
- rake (13.0.6)
19
- regexp_parser (2.3.0)
24
+ rake (13.1.0)
25
+ regexp_parser (2.8.1)
20
26
  rexml (3.2.5)
21
- rspec (3.11.0)
22
- rspec-core (~> 3.11.0)
23
- rspec-expectations (~> 3.11.0)
24
- rspec-mocks (~> 3.11.0)
25
- rspec-core (3.11.0)
26
- rspec-support (~> 3.11.0)
27
- rspec-expectations (3.11.0)
27
+ rspec (3.12.0)
28
+ rspec-core (~> 3.12.0)
29
+ rspec-expectations (~> 3.12.0)
30
+ rspec-mocks (~> 3.12.0)
31
+ rspec-core (3.12.2)
32
+ rspec-support (~> 3.12.0)
33
+ rspec-expectations (3.12.3)
28
34
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.11.0)
30
- rspec-mocks (3.11.1)
35
+ rspec-support (~> 3.12.0)
36
+ rspec-mocks (3.12.6)
31
37
  diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.11.0)
33
- rspec-support (3.11.0)
34
- rubocop (1.27.0)
38
+ rspec-support (~> 3.12.0)
39
+ rspec-support (3.12.1)
40
+ rubocop (1.52.0)
41
+ json (~> 2.3)
35
42
  parallel (~> 1.10)
36
- parser (>= 3.1.0.0)
43
+ parser (>= 3.2.0.0)
37
44
  rainbow (>= 2.2.2, < 4.0)
38
45
  regexp_parser (>= 1.8, < 3.0)
39
- rexml
40
- rubocop-ast (>= 1.16.0, < 2.0)
46
+ rexml (>= 3.2.5, < 4.0)
47
+ rubocop-ast (>= 1.28.0, < 2.0)
41
48
  ruby-progressbar (~> 1.7)
42
- unicode-display_width (>= 1.4.0, < 3.0)
43
- rubocop-ast (1.17.0)
44
- parser (>= 3.1.1.0)
45
- rubocop-performance (1.13.3)
49
+ unicode-display_width (>= 2.4.0, < 3.0)
50
+ rubocop-ast (1.29.0)
51
+ parser (>= 3.2.1.0)
52
+ rubocop-performance (1.18.0)
46
53
  rubocop (>= 1.7.0, < 2.0)
47
54
  rubocop-ast (>= 0.4.0)
48
- ruby-progressbar (1.11.0)
49
- standard (1.10.0)
50
- rubocop (= 1.27.0)
51
- rubocop-performance (= 1.13.3)
52
- unicode-display_width (2.1.0)
55
+ ruby-progressbar (1.13.0)
56
+ standard (1.29.0)
57
+ language_server-protocol (~> 3.17.0.2)
58
+ lint_roller (~> 1.0)
59
+ rubocop (~> 1.52.0)
60
+ standard-custom (~> 1.0.0)
61
+ standard-performance (~> 1.1.0)
62
+ standard-custom (1.0.1)
63
+ lint_roller (~> 1.0)
64
+ standard-performance (1.1.0)
65
+ lint_roller (~> 1.0)
66
+ rubocop-performance (~> 1.18.0)
67
+ unicode-display_width (2.4.2)
53
68
 
54
69
  PLATFORMS
70
+ ruby
55
71
  x86_64-linux
56
72
 
57
73
  DEPENDENCIES
58
74
  astronoby!
59
- prettier (~> 2.0)
60
75
  standard (~> 1.3)
61
76
 
62
77
  BUNDLED WITH
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
- # Astronoby ![specs](https://github.com/rhannequin/astronoby/workflows/Ruby/badge.svg)
1
+ # Astronoby
2
2
 
3
- Ruby library to provide a useful API to compute astronomical calculations, based on _Astronomical Algorithms_ by Jean Meeus.
3
+ [![Tests](https://github.com/rhannequin/astronoby/workflows/Ruby/badge.svg)](https://github.com/rhannequin/astronoby/actions?query=workflow%3ARuby)
4
+
5
+ Ruby library to provide a useful API to compute astronomical calculations, based
6
+ on astrometry books.
7
+
8
+ The main reference is:
9
+ - _Astronomical Algorithms_ by Jean Meeus
10
+ - _Celestial Calculations_ by J. L. Lawrence
11
+ - _Practical Astronomy with your Calculator or Spreadsheet_ by Peter
12
+ Duffet-Smith and Jonathan Zwart
4
13
 
5
14
  ## Installation
6
15
 
@@ -8,28 +17,76 @@ Install the gem and add to the application's Gemfile by executing:
8
17
 
9
18
  $ bundle add astronoby
10
19
 
11
- If bundler is not being used to manage dependencies, install the gem by executing:
20
+ If bundler is not being used to manage dependencies, install the gem by
21
+ executing:
12
22
 
13
23
  $ gem install astronoby
14
24
 
15
25
  ## Usage
16
26
 
17
- Library still heavily in development.
27
+ This library is still in heavy development. The following API is likely to
28
+ change any time.
29
+
30
+ ```rb
31
+ time = Time.utc(2023, 2, 17, 11, 0, 0)
32
+ epoch = Astronoby::Epoch.from_time(time)
33
+
34
+ latitude = Astronoby::Angle.as_degrees(48.8566)
35
+ longitude = Astronoby::Angle.as_degrees(2.3522)
36
+
37
+ sun = Astronoby::Sun.new(epoch: epoch)
38
+
39
+ horizontal_coordinates = sun.horizontal_coordinates(
40
+ latitude: latitude,
41
+ longitude: longitude
42
+ )
43
+
44
+ horizontal_coordinates.altitude.degrees.to_f
45
+ # => 27.502365130176567
46
+
47
+ horizontal_coordinates.altitude.str(:dms)
48
+ # => "+27° 30′ 8.5144″"
49
+ ```
50
+
51
+ ## Precision
52
+
53
+ The current precision for the Sun's apparent location in the sky, compared
54
+ to values computed by the [IMCCE] is approximately 1 arc minute. It corresponds
55
+ to twice the apparent size of Jupiter when it is the closest to Earth.
56
+
57
+ While the precision is not enough for very high accuracy computations, it is
58
+ equal to the Human naked eye's angular resolution.
59
+
60
+ [IMCCE]: https://www.imcce.fr
18
61
 
19
62
  ## Development
20
63
 
21
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
64
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
65
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
66
+ prompt that will allow you to experiment.
22
67
 
23
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+ To install this gem onto your local machine, run `bundle exec rake install`. To
69
+ release a new version, update the version number in `version.rb`, and then run
70
+ `bundle exec rake release`, which will create a git tag for the version, push
71
+ git commits and the created tag, and push the `.gem` file to [rubygems.org].
72
+
73
+ [rubygems.org]: https://rubygems.org
24
74
 
25
75
  ## Contributing
26
76
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/rhannequin/astronoby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/rhannequin/astronoby/blob/main/CODE_OF_CONDUCT.md).
77
+ Please see [CONTRIBUTING.md](https://github.com/rhannequin/astronoby/blob/main/CONTRIBUTING.md).
78
+
79
+ [code of conduct]: https://github.com/rhannequin/astronoby/blob/main/CODE_OF_CONDUCT.md
28
80
 
29
81
  ## License
30
82
 
31
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
83
+ The gem is available as open source under the terms of the [MIT License].
84
+
85
+ [MIT License]: https://opensource.org/licenses/MIT
32
86
 
33
87
  ## Code of Conduct
34
88
 
35
- Everyone interacting in the Astronoby project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rhannequin/astronoby/blob/main/CODE_OF_CONDUCT.md).
89
+ Everyone interacting in the Astronoby project's codebases, issue trackers, chat
90
+ rooms and mailing lists is expected to follow the [code of conduct].
91
+
92
+ [code of conduct]: https://github.com/rhannequin/astronoby/blob/main/CODE_OF_CONDUCT.md
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Aberration
5
+ def self.for_ecliptic_coordinates(coordinates:, epoch:)
6
+ new(coordinates, epoch).apply
7
+ end
8
+
9
+ def initialize(coordinates, epoch)
10
+ @coordinates = coordinates
11
+ @epoch = epoch
12
+ end
13
+
14
+ # Source:
15
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
16
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
17
+ # Edition: Cambridge University Press
18
+ # Chapter: 36 - Aberration
19
+ def apply
20
+ delta_longitude = Angle.as_degrees(
21
+ -20.5 * (
22
+ sun_longitude - @coordinates.longitude
23
+ ).cos / @coordinates.latitude.cos / 3600
24
+ )
25
+
26
+ delta_latitude = Angle.as_degrees(
27
+ -20.5 *
28
+ (sun_longitude - @coordinates.longitude).sin *
29
+ @coordinates.latitude.sin / 3600
30
+ )
31
+
32
+ Coordinates::Ecliptic.new(
33
+ latitude: @coordinates.latitude + delta_latitude,
34
+ longitude: @coordinates.longitude + delta_longitude
35
+ )
36
+ end
37
+
38
+ def sun_longitude
39
+ @_sun_longitude ||= Sun.new(epoch: @epoch).ecliptic_coordinates.longitude
40
+ end
41
+ end
42
+ end
@@ -4,39 +4,173 @@ require "bigdecimal/math"
4
4
 
5
5
  module Astronoby
6
6
  class Angle
7
- UNITS = [
8
- DEGREES = :degrees,
9
- RADIANS = :radians
10
- ].freeze
7
+ PRECISION = 14
8
+ PI = BigMath.PI(PRECISION)
9
+ PI_IN_DEGREES = BigDecimal("180")
11
10
 
12
- UNIT_CLASS_NAMES = {
13
- DEGREES => "Astronoby::Degree",
14
- RADIANS => "Astronoby::Radian"
15
- }
11
+ FULL_CIRCLE_IN_RADIANS = (2 * PI)
16
12
 
17
- PI = BigMath.PI(10)
13
+ RADIAN_PER_HOUR = PI / BigDecimal("12")
14
+ MINUTES_PER_DEGREE = BigDecimal("60")
15
+ MINUTES_PER_HOUR = BigDecimal("60")
16
+ SECONDS_PER_MINUTE = BigDecimal("60")
17
+ SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE
18
+
19
+ FORMATS = %i[dms hms].freeze
18
20
 
19
21
  class << self
20
- UNIT_CLASS_NAMES.each do |unit, class_name|
21
- define_method("as_#{unit}") do |angle|
22
- Kernel.const_get(class_name).new(angle)
23
- end
22
+ def zero
23
+ new(0)
24
+ end
25
+
26
+ def as_radians(radians)
27
+ normalized_radians = radians.remainder(FULL_CIRCLE_IN_RADIANS)
28
+ new(normalized_radians)
29
+ end
30
+
31
+ def as_degrees(degrees)
32
+ radians = degrees / PI_IN_DEGREES * PI
33
+ as_radians(radians)
34
+ end
35
+
36
+ def as_hours(hours)
37
+ radians = hours * RADIAN_PER_HOUR
38
+ as_radians(radians)
39
+ end
40
+
41
+ def as_hms(hour, minute, second)
42
+ hours = hour + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
43
+ as_hours(hours)
44
+ end
45
+
46
+ def as_dms(degree, minute, second)
47
+ sign = degree.negative? ? -1 : 1
48
+ degrees = degree.abs + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
49
+ as_degrees(sign * degrees)
50
+ end
51
+
52
+ def asin(ratio)
53
+ radians = Math.asin(ratio)
54
+ as_radians(radians)
24
55
  end
56
+
57
+ def acos(ratio)
58
+ radians = Math.acos(ratio)
59
+ as_radians(radians)
60
+ end
61
+
62
+ def atan(ratio)
63
+ radians = Math.atan(ratio)
64
+ as_radians(radians)
65
+ end
66
+ end
67
+
68
+ attr_reader :radians
69
+
70
+ def initialize(radians)
71
+ @radians = if radians.is_a?(Integer) || radians.is_a?(BigDecimal)
72
+ BigDecimal(radians)
73
+ else
74
+ BigDecimal(radians, PRECISION)
75
+ end
76
+ freeze
77
+ end
78
+
79
+ def degrees
80
+ @radians * PI_IN_DEGREES / PI
81
+ end
82
+
83
+ def hours
84
+ @radians / RADIAN_PER_HOUR
25
85
  end
26
86
 
27
- UNITS.each do |unit|
28
- define_method("to_#{unit}") do
29
- raise NotImplementedError, "#{self.class} must implement #to_#{unit} method."
87
+ def +(other)
88
+ self.class.as_radians(radians + other.radians)
89
+ end
90
+
91
+ def -(other)
92
+ self.class.as_radians(@radians - other.radians)
93
+ end
94
+
95
+ def sin
96
+ Math.sin(radians)
97
+ end
98
+
99
+ def cos
100
+ Math.cos(radians)
101
+ end
102
+
103
+ def tan
104
+ Math.tan(radians)
105
+ end
106
+
107
+ def positive?
108
+ radians > 0
109
+ end
110
+
111
+ def negative?
112
+ radians < 0
113
+ end
114
+
115
+ def zero?
116
+ radians.zero?
117
+ end
118
+
119
+ def ==(other)
120
+ other.is_a?(self.class) && radians == other.radians
121
+ end
122
+ alias_method :eql?, :==
123
+
124
+ def hash
125
+ [radians, self.class].hash
126
+ end
127
+
128
+ def <=>(other)
129
+ return nil unless other.is_a?(self.class)
130
+
131
+ radians <=> other.radians
132
+ end
133
+
134
+ def str(format)
135
+ case format
136
+ when :dms then to_dms(degrees).format
137
+ when :hms then to_hms(hours).format
138
+ else
139
+ raise UnsupportedFormatError.new(
140
+ "Expected a format between #{FORMATS.join(", ")}, got #{format}"
141
+ )
30
142
  end
31
143
  end
32
144
 
33
- def initialize(angle, unit:)
34
- @angle = BigDecimal(angle)
35
- @unit = unit
145
+ def to_dms(deg)
146
+ sign = deg.negative? ? "-" : "+"
147
+ absolute_degrees = deg.abs
148
+ degrees = absolute_degrees.floor
149
+ decimal_minutes = MINUTES_PER_DEGREE * (absolute_degrees - degrees)
150
+ absolute_decimal_minutes = (
151
+ MINUTES_PER_DEGREE * (absolute_degrees - degrees)
152
+ ).abs
153
+ minutes = decimal_minutes.floor
154
+ seconds = SECONDS_PER_MINUTE * (
155
+ absolute_decimal_minutes - absolute_decimal_minutes.floor
156
+ )
157
+
158
+ Dms.new(sign, degrees, minutes, seconds.to_f.floor(4))
36
159
  end
37
160
 
38
- def value
39
- @angle
161
+ def to_hms(hrs)
162
+ absolute_hours = hrs.abs
163
+ hours = absolute_hours.floor
164
+ decimal_minutes = MINUTES_PER_HOUR * (absolute_hours - hours)
165
+ absolute_decimal_minutes = (
166
+ MINUTES_PER_HOUR * (absolute_hours - hours)
167
+ ).abs
168
+ minutes = decimal_minutes.floor
169
+ seconds = SECONDS_PER_MINUTE * (
170
+ absolute_decimal_minutes - absolute_decimal_minutes.floor
171
+ )
172
+
173
+ Hms.new(hours, minutes, seconds.to_f.floor(4))
40
174
  end
41
175
  end
42
176
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Dms
5
+ attr_reader :sign, :degrees, :minutes, :seconds
6
+
7
+ def initialize(sign, degrees, minutes, seconds)
8
+ @sign = sign
9
+ @degrees = degrees
10
+ @minutes = minutes
11
+ @seconds = seconds
12
+ end
13
+
14
+ def format
15
+ "#{sign}#{degrees}° #{minutes}′ #{seconds}″"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Hms
5
+ attr_reader :hours, :minutes, :seconds
6
+
7
+ def initialize(hours, minutes, seconds)
8
+ @hours = hours
9
+ @minutes = minutes
10
+ @seconds = seconds
11
+ end
12
+
13
+ def format
14
+ "#{hours}h #{minutes}m #{seconds}s"
15
+ end
16
+ end
17
+ end