Many popular programming languages these days ship with powerful Time
classes/interfaces to make the inevitable task of dealing with
time-related computations a more DRY experience. Humans like their
time representations to be stringy concatenations of various numbers
and alphabets that enable a large variety of date and time formats to
mean the same thing and still make instant sense to the human eye. It
was the creation of time zones that made our convenient 24-hour clocks
stay the way they are without experiencing day and night at the same
“time” across the globe. So now 00:00 A.M. UTC
is 5:30 A.M. IST
for me, and the world remains sane.
But what happens when you accept the fact that you’re just a speck of micro-dust adjusting time relatively for an only slightly bigger speck of dust floating in the universe? Twenty-four hours in a day and thus we reset after 2300, but consider: how would a resident of Venus know when tea-time is on Venus if he had an Earth wristwatch that reset after twenty-four hours? Barely a tenth of Venus’ day is complete in that time! (If you know anybody intent on relocating to Mars, do not gift them a clock or watch.)
So a decimal floating point representation must be the answer for uniformity. Time zones can be dealt with; we’ll just pick a convenient point in time and count the seconds from there onwards so that the location on Earth doesn’t matter henceforth. It’ll drive humans insane with the arithmetic but machines will work just fine with this. This sort of a time system is called epoch time.
And so the internal time of most UNIX machines is the number of
seconds after midnight on Thursday, 1 January 1970 UTC
. (And this
very convention is going to open a can of worms by
2038 if there is even a small set of critical machines
that haven’t moved on from 32-bit architectures.)
But we’re still not okay universally. Try going on an infinite journey to space and you’ll find that counting seconds leads to some inconsistencies with your local time when you try to synchronize with Earth. How can the number of seconds after January 1970 be different in any case? Well, your MacBook Pro has not been adjusted for … relativity! Gravity bends light and thus the perception of time. There’s a lot more mass, and thus a lot more gravitatonal fields in neighborhoods away from Earth. The exact details of how this works is beyond the scope of this blog post.
If the past few paragraphs were incessant and seemingly irrelevant, they were there to drive home the point that Earth time simply will not do when we step out of the ghetto to see what’s happening. But astronomy’s been around for way longer, and astrophysicists came forth with a time system adjusted for the relativity effects of the solar system, called Barycentric Dynamical Time, or TDB. Like our machines, it counts the seconds after a certain known reference time point, except that it adjusts for relativity and can become a standard for astronomical time.
There are many similar time scales like this, but SPICE has chosen to
use TDB as the standard for most of their design. Within the SPICE
API, TDB is the same as Ephemeris Time which is the main system used
to specify time points of astronomical bodies. Even though spacecrafts
have their clocks coordinated with UTC on Earth, you would require
that time in Ephemeris Time to be able to calculate their positions and
velocities using SPICE. SpiceRub::Time
is built for this very purpose,
to revolve around a main attribute @et
for Ephemeris Time and
provide many methods to convert to and from.
If you’d like to proceed with the examples, you’ll need a Leap Second
Kernel file to use SpiceRub::Time
. This is a generic kernel, so you
can easily use naif0011.tls
in spec/data/kernels
of the repository
folder.
So Ephemeris Time is the number of seconds elapsed after Noon, January 1, 2000, TDB
. This point in time is also known as the J2000
epoch. We find that out in an instant by using the Time.parse
function which is a wrapper function for SPICE’s str2et_c
that
converts many formats of strings to Ephemeris Time
. You can have a
look at the various string formats supported in its documentation
here
1 2 |
|
So as a base case, using the reference epoch gives us 0 seconds as we would expect. Now would also be a good time to find out the discrepancy in UTC
as well.
1 2 |
|
So right away we know that UTC was 64-ish seconds off from TDB / ET at the time of the reference J2000 epoch. What would the difference be around right now?
1 2 3 4 5 |
|
Well, here’s a surprise, it’s 68.18 now. Before I explain why that is, here is a brief overview of what the above code does:
Time.now
is a convenient way to specify your current UTC
timezone. It uses Ruby’s core Time.now
method so this method is only
good if you’re working in UTC or Earth like Timezones. For a similar
purpose, the function Time.from_time
let’s you create a SpiceRub
Time object from a Ruby Time object.
The +/-
operators return a new Time object where the right operand
is added/subtracted to the left operand’s @et
when it is a float or
integer. If a Time object is supplied , then it does the same with the
right operand’s ephemeris time instead. (Note that there really isn’t
a significant meaning to having a Time object whose @et is the
difference/sum of two other epochs, however you can increase a certain
epoch or decrease it by a constant offset of seconds)
In our case we used #to_utc
to convert from ephemeris time to UTC,
and then the minus operator gave us a Time object that wasn’t really
an epoch, but a difference of two epochs, so using #to_f
got us
exactly that.
It appears that UTC has changed by 4 seconds since 2000 with respect to ephemeris time. This is actually the adjustment of “leap seconds” that gets added to UTC to prevent it from falling too far behind other time systems. (Humans really like to hack everything, don’t they?)
To verify this yourself, if you open up the kernel naif0011.tls
in your
text editor and search for DELTET/DELTA_AT
, you’ll find a list like
representation of the following sort :-
1 2 3 4 5 6 7 |
|
Here you can see that just before the year 2000, there were 32 leap seconds added to UTC, and in 2015 when the last leap second was added, there were 36. It’s an ongoing and indefinite process and so there really is no way to account for leap second errors far in the future for leap seconds that are yet to be added. As of now, the next scheduled addition is in December, 2016.
Coming back to our Time object, let’s look at its basic
construction. One tricky task in the API was the option to specify
different epochs of reference in different time scales, like
International Atomic Time. As of now, Time.new
requires that you
have kept your word of using the J2000 epoch and allows you to use a
named parameter seconds:
for specifying the time scale. The use of
scale
as a key was avoided as it sometimes is also used to refer to the
reference epoch used.
1 2 3 4 5 6 |
|
:tai
here refers to International Atomic Time. For a list of more
parameters and their keyword abbreviations, have a look at
this SPICE documentation for the function that the
conversion is wrapped on top of.
But there is also a way to reference other epochs without doing the
manual conversions yourself, you can call the class method Time.at
to perform the same function as the constructor, with the option of a
different reference epoch. The resultant Time object will however have
its internal time referring to J2000.
A more readable way would involve step by step calculations, but that
would consume runtime resources everytime Time.at
is called, so I’ve
basically pre-calculated the ephemeris times of the reference epochs
and subtracted them from the epoch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
To quickly verify the last one with the #to_s
method:
1 2 |
|
It’s exactly the UNIX epoch! Let’s try out 1 day (86400 seconds) after this epoch:
1 2 |
|
Just a second short of heading into the next day, because we’ve added 86400 TDB seconds and converted the time into a UTC string.
There are some more functions provided to work in tandem with the
Body
class that I’ll explain more about in the next blog post, but
this more or less covers the core of SpiceRub:Time
. Till then,
thanks for reading.