From 736ad12f5ec8aeae2eb7bcf8ad6c40a32b929095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Hannequin?= Date: Sun, 14 Apr 2024 15:34:02 +0200 Subject: [PATCH] Twilight events dedicated class In the same way `Astronoby::Events::ObservationEvents` represent observation events for any body on the celestial sphere, twilight events exist but are related only to the Sun. For example, it doesn't make sense to talk about twilight related to a planet or a distant star. In a goal of making events more centralized, twilight times are now extract from `Astronoby::Sun` into their own dedicated `Astronoby::Events::TwilightEvents` class. The same methods are exposed. This adds natural memoization as all the times are calculated when the object initialized, which is when the `Astronoby::Sun#twilight_events` is called. --- README.md | 30 ++ lib/astronoby.rb | 1 + lib/astronoby/bodies/sun.rb | 101 +---- lib/astronoby/events/twilight_events.rb | 114 ++++++ spec/astronoby/bodies/sun_spec.rb | 280 -------------- spec/astronoby/events/twilight_events_spec.rb | 351 ++++++++++++++++++ 6 files changed, 502 insertions(+), 375 deletions(-) create mode 100644 lib/astronoby/events/twilight_events.rb create mode 100644 spec/astronoby/events/twilight_events_spec.rb diff --git a/README.md b/README.md index abb6611..a45a90c 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,36 @@ observation_events.setting_azimuth.str(:dms) # => "+250° 23′ 33.6177″" ``` +### Twilight times + +```rb +epoch = Astronoby::Epoch.from_time(Date.new(2024, 1, 1)) +sun = Astronoby::Sun.new(epoch: epoch) +observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(48.8566), + longitude: Astronoby::Angle.from_degrees(2.3522) +) +twilight_events = sun.twilight_events(observer: observer) + +twilight_events.morning_astronomical_twilight_time +# => 2024-01-01 05:47:24 UTC + +twilight_events.morning_nautical_twilight_time +# => 2024-01-01 06:25:41 UTC + +twilight_events.morning_civil_twilight_time +# => 2024-01-01 07:05:51 UTC + +twilight_events.evening_civil_twilight_time +# => 2024-01-01 16:37:37 UTC + +twilight_events.evening_nautical_twilight_time +# => 2024-01-01 17:17:46 UTC + +twilight_events.evening_astronomical_twilight_time +# => 2024-01-01 17:56:03 UTC +``` + ### Solstice and Equinox times ```rb diff --git a/lib/astronoby.rb b/lib/astronoby.rb index c5b3342..49eb0df 100644 --- a/lib/astronoby.rb +++ b/lib/astronoby.rb @@ -12,6 +12,7 @@ require "astronoby/equinox_solstice" require "astronoby/errors" require "astronoby/events/observation_events" +require "astronoby/events/twilight_events" require "astronoby/geocentric_parallax" require "astronoby/mean_obliquity" require "astronoby/nutation" diff --git a/lib/astronoby/bodies/sun.rb b/lib/astronoby/bodies/sun.rb index af7f242..45e534a 100644 --- a/lib/astronoby/bodies/sun.rb +++ b/lib/astronoby/bodies/sun.rb @@ -23,6 +23,8 @@ class Sun EVENING = :evening ].freeze + attr_reader :epoch + # Source: # Title: Practical Astronomy with your Calculator or Spreadsheet # Authors: Peter Duffett-Smith and Jonathan Zwart @@ -120,40 +122,10 @@ def observation_events(observer:) ) end - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the morning civil twilight starts - def morning_civil_twilight_time(observer:) - twilight_time(CIVIL, MORNING, observer) - end - - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the evening civil twilight ends - def evening_civil_twilight_time(observer:) - twilight_time(CIVIL, EVENING, observer) - end - - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the morning nautical twilight starts - def morning_nautical_twilight_time(observer:) - twilight_time(NAUTICAL, MORNING, observer) - end - - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the evening nautical twilight ends - def evening_nautical_twilight_time(observer:) - twilight_time(NAUTICAL, EVENING, observer) - end - - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the morning astronomical twilight starts - def morning_astronomical_twilight_time(observer:) - twilight_time(ASTRONOMICAL, MORNING, observer) - end - - # @param observer [Astronoby::Observer] Observer of the event - # @return [Time, nil] Time the evening astronomical twilight ends - def evening_astronomical_twilight_time(observer:) - twilight_time(ASTRONOMICAL, EVENING, observer) + # @param observer [Astronoby::Observer] Observer of the events + # @return [Astronoby::Events::TwilightEvents] Sun's twilight events + def twilight_events(observer:) + Events::TwilightEvents.new(sun: self, observer: observer) end # @return [Numeric] Earth-Sun distance in meters @@ -232,66 +204,5 @@ def distance_angular_size_factor term1 / term2 end - - def current_date - Epoch.to_utc(@epoch).to_date - end - - # Source: - # Title: Practical Astronomy with your Calculator or Spreadsheet - # Authors: Peter Duffett-Smith and Jonathan Zwart - # Edition: Cambridge University Press - # Chapter: 50 - Twilight - def twilight_time(twilight, period_of_the_day, observer) - observation_events = observation_events(observer: observer) - period_time = if period_of_the_day == MORNING - observation_events.rising_time - else - observation_events.setting_time - end - - hour_angle_at_period = equatorial_coordinates_at_midday - .compute_hour_angle(time: period_time, longitude: observer.longitude) - - zenith_angle = TWILIGHT_ANGLES[twilight] - - term1 = zenith_angle.cos - - observer.latitude.sin * - equatorial_coordinates_at_midday.declination.sin - term2 = observer.latitude.cos * - equatorial_coordinates_at_midday.declination.cos - hour_angle_ratio_at_twilight = term1 / term2 - return nil unless hour_angle_ratio_at_twilight.between?(-1, 1) - - hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight) - time_sign = -1 - - if period_of_the_day == MORNING - hour_angle_at_twilight = - Angle.from_degrees(360 - hour_angle_at_twilight.degrees) - time_sign = 1 - end - - twilight_in_hours = - time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours * - GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE - twilight_in_seconds = time_sign * twilight_in_hours * 3600 - (period_time + twilight_in_seconds).round - end - - def midday - utc_from_epoch = Epoch.to_utc(@epoch) - Time.utc( - utc_from_epoch.year, - utc_from_epoch.month, - utc_from_epoch.day, - 12 - ) - end - - def equatorial_coordinates_at_midday - apparent_ecliptic_coordinates - .to_apparent_equatorial(epoch: Epoch.from_time(midday)) - end end end diff --git a/lib/astronoby/events/twilight_events.rb b/lib/astronoby/events/twilight_events.rb new file mode 100644 index 0000000..3d0f2da --- /dev/null +++ b/lib/astronoby/events/twilight_events.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Astronoby + module Events + class TwilightEvents + TWILIGHTS = [ + CIVIL = :civil, + NAUTICAL = :nautical, + ASTRONOMICAL = :astronomical + ].freeze + + TWILIGHT_ANGLES = { + CIVIL => Angle.from_degrees(96), + NAUTICAL => Angle.from_degrees(102), + ASTRONOMICAL => Angle.from_degrees(108) + }.freeze + + PERIODS_OF_THE_DAY = [ + MORNING = :morning, + EVENING = :evening + ].freeze + + attr_reader :morning_civil_twilight_time, + :evening_civil_twilight_time, + :morning_nautical_twilight_time, + :evening_nautical_twilight_time, + :morning_astronomical_twilight_time, + :evening_astronomical_twilight_time + + def initialize(observer:, sun:) + @observer = observer + @sun = sun + PERIODS_OF_THE_DAY.each do |period_of_the_day| + TWILIGHT_ANGLES.each do |twilight, _| + compute(period_of_the_day, twilight) + end + end + end + + private + + # Source: + # Title: Practical Astronomy with your Calculator or Spreadsheet + # Authors: Peter Duffett-Smith and Jonathan Zwart + # Edition: Cambridge University Press + # Chapter: 50 - Twilight + def compute(period_of_the_day, twilight) + period_time = if period_of_the_day == MORNING + observation_events.rising_time + else + observation_events.setting_time + end + + hour_angle_at_period = equatorial_coordinates_at_midday + .compute_hour_angle(time: period_time, longitude: @observer.longitude) + + zenith_angle = TWILIGHT_ANGLES[twilight] + + term1 = zenith_angle.cos - + @observer.latitude.sin * + @equatorial_coordinates_at_midday.declination.sin + term2 = @observer.latitude.cos * + equatorial_coordinates_at_midday.declination.cos + hour_angle_ratio_at_twilight = term1 / term2 + + unless hour_angle_ratio_at_twilight.between?(-1, 1) + return instance_variable_set( + :"@#{period_of_the_day}_#{twilight}_twilight_time", + nil + ) + end + + hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight) + time_sign = -1 + + if period_of_the_day == MORNING + hour_angle_at_twilight = + Angle.from_degrees(360 - hour_angle_at_twilight.degrees) + time_sign = 1 + end + + twilight_in_hours = + time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours * + GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE + twilight_in_seconds = time_sign * twilight_in_hours * 3600 + + instance_variable_set( + :"@#{period_of_the_day}_#{twilight}_twilight_time", + (period_time + twilight_in_seconds).round + ) + end + + def observation_events + @observation_events ||= @sun.observation_events(observer: @observer) + end + + def midday + utc_from_epoch = Epoch.to_utc(@sun.epoch) + Time.utc( + utc_from_epoch.year, + utc_from_epoch.month, + utc_from_epoch.day, + 12 + ) + end + + def equatorial_coordinates_at_midday + @equatorial_coordinates_at_midday ||= + @sun.apparent_ecliptic_coordinates + .to_apparent_equatorial(epoch: Epoch.from_time(midday)) + end + end + end +end diff --git a/spec/astronoby/bodies/sun_spec.rb b/spec/astronoby/bodies/sun_spec.rb index 00595df..1401547 100644 --- a/spec/astronoby/bodies/sun_spec.rb +++ b/spec/astronoby/bodies/sun_spec.rb @@ -677,286 +677,6 @@ end end - describe "#morning_civil_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_civil_twilight_time(observer: observer)) - .to be_a Time - end - - it "returns when the morning civil twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_civil_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 4, 44, 23) - # Time from IMCCE: 04:46 - end - - it "returns when the morning civil twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.morning_civil_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 19, 25, 38) - # Time from IMCCE: 19:29:29 - end - end - - describe "#evening_civil_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_civil_twilight_time(observer: observer)) - .to be_a Time - end - - it "returns when the evening civil twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_civil_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 19, 8, 22) - # Time from IMCCE: 19:10 - end - - it "returns when the evening civil twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.evening_civil_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 8, 38, 28) - # Time from IMCCE: 08:39:23 - end - end - - describe "#morning_nautical_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_nautical_twilight_time(observer: observer)) - .to be_a Time - end - - it "returns when the morning nautical twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_nautical_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 4, 2, 11) - # Time from IMCCE: 04:03 - end - - it "returns when the morning nautical twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.morning_nautical_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 18, 56, 26) - # Time from IMCCE: 19:00:13 - end - end - - describe "#evening_nautical_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_nautical_twilight_time(observer: observer)) - .to be_a Time - end - - it "returns when the evening nautical twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_nautical_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 19, 50, 34) - # Time from IMCCE: 19:52 - end - - it "returns when the evening nautical twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.evening_nautical_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 9, 7, 39) - # Time from IMCCE: 09:08:37 - end - end - - describe "#morning_astronomical_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_astronomical_twilight_time(observer: observer)) - .to be_a Time - end - - # Source: - # Title: Practical Astronomy with your Calculator or Spreadsheet - # Authors: Peter Duffett-Smith and Jonathan Zwart - # Edition: Cambridge University Press - # Chapter: 50 - Twilight, p.114 - it "returns when the morning astronomical twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_astronomical_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 3, 16, 13) - # Time from Practical Astronomy: 03:12 - # Time from IMCCE: 03:17 - end - - it "returns when the morning astronomical twilight starts" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.morning_astronomical_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 18, 26, 47) - # Time from IMCCE: 18:30:31 - end - - context "when the twilight never ends" do - it "returns nil" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(65), - longitude: Astronoby::Angle.zero - ) - - expect(sun.morning_astronomical_twilight_time(observer: observer)) - .to be_nil - end - end - end - - describe "#evening_astronomical_twilight_time" do - it "returns a time" do - epoch = Astronoby::Epoch.from_time(Time.new) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.zero, - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_astronomical_twilight_time(observer: observer)) - .to be_a Time - end - - # Source: - # Title: Practical Astronomy with your Calculator or Spreadsheet - # Authors: Peter Duffett-Smith and Jonathan Zwart - # Edition: Cambridge University Press - # Chapter: 50 - Twilight, p.114 - it "returns when the evening astronomical twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(52), - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_astronomical_twilight_time(observer: observer)) - .to eq Time.utc(1979, 9, 7, 20, 36, 33) - # Time from Practical Astronomy: 20:43 - # Time from IMCCE: 20:37 - end - - it "returns when the evening astronomical twilight ends" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_dms(-33, 52, 4), - longitude: Astronoby::Angle.from_dms(151, 12, 26) - ) - - expect(sun.evening_astronomical_twilight_time(observer: observer)) - .to eq Time.utc(2024, 3, 14, 9, 37, 18) - # Time from IMCCE: 09:38:17 - end - - context "when the twilight never ends" do - it "returns nil" do - epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) - sun = described_class.new(epoch: epoch) - observer = Astronoby::Observer.new( - latitude: Astronoby::Angle.from_degrees(65), - longitude: Astronoby::Angle.zero - ) - - expect(sun.evening_astronomical_twilight_time(observer: observer)) - .to be_nil - end - end - end - describe "::equation_of_time" do it "returns an Integer" do date = Date.new diff --git a/spec/astronoby/events/twilight_events_spec.rb b/spec/astronoby/events/twilight_events_spec.rb new file mode 100644 index 0000000..d2f06af --- /dev/null +++ b/spec/astronoby/events/twilight_events_spec.rb @@ -0,0 +1,351 @@ +# frozen_string_literal: true + +RSpec.describe Astronoby::Events::TwilightEvents do + describe "#morning_civil_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_civil_twilight_time).to be_a(Time) + end + + it "returns when the morning civil twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_civil_twilight_time) + .to eq Time.utc(1979, 9, 7, 4, 44, 23) + # Time from IMCCE: 04:46 + end + + it "returns when the morning civil twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_civil_twilight_time) + .to eq Time.utc(2024, 3, 14, 19, 25, 38) + # Time from IMCCE: 19:29:29 + end + + context "when the civil twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(65), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_civil_twilight_time).to be_nil + end + end + end + + describe "#evening_civil_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_civil_twilight_time).to be_a(Time) + end + + it "returns when the evening civil twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_civil_twilight_time) + .to eq Time.utc(1979, 9, 7, 19, 8, 22) + # Time from IMCCE: 19:10 + end + + it "returns when the evening civil twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_civil_twilight_time) + .to eq Time.utc(2024, 3, 14, 8, 38, 28) + # Time from IMCCE: 08:39:23 + end + + context "when the civil twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(65), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_civil_twilight_time).to be_nil + end + end + end + + describe "#morning_nautical_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_nautical_twilight_time).to be_a(Time) + end + + it "returns when the morning nautical twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_nautical_twilight_time) + .to eq Time.utc(1979, 9, 7, 4, 2, 11) + # Time from IMCCE: 04:03 + end + + it "returns when the morning nautical twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_nautical_twilight_time) + .to eq Time.utc(2024, 3, 14, 18, 56, 26) + # Time from IMCCE: 19:00:13 + end + + context "when the nautical twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(55), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_nautical_twilight_time).to be_nil + end + end + end + + describe "#evening_nautical_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_nautical_twilight_time).to be_a(Time) + end + + it "returns when the evening nautical twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_nautical_twilight_time) + .to eq Time.utc(1979, 9, 7, 19, 50, 34) + # Time from IMCCE: 19:52 + end + + it "returns when the evening nautical twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_nautical_twilight_time) + .to eq Time.utc(2024, 3, 14, 9, 7, 39) + # Time from IMCCE: 09:08:37 + end + + context "when the nautical twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(55), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_nautical_twilight_time).to be_nil + end + end + end + + describe "#morning_astronomical_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_astronomical_twilight_time).to be_a(Time) + end + + # Source: + # Title: Practical Astronomy with your Calculator or Spreadsheet + # Authors: Peter Duffett-Smith and Jonathan Zwart + # Edition: Cambridge University Press + # Chapter: 50 - Twilight, p.114 + it "returns when the morning astronomical twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_astronomical_twilight_time) + .to eq Time.utc(1979, 9, 7, 3, 16, 13) + # Time from Practical Astronomy: 03:12 + # Time from IMCCE: 03:17 + end + + it "returns when the morning astronomical twilight starts" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_astronomical_twilight_time) + .to eq Time.utc(2024, 3, 14, 18, 26, 47) + # Time from IMCCE: 18:30:31 + end + + context "when the astronomical twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(49), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.morning_astronomical_twilight_time).to be_nil + end + end + end + + describe "#evening_astronomical_twilight_time" do + it "returns a time" do + epoch = Astronoby::Epoch.from_time(Date.new) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.zero, + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_astronomical_twilight_time).to be_a(Time) + end + + # Source: + # Title: Practical Astronomy with your Calculator or Spreadsheet + # Authors: Peter Duffett-Smith and Jonathan Zwart + # Edition: Cambridge University Press + # Chapter: 50 - Twilight, p.114 + it "returns when the evening astronomical twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(1979, 9, 7)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(52), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_astronomical_twilight_time) + .to eq Time.utc(1979, 9, 7, 20, 36, 33) + # Time from Practical Astronomy: 20:43 + # Time from IMCCE: 20:37 + end + + it "returns when the evening astronomical twilight ends" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 3, 14)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_dms(-33, 52, 4), + longitude: Astronoby::Angle.from_dms(151, 12, 26) + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_astronomical_twilight_time) + .to eq Time.utc(2024, 3, 14, 9, 37, 18) + # Time from IMCCE: 09:38:17 + end + + context "when the astronomical twilight doesn't start" do + it "returns nil" do + epoch = Astronoby::Epoch.from_time(Time.utc(2024, 6, 20)) + sun = Astronoby::Sun.new(epoch: epoch) + observer = Astronoby::Observer.new( + latitude: Astronoby::Angle.from_degrees(49), + longitude: Astronoby::Angle.zero + ) + twilight_events = described_class.new(observer: observer, sun: sun) + + expect(twilight_events.evening_astronomical_twilight_time).to be_nil + end + end + end +end