iers 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +4 -3
- data/Rakefile +0 -1
- data/data/Leap_Second.dat +2 -2
- data/data/finals2000A.all +860 -797
- data/lib/iers/configuration.rb +13 -0
- data/lib/iers/data.rb +25 -4
- data/lib/iers/delta_t.rb +61 -18
- data/lib/iers/time_scale.rb +28 -2
- data/lib/iers/version.rb +1 -1
- data/lib/iers.rb +7 -1
- metadata +1 -1
data/lib/iers/configuration.rb
CHANGED
|
@@ -39,6 +39,7 @@ module IERS
|
|
|
39
39
|
|
|
40
40
|
def cache_dir=(value)
|
|
41
41
|
@cache_dir = Pathname(value)
|
|
42
|
+
invalidate
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def sources=(value)
|
|
@@ -59,10 +60,12 @@ module IERS
|
|
|
59
60
|
|
|
60
61
|
def finals_path=(value)
|
|
61
62
|
@finals_path = value && Pathname(value)
|
|
63
|
+
invalidate(:finals)
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def leap_second_path=(value)
|
|
65
67
|
@leap_second_path = value && Pathname(value)
|
|
68
|
+
invalidate(:leap_seconds)
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
def interpolation=(value)
|
|
@@ -82,5 +85,15 @@ module IERS
|
|
|
82
85
|
|
|
83
86
|
@lagrange_order = value
|
|
84
87
|
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Settings staged on a detached Configuration govern nothing, so they must
|
|
92
|
+
# not reach into the parse the gem is actually serving.
|
|
93
|
+
def invalidate(*sources)
|
|
94
|
+
return unless IERS.active_configuration?(self)
|
|
95
|
+
|
|
96
|
+
Data.clear_loaded!(*sources)
|
|
97
|
+
end
|
|
85
98
|
end
|
|
86
99
|
end
|
data/lib/iers/data.rb
CHANGED
|
@@ -37,6 +37,7 @@ module IERS
|
|
|
37
37
|
begin
|
|
38
38
|
Downloader.new(timeout: config.download_timeout).fetch(url, dest)
|
|
39
39
|
updated << source
|
|
40
|
+
clear_loaded!(source)
|
|
40
41
|
rescue DownloadError => e
|
|
41
42
|
errors[source] = e
|
|
42
43
|
end
|
|
@@ -67,6 +68,8 @@ module IERS
|
|
|
67
68
|
path = config.cache_dir.join(filename)
|
|
68
69
|
path.delete if path.exist?
|
|
69
70
|
end
|
|
71
|
+
|
|
72
|
+
clear_loaded!
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
# @param coverage_days_ahead [Integer, nil]
|
|
@@ -161,15 +164,33 @@ module IERS
|
|
|
161
164
|
(Time.now - mtimes.min).to_i
|
|
162
165
|
end
|
|
163
166
|
|
|
167
|
+
# @param sources [Array<Symbol>] data sources to drop (default: all)
|
|
164
168
|
# @return [void]
|
|
165
|
-
def clear_loaded!
|
|
169
|
+
def clear_loaded!(*sources)
|
|
170
|
+
sources = FILENAMES.keys if sources.empty?
|
|
171
|
+
validate_sources!(sources)
|
|
172
|
+
|
|
166
173
|
@mutex.synchronize do
|
|
167
|
-
@finals = nil
|
|
168
|
-
@leap_second_table = nil
|
|
174
|
+
@finals = nil if sources.include?(:finals)
|
|
175
|
+
@leap_second_table = nil if sources.include?(:leap_seconds)
|
|
169
176
|
end
|
|
177
|
+
|
|
178
|
+
# Outside the mutex above: LeapSecond takes its own lock before calling
|
|
179
|
+
# back into Data, so taking them in that order here would invert it.
|
|
180
|
+
LeapSecond.clear_cached! if sources.include?(:leap_seconds)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def validate_sources!(sources)
|
|
184
|
+
unknown = sources - FILENAMES.keys
|
|
185
|
+
return if unknown.empty?
|
|
186
|
+
|
|
187
|
+
raise ConfigurationError,
|
|
188
|
+
"Unknown data source: #{unknown.map(&:inspect).join(", ")}. " \
|
|
189
|
+
"Valid sources: #{FILENAMES.keys.inspect}"
|
|
170
190
|
end
|
|
171
191
|
|
|
172
|
-
private_class_method :
|
|
192
|
+
private_class_method :validate_sources!,
|
|
193
|
+
:resolve_path,
|
|
173
194
|
:resolve_read_path,
|
|
174
195
|
:validate_source!,
|
|
175
196
|
:custom_paths_configured?,
|
data/lib/iers/delta_t.rb
CHANGED
|
@@ -14,11 +14,11 @@ module IERS
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
EARLIEST_YEAR = 1800.0
|
|
17
|
-
|
|
17
|
+
LATEST_POLYNOMIAL_YEAR = 1986.0
|
|
18
18
|
DAYS_PER_JULIAN_YEAR = 365.25
|
|
19
19
|
YEAR_J2000 = 2000.0
|
|
20
20
|
|
|
21
|
-
# Espenak & Meeus (2014) polynomial segments for 1800–
|
|
21
|
+
# Espenak & Meeus (2014) polynomial segments for 1800–1986.
|
|
22
22
|
# Each segment: [year_start, year_end, epoch, coefficients]
|
|
23
23
|
# ΔT = c₀ + c₁·t + c₂·t² + ... where t = y − epoch
|
|
24
24
|
# Reference: https://eclipsewise.com/help/deltatpoly2014.html
|
|
@@ -46,7 +46,7 @@ module IERS
|
|
|
46
46
|
].freeze
|
|
47
47
|
|
|
48
48
|
private_constant :EARLIEST_YEAR,
|
|
49
|
-
:
|
|
49
|
+
:LATEST_POLYNOMIAL_YEAR,
|
|
50
50
|
:DAYS_PER_JULIAN_YEAR,
|
|
51
51
|
:YEAR_J2000,
|
|
52
52
|
:POLYNOMIALS
|
|
@@ -69,24 +69,62 @@ module IERS
|
|
|
69
69
|
)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
if query_mjd
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
source: :estimated
|
|
77
|
-
)
|
|
72
|
+
if series_covers?(query_mjd)
|
|
73
|
+
measured_entry(query_mjd)
|
|
74
|
+
elsif y <= LATEST_POLYNOMIAL_YEAR
|
|
75
|
+
estimated_entry(query_mjd, y)
|
|
78
76
|
else
|
|
79
|
-
|
|
80
|
-
ut1_utc = UT1.at(mjd: query_mjd).ut1_utc
|
|
81
|
-
|
|
82
|
-
Entry.new(
|
|
83
|
-
delta_t: tai_utc + TimeScale::TT_TAI - ut1_utc,
|
|
84
|
-
mjd: query_mjd,
|
|
85
|
-
source: :measured
|
|
86
|
-
)
|
|
77
|
+
raise_uncovered!(query_mjd)
|
|
87
78
|
end
|
|
88
79
|
end
|
|
89
80
|
|
|
81
|
+
# The EOP series is the better source wherever it reaches, so it is asked
|
|
82
|
+
# first; the polynomial covers whatever is left within its own validity.
|
|
83
|
+
def series_covers?(mjd)
|
|
84
|
+
entries = Data.finals_entries
|
|
85
|
+
return false if entries.empty?
|
|
86
|
+
|
|
87
|
+
mjd.between?(entries.first.mjd, entries.last.mjd)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def estimated_entry(query_mjd, y)
|
|
91
|
+
Entry.new(
|
|
92
|
+
delta_t: polynomial_delta_t(y),
|
|
93
|
+
mjd: query_mjd,
|
|
94
|
+
source: :estimated
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def raise_uncovered!(query_mjd)
|
|
99
|
+
entries = Data.finals_entries
|
|
100
|
+
series =
|
|
101
|
+
if entries.empty?
|
|
102
|
+
"is empty"
|
|
103
|
+
else
|
|
104
|
+
"covers #{entries.first.mjd}..#{entries.last.mjd}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# No `available_range`: what DeltaT can answer is two disjoint spans, and
|
|
108
|
+
# naming either one alone is what made the old message misleading.
|
|
109
|
+
raise OutOfRangeError.new(
|
|
110
|
+
"No DeltaT available for MJD #{query_mjd}: the polynomial covers " \
|
|
111
|
+
"#{EARLIEST_YEAR.to_i}–#{LATEST_POLYNOMIAL_YEAR.to_i} and the EOP " \
|
|
112
|
+
"series #{series}",
|
|
113
|
+
requested_mjd: query_mjd
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def measured_entry(query_mjd)
|
|
118
|
+
tai_utc = LeapSecond.at(mjd: query_mjd)
|
|
119
|
+
ut1_utc = UT1.at(mjd: query_mjd).ut1_utc
|
|
120
|
+
|
|
121
|
+
Entry.new(
|
|
122
|
+
delta_t: tai_utc + TimeScale::TT_TAI - ut1_utc,
|
|
123
|
+
mjd: query_mjd,
|
|
124
|
+
source: :measured
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
90
128
|
def polynomial_delta_t(y)
|
|
91
129
|
segment = POLYNOMIALS.find { |s| y < s[1] } || POLYNOMIALS.last
|
|
92
130
|
t = y - segment[2]
|
|
@@ -97,6 +135,11 @@ module IERS
|
|
|
97
135
|
YEAR_J2000 + (mjd - TimeScale::MJD_J2000) / DAYS_PER_JULIAN_YEAR
|
|
98
136
|
end
|
|
99
137
|
|
|
100
|
-
private_class_method :
|
|
138
|
+
private_class_method :series_covers?,
|
|
139
|
+
:estimated_entry,
|
|
140
|
+
:raise_uncovered!,
|
|
141
|
+
:measured_entry,
|
|
142
|
+
:polynomial_delta_t,
|
|
143
|
+
:mjd_to_decimal_year
|
|
101
144
|
end
|
|
102
145
|
end
|
data/lib/iers/time_scale.rb
CHANGED
|
@@ -28,9 +28,9 @@ module IERS
|
|
|
28
28
|
# @raise [ArgumentError] if no valid input is provided
|
|
29
29
|
def to_mjd(input = nil, jd: nil, mjd: nil)
|
|
30
30
|
if mjd
|
|
31
|
-
|
|
31
|
+
to_float(mjd)
|
|
32
32
|
elsif jd
|
|
33
|
-
|
|
33
|
+
to_float(jd) - JD_MJD_OFFSET
|
|
34
34
|
elsif input.is_a?(Time)
|
|
35
35
|
input.to_datetime.ajd.to_f - JD_MJD_OFFSET
|
|
36
36
|
elsif input.is_a?(Date)
|
|
@@ -41,5 +41,31 @@ module IERS
|
|
|
41
41
|
"got #{input.inspect}"
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
# A date as a Float, without writing to the caller's stderr on the way.
|
|
46
|
+
#
|
|
47
|
+
# +Kernel#Float+ warns "Integer out of Float range" for a magnitude that
|
|
48
|
+
# does not fit one, and for a Rational whose numerator or denominator does
|
|
49
|
+
# not either, so a date far outside the data announced itself before being
|
|
50
|
+
# refused. An Integer or a Rational is measured first and converted
|
|
51
|
+
# directly, which is quiet; everything else still goes through
|
|
52
|
+
# +Kernel#Float+, which is what refuses a String or a nil.
|
|
53
|
+
#
|
|
54
|
+
# The value is unchanged: a magnitude past the range of a Float was an
|
|
55
|
+
# Infinity before and is one now.
|
|
56
|
+
#
|
|
57
|
+
# @param value [Numeric, String] the date to read
|
|
58
|
+
# @return [Float]
|
|
59
|
+
# @raise [ArgumentError, TypeError] if it is not a number
|
|
60
|
+
def to_float(value)
|
|
61
|
+
case value
|
|
62
|
+
when Float then value
|
|
63
|
+
when Integer, Rational
|
|
64
|
+
return value.to_f unless value.abs > Float::MAX
|
|
65
|
+
|
|
66
|
+
value.negative? ? -Float::INFINITY : Float::INFINITY
|
|
67
|
+
else Float(value)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
44
70
|
end
|
|
45
71
|
end
|
data/lib/iers/version.rb
CHANGED
data/lib/iers.rb
CHANGED
|
@@ -40,11 +40,17 @@ module IERS
|
|
|
40
40
|
yield configuration
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# @api private
|
|
44
|
+
# @param candidate [Configuration]
|
|
45
|
+
# @return [Boolean] whether the gem reads its data through this object
|
|
46
|
+
def active_configuration?(candidate)
|
|
47
|
+
@configuration.equal?(candidate)
|
|
48
|
+
end
|
|
49
|
+
|
|
43
50
|
# @return [void]
|
|
44
51
|
def reset_configuration!
|
|
45
52
|
@configuration = nil
|
|
46
53
|
Data.clear_loaded!
|
|
47
|
-
LeapSecond.clear_cached!
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
# @return [void]
|