TimeTag System Design

This document is intended to give an overview of why the TimeTag System was designed, what it is, and how it is intended to be used.

Reasoning

The TimeTag System was designed to handle conflicts between two application design paradigms: one where time representation conversions are frequent and manipulations are few, and another where time manipulation is frequent and conversions are few.

DayTime, the predecessor to the TimeTag System, slowly evolved in an attempt to handle both of these paradigms. The DayTime class is a single, large class which handles everything time-related including storing time (an epoch) in various representations, conversions between time representations, computation of intervals between epochs, and modification of epochs. This has led to a complex system that proved difficult to maintain.

The TimeTag System's design takes a step away from such a monolithic design in order to ease maintenance and allow developers more flexibility in the nature and timing of epoch conversion and manipulation. It also significantly reduces the overhead of conversions when only a few representations are used. Essentially, it divides and encapsulates the DayTime functionality into several parts.

What is the TimeTag System?

The TimeTag System is a collection of classes and helper functions:
  • TimeTag classes for epoch representation,
  • CommonTime, a high-precision "common format," for facilitating epoch representation conversions, modifications, and interval computation,
  • TimeString.hpp for converting times to formatted strings and back,
  • TimeConverters.hpp for common epoch conversions,
  • TimeConstants.hpp for constants used by all other TimeTag System code.

Commonly Used Terms:

epoch
a single 'point' in time; can have multiple, equivalent representations
representation
as set of values that represent a single epoch
time system
a definition of a set of values that can represent an epoch along with a origin or start of the system. The canonical example is the set ( year, month, day, hour, minute, second). Another example is julian day.
time system epoch
the 'zero' value or beginning of a time system

TimeTag Classes

The TimeTag classes are the TimeTag System's interface to the real world. Each TimeTag class instantiates a particular representation of a point in time. Here are the different TimeTag-descendant classes and what they represent:
ANSITime
seconds since the Unix epoch (1 Jan 1970, 00:00 UTC)
CivilTime
year, month, day-of-month, hour, minute, and second
GPSWeek
GPS Epoch, GPS Week (GPS Epoch 0 begins on 6 Jan 1980, 00:00 UTC)
GPSWeekSecond
GPSWeek, second-of-week
GPSWeekZcount
GPSWeek, GPS Z-count-of-week (a Z-count is 1.5s)
JulianDate
Julian Date (JD + 0.5 days) the count of days since 0JD (1 Jan 4713 B.C.)
MJD
Modified Julian Date (JD - 2400000.5)
UnixTime
seconds and microseconds since the Unix epoch, this is as defined by the POSIX system calls and is useful even on non-unix systems
SystemTime
a modified form of UnixTime which sets itself to the current system time
YDSTime
year, day-of-year, and seconds-of-day

Several requirements are levied against each of the TimeTag-derived classes by this design:
  • there must be constructors that accept discrete time components, TimeTag references, and CommonTime references.
  • conversion to and from CommonTime
  • no knowledge of other TimeTag classes; this forces conversions to go through CommonTime.
  • printing time to a string using formatting specifiers
  • scanning time from a string using formatting specifiers
  • verify whether the data stored by an object is valid
  • resetting an object to a default state

Leveraging the above requirements allows some interesting functionality:
  • conversion between TimeTag-derived classes takes place at the TimeTag level without breaking TimeTag class encapsulation.
  • implicit conversion to CommonTime
  • developer has control over what and when representation conversions occur.

The CommonTime Class

The CommonTime class is not a TimeTag-derived class and knows nothing about any of the TimeTag classes. While it does encapsulate a high-precision representation of an epoch (integer JD, integer milliseconds-of-day, and fractional milliseconds-of-day), the CommonTime class's design goal is to facilitate both inter-!TimeTag conversions and computation of time intervals between TimeTags with little or no loss of precision.

CommonTime is the time representation through which all TimeTag classes convert when converting between each other; hence its name. This allows the decoupling of inter-TimeTag conversions; a TimeTag only needs to know how to convert to and from CommonTime.

