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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39a64a5333955d6d28a26abc77c01371004f91b8fb8ca4b958f3ac5d415cca13
|
|
4
|
+
data.tar.gz: 1f219ef6f35bc93ab6263832546a3af6534a7d2a29007425cfa54b2abea6c284
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd2df5b1019640ac16f44d7289a5af2721fc90bef064785921701992fd6798c96c94ce9ef9f34f69323e79baba9b4d2ab28310520045b18c7ac1332d63fc6b69
|
|
7
|
+
data.tar.gz: 42cc738efcb811f3f01f2de2d303d89b333a33ae394cbacc27f261cda39ab00e8e982ff2e3706eaac93b40a25a0e2522cfeb3d2dfe78c196137f7e3fbb8fd7b0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-09-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `DeltaT.at` answers every date in 1972 again. The switch from the Espenak &
|
|
8
|
+
Meeus polynomial to measured data was pinned to 1972-01-01, the start of the
|
|
9
|
+
modern UTC era, but the bundled EOP series only starts at 1973-01-02, so the
|
|
10
|
+
367 days in between belonged to neither source and raised `OutOfRangeError`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `DeltaT.at` picks its source by asking what the loaded data actually covers
|
|
15
|
+
rather than by calendar date. The EOP series is used wherever it reaches, the
|
|
16
|
+
polynomial covers the rest of 1800–1986, and only a date outside both raises.
|
|
17
|
+
A configured or extended series therefore moves the seam with it, and no
|
|
18
|
+
fixed date can drift away from the data behind it again.
|
|
19
|
+
|
|
20
|
+
Two consequences for a series that does not span the query. A date past the
|
|
21
|
+
end of a short series now returns a polynomial estimate if it falls before
|
|
22
|
+
1986, where it used to raise; this cannot arise with the bundled data, which
|
|
23
|
+
ends decades beyond 1986. And a date outside both sources now raises with
|
|
24
|
+
a message naming the polynomial range and the series range, instead of the
|
|
25
|
+
EOP range alone, which read as if the caller had asked for something out of
|
|
26
|
+
range when the polynomial covered it.
|
|
27
|
+
|
|
28
|
+
Where the polynomial and the series meet in the bundled data they differ by
|
|
29
|
+
about 61 ms, well inside the polynomial's own error in that era.
|
|
30
|
+
|
|
31
|
+
- Pointing the gem at a different data file now takes effect. `finals_path`,
|
|
32
|
+
`leap_second_path` and `cache_dir` memoised their parse on first read and
|
|
33
|
+
never dropped it, so setting any of them after a lookup kept serving the
|
|
34
|
+
previous file until `IERS.reset!`. Each now invalidates just the data it
|
|
35
|
+
governs; `interpolation`, `sources` and `download_timeout` leave the parse
|
|
36
|
+
in place, since they do not change which file is read, and a `Configuration`
|
|
37
|
+
the gem is not reading through governs nothing.
|
|
38
|
+
|
|
39
|
+
- `Data.clear_cache!` drops the parse along with the files it deletes. It
|
|
40
|
+
removed the cached files and left their contents in memory, so the process
|
|
41
|
+
kept serving a deleted cache while `Data.status` already reported `:bundled`.
|
|
42
|
+
|
|
43
|
+
- `Data.update!` drops the parse for each source as soon as it downloads it.
|
|
44
|
+
It replaced the files on disk but left the old contents in memory, so a
|
|
45
|
+
process that had already read them carried on with the pre-download data.
|
|
46
|
+
|
|
47
|
+
- `OutOfRangeError` raised by `DeltaT.at` no longer carries an
|
|
48
|
+
`available_range`. What the gem can answer is two disjoint spans, and naming
|
|
49
|
+
one of them in a field called available range is the same thing that made the
|
|
50
|
+
old message misleading. The message names both spans; `requested_mjd` is
|
|
51
|
+
unchanged.
|
|
52
|
+
|
|
53
|
+
- `Data.clear_loaded!` raises `ConfigurationError` for a source it does not
|
|
54
|
+
recognise, rather than clearing nothing and reporting success.
|
|
55
|
+
|
|
56
|
+
- `DeltaT.at` consults the EOP series for every query, so a finals file that
|
|
57
|
+
fails to parse now raises `ParseError` even for a date the polynomial owns
|
|
58
|
+
outright, where it used to answer without reading the file. A file that
|
|
59
|
+
parses to no rows is a series covering nothing and still falls back to the
|
|
60
|
+
polynomial. A corrupt data file is worth surfacing whichever query reaches
|
|
61
|
+
it.
|
|
62
|
+
|
|
63
|
+
- Refreshed the bundled `finals2000A.all` and `Leap_Second.dat` snapshots from
|
|
64
|
+
IERS. Final values now extend through 2026-08-27 (previously 2026-06-25) and
|
|
65
|
+
predictions through 2027-09-04 (previously 2027-07-03).
|
|
66
|
+
|
|
67
|
+
## 0.2.0 - 2026-07-25
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- `LeapSecond.expires_on`, `LeapSecond.expired?` and
|
|
72
|
+
`LeapSecond.updated_through` expose the expiry date and IERS bulletin that
|
|
73
|
+
`Leap_Second.dat` states in its header. Both were previously discarded with
|
|
74
|
+
the rest of the comment lines. Unlike `Data.status.cache_age`, which measures
|
|
75
|
+
when a file was downloaded, these describe how long the data itself stays
|
|
76
|
+
authoritative.
|
|
77
|
+
- `LeapSecond.at` now answers 1961-01-01 through 1972-01-01, the era when UTC
|
|
78
|
+
was steered by rate adjustments rather than whole leap seconds. It returns
|
|
79
|
+
those values as exact `Rational`s. The coefficients come from the USNO
|
|
80
|
+
`tai-utc.dat` file and are bundled in source, since they are fixed. The
|
|
81
|
+
change is additive: the pre-1972 range previously raised, so no successful
|
|
82
|
+
call returns anything different. `TAI.utc_to_tai` and `TAI.tai_to_utc` gain
|
|
83
|
+
the same range for free.
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
|
|
87
|
+
- `Parsers::LeapSecond.parse` returns a `Table` of `entries` and `metadata`
|
|
88
|
+
instead of an array of entries. `Parsers` is internal; the public API is
|
|
89
|
+
unaffected.
|
|
90
|
+
- `Parsers::LeapSecond.parse` no longer raises `Encoding::CompatibilityError`
|
|
91
|
+
on a file containing invalid UTF-8. Header metadata is read on a best-effort
|
|
92
|
+
basis and an unreadable data row raises `ParseError` as before.
|
|
93
|
+
|
|
3
94
|
## 0.1.1 - 2026-06-27
|
|
4
95
|
|
|
5
96
|
### Changed
|
data/README.md
CHANGED
|
@@ -167,9 +167,10 @@ entry.observed? # => true
|
|
|
167
167
|
|
|
168
168
|
### Delta T
|
|
169
169
|
|
|
170
|
-
Compute Delta T (TT − UT1).
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
Compute Delta T (TT − UT1). Wherever the loaded EOP series reaches, the value is
|
|
171
|
+
derived from IERS data. Outside it, Espenak & Meeus polynomial approximations
|
|
172
|
+
cover 1800 to 1986. The bundled series starts at MJD 41684 (1973-01-02), so
|
|
173
|
+
earlier dates are estimated; `measured?` and `estimated?` say which you got:
|
|
173
174
|
|
|
174
175
|
```ruby
|
|
175
176
|
entry = IERS::DeltaT.at(Time.utc(2020, 6, 15))
|
|
@@ -242,14 +243,28 @@ entries = IERS::EOP.between(
|
|
|
242
243
|
Look up TAI−UTC at a given date:
|
|
243
244
|
|
|
244
245
|
```ruby
|
|
245
|
-
IERS::LeapSecond.at(Time.utc(2017, 1, 1)) # => 37
|
|
246
|
+
IERS::LeapSecond.at(Time.utc(2017, 1, 1)) # => 37 (seconds)
|
|
246
247
|
```
|
|
247
248
|
|
|
249
|
+
`at` covers 1961-01-01 onward. From 1972 it returns a whole number of seconds
|
|
250
|
+
as an `Integer`. Between 1961 and 1972, UTC was steered by small rate
|
|
251
|
+
adjustments rather than whole leap seconds, so TAI−UTC was a fraction of a
|
|
252
|
+
second that changed daily. `at` returns those values as exact `Rational`s:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
IERS::LeapSecond.at(mjd: 38_900) # => (1910137/500000)
|
|
256
|
+
IERS::LeapSecond.at(mjd: 38_900).to_f # => 3.820274 (seconds, 1965-05-20)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The coefficients for that era are fixed and will never change, so they are
|
|
260
|
+
bundled in the gem rather than downloaded. Anything before 1961-01-01 has no
|
|
261
|
+
published UTC and raises `OutOfRangeError`.
|
|
262
|
+
|
|
248
263
|
List all leap seconds:
|
|
249
264
|
|
|
250
265
|
```ruby
|
|
251
266
|
IERS::LeapSecond.all
|
|
252
|
-
# => [#<data IERS::LeapSecond::Entry effective_date=#<Date: 1972-01-01>, tai_utc=10
|
|
267
|
+
# => [#<data IERS::LeapSecond::Entry effective_date=#<Date: 1972-01-01>, tai_utc=10>, ...]
|
|
253
268
|
```
|
|
254
269
|
|
|
255
270
|
Check for a future scheduled leap second:
|
|
@@ -258,6 +273,27 @@ Check for a future scheduled leap second:
|
|
|
258
273
|
IERS::LeapSecond.next_scheduled # => #<data IERS::LeapSecond::Entry ...> or nil
|
|
259
274
|
```
|
|
260
275
|
|
|
276
|
+
#### Leap second file validity
|
|
277
|
+
|
|
278
|
+
`Leap_Second.dat` states when it stops being authoritative and which IERS
|
|
279
|
+
bulletin it was updated through. Both are read from the file that is actually
|
|
280
|
+
loaded, so a custom or cached file reports its own header:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
IERS::LeapSecond.expires_on # => #<Date: 2026-12-28> or nil
|
|
284
|
+
IERS::LeapSecond.expired? # => false
|
|
285
|
+
IERS::LeapSecond.updated_through # => "IERS Bulletin 71 issued in January 2026"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The stated date is itself still valid, so a file expiring on 28 December 2026
|
|
289
|
+
is expired on the 29th. An expired file is not wrong: it means the IERS has
|
|
290
|
+
published a newer one and a leap second may have been announced since.
|
|
291
|
+
|
|
292
|
+
This is unrelated to `Data.status.cache_age`, which measures when the file was
|
|
293
|
+
downloaded rather than how long it stays valid. A bundled snapshot can be a year
|
|
294
|
+
old and still valid while a file downloaded this morning can be a week from
|
|
295
|
+
expiry.
|
|
296
|
+
|
|
261
297
|
### TAI
|
|
262
298
|
|
|
263
299
|
Convert between UTC and TAI time scales:
|
|
@@ -295,6 +331,10 @@ end
|
|
|
295
331
|
|
|
296
332
|
Without `coverage_days_ahead`, the check ensures predictions cover today.
|
|
297
333
|
|
|
334
|
+
`ensure_fresh!` is about prediction coverage in `finals2000A.all` only. Leap
|
|
335
|
+
second file validity is a separate question, answered by
|
|
336
|
+
`IERS::LeapSecond.expired?`.
|
|
337
|
+
|
|
298
338
|
### Data status and cache management
|
|
299
339
|
|
|
300
340
|
```ruby
|
|
@@ -358,6 +398,9 @@ release a new version, update the version number in `version.rb`, and then run
|
|
|
358
398
|
`bundle exec rake release`, which will create a git tag for the version, push
|
|
359
399
|
git commits and the created tag, and push the `.gem` file to [rubygems.org].
|
|
360
400
|
|
|
401
|
+
To refresh the bundled snapshot in `data/`, run `bundle exec rake data:update`.
|
|
402
|
+
A monthly workflow runs this and opens a pull request automatically.
|
|
403
|
+
|
|
361
404
|
## License
|
|
362
405
|
|
|
363
406
|
The gem is available as open source under the terms of the [MIT License].
|
data/Rakefile
CHANGED
|
@@ -10,3 +10,30 @@ require "rubocop/rake_task"
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
12
|
task default: %i[test rubocop]
|
|
13
|
+
|
|
14
|
+
namespace :data do
|
|
15
|
+
desc "Download the latest IERS data"
|
|
16
|
+
task :update do
|
|
17
|
+
require "iers"
|
|
18
|
+
|
|
19
|
+
data_dir = File.expand_path("data", __dir__)
|
|
20
|
+
IERS.configure do |config|
|
|
21
|
+
config.finals_path = File.join(data_dir, "finals2000A.all")
|
|
22
|
+
config.leap_second_path = File.join(data_dir, "Leap_Second.dat")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
result = IERS::Data.update!
|
|
26
|
+
|
|
27
|
+
unless result.success?
|
|
28
|
+
result.errors.each do |source, error|
|
|
29
|
+
warn "#{source}: #{error.class} - #{error.message}"
|
|
30
|
+
end
|
|
31
|
+
abort "IERS data update failed"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
IERS::Data.ensure_fresh!(coverage_days_ahead: 90)
|
|
35
|
+
IERS::Data.leap_second_entries
|
|
36
|
+
|
|
37
|
+
puts "Updated: #{result.updated_files.join(", ")}"
|
|
38
|
+
end
|
|
39
|
+
end
|
data/data/Leap_Second.dat
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Value of TAI-UTC in second valid beetween the initial value until
|
|
2
2
|
# the epoch given on the next line. The last line reads that NO
|
|
3
3
|
# leap second was introduced since the corresponding date
|
|
4
|
-
# Updated through IERS Bulletin
|
|
4
|
+
# Updated through IERS Bulletin 72 issued in July 2026
|
|
5
5
|
#
|
|
6
6
|
#
|
|
7
|
-
# File expires on 28
|
|
7
|
+
# File expires on 28 June 2027
|
|
8
8
|
#
|
|
9
9
|
#
|
|
10
10
|
# MJD Date TAI-UTC (s)
|