Access to Earth orientation parameters and time scale values from the International Earth Rotation and Reference Systems Service (IERS).
The IERS is an international service that monitors the irregularities of Earth's rotation and orientation in space. Because Earth's rotation is not perfectly uniform, precise timekeeping, satellite navigation, and telescope pointing all depend on regularly updated measurements.
The key quantities tracked by the IERS are known as Earth Orientation Parameters (EOP):
- Polar motion (x, y) — the position of Earth's rotational pole relative to its crust, expressed in arcseconds. The pole wanders in a roughly circular path of a few tenths of an arcsecond over ~14 months (the Chandler wobble).
- UT1−UTC — the difference between astronomical time (UT1, tied to Earth's actual rotation angle) and coordinated universal time (UTC, maintained by atomic clocks). This difference drifts by up to ~0.9 s before a leap second is introduced to keep them close.
- Leap seconds — occasional one-second adjustments applied to UTC so that it stays within 0.9 s of UT1. Since 1972, 27 leap seconds have been added.
This library works with two data files published by the IERS:
- finals2000A — a daily table spanning from 1973 to the present (plus predictions ~1 year ahead). Each row contains polar motion, UT1−UTC, and other EOP for one Modified Julian Date. Recent rows carry rapid "Series A" values; older rows also include refined "Bulletin B" values.
- Leap_Second.dat — the complete history of leap seconds with their effective dates and the cumulative TAI−UTC offset.
Both files are downloaded automatically by IERS::Data.update! and cached
locally.
Install the gem and add to the application's Gemfile by executing:
$ bundle add iers
If Bundler is not being used to manage dependencies, install the gem by executing:
$ gem install iers
The gem ships with a snapshot of the IERS data files taken at release time, so queries work immediately without any download:
require "iers"
IERS::UT1.at(Time.utc(2020, 6, 15)) # works out of the boxThe bundled snapshot includes predictions roughly one year into the future from the release date. As time passes those predictions expire, so you should download fresh data periodically:
result = IERS::Data.update!
result.success? # => trueYou can also update a single source:
IERS::Data.update!(:finals)
IERS::Data.update!(:leap_seconds)Downloaded files are cached in ~/.cache/iers/ by default and take precedence
over the bundled snapshot.
The bundled data files are sourced from the IERS Earth Orientation Center and the USNO Rapid Service/Prediction Center.
Query the pole position at any point in time:
pm = IERS::PolarMotion.at(Time.utc(2020, 6, 15))
pm.x # => 0.070979... (arcseconds)
pm.y # => 0.456571... (arcseconds)
pm.observed? # => trueRetrieve daily grid values over a date range. between returns a lazy
Enumerator, so entries are computed on demand:
entries = IERS::PolarMotion.between(
Date.new(2020, 1, 1),
Date.new(2020, 1, 31)
)
entries.count # => 31Compute the polar motion rotation matrix W (IERS Conventions 2010, §5.4.1):
w = IERS::PolarMotion.rotation_matrix_at(Time.utc(2020, 6, 15))
w.length # => 3
w[0].length # => 3Returns a nested Array (3×3, row-major).
The matrix is also available on any PolarMotion::Entry:
pm = IERS::PolarMotion.at(Time.utc(2020, 6, 15))
pm.rotation_matrix # => same nested ArrayQuery the difference between UT1 and UTC:
entry = IERS::UT1.at(Time.utc(2020, 6, 15))
entry.ut1_utc # => -0.178182...
entry.observed? # => trueDaily grid values:
entries = IERS::UT1.between(
Date.new(2020, 1, 1),
Date.new(2020, 1, 31)
)Query the celestial pole offset corrections (dX, dY):
cpo = IERS::CelestialPoleOffset.at(Time.utc(2020, 6, 15))
cpo.x # => dX correction (milliarcseconds)
cpo.y # => dY correction (milliarcseconds)Query the excess length of day:
entry = IERS::LengthOfDay.at(Time.utc(2020, 6, 15))
entry.length_of_day # => excess LOD (seconds)
entry.observed? # => trueCompute Delta T (TT − UT1). From 1972 onward the value is derived from IERS data; before 1972 (back to 1800) it uses Espenak & Meeus polynomial approximations:
entry = IERS::DeltaT.at(Time.utc(2020, 6, 15))
entry.delta_t # => ~69.36 (seconds)
entry.measured? # => true
entry = IERS::DeltaT.at(Time.utc(1900, 6, 15))
entry.delta_t # => ~-2.12 (seconds)
entry.estimated? # => trueCompute ERA (IERS Conventions 2010, eq. 5.15). The UT1-UTC correction is looked up internally:
IERS::EarthRotationAngle.at(Time.utc(2020, 6, 15)) # => radians, in [0, 2π)Compute GMST (IERS Conventions 2010, eq. 5.32). Uses ERA internally and adds the equinox-based polynomial evaluated at TT:
IERS::GMST.at(Time.utc(2020, 6, 15)) # => radians, in [0, 2π)Compute R(ERA) × W, the rotation from ITRS to TIRS (IERS Conventions 2010, eq. 5.1), combining the Earth Rotation Angle and the polar motion matrix (W):
r = IERS::TerrestrialRotation.at(Time.utc(2020, 6, 15))
r.length # => 3
r[0].length # => 3Returns a 3×3 nested Array (row-major).
Query all EOP components at once:
eop = IERS::EOP.at(Time.utc(2020, 6, 15))
eop.polar_motion_x # => arcseconds
eop.polar_motion_y # => arcseconds
eop.ut1_utc # => seconds
eop.length_of_day # => seconds
eop.celestial_pole_x # => milliarcseconds
eop.celestial_pole_y # => milliarcseconds
eop.observed? # => true
eop.date # => #<Date: 2020-06-15>Retrieve daily EOP over a date range:
entries = IERS::EOP.between(
Date.new(2020, 1, 1),
Date.new(2020, 1, 31)
)Look up TAI−UTC at a given date:
IERS::LeapSecond.at(Time.utc(2017, 1, 1)) # => 37.0 (seconds)List all leap seconds:
IERS::LeapSecond.all
# => [#<data IERS::LeapSecond::Entry effective_date=#<Date: 1972-01-01>, tai_utc=10.0>, ...]Check for a future scheduled leap second:
IERS::LeapSecond.next_scheduled # => #<data IERS::LeapSecond::Entry ...> or nilConvert between UTC and TAI time scales:
tai_mjd = IERS::TAI.utc_to_tai(Time.utc(2017, 1, 1)) # => MJD in TAI
utc_mjd = IERS::TAI.tai_to_utc(mjd: tai_mjd) # => MJD in UTCAll query methods accept Ruby Time, Date, and DateTime objects as
positional arguments. You can also use keyword arguments for numeric Julian
Dates:
IERS::UT1.at(mjd: 58849.0) # Modified Julian Date
IERS::UT1.at(jd: 2458849.5) # Julian Date
IERS::UT1.at(Time.utc(2020, 1, 1)) # Ruby TimeCheck that predictions cover enough of the future before relying on query results:
begin
IERS::Data.ensure_fresh!(coverage_days_ahead: 90)
rescue IERS::StaleDataError => e
puts "Predictions end #{e.predicted_until}, need #{e.required_until}"
IERS::Data.update!
endWithout coverage_days_ahead, the check ensures predictions cover today.
status = IERS::Data.status
status.cached? # => true if downloaded data exists
status.cache_age # => age in seconds, or nil
IERS::Data.clear_cache! # remove downloaded filesPoint the gem at your own copies of the IERS data files:
IERS.configure do |config|
config.finals_path = "/path/to/finals2000A.all"
config.leap_second_path = "/path/to/Leap_Second.dat"
endIERS.configure do |config|
config.cache_dir = "/path/to/cache"
config.interpolation = :linear # default: :lagrange
config.lagrange_order = 6 # default: 4
config.download_timeout = 60 # default: 30 (seconds)
endTo fully reset configuration and cached data:
IERS.reset!All errors inherit from IERS::Error:
IERS::DataError: base for data-related errorsIERS::ParseError: malformed data fileIERS::FileNotFoundError: data file not foundIERS::StaleDataError: predictions don't extend far enough
IERS::DownloadError: base for download-related errorsIERS::NetworkError: HTTP or connection failureIERS::ValidationError: downloaded file failed validation
IERS::OutOfRangeError— query outside data coverageIERS::ConfigurationError— invalid configuration
After checking out the repo, run bin/setup to install dependencies. Then, run
rake test to run the tests. You can also run bin/console for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To
release a new version, update the version number in version.rb, and then run
bundle exec rake release, which will create a git tag for the version, push
git commits and the created tag, and push the .gem file to rubygems.org.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the IERS Ruby project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.