The CommonTime interface is based on three quantities: days, seconds of day, and fractional seconds of day. The internal representation, however, is slightly different. It consists of a day, milliseconds of day, and fractional seconds of day. Their valid ranges are shown below:
Quantity >= <
day 0 2^31
msod 0 86400000
fsod 0 0.001

The reason for keeping the fractional part of time in seconds is due to the fact that the time formats usually break at seconds and partial seconds not at milliseconds and partial milliseconds. By keeping the values in seconds, we save ourselves additional work and loss of precision through conversion of fractional seconds to fractional milliseconds.

CommonTime Manipulation

TimeTag classes are not permitted to perform manipulation on their own because it is difficult to handle all the cases for non-standard roll-overs and interactions between the various elements of each TimeTag type. By forcing the TimeTags to convert to a "common time" and keeping all of the time manipulation logic in one place we are able to separate the roll-overs and time element interactions inherent in conversions from the manipulation logic. CommonTime provides the following ways to manipulate itself:
  • adding or subtracting days (integer)
  • adding or subtracting seconds (integer or floating-point)
  • adding or subtracting milliseconds (integer)

Interval Computation

TimeTag classes are not permitted to perform their own interval computation. To allow them this ability would require all TimeTag classes to know about each other and how to convert to each other's internal elements. In addition, leap seconds, leap days, and other kinds of time non-linearities would have to be handled in the computation. Conversion to CommonTime by both objects and then a single function call to subtract the two simplifies this process for all of the TimeTag classes.

Helper Functions and Definitions

TimeString.hpp

With the step away from a monolithic design as in DayTime, the TimeTag classes and CommonTime lack a unified method to turn an object (along with a format string) into a human-readable string as well as a method to convert a string into an object. The functions within TimeString.hpp contain much of that functionality.
printTime(CommonTime, formatString)
Prints a given CommonTime using a given string format
printAs(CommonTime, formatString)
Prints a given CommonTime by first converting it to a TimeTag type 'T', then using a given string format
scanTime(TimeTag, dataString, formatString)
Fill a TimeTag object from a given data string using a given format string
scanTime(CommonTime, dataString, formatString)
Fill a CommonTime object from a given data string using a given format string
mixedScanTime(CommonTime, dataString, formatString)
Fill a CommonTime object from a given data string using a given format string. Additionally, this function allows mixed time formats (e.g. year, GPS 10-bit week, second-of-week)

TimeConverters.hpp

These are the helper functions that the TimeTag classes use when converting to and from CommonTime. Separate functions are used for day and sub-day conversions:
  • convertJDtoCalendar - converts JD to year, month and day-of-month
  • convertCalendarToJD - converts year, month and day-of-month to JD
  • convertSODtoTime - converts seconds-of-day to hours, minutes, and seconds
  • convertTimeToSOD - converts hours, minutes, and seconds to seconds-of-day

TimeConstants.hpp

These are helper definitions that the TimeTag classes and CommonTime use for conversions and comparisons. This includes several standard points-in-time (e.g. GPS Epoch in MJD) and divisions of larger timespans into smaller ones (e.g. seconds per week).

Examples

Converting a TimeTag object to a CommonTime object

This can be done explicitly...
GPSWeekSecond gpsws(someWeek, someSec, TimeSystem::GPS);
CommonTime ct = gpsws.convertToCommonTime();
... or implicitly:
CommonTime ct = GPSWeekSecond(some_week, some_sec, TimeSystem::GPS);

Accessing a TimeTag element from a CommonTime object

For one element, this is fine:
short gpsWeek = static_cast<GPSWeekSecond>(ct).week;
For accessing more than one element, one can do this...
int year = static_cast<CivilTime>(ct).year;
int month = static_cast<CivilTime>(ct).month;
int day = static_cast<CivilTime>(ct).day;
int hour = static_cast<CivilTime>(ct).hour;
int min = static_cast<CivilTime>(ct).min;
double sec = static_cast<CivilTime>(ct).sec;
... but this is more efficient:
CivilTime civ(ct);
int year = civ.year;
int month = civ.month;
int day = civ.day;
int hour = civ.hour;
int min = civ.min;
double sec = civ.sec;

