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