Time Manager (TimeMgr)

The Omega Time Manager module tracks simulation time during a run and manages alarms for triggering events at specified times This is handled by six interacting defined classes, each of which have a number of methods defined for accessing, comparing, and manipulating the class members.

Class Descriptions

1. TimeFrac

The TimeFrac class is the fundamental base time representation. To avoid potential rounding errors that can accumulate over millions of time steps, time is tracked in seconds as a fractional representation using three 8-byte integers representing the whole number, numerator, and denominator. Most of the other classes in the Time Manager are based on the TimeFrac class and generally convert time representations from other units and store them as a TimeFrac object.

2. Calendar

The Calendar class is mostly an immutable class that stores all information for supported calendars that can be utilized in a simulation. The supported calendars are: Gregorian, No Leap, Julian, Julian Day, Modified Julian Day, 360 Day, Custom, and No Calendar. These supported calendars are specified via an enum defined in the Time Manager header file:

enum CalendarKind {
   CalendarGregorian = 1, ///< usual Gregorian calendar
   CalendarNoLeap,        ///< Gregorian, but without leap yrs
   CalendarJulian,        ///< Julian
   CalendarJulianDay,     ///< Julian day
   CalendarModJulianDay,  ///< modified Julian day
   Calendar360Day,        ///< 12 months, 30 days each
   CalendarCustom,        ///< user defined
   CalendarNoCalendar,    ///< track elapsed time only
   CalendarUnknown        ///< uninitialized or invalid
};

During a simulation, only one calendar can be defined and is set with the call:

Calendar::init("CalendarName");

that must be set early in the model initialization before other time quantities are defined. CalendarName is a string associated with the defined calendars above and found in the list CalendarKindName in TimeMgr.h. Typically, they are the same as the kinds above with a space for multi-word names (eg “No Leap”). If a custom calendar is desired rather than the supported calendars, a custom calendar can be defined with a different init interface in which all the relevant quantities are provided:

Calendar::init(
   std::vector<I4> &InDaysPerMonth, ///< [in] array of days per month
   I4 InSecondsPerDay,              ///< [in] seconds per day
   I4 InSecondsPerYear,             ///< [in] seconds per year
   I4 InDaysPerYear                 ///< [in] days per year (dpy)
);

Time is generally tracked as the amount of time elapsed from a reference date for a given calendar. A number of public functions exist to retrieve Calendar information, convert between dates and elapsed time, check for leap years and other checks. However, these are typically used by other TimeMgr classes and not directly by the developer. Examples of their use can be found in the TimeMgr unit test.

3. TimeInstant

The TimeInstant class represents a moment in time with the particular calendar that has been defined for the simulation. It consists of a TimeFrac that represents the time since a reference time associated with the defined calendar. There are three constructors for initializing a TimeInstant. One method is with five 8-byte integers, and an 8-byte real value:

OMEGA::TimeInstant TI1(Year, Month, Day, Hour, Minute, RealSecond);

Alternatively, the TimeInstant can be initialized with eight 8-byte integers, with the seconds value represented by three 8-byte integers:

OMEGA::TimeInstant TI2(Y, M, D, H, M, Whole, Numer, Denom);

A final constructor creates a time instant based on a time string that conforms roughly to the ISO standard "YYYYYY-MM-DD_HH:MM:SS.SSSS" though the constructor allows for any single-character non-numeric separator between each of the numeric fields and the width of the YY and SS fields can be up to the 8-byte standards for integer and floats, respectively.

OMEGA::TimeInstant TI3(TimeString);

Among the methods defined in the TimeInstant class is getString which will produce a std::string representation of the time conforming to ISO standards, e.g. "YYYYYY-MM-DD HH:MM:SS.SSSSS". The getString method takes two 4-byte integers which represent the number of digits to use for the year and the number of digits for the decimal portion of seconds, as well as a std::string for the seprator between the Y/M/D calendar date and the H/M/S clock time. A string formatted as above is returned with the following call:

TI1.getString(6, 5, " ");

A range of accessor functions can also get/set the TimeInstant in various forms.

4. TimeInterval

The TimeInterval class represents a length of time between two moments in time. A TimeInterval can represent the time step, but is also utilized by other classes for computing differences between TimeInstant objects, such as when checking to trigger an Alarm. A TimeInterval can be initialized with a fractional second representation, by supplying three 8-byte integers:

OMEGA::TimeInterval Interval1(Whole, Numerator, Denominator);

Alternatively, a TimeInterval can also be initialized with either an integer or floating point value and a specified unit of time:

OMEGA::TimeInterval Interval2(Length, UnitType);

The supported time units are defined via an enum class in the Time Manager header file:

enum class TimeUnits {
   None = 0, ///< value for undefined units
   Seconds,  ///< time units in seconds (typical)
   Minutes,  ///< time units in minutes
   Hours,    ///< time units in hours
   Days,     ///< time units in days
   Months,   ///< time units in months
   Years,    ///< time units in years
};

Finally, a time interval can be defined as the time between two time instants:

OMEGA::TimeInterval MyDeltaTime = MyTimeInstant2 - MyTimeInstant1;

5. Alarm

The Alarm class is designed to trigger events at specified times. Alarms can be set to ring at a single time or to ring periodically. A one-time Alarm is initialized with a std::string name and a TimeInstant:

OMEGA::Alarm SingleAlarm("Single Alarm", AlarmInstant);

A periodic Alarm is initialized with a std::string name, a TimeInterval, and a TimeInstant representing the start of the first interval:

OMEGA::Alarm PeriodicAlarm("Periodic Alarm", AlarmInterval, AlarmStart);

The status of an Alarm can be checked using the isRinging method, which returns true or false depending on the Alarm status:

SingleAlarm.isRinging();

The reset method takes a TimeInstant as input and will switch a ringing Alarm off, as well as set a new ring time. If it is a one-time Alarm, the input time is set as the new ring time:

SingleAlarm.reset(NewAlarmTime);

If the Alarm is periodic, the ringing will be switched off as above and the new ring time will be set to the next interval boundary after the input time. The interval boundary is an integer number of intervals after the start time provided on Alarm creation.

PeriodicAlarm.reset(CurrentTime);

An Alarm can be permanently stopped using the stop method:

SingleAlarm.stop();

It is sometimes useful to retrieve other aspects of a periodic Alarm, namely the Alarm interval and the last time the Alarm was ringing. Two retrieval functions are provided that return const pointers to these values:

const TimeInterval *AlarmInterval = PeriodicAlarm.getInterval();
const TimeInstant *PriorRingTime = PeriodicAlarm.getRingTimePrev();

6. Clock

The Clock class is the primary object for managing time during a simulation. A Clock is initialized with a TimeInstant representing the start time and a TimeInterval representing the time step the Clock advances with each integration step:

OMEGA::Clock ModelClock(StartTime, TimeStep);

The class contains a std::vector of pointers to Alarms. Any number of Alarms can be attached to the Clock using the attachAlarm method:

ModelClock.attachAlarm(&SingleAlarm);

The Clock is advanced during each time step with the advance method:

ModelClock.advance();

The Clock will automatically trigger any attached Alarms as the Clock marches forward. The time step for a Clock can be changed by passing a TimeInterval to the changeTimeStep method:

ModelClock.changeTimeStep(NewTimeStep);

During a simulation, the getCurrentTime method will return the current time stored in the Clock as a TimeInstant:

OMEGA::TimeInstant CurrentTime = ModelClock.getCurrentTime();

Implementation

In order to utilize the Time Manager, the first step is to initialize a Calendar object:

OMEGA::Calendar CalGreg("Gregorian", OMEGA::CalendarGregorian);

A start time and a time step are needed in order to initialize the Clock. The following lines will initialize a Clock that starts Jan 1st, 2000 at midnight with a 20 minute time step:

OMEGA::TimeInstant StartTime(&CalGreg, 2000, 1, 1, 0, 0, 0.0);
OMEGA::TimeInterval TimeStep(20, OMEGA::TimeUnits::Minutes);

OMEGA::Clock ModelClock(StartTime, TimeStep);

Now, Alarms can be attached to the Clock. For example, if some output is desired on the first of each month, the following lines will attach a monthly Alarm:

OMEGA::TimeInterval IntervalMonthly(1, OMEGA::TimeUnits::Months);
OMEGA::Alarm AlarmMonthly("Every Month", IntervalMontly, StartTime);

Err = ModelClock.attachAlarm(AlarmMonthly);

The Clock can be used to control the duration of a simulation by using a TimeInstant that represents the stop time:

OMEGA::TimeInstant StopTime(&CalGreg, 2025, 1, 1, 0, 0, 0.0);
OMEGA::TimeInstant CurrentTime = StartTime;

while (CurrentTime < StopTime) {

   Err = ModelClock.advance();
   CurrentTime = ModelClock.getCurrentTime();

  ...

}

To utilize any attached Alarms, the ringing status needs to be checked each time step:

if (AlarmMonthly.isRinging()) {
   AlarmMonthly.reset(CurrentTime);

   ...
}