IERS
Access to Earth orientation parameters and time scale values from the International Earth Rotation and Reference Systems Service (IERS).
About IERS and Earth Orientation Parameters
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.
Data files
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.
Installation
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
Usage
Bundled data
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.
Polar motion
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 # => 31Rotation matrix
Compute 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 ArrayUT1−UTC
Query 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)
)Celestial pole offsets
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)Length of day
Query the excess length of day:
entry = IERS::LengthOfDay.at(Time.utc(2020, 6, 15))
entry.length_of_day # => excess LOD (seconds)
entry.observed? # => trueDelta T
Compute 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? # => trueEarth Rotation Angle
Compute 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π)Greenwich Mean Sidereal Time
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π)Terrestrial rotation matrix
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).
Earth Orientation Parameters (unified)
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)
)Leap seconds
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 nilTAI
Convert 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 UTCTime input
All 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 TimeData freshness
Check 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.
Data status and cache management
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 filesCustom data paths
Point 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"
endConfiguration
IERS.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!Error handling
All errors inherit from IERS::Error:
-
IERS::DataError: base for data-related errors-
IERS::ParseError: malformed data file -
IERS::FileNotFoundError: data file not found -
IERS::StaleDataError: predictions don't extend far enough
-
-
IERS::DownloadError: base for download-related errors-
IERS::NetworkError: HTTP or connection failure -
IERS::ValidationError: downloaded file failed validation
-
-
IERS::OutOfRangeError— query outside data coverage -
IERS::ConfigurationError— invalid configuration
Development
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.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the IERS Ruby project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.