iers 0.1.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -0
- data/README.md +48 -5
- data/Rakefile +27 -0
- 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 +41 -10
- data/lib/iers/data_status.rb +1 -1
- data/lib/iers/delta_t.rb +61 -18
- data/lib/iers/leap_second.rb +32 -5
- data/lib/iers/parsers/finals.rb +1 -1
- data/lib/iers/parsers/leap_second.rb +54 -4
- data/lib/iers/tai_utc_drift.rb +144 -0
- data/lib/iers/update_result.rb +1 -1
- data/lib/iers/version.rb +1 -1
- data/lib/iers.rb +8 -1
- metadata +2 -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
|
@@ -11,13 +11,13 @@ module IERS
|
|
|
11
11
|
|
|
12
12
|
@mutex = Mutex.new
|
|
13
13
|
@finals = nil
|
|
14
|
-
@
|
|
14
|
+
@leap_second_table = nil
|
|
15
15
|
|
|
16
16
|
module_function
|
|
17
17
|
|
|
18
18
|
# @return [Boolean]
|
|
19
19
|
def loaded?
|
|
20
|
-
!@finals.nil? || !@
|
|
20
|
+
!@finals.nil? || !@leap_second_table.nil?
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# @param sources [Array<Symbol>] data sources to update (default: all)
|
|
@@ -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]
|
|
@@ -96,16 +99,26 @@ module IERS
|
|
|
96
99
|
end
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
# @return [
|
|
100
|
-
def
|
|
102
|
+
# @return [Parsers::LeapSecond::Table]
|
|
103
|
+
def leap_second_table
|
|
101
104
|
@mutex.synchronize do
|
|
102
|
-
@
|
|
105
|
+
@leap_second_table ||= begin
|
|
103
106
|
path = resolve_read_path(:leap_seconds)
|
|
104
|
-
Parsers::LeapSecond.parse(path)
|
|
107
|
+
Parsers::LeapSecond.parse(path)
|
|
105
108
|
end
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
111
|
|
|
112
|
+
# @return [Array<Parsers::LeapSecond::Entry>]
|
|
113
|
+
def leap_second_entries
|
|
114
|
+
leap_second_table.entries
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @return [Parsers::LeapSecond::Metadata]
|
|
118
|
+
def leap_second_metadata
|
|
119
|
+
leap_second_table.metadata
|
|
120
|
+
end
|
|
121
|
+
|
|
109
122
|
def resolve_path(source, config = IERS.configuration)
|
|
110
123
|
case source
|
|
111
124
|
when :finals
|
|
@@ -151,15 +164,33 @@ module IERS
|
|
|
151
164
|
(Time.now - mtimes.min).to_i
|
|
152
165
|
end
|
|
153
166
|
|
|
167
|
+
# @param sources [Array<Symbol>] data sources to drop (default: all)
|
|
154
168
|
# @return [void]
|
|
155
|
-
def clear_loaded!
|
|
169
|
+
def clear_loaded!(*sources)
|
|
170
|
+
sources = FILENAMES.keys if sources.empty?
|
|
171
|
+
validate_sources!(sources)
|
|
172
|
+
|
|
156
173
|
@mutex.synchronize do
|
|
157
|
-
@finals = nil
|
|
158
|
-
@
|
|
174
|
+
@finals = nil if sources.include?(:finals)
|
|
175
|
+
@leap_second_table = nil if sources.include?(:leap_seconds)
|
|
159
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}"
|
|
160
190
|
end
|
|
161
191
|
|
|
162
|
-
private_class_method :
|
|
192
|
+
private_class_method :validate_sources!,
|
|
193
|
+
:resolve_path,
|
|
163
194
|
:resolve_read_path,
|
|
164
195
|
:validate_source!,
|
|
165
196
|
:custom_paths_configured?,
|
data/lib/iers/data_status.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module IERS
|
|
4
4
|
# @attr source [Symbol] +:cached+, +:custom+, or +:bundled+
|
|
5
5
|
# @attr cache_age [Integer, nil] age in seconds, or +nil+
|
|
6
|
-
DataStatus = Data.define(:source, :cache_age) do
|
|
6
|
+
DataStatus = ::Data.define(:source, :cache_age) do
|
|
7
7
|
# @return [Boolean]
|
|
8
8
|
def cached?
|
|
9
9
|
source == :cached
|
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/leap_second.rb
CHANGED
|
@@ -30,17 +30,41 @@ module IERS
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# @return [Date, nil]
|
|
34
|
+
def expires_on
|
|
35
|
+
IERS::Data.leap_second_metadata.expires_on
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param as_of [Date]
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def expired?(as_of: Date.today)
|
|
41
|
+
expiry = expires_on
|
|
42
|
+
return false if expiry.nil?
|
|
43
|
+
|
|
44
|
+
as_of > expiry
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [String, nil]
|
|
48
|
+
def updated_through
|
|
49
|
+
IERS::Data.leap_second_metadata.updated_through
|
|
50
|
+
end
|
|
51
|
+
|
|
33
52
|
# @return [Entry, nil]
|
|
34
53
|
def next_scheduled
|
|
35
54
|
today = Date.today
|
|
36
55
|
all.find { |entry| entry.effective_date > today }
|
|
37
56
|
end
|
|
38
57
|
|
|
58
|
+
# TAI−UTC covers 1961-01-01 onward. From 1972 the value is a whole number
|
|
59
|
+
# of seconds read from Leap_Second.dat and returned as an Integer. Between
|
|
60
|
+
# 1961 and 1972 UTC was steered by rate adjustments rather than whole leap
|
|
61
|
+
# seconds, so the value is a fraction of a second returned as a Rational.
|
|
62
|
+
#
|
|
39
63
|
# @param input [Time, Date, DateTime, nil]
|
|
40
64
|
# @param jd [Float, nil] Julian Date
|
|
41
65
|
# @param mjd [Float, nil] Modified Julian Date
|
|
42
|
-
# @return [Integer] TAI−UTC in seconds
|
|
43
|
-
# @raise [OutOfRangeError]
|
|
66
|
+
# @return [Integer, Rational] TAI−UTC in seconds
|
|
67
|
+
# @raise [OutOfRangeError] before 1961-01-01
|
|
44
68
|
def at(input = nil, jd: nil, mjd: nil)
|
|
45
69
|
query_mjd = TimeScale.to_mjd(input, jd: jd, mjd: mjd)
|
|
46
70
|
parser_entries = IERS::Data.leap_second_entries
|
|
@@ -49,11 +73,14 @@ module IERS
|
|
|
49
73
|
last_mjd = parser_entries.last.mjd
|
|
50
74
|
|
|
51
75
|
if query_mjd < first_mjd
|
|
76
|
+
return TaiUtcDrift.at(query_mjd) if TaiUtcDrift.covers?(query_mjd)
|
|
77
|
+
|
|
52
78
|
raise OutOfRangeError.new(
|
|
53
|
-
"Requested MJD #{query_mjd} is before
|
|
54
|
-
"
|
|
79
|
+
"Requested MJD #{query_mjd} is before TAI−UTC was defined " \
|
|
80
|
+
"(MJD #{TaiUtcDrift::FIRST_MJD}, 1961-01-01; no published UTC " \
|
|
81
|
+
"before then)",
|
|
55
82
|
requested_mjd: query_mjd,
|
|
56
|
-
available_range:
|
|
83
|
+
available_range: TaiUtcDrift::FIRST_MJD..last_mjd
|
|
57
84
|
)
|
|
58
85
|
end
|
|
59
86
|
|
data/lib/iers/parsers/finals.rb
CHANGED
|
@@ -5,10 +5,23 @@ require "date"
|
|
|
5
5
|
module IERS
|
|
6
6
|
module Parsers
|
|
7
7
|
module LeapSecond
|
|
8
|
-
Entry = Data.define(:mjd, :date, :tai_utc)
|
|
8
|
+
Entry = ::Data.define(:mjd, :date, :tai_utc)
|
|
9
|
+
|
|
10
|
+
# @attr expires_on [Date, nil]
|
|
11
|
+
# @attr updated_through [String, nil]
|
|
12
|
+
Metadata = ::Data.define(:expires_on, :updated_through)
|
|
13
|
+
|
|
14
|
+
# @attr entries [Array<Entry>]
|
|
15
|
+
# @attr metadata [Metadata]
|
|
16
|
+
Table = ::Data.define(:entries, :metadata)
|
|
17
|
+
|
|
18
|
+
EXPIRES_ON = /File expires on\s+(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})/i
|
|
19
|
+
UPDATED_THROUGH = /\AUpdated through\s+(.+?)\s*\z/i
|
|
20
|
+
COMMENT_PREFIX = /\A#+\s*/
|
|
9
21
|
|
|
10
22
|
module_function
|
|
11
23
|
|
|
24
|
+
# @return [Table]
|
|
12
25
|
def parse(path)
|
|
13
26
|
path = Pathname(path)
|
|
14
27
|
|
|
@@ -20,14 +33,30 @@ module IERS
|
|
|
20
33
|
end
|
|
21
34
|
|
|
22
35
|
entries = []
|
|
36
|
+
expires_on = nil
|
|
37
|
+
updated_through = nil
|
|
23
38
|
|
|
24
39
|
path.each_line.with_index(1) do |line, line_number|
|
|
25
|
-
|
|
40
|
+
stripped = line.scrub.strip
|
|
41
|
+
next if stripped.empty?
|
|
42
|
+
|
|
43
|
+
if stripped.start_with?("#")
|
|
44
|
+
comment = stripped.sub(COMMENT_PREFIX, "")
|
|
45
|
+
expires_on ||= parse_expiry(comment)
|
|
46
|
+
updated_through ||= parse_updated_through(comment)
|
|
47
|
+
next
|
|
48
|
+
end
|
|
26
49
|
|
|
27
50
|
entries << parse_line(line, path, line_number)
|
|
28
51
|
end
|
|
29
52
|
|
|
30
|
-
|
|
53
|
+
Table.new(
|
|
54
|
+
entries: entries.freeze,
|
|
55
|
+
metadata: Metadata.new(
|
|
56
|
+
expires_on: expires_on,
|
|
57
|
+
updated_through: updated_through
|
|
58
|
+
)
|
|
59
|
+
)
|
|
31
60
|
end
|
|
32
61
|
|
|
33
62
|
def parse_line(line, path, line_number)
|
|
@@ -50,7 +79,28 @@ module IERS
|
|
|
50
79
|
)
|
|
51
80
|
end
|
|
52
81
|
|
|
53
|
-
|
|
82
|
+
# @return [Date, nil]
|
|
83
|
+
def parse_expiry(comment)
|
|
84
|
+
match = EXPIRES_ON.match(comment)
|
|
85
|
+
return nil if match.nil?
|
|
86
|
+
|
|
87
|
+
month = Date::MONTHNAMES.index do |name|
|
|
88
|
+
name&.casecmp?(match[2])
|
|
89
|
+
end
|
|
90
|
+
return nil if month.nil?
|
|
91
|
+
|
|
92
|
+
Date.new(Integer(match[3], 10), month, Integer(match[1], 10))
|
|
93
|
+
rescue ArgumentError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [String, nil]
|
|
98
|
+
def parse_updated_through(comment)
|
|
99
|
+
match = UPDATED_THROUGH.match(comment)
|
|
100
|
+
match && match[1]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private_class_method :parse_line, :parse_expiry, :parse_updated_through
|
|
54
104
|
end
|
|
55
105
|
end
|
|
56
106
|
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IERS
|
|
4
|
+
# TAI-UTC from 1961-01-01 to 1972-01-01, when UTC was steered by rate
|
|
5
|
+
# adjustments instead of whole leap seconds. In this era TAI-UTC is a linear
|
|
6
|
+
# function of MJD, restarting at each published step, so a value is
|
|
7
|
+
# +offset + (mjd - reference_mjd) * rate+.
|
|
8
|
+
#
|
|
9
|
+
# The table is closed: the last segment ends where Leap_Second.dat begins and
|
|
10
|
+
# no new segment can ever be added.
|
|
11
|
+
#
|
|
12
|
+
# Coefficients from the USNO tai-utc.dat file, the same values ERFA carries in
|
|
13
|
+
# dat.c. They are exact decimals, so they are stored as rationals rather than
|
|
14
|
+
# floats.
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
module TaiUtcDrift
|
|
18
|
+
# @attr start_mjd [Integer] first MJD the segment applies to
|
|
19
|
+
# @attr offset [Rational] TAI-UTC in seconds at reference_mjd (not at
|
|
20
|
+
# start_mjd: several segments measure their rate from an earlier MJD)
|
|
21
|
+
# @attr reference_mjd [Integer] the MJD the rate is measured from
|
|
22
|
+
# @attr rate [Rational] seconds of TAI-UTC per day of MJD
|
|
23
|
+
Segment = ::Data.define(:start_mjd, :offset, :reference_mjd, :rate)
|
|
24
|
+
|
|
25
|
+
# 1961-01-01. Below this there is no published UTC, so TAI-UTC is undefined.
|
|
26
|
+
FIRST_MJD = 37_300
|
|
27
|
+
|
|
28
|
+
# 1972-01-01, where Leap_Second.dat takes over. The drift era stops here.
|
|
29
|
+
LAST_MJD = 41_317
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @param mjd [Numeric]
|
|
34
|
+
# @return [Rational] TAI-UTC in seconds
|
|
35
|
+
def at(mjd)
|
|
36
|
+
segment = SEGMENTS.reverse_each.find { |s| mjd >= s.start_mjd }
|
|
37
|
+
|
|
38
|
+
segment.offset +
|
|
39
|
+
(Rational(mjd) - segment.reference_mjd) * segment.rate
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Whether +at+ has a value for this MJD: 1961-01-01 up to but not including
|
|
43
|
+
# 1972-01-01, where Leap_Second.dat takes over. A caller with a custom leap
|
|
44
|
+
# second file starting after 1972 relies on the upper bound to fall through
|
|
45
|
+
# to its own error rather than reading a stale drift value.
|
|
46
|
+
#
|
|
47
|
+
# @param mjd [Numeric]
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def covers?(mjd)
|
|
50
|
+
mjd >= FIRST_MJD && mjd < LAST_MJD
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
SEGMENTS = [
|
|
54
|
+
Segment.new(
|
|
55
|
+
start_mjd: 37_300,
|
|
56
|
+
offset: Rational(14_228_180, 10_000_000),
|
|
57
|
+
reference_mjd: 37_300,
|
|
58
|
+
rate: Rational(1_296, 1_000_000)
|
|
59
|
+
),
|
|
60
|
+
Segment.new(
|
|
61
|
+
start_mjd: 37_512,
|
|
62
|
+
offset: Rational(13_728_180, 10_000_000),
|
|
63
|
+
reference_mjd: 37_300,
|
|
64
|
+
rate: Rational(1_296, 1_000_000)
|
|
65
|
+
),
|
|
66
|
+
Segment.new(
|
|
67
|
+
start_mjd: 37_665,
|
|
68
|
+
offset: Rational(18_458_580, 10_000_000),
|
|
69
|
+
reference_mjd: 37_665,
|
|
70
|
+
rate: Rational(11_232, 10_000_000)
|
|
71
|
+
),
|
|
72
|
+
Segment.new(
|
|
73
|
+
start_mjd: 38_334,
|
|
74
|
+
offset: Rational(19_458_580, 10_000_000),
|
|
75
|
+
reference_mjd: 37_665,
|
|
76
|
+
rate: Rational(11_232, 10_000_000)
|
|
77
|
+
),
|
|
78
|
+
Segment.new(
|
|
79
|
+
start_mjd: 38_395,
|
|
80
|
+
offset: Rational(32_401_300, 10_000_000),
|
|
81
|
+
reference_mjd: 38_761,
|
|
82
|
+
rate: Rational(1_296, 1_000_000)
|
|
83
|
+
),
|
|
84
|
+
Segment.new(
|
|
85
|
+
start_mjd: 38_486,
|
|
86
|
+
offset: Rational(33_401_300, 10_000_000),
|
|
87
|
+
reference_mjd: 38_761,
|
|
88
|
+
rate: Rational(1_296, 1_000_000)
|
|
89
|
+
),
|
|
90
|
+
Segment.new(
|
|
91
|
+
start_mjd: 38_639,
|
|
92
|
+
offset: Rational(34_401_300, 10_000_000),
|
|
93
|
+
reference_mjd: 38_761,
|
|
94
|
+
rate: Rational(1_296, 1_000_000)
|
|
95
|
+
),
|
|
96
|
+
Segment.new(
|
|
97
|
+
start_mjd: 38_761,
|
|
98
|
+
offset: Rational(35_401_300, 10_000_000),
|
|
99
|
+
reference_mjd: 38_761,
|
|
100
|
+
rate: Rational(1_296, 1_000_000)
|
|
101
|
+
),
|
|
102
|
+
Segment.new(
|
|
103
|
+
start_mjd: 38_820,
|
|
104
|
+
offset: Rational(36_401_300, 10_000_000),
|
|
105
|
+
reference_mjd: 38_761,
|
|
106
|
+
rate: Rational(1_296, 1_000_000)
|
|
107
|
+
),
|
|
108
|
+
Segment.new(
|
|
109
|
+
start_mjd: 38_942,
|
|
110
|
+
offset: Rational(37_401_300, 10_000_000),
|
|
111
|
+
reference_mjd: 38_761,
|
|
112
|
+
rate: Rational(1_296, 1_000_000)
|
|
113
|
+
),
|
|
114
|
+
Segment.new(
|
|
115
|
+
start_mjd: 39_004,
|
|
116
|
+
offset: Rational(38_401_300, 10_000_000),
|
|
117
|
+
reference_mjd: 38_761,
|
|
118
|
+
rate: Rational(1_296, 1_000_000)
|
|
119
|
+
),
|
|
120
|
+
Segment.new(
|
|
121
|
+
start_mjd: 39_126,
|
|
122
|
+
offset: Rational(43_131_700, 10_000_000),
|
|
123
|
+
reference_mjd: 39_126,
|
|
124
|
+
rate: Rational(2_592, 1_000_000)
|
|
125
|
+
),
|
|
126
|
+
Segment.new(
|
|
127
|
+
start_mjd: 39_887,
|
|
128
|
+
offset: Rational(42_131_700, 10_000_000),
|
|
129
|
+
reference_mjd: 39_126,
|
|
130
|
+
rate: Rational(2_592, 1_000_000)
|
|
131
|
+
),
|
|
132
|
+
# Reached only by a direct TaiUtcDrift.at(41_317): LeapSecond.at's drift
|
|
133
|
+
# branch requires query_mjd < 41_317. It pins the join to exactly 10,
|
|
134
|
+
# matching Leap_Second.dat's first row; without it the 1968 segment would
|
|
135
|
+
# give ~9.8922 here. Do not remove.
|
|
136
|
+
Segment.new(
|
|
137
|
+
start_mjd: 41_317,
|
|
138
|
+
offset: Rational(10),
|
|
139
|
+
reference_mjd: 41_317,
|
|
140
|
+
rate: Rational(0)
|
|
141
|
+
)
|
|
142
|
+
].freeze
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/iers/update_result.rb
CHANGED
data/lib/iers/version.rb
CHANGED
data/lib/iers.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "iers/has_data_quality"
|
|
|
14
14
|
require_relative "iers/interpolation"
|
|
15
15
|
require_relative "iers/eop_lookup"
|
|
16
16
|
require_relative "iers/eop_parameter"
|
|
17
|
+
require_relative "iers/tai_utc_drift"
|
|
17
18
|
require_relative "iers/leap_second"
|
|
18
19
|
require_relative "iers/tai"
|
|
19
20
|
require_relative "iers/ut1"
|
|
@@ -39,11 +40,17 @@ module IERS
|
|
|
39
40
|
yield configuration
|
|
40
41
|
end
|
|
41
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
|
+
|
|
42
50
|
# @return [void]
|
|
43
51
|
def reset_configuration!
|
|
44
52
|
@configuration = nil
|
|
45
53
|
Data.clear_loaded!
|
|
46
|
-
LeapSecond.clear_cached!
|
|
47
54
|
end
|
|
48
55
|
|
|
49
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rémy Hannequin
|
|
@@ -174,6 +174,7 @@ files:
|
|
|
174
174
|
- lib/iers/parsers/leap_second.rb
|
|
175
175
|
- lib/iers/polar_motion.rb
|
|
176
176
|
- lib/iers/tai.rb
|
|
177
|
+
- lib/iers/tai_utc_drift.rb
|
|
177
178
|
- lib/iers/terrestrial_rotation.rb
|
|
178
179
|
- lib/iers/time_scale.rb
|
|
179
180
|
- lib/iers/update_result.rb
|