Getting Current System Time

To get a TimeTag with the current system time, just create a SystemTime object and pass it as an argument to any other TimeTag or CommonTime:
SystemTime now;
CommonTime ct = now.convertToCommonTime();
YDSTime yds(ct);
The following line does exactly the same as the above:
CommonTime ct = SystemTime();
YDSTime yds(ct);
This also does the same thing:
YDSTime yds(SystemTime());

Printing

Each TimeTag class has a printf() which enables it to create a string using the identifiers for the elements that it contains. To print the current year, day-of-year, and second-of-day, do this:
YDSTime ydst(SystemTime());
string dateString = ydst.printf("%Y %03j %s");
If an element from another TimeTag class is required, run the result through that TimeTag class' printf() as well. Identifiers for other TimeTag class' elements are left untouched in the output string which can then be used as the format string for the next class' printf():
YDSTime ydst(SystemTime());
string dateString = ydst.printf("%Y %03j %s  |  %F  %g");
dateString = GPSWeekSecond(ydst).printf(dateString);
To simplify this, or if the format string is unknown, the function printString() in the TimeString.hpp file can handle this for you:
string dateString = printAs<YDSTime>(SystemTime(), "%Y %03j %s");
... or ...
string dateString = printTime(SystemTime(), "%Y %03j %s  |  %F  %g");
... or ...
string dateString = printTime(SystemTime(), formatString);

Scanning

Each TimeTag class has a scanf() function which can parse its elements from strings:
YDSTime ydst;
ydst.scanf(stringToParse, "%Y %03j %s");

If the string format is unknown, the TimeString.hpp file has a scanTime() function that can handle it:
CommonTime ct;
scanTime(ct, stringToParse, formatString);

For parsing strings with elements from multiple TimeTag classes (e.g. using a year, 10-bit GPS week, and second-of-week), the TimeString.hpp file contains a mixedScanTime() function:
CommonTime ct;
string formatString = "%Y %G %g";
mixedScanTime(ct, stringToParse, formatString);

Time Interval Computation

CommonTime performs all time interval computation; therefore, both objects involved must be CommonTime objects (or converted to CommonTime) to compute the difference.
GPSWeekSecond ws1(someWeek, someSOW);
GPSWeekSecond ws2(someOtherWeek, someOtherSOW);
double diffInSeconds = ws2.convertToCommonTime() - ws1.convertToCommonTime();

Time Manipulation

CommonTime performs all time manipulation; therefore, all objects must be CommonTime objects (or converted to CommonTime) to add (or subtract) time.
YDSTime ydst(someYear, someDOY, someSOD);
CommonTime ct(ydst);
ct += 50.456;         // add seconds
ct -= 32456.2456;  // subtract seconds
CommonTime ct2 = ct + 45.678;  // create a new object without modifying 'ct'
ct.addDays(5);
ct.addMilliseconds(-500);
If needed, when the computations are done, convert the CommonTime object into whatever TimeTag type that is required.
return CivilTime(ct);

Common Mistakes

Often when transitioning from using DayTime to using CommonTime, one may fall back into using familiar usage patterns. However, due do the different design philosophy with the TimeTag System, these patterns won't work. The code may compile, but it won't do what was intended.

Directly Creating a CommonTime object with time elements

Create a CommonTime from a GPS week and second-of-week. While this will compile, it won't do what was intended because CommonTime has constructors which allow setting its own version of time. One of these accepts a Julian date and a second-of-day; which being an int and a double, will match the signature for a GPS week and second-of-week:
CommonTime ct1(gpsWeek, gpsSOW);    // This won't work like intended.
CommonTime ct2(GPSWeekSecond(gpsWeek, gpsSOW)); // This will.
Topic revision: r2 - 20 Jul 2012, JonLittle
 

This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback