iers 0.2.0 → 0.3.0

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.
@@ -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 :resolve_path,
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
- PRE_1972_MJD = 41317.0
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–1972.
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
- :PRE_1972_MJD,
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 < PRE_1972_MJD
73
- Entry.new(
74
- delta_t: polynomial_delta_t(y),
75
- mjd: query_mjd,
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
- tai_utc = LeapSecond.at(mjd: query_mjd)
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 :polynomial_delta_t, :mjd_to_decimal_year
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/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IERS
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
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]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémy Hannequin