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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe885be7911b137f1052c4671b28d5ed63ec1f0a887745fbeede898e1d15eb68
4
- data.tar.gz: 42763ecd8056ee961e489b666e6445553726c8dbe0d1606f1857eea6a427ba38
3
+ metadata.gz: 39a64a5333955d6d28a26abc77c01371004f91b8fb8ca4b958f3ac5d415cca13
4
+ data.tar.gz: 1f219ef6f35bc93ab6263832546a3af6534a7d2a29007425cfa54b2abea6c284
5
5
  SHA512:
6
- metadata.gz: c2282980b523a94f9befbaf3146cb70bbe3284d73305cebf6073826bd60280da002b5a82dc9228fc6552d7db31c5ad38f6f05e9d6150aded290425aad2e47f56
7
- data.tar.gz: b0579c1cfb2b2ef069e649ad80df7fddfe1d3828e360e28a6626a42f0a6d03ba1d563e622ee1e861e81bde0c4bc1a2373e112b5980ea2d1f8679bf66cd7e800e
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). From 1972 onward the value is derived from IERS
171
- data; before 1972 (back to 1800) it uses Espenak & Meeus polynomial
172
- approximations:
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.0 (seconds)
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.0>, ...]
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 71 issued in January 2026
4
+ # Updated through IERS Bulletin 72 issued in July 2026
5
5
  #
6
6
  #
7
- # File expires on 28 December 2026
7
+ # File expires on 28 June 2027
8
8
  #
9
9
  #
10
10
  # MJD Date TAI-UTC (s)