Skip to content

Commit

Permalink
Add Instant value object (#121)
Browse files Browse the repository at this point in the history
Time can be an ambiguous concept in Ruby and in astronomy.

In Astronoby, we often have to convert time into Julian Date, into Terrestrial Time, make mathematical operations on it, etc.

This introduces `Instant`, a value object that stores an instant in time in terrestrial time as a Julian Date. It allows for conversion into `Time`, `DateTime`, `Date`, but also TAI, GMST, get ΔT, get the difference with UTC in Julian Date.

It also supports comparison with other instances, and to get the difference in Julian Date with another instant.
  • Loading branch information
rhannequin authored Jan 31, 2025
1 parent b8d9b8a commit 35ea8e3
Show file tree
Hide file tree
Showing 4 changed files with 398 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/astronoby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require "astronoby/angles/hms"
require "astronoby/distance"
require "astronoby/epoch"
require "astronoby/instant"
require "astronoby/astronomical_models/ephemeride_lunaire_parisienne"
require "astronoby/astronomical_models/moon_phases_periodic_terms"
require "astronoby/bodies/moon"
Expand Down
2 changes: 2 additions & 0 deletions lib/astronoby/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ class Constants
EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378140

EARTH_FLATTENING_CORRECTION = 0.996647

TAI_TT_OFFSET = 32.184
end
end
171 changes: 171 additions & 0 deletions lib/astronoby/instant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# frozen_string_literal: true

require "date"

module Astronoby
# Represents a specific instant in time using Terrestrial Time (TT) as its
# internal representation. This class provides conversions between different
# time scales commonly used in astronomy:
# - Terrestrial Time (TT)
# - International Atomic Time (TAI)
# - Universal Time Coordinated (UTC)
# - Greenwich Mean Sidereal Time (GMST)
#
# @example Create an instant from the current time
# instant = Astronoby::Instant.from_time(Time.now)
# instant.tai # Get International Atomic Time
#
# @example Create an instant from Terrestrial Time
# instant = Astronoby::Instant.from_terrestrial_time(2460000.5)
#
class Instant
include Comparable

JULIAN_DAY_NUMBER_OFFSET = 0.5

class << self
# Creates a new Instant from a Terrestrial Time value
#
# @param terrestrial_time [Numeric] the Terrestrial Time as a Julian Date
# @return [Astronoby::Instant] a new Instant object
# @raise [Astronoby::UnsupportedFormatError] if terrestrial_time is not
# numeric
def from_terrestrial_time(terrestrial_time)
new(terrestrial_time)
end

# Creates a new Instant from a Time object
#
# @param time [Time] a Time object to convert
# @return [Astronoby::Instant] a new Instant object
def from_time(time)
delta_t = Util::Time.terrestrial_universal_time_delta(time)
terrestrial_time = time.utc.to_datetime.ajd +
Rational(delta_t, Constants::SECONDS_PER_DAY)
from_terrestrial_time(terrestrial_time)
end

# Creates a new Instant from a Julian Date in UTC
#
# @param julian_date [Numeric] the Julian Date in UTC
# @return [Astronoby::Instant] a new Instant object
def from_utc_julian_date(julian_date)
delta_t = Util::Time.terrestrial_universal_time_delta(julian_date)
terrestrial_time = julian_date +
Rational(delta_t, Constants::SECONDS_PER_DAY)
from_terrestrial_time(terrestrial_time)
end
end

attr_reader :terrestrial_time
alias_method :tt, :terrestrial_time
alias_method :julian_date, :terrestrial_time

# Initialize a new Instant
#
# @param terrestrial_time [Numeric] the Terrestrial Time as Julian Date
# @raise [Astronoby::UnsupportedFormatError] if terrestrial_time is not
# numeric
def initialize(terrestrial_time)
unless terrestrial_time.is_a?(Numeric)
raise UnsupportedFormatError, "terrestrial_time must be a Numeric"
end

@terrestrial_time = terrestrial_time
freeze
end

# Calculate the time difference between two Instant objects
#
# @param other [Astronoby::Instant] another Instant to compare with
# @return [Numeric] the difference in days
def diff(other)
@terrestrial_time - other.terrestrial_time
end

# Convert to DateTime (UTC)
#
# @return [DateTime] the UTC time as DateTime
def to_datetime
DateTime.jd(
@terrestrial_time -
Rational(delta_t / Constants::SECONDS_PER_DAY) +
JULIAN_DAY_NUMBER_OFFSET
)
end

# Convert to Date (UTC)
#
# @return [Date] the UTC date
def to_date
to_datetime.to_date
end

# Convert to Time (UTC)
#
# @return [Time] the UTC time
def to_time
to_datetime.to_time.utc
end

# Get the ΔT (Delta T) value for this instant
# ΔT is the difference between TT and UT1
#
# @return [Numeric] Delta T in seconds
def delta_t
Util::Time.terrestrial_universal_time_delta(@terrestrial_time)
end

# Get the Greenwich Mean Sidereal Time
#
# @return [Numeric] the sidereal time in radians
def gmst
GreenwichSiderealTime.from_utc(to_time).time
end

# Get the International Atomic Time (TAI)
#
# @return [Numeric] TAI as Julian Date
def tai
@terrestrial_time -
Rational(Constants::TAI_TT_OFFSET, Constants::SECONDS_PER_DAY)
end

# Get the Barycentric Dynamical Time (TDB)
# Note: Currently approximated as equal to TT
#
# @return [Numeric] TDB as Julian Date
def tdb
# This is technically false, there is a slight difference between TT and
# TDB. However, this difference is so small that currenly Astronoby
# doesn't support it and consider they are the same value.
@terrestrial_time
end

# Get the offset between TT and UTC for this instant
#
# @return [Numeric] the offset in days
def utc_offset
@terrestrial_time - to_time.utc.to_datetime.ajd
end

# Calculate hash value for the instant
#
# @return [Integer] hash value
def hash
[@terrestrial_time, self.class].hash
end

# Compare this instant with another
#
# @param other [Astronoby::Instant] another instant to compare with
# @return [Integer, nil] -1, 0, 1 for less than, equal to, greater than;
# nil if other is not an Instant
def <=>(other)
return unless other.is_a?(self.class)

@terrestrial_time <=> other.terrestrial_time
end
alias_method :eql?, :==
end
end
Loading

0 comments on commit 35ea8e3

Please sign in to comment.