TimeManager

1 Overview

When simulating the Earth System, a model must track the simulation time as it steps forward with a given time step. Time must be kept in the appropriate calendar system, accounting for leap years and other adjustments as needed. Because these simulations may extend over millions of time steps or more, time must be accumulated without roundoff so that accumulated time does not drift and events will occur at precise time intervals. This document describes various classes needed to track the model’s simulation time accurately and manage events in the appropriate calendar conventions.

2 Requirements

2.1 Required: Simulation time

The time manager must be able to track the current simulation time as the model integration steps forward.

2.2 Required: No accumulation roundoff

Because integrations may extend over millions of time steps or longer, the model must track time without accumulating roundoff.

2.3 Required: Calendar support

The time manager must be able to track time in supported calendars, starting with:

  • Gregorian

  • Gregorian with no leap year

  • Idealized 360-day calendar with equal 30-day months

  • No calendar (some test cases only need elapsed time in sec) Time for standard calendars will assume Coordinated Universal Time (UTC).

2.4 Required: Time intervals

The model must be able to compute time intervals, not only to represent the model time step itself, but also to be able to compute time between events, track periodic events or perform time averaging. These intervals can be both absolute time (seconds) or represented as calendar intervals (monthly, yearly, number of days). Conversion from a calendar interval to absolute interval (in seconds) will be needed to compute time-related quantities like time averaging.

2.5 Required: Events or alarms

The time manager must be able to trigger events that occur at specific times or at specified periodic time intervals. This requires being able to compare time instants and trigger an alarm if a time instant or interval has been reached. It will also be useful to track when a previous or next event occurs in a periodic sequence.

2.6 Required: Long times

To support very long time integrations or potential paleoclimate simulations, the model must support years that extend beyond the 4-digit representation and support negative years to represent years Before the Common Era (BCE)

2.7 Required: Time string representation

For model output or metadata, the model must be able to represent a time in accepted formats. Following ISO standards (eg ISO 8601 and prior), these will typically represent time in decreasing intervals, eg YYYYYY-MM-DD HH:MM:SS.SSSSS though variations on this format can be supported to use, for example, different separators and different widths for years and fractional seconds.

3 Algorithmic Formulation

The requirement for no roundoff accumulation implies the need to use an integer fraction representation as in the ESMF time manager. Similarly, calendar days are best tracked using the Julian Day that counts days since a specified start time (Noon UTC on 1 Jan, 4713 BCE in the Gregorian calendar - a beautiful Monday by most accounts) with conversion to various calendars following the algorithms in:

Fliegel, H. F., and Van Flandern, T. C., 1968, A Machine Algorithm for Processing Calendar Dates, Communications of the Association of Computing Machines, 11, 657.

Hatcher, D.A., 1984. Simple Formulae for Julian Day Numbers and Calendar Dates, Quart. J. of R. Astr. Soc., 25, 53-55.

4 Design

The time manager design will follow an ESMF-like approach, though simplified and not requiring linking to the full ESMF library. Previous simplifications were performed for WRF by John Michalakes in Fortran and are used in MPAS, E3SM. A similar simplification was performed by Phil Jones from the ESMF C++ version for the SciDAC CANGA project and will be used here.

The time manager will consist of a number of classes/modules:

  • TimeFrac: a base fraction representation

  • TimeInstant: representation of a point in time

  • TimeInterval: a time step or difference between time instants

  • Calendar: support for various calendars

  • Alarm: alarms that trigger at time instants or periodic intervals

  • Clock: a clock that keeps track of model time as it marches forward

4.1 Data types and parameters

4.1.1 Parameters

A number of parameters are defined among the above classes. For all classes, it will be useful to define times and intervals with a number and units, so we define an enum class for time units:

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
                 };

For calendars, there will be an enum for supported calendars as well as a string name for each.

#define NUM_SUPPORTED_CALENDARS=9

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

const std::string CalendarKindName[CALENDAR_KIND_COUNT] = {
     "Gregorian",
     "No Leap",
     "Julian",
     "Julian Day",
     "Modified Julian Day",
     "360 Day",
     "Custom",
     "No Calendar",
     "Invalid" };

4.1.2 Class/structs/data types

There are six classes that will make up the time interval.

4.1.2.1 TimeFrac class

There will be a TimeFrac class for the base fractional time representation:

class TimeFrac {

   // private variables
   private:
      long long whole;  ///< whole seconds
      long long numer;  ///< fractional second (n/d) numerator
      long long denom;  ///< fractional second (n/d) denominator

   public:
      [methods described below]
};
4.1.2.2 Calendar class

The calendar class holds useful information for the calendar to be used:

#define MONTHS_PER_YEAR=12

class Calendar {

  // private variables
  private:

     int id;                   ///< unique id for quick checks
     static int numCalendars;  ///< number of calendars created
     std::string name;         ///< name of calendar
     CalendarKind calKind;     ///< enum for calendar kind
     std::string calKindName;  ///< name of calendar kind

     // variables defining calendar characteristics for time
     int daysPerMonth[MONTHS_PER_YEAR]; ///< days in each month
     int monthsPerYear;             ///< num months in year
     int secondsPerDay;             ///< seconds per day
     int secondsPerYear;            ///< seconds per normal year
     int daysPerYear;               ///< days per normal year

     // public methods
     public:
        [methods described below]
};
4.1.2.3 TimeInstant class

The time instant class represents a point in time within a given calendar:

class TimeInstant {

   // private variables
   private:
      TimeFrac elapsedTime; ///< Fractional seconds since reference time
      Calendar *calPtr;  ///< Pointer to calendar in which time is based

   public:
      [methods described below]
};
4.1.2.4 TimeInterval class

The time interval is used for both time steps and to compute differences between time instants. The time interval can either be an time interval in seconds or it can be used to hold a time interval in calendar units (eg number of days, months or years). The latter is useful for periodic events that occur once per year, month or ndays.

class TimeInterval {

   // private variables
   private:
      TimeFrac interval; ///< Non-calendar interval in fractional seconds
      bool isCalendar;       ///< True if calendar interval
      long long calInterval; ///< Calendar interval length
      TimeUnits units;       ///< Calendar interval units

   public:
      [methods described below]
};
4.1.2.5 Alarm class

The alarm class allows a user to set either one-time or periodic alarms to trigger events, like forcing updates, I/O, etc. that occur at specific times.

class Alarm {

   // private variables
   private:
      std::string name; ///< name for the alarm

      bool ringing;   ///< alarm is currently ringing
      bool periodic;  ///< alarm rings periodically on interval
      bool stopped;   ///< alarm has been stopped and not reset

      TimeInstant  ringTime;     ///< time at/after which alarm rings
      TimeInterval ringInterval; ///< interval at which this alarm rings
      TimeInstant  ringTimePrev; ///< previous alarm time for interval alarms

   public:
      [methods described below]
};
4.1.2.6 Clock class

The clock class is meant to track and manage the time for a model advancing in time. Alarms can be attached to the clock so that the ringing status can be updated as the clock marches forward.

class Clock {

   // private variables
   private:

      TimeInstant  startTime; ///< initial time for this clock
      TimeInstant  currTime;  ///< current time
      TimeInstant  prevTime;  ///< time at previous timestep
      TimeInstant  nextTime;  ///< time at next timestep
      TimeInterval timeStep;  ///< interval at which this clock advances

      int numAlarms; ///< current number of attached alarms

      std::vector<Alarm *> alarms; ///< pointers to alarms associated with this clock

   public:
      [methods described below]
};

4.2 Methods

The classes above have a number of methods associated with them for managing time.

4.2.1 TimeFrac class

The TimeFrac class is a base class that is not meant to be accessed by users but provides the integer fraction time capability needed for accumulating time. It contains a number of operators to perform arithmetic on fractional integers and a number of accessor functions to convert units into a fractional time.

// Accessor methods

/// Single call to set all native base time components
/// \return error code
int set(const long long whole, ///< [in] whole seconds
        const long long numer, ///< [in] fractional second numerator
        const long long denom  ///< [in] fractional second denominator
        );
/// Set base time by converting from integer hours, minutes, seconds
/// \return error code
int setHMS(const int hours,   ///< [in] integer hours
           const int minutes, ///< [in] integer minutes
           const int seconds  ///< [in] integer seconds

/// Set base time by converting from a real number of seconds
/// \return error code
int setSeconds(const double seconds ///< [in] Time in real seconds
              );
/// Set base time by converting from a real number of hours
/// \return error code
int setHours(const double hours ///< [in] Time in real hours
            );
/// Set base time by converting from a real number of minutes
/// \return error code
int setMinutes(const double minutes ///< [in] Time in real minutes
              );

/// Set whole seconds separately
/// \return error code
int setWhole(
       const long long whole ///< [in] Whole number of seconds
       );
/// Set numerator of fractional seconds separately
/// \return error code
int setNumer(
       const long long numer ///< [in] Numerator of fractional seconds
       );
/// Set denominator of fractional seconds separately
/// \return error code
int setDenom(
       const long long denom ///< [in] Denominator of fractional seconds
       );

/// Single call to retrieve native base time components
/// \return error code
int get(long long &whole, ///< [out] whole seconds
        long long &numer, ///< [out] fractional second numerator
        long long &denom  ///< [out] fractional second denominator
        ) const;
/// Get base time converted to integer hours, minutes, seconds
/// \return error code
int getHMS(int &hours,   ///< [out] integer hours
           int &minutes, ///< [out] integer minutes
           int &seconds  ///< [out] integer seconds
           ) const;
/// Get base time and convert to a real number of seconds
/// \return Time in real seconds
double getSeconds(void) const;
/// Get base time and convert to a real number of hours
/// \return Time in real hours
double getHours(void) const;
/// Get base time and convert to a real number of minutes
/// \return Time in real minutes
double getMinutes(void) const;
/// Retrieve the whole seconds component of base time
/// \return Whole number of seconds
long long getWhole(void) const;
/// Retrieve the numerator component of fractional base time
/// \return Numerator of fractional seconds
long long getNumer(void) const;
/// Retrieve the denominator component of fractional base time
/// \return Denominator of fractional seconds
long long getDenom(void) const;

// constructors/destructors
/// Default base time constructor
TimeFrac(void);
/// Copy constructor for base time
TimeFrac(const TimeFrac& ///< [in] existing base time to be copied
        );
/// Construct base time by component
TimeFrac(
    const long long whole, ///< [in] whole seconds
    const long long numer, ///< [in] fractional second numerator
    const long long denom  ///< [in] fractional second denominator
    );
/// Construct base time by converting from a real number of seconds
TimeFrac(const double seconds ///< [in] Time in real seconds
        );
/// Destructor for base time
~TimeFrac(void);

// operators
/// Equivalence comparison operator for TimeFrac
bool operator==(const TimeFrac &) const;
/// Non-equivalence comparison operator for TimeFrac
bool operator!=(const TimeFrac &) const;
/// Less than comparison operator for TimeFrac
bool operator< (const TimeFrac &) const;
/// Greater than comparison operator for TimeFrac
bool operator> (const TimeFrac &) const;
/// Less than or equal comparison operator for TimeFrac
bool operator<=(const TimeFrac &) const;
/// Greater than or equal comparison operator for TimeFrac
bool operator>=(const TimeFrac &) const;
/// Addition operator for TimeFrac
TimeFrac  operator+ (const TimeFrac &) const;
/// Subtraction operator for TimeFrac
TimeFrac  operator- (const TimeFrac &) const;
/// Increment operator for TimeFrac
TimeFrac& operator+=(const TimeFrac &);
/// Decrement operator for TimeFrac
TimeFrac& operator-=(const TimeFrac &);
/// Multiplication by integer scalar
TimeFrac  operator* (const int multiplier) const;
/// Multiplication in place by integer scalar
TimeFrac& operator*=(const int multiplier);
/// Multiplication by real scalar
TimeFrac  operator* (const double multiplier) const;
/// Multiplication in place by real scalar
TimeFrac& operator*=(const double multiplier);
/// Divide TimeFrac by integer scalar
TimeFrac  operator/ (const int divisor) const;
/// Divide TimeFrac in place by integer scalar
TimeFrac& operator/=(const int divisor);
/// Divide two TimeFracs and return a real result
double   operator/ (const TimeFrac &) const;
/// Modulus method for TimeFrac
TimeFrac  operator% (const TimeFrac &) const;
/// Modulus method in place
TimeFrac& operator%=(const TimeFrac &);
/// Assignment operator for TimeFrac
TimeFrac& operator=(const TimeFrac &);

// Other utility methods
/// Convert a time fraction to new denominator
/// \return error code
int convert(const long long denom ///< [in] new denominator
           );
/// Reduce a time fraction to simplest form
int simplify(void);

4.2.2 Calendar class

The calendar class is a mostly immutable class that holds information about the chosen calendar for use by other time manager classes. It mostly constructs an instance based on the user-selected calendar. Retrieval functions and query functions are provided. An equivalence/non-equivalence operator is needed for the later TimeInstant equivalence. Utility functions to convert between elapsed time and calendar dates and incrementing calendar time are supplied for use by other time manager routines.

// accessor functions
// this is (mostly) an immutable class so use constructors
// and provide only one set accessor for renaming

// the only set function is for renaming
/// Renames a Calendar to the input string
/// \return Error code
int rename(const std::string inName ///< [in] name to use for calendar
           );

/// Retrieve any/all calendar properties
/// \return Error code
int get(int           *outId,   ///< [out] id assigned to calendar
        std::string   *outName, ///< [out] Name of calendar
        CalendarKind  *outKind, ///< [out] Kind of calendar
        int           *outDaysPerMonth,  ///< [out] Days per month
        int           *outMonthsPerYear, ///< [out] Months per year
        int           *outSecondsPerDay, ///< [out] Seconds per day
        int           *outSecondsPerYear,///< [out] Seconds per year
        int           *outDaysPerYear    ///< [out] Days per year (DPY)
        ) const;

// Might also add specific retrievals for individual
// components, eg seconds per day/year?

/// Default constructor
Calendar(void);
/// Copy constructor
Calendar(const Calendar &calendar);
/// Constructor based on kind of calendar
Calendar(std::string inName,       ///< [in] name of calendar
         CalendarKind calKind      ///< [in] choice of calendar kind
         );
/// Constructs custom calendar based in inputs
Calendar(const std::string inName, ///< [in] name of calendar
         int *inDaysPerMonth,      ///< [in] array of days per month
         int inSecondsPerDay,      ///< [in] seconds per day
         int inSecondsPerYear,     ///< [in] seconds per year
         int inDaysPerYear         ///< [in] days per year (dpy)
         );
/// Calendar destructor
~Calendar(void);

/// Calendar equivalence operator
bool operator==(const Calendar &calendar) const;
/// Calendar non-equivalence operator
bool operator!=(const Calendar &calendar) const;

/// Checks whether input year is a leap year
/// \return true if year is a leap year, false otherwise
bool isLeapYear(long long year, ///< [in]  year to check
                int &rc         ///< [out] return code to flag errors
                ) const;

/// Computes the total elapsed time in seconds (in TimeFrac form)
/// since the calendar reference time, given a calendar date, time.
/// \return Elapsed time in TimeFrac form
TimeFrac getElapsedTime(
    const long long year,   ///< [in] calendar year
    const long long month,  ///< [in] calendar month
    const long long day,    ///< [in] calendar day
    const long long hour,   ///< [in] time of day-hour
    const long long minute, ///< [in] time of day-min
    const long long whole,  ///< [in] time of day-whole seconds
    const long long numer,  ///< [in] time of day-frac secs (numerator)
    const long long denom   ///< [in] time of day-frac secs (denom)
    ) const;

/// Determines the calendar date and time of day, given an
/// elapsed time since the calendar reference time.
/// \return error code
int getDateTime(
    const TimeFrac elapsedTime, ///< [in] time in secs from ref time
    long long &year,   ///< [out] calendar year
    long long &month,  ///< [out] calendar month
    long long &day,    ///< [out] calendar day
    long long &hour,   ///< [out] time of day-hours
    long long &minute, ///< [out] time of day-minutes
    long long &whole,  ///< [out] time of day-whole seconds
    long long &numer,  ///< [out] time of day-frac secs (numerator)
    long long &denom   ///< [out] time of day-frac secs (denom)
    ) const;

/// Increments (or decrements) a calendar date by a specified
/// interval, supplied by an integer interval in given time units.
/// Only calendar based intervals (years, months or days) are
/// supported. This is primarily meant to be called by other time
/// manager routines (eg to add/subtract time instants) for those
/// time intervals that are dependent on date and sensitive to
/// calendar features like leap years and varying days of the month.
/// \return error code
int incrementDate(
    const long long interval, ///< [in] time interval to advance date
    const TimeUnits units,    ///< [in] time units for interval
    long long &year,   ///< [in,out] calendar year of time to be changed
    long long &month,  ///< [in,out] calendar month of ...
    long long &day     ///< [in,out] calendar day
    ) const;

4.2.3 TimeInstant class

The time instant class represents a single instant in time. Most of the methods are accessor methods for getting/setting a time instant or its components. In addition, a number of operators are defined for performing basic arithmetic with time instants and time intervals (see following interval class). Finally, a method for creating a time string for a time instant is supplied.

// constructors/destructors
/// Default constructor creates empty time instant
TimeInstant(void);

/// Construct time instant from date, time, calendar
/// Where seconds is supplied as real number.
TimeInstant(Calendar       *Cal,    ///< [in] Calendar to use
            const long long year,   ///< [in] year
            const long long month,  ///< [in] month
            const long long day,    ///< [in] day
            const long long hour,   ///< [in] hour
            const long long minute, ///< [in] minute
            const double    rSecond ///< [in] second (real)
            );

/// Construct time instant from date, time, calendar
/// Where seconds is supplied in integer fractional seconds.
TimeInstant(      Calendar *Cal,    ///< [in] Calendar to use
            const long long year,   ///< [in] year
            const long long month,  ///< [in] month
            const long long day,    ///< [in] day
            const long long hour,   ///< [in] hour
            const long long minute, ///< [in] minute
            const long long whole,  ///< [in] second (whole integer)
            const long long numer,  ///< [in] second (fraction numerator)
            const long long denom   ///< [in] second (fraction denominator)
            );

/// Destructor for time interval
~TimeInstant(void);

// Accessor methods
/// Set time instant calendar
/// \return error code
int set(Calendar *Cal   ///< [in] Calendar to use for this time
        );

/// Set time instant from date and time, where seconds is supplied
/// as a real number.
/// \return error code
int set(const long long year,   ///< [in] year
        const long long month,  ///< [in] month
        const long long day,    ///< [in] day
        const long long hour,   ///< [in] hour
        const long long minute, ///< [in] minute
        const double    rSecond ///< [in] second (real)
        );

/// Set time instant from date and time, where seconds is supplied
/// in integer fractional seconds.
/// \return error code
int set(const long long year,   ///< [in] year
        const long long month,  ///< [in] month
        const long long day,    ///< [in] day
        const long long hour,   ///< [in] hour
        const long long minute, ///< [in] minute
        const long long whole,  ///< [in] second (whole integer)
        const long long numer,  ///< [in] second (fraction numerator)
        const long long denom   ///< [in] second (fraction denominator)
        );

/// Retrieve calendar from time instant
/// \return error code
int get(Calendar *&cal ///< [out] Calendar ptr in which instant defined
        ) const;

/// Retrieve time in date, time form with real seconds.
/// \return error code
int get(long long &year,   ///< [out] year   of this time instant
        long long &month,  ///< [out] month  of this time instant
        long long &day,    ///< [out] day    of this time instant
        long long &hour,   ///< [out] hour   of this time instant
        long long &minute, ///< [out] minute of this time instant
        double    &second  ///< [out] second of this time instant
        ) const;

/// Retrieve time in date, time form with fractional integer seconds.
/// \return error code
int get(long long &year,   ///< [out] year   of this time instant
        long long &month,  ///< [out] month  of this time instant
        long long &day,    ///< [out] day    of this time instant
        long long &hour,   ///< [out] hour   of this time instant
        long long &minute, ///< [out] minute of this time instant
        long long &whole,  ///< [out] whole seconds of this time
        long long &numer,  ///< [out] frac second numerator
        long long &denom   ///< [out] frac second denominator
        ) const;

// Operators on time instants
/// Equivalence comparison for TimeInstant
bool operator==(const TimeInstant &) const;
/// Non-equivalence comparison operator for TimeInstant
bool operator!=(const TimeInstant &) const;
/// Less than comparison operator for TimeInstant
bool operator< (const TimeInstant &) const;
/// Greater than comparison operator for TimeInstant
bool operator> (const TimeInstant &) const;
/// Less than or equal comparison operator for TimeInstant
bool operator<=(const TimeInstant &) const;
/// Greater than or equal comparison operator for TimeInstant
bool operator>=(const TimeInstant &) const;
/// Increment time by adding a time interval
TimeInstant  operator+ (const TimeInterval &) const;
/// Decrement time by subtracting a time interval
TimeInstant  operator- (const TimeInterval &) const;
/// Create a time interval by subtracting two time instants
TimeInterval operator- (const TimeInstant &) const;
/// Increment time in place by adding time interval
/// Increment time in place by adding time interval
TimeInstant& operator+=(const TimeInterval &);
/// Decrement time in place by subtracting time interval
TimeInstant& operator-=(const TimeInterval &);

// Other utility methods
/// Get time as a string in the format
/// 'YYYYYY-MM-DD{separator}HH:MM:SS.SSSSSS' where the number
/// of digits in year, number of digits after the decimal in
/// seconds and the character to use as separator are all input
/// by the user.
/// \return time string
std::string getString(
    const int yearWidth,   ///< [in] number of digits in year
    const int secondWidth, ///< [in] num digits after decimal in seconds
    std::string separator  ///< [in] string(char) to separate date/time
    ) const;

4.2.4 TimeInterval class

The time interval class manages differences or increments in time. The user can create or set a time interval with a value and units. Retrieval functions for the time interval in various forms are also supplied. A number of operators are defined for a variety of mathematical operations on time intervals (add, subtract, multiply, equivalence, absolute value). Finally, the time instant class is treated as a friend class so that time instants can be incremented by a time interval and time intervals can be created by subtracting two time instants.

// constructors/destructors
/// Default time interval constructor
TimeInterval(void);

/// Construct time interval from base time fractional integer seconds
TimeInterval(const long long whole, ///< Whole integer seconds
             const long long numer, ///< Fractional seconds numerator
             const long long denom  ///< Fractional seconds denominator
             );

/// Construct time interval from an integer length and units
TimeInterval(const int       length, ///< length of time interval
             const TimeUnits units   ///< unit of time for interval
             );

/// Construct time interval from a long long integer length and units
TimeInterval(const long long length, ///< length of time interval
             const TimeUnits units   ///< unit of time for interval
             );

/// Construct time interval from an real length and units
TimeInterval(const double length,    ///< length of time interval
             const TimeUnits units   ///< unit of time for interval
             );

/// Destructor for time interval
~TimeInterval(void);

// Accessor methods
/// Set a non-calendar interval in native fractional integer seconds
/// \return error code
int set(const long long whole, ///< Whole integer seconds
        const long long numer, ///< Fractional seconds numerator
        const long long denom  ///< Fractional seconds denominator
        );

/// Set a time interval from integer length and units
/// \return error code
int set(const int       length, ///< length of time interval
        const TimeUnits units   ///< unit of time for interval
        );

/// Set a time interval from long long integer length and units
/// \return error code
int set(const long long length, ///< length of time interval
        const TimeUnits units   ///< unit of time for interval
        );

/// Set a time interval from an real length and units
/// Real length is only supported for non-calendar intervals since
/// a non-integral length has ambiguous meaning when months, years
/// have variable length.
/// \return error code
int set(const double length,    ///< length of time interval
        const TimeUnits units   ///< unit of time for interval
        );

/// Retrieve non-calendar interval in native fractional integer form
/// \return error code
int get(long long &whole, ///< [out] whole seconds
        long long &numer, ///< [out] fractional second numerator
        long long &denom  ///< [out] fractional second denominator
        ) const;

/// Retrieve a time interval in integer length in specified units.
/// To avoid roundoff issues during conversions, integer retrieval
/// is only permitted in the same units in which the interval was
/// defined.
/// \return error code
int get(long long &length, ///< [out] requested integer length of interval
        const TimeUnits units ///< [in] unit of time for interval
        ) const;

/// Retrieve a time interval in real length and specified units.
/// For calendar intervals, the units must match the units in which
/// the interval was defined. For non-calendar intervals, only conversions
/// between hours, minutes and seconds are allowed.
/// \return error code
int get(double &length, ///< [out] Requested time interval length
        const TimeUnits units ///< [in] unit of time for interval
       ) const;

/// Equivalence comparison operator for TimeInterval
bool operator==(const TimeInterval &) const;
/// Non-equivalence comparison operator for TimeInterval
bool operator!=(const TimeInterval &) const;
/// Less than comparison operator for TimeInterval
bool operator< (const TimeInterval &) const;
/// Greater than comparison operator for TimeInterval
bool operator> (const TimeInterval &) const;
/// Less than or equal comparison operator for TimeInterval
bool operator<=(const TimeInterval &) const;
/// Greater than or equal comparison operator for TimeInterval
bool operator>=(const TimeInterval &) const;
/// Addition operator for TimeInterval
TimeInterval  operator+ (const TimeInterval &) const;
/// Subtraction operator for TimeInterval
TimeInterval  operator- (const TimeInterval &) const;
/// Increment operator for TimeInterval
TimeInterval& operator+=(const TimeInterval &);
/// Decrement operator for TimeInterval
TimeInterval& operator-=(const TimeInterval &);
/// Multiplication by integer scalar
TimeInterval  operator* (const int multiplier) const;
/// Multiplication by real scalar
/// This is primarily meant for non-calendar intervals, but
/// for calendar intervals, it will multiply the year, month or
/// day interval and convert to the nearest integer.
TimeInterval  operator* (const double multiplier) const;
/// Multiplication in place by integer scalar
TimeInterval& operator*=(const int multiplier);
/// Multiplication in place by real scalar
/// This is primarily meant for non-calendar intervals, but
/// for calendar intervals, it will multiply the year, month or
/// day interval and convert to the nearest integer.
TimeInterval& operator*=(const double multiplier);
/// Divide interval by integer scalar
TimeInterval  operator/ (const int divisor) const;
/// Divide interval in place by integer scalar
TimeInterval& operator/=(const int divisor);

/// Absolute value
static TimeInterval absValue(const TimeInterval &);
/// Negative absolute value
static TimeInterval negAbsValue(const TimeInterval &);

// Other utility methods
/// Check whether a time interval is positive
bool isPositive(void);

/// Give the Time Instant access to Time Interval so
/// that incrementing/decrementing time is easier.
friend class TimeInstant;

}

4.2.5 Alarm Class

The alarm class allows the creation of both one-time and periodic alarms. There are also methods for querying the state of an alarm and for resetting.

// constructors/destructors
/// Constructs a one-time alarm using the input ring time.
Alarm(const std::string inName,   ///< [in] Name of alarm
      const TimeInstant alarmTime ///< [in] Time at/after which alarm rings
     );

/// Constructs a periodic/interval alarm based on an input periodic
/// time interval and a start time for the interval period.
Alarm(const std::string  inName,        ///< [in] Name of alarm
      const TimeInterval alarmInterval, ///< [in] interval at which alarm rings
      const TimeInstant  intervalStart  ///< [in] start time of first interval
     );

/// Destructor for alarm
~Alarm(void);

/// Check whether an alarm is ringing
/// \return true if alarm is ringing, false otherwise
bool isRinging(void);

/// Checks whether the alarm should ring based on the current
/// (or supplied) time instant (returns error code)
int updateStatus(const TimeInstant currentTime ///< [in] current time
                );

/// Stops a ringing alarm and sets next a new ring time. If the alarm
/// is a periodic/interval alarm, the next ring time is set to be the
/// next interval boundary after the input time.  If the alarm is a
/// single instance, the input time is used as the next alarm time.
/// (returns error code)
int reset(const TimeInstant inTime ///< [in] new basis for alarm time
         );

/// Stops a ringing alarm (returns error code).
int stop(void);

/// Rename an alarm (returns error code)
int rename(const std::string newName ///< [in] new name for alarm
          );

/// Get alarm name
std::string getName(void) const;

4.2.6 Clock Class

The Clock class defines the model clock and is created based on a start time and time step. There are a number of retrieval functions to get the current, past, and next times. A number of alarms can be attached to a clock. Finally, there is a method to advance the clock one time and update the state of all attached alarms.

// constructors/destructors
/// Construct a clock from start time and time step
Clock(const TimeInstant  startTime, ///< [in] Start time for clock
      const TimeInterval timeStep   ///< [in] Time step to advance clock
     );

/// Destructor for clocks
~Clock(void);

// Accessor Methods
/// Set the current time (returns error code)
int setCurrentTime(
    const TimeInstant currTime ///< [in] new value for current time
    );

/// Changes the time step for this clock (returns error code)
int changeTimeStep(
    const TimeInterval newTimeStep ///< [in] new value for time step
    );

/// Retrieves current time of this clock
TimeInstant getCurrentTime(void) const;

/// Retrieves time at the previous time step
TimeInstant getPreviousTime(void) const;

/// Retrieves time at the next time step
TimeInstant getNextTime(void) const;

/// Retrieves start time for this clock
TimeInstant getStartTime(void) const;

/// Retrieves time step for clock
TimeInterval getTimeStep(void) const;

/// Attaches an alarm to this clock. The clock simply stores a
/// pointer to this alarm. (returns error code)
int attachAlarm(Alarm* inAlarm ///< [in] pointer to alarm to attach
               );

/// Advance a clock one timestep and update status of any attached
/// alarms. (returns error code)
int advance(void);

5 Verification and Testing

5.1 Test TimeFrac

We will test the base TimeFrac class by creating some reference times, one of which will be a fractional time that cannot be exactly represented by a float (eg 1/3 seconds). Constructors and set functions will be paired with get functions to make sure both set/get are working. All operators (equivalence, relational, algebraic) operators will be tested with known values. We will try to anticipate values that could fail.

  • tests parts of requirement 2.2, 2.5, 2.6

5.2 Test TimeInstant

Similar to TimeFrac, create reference times in each of the supported calendars using constructor and set functions. Test retrievals (get) to verify they return the reference value. Test all the supported TimeInstant operators with known values in each of the calendars.

  • tests parts of requirement 2.1, 2.3

5.3 Test Calendar

For each of the supported calendars (including a custom calendar), create an instance of the calendar and use the retrieval (get) functions to verify that various calendar properties are as expected (eg days per year, days per month, leap years/days, etc.). For some calendars, known reference dates exist from the references above and we will verify that these result in correct elapsed time or Julian day. Test the few operators (equivalence, copy constructors).

  • tests most of requirement 2.3

5.4 Test Time Intervals

As in the others, test constructors and set functions by pairing with retrieval (get) functions for a given reference time interval. Test assignment constructors by creating a time interval for a given time step in various time units (seconds, hours, days, etc.). Test equivalence/non-equivalence by comparing known time intervals. Test algebraic operators (addition, subtraction, increment, decrement, multiply, divide, etc.) using reference values. Test creation of time interval by subtraction of time instants.

  • tests requirement 2.4, parts of 2.1

5.5 Test Alarms

Test both one-time and periodic alarms at various supported intervals, including annual, monthly/nmonths, daily/ndays, hourly/nhours, nminutes, seconds. This is accomplished by choosing a start time in a calendar and integrating over a time period that covers the alarm. Use the Gregorian calendar (as most complex) and choose an interval that includes leap years/days. Also verify that alarms are not ringing at arbitrary selected times in between. Check the reset function and verify alarms are reset appropriately.

  • tests requirement 2.5

5.6 Test Clock

This is the primary test of putting it all together and verifying that we can advance a model clock. Construct a clock with a given calendar and time step. Test various retrieval functions to get known quantities - also test the ability to change time step with set/get pair. Set a number of alarms at various times and periodic intervals. Integrate forward in time for N years and verify all alarms are triggered at proper times. Also verify that the final time is exact with no roundoff error accumulated. Create another clock with large time steps (years) that extend over a potential paleoclimate interval to test large number representations are supported correctly.

  • tests requirement 2.1, 2.2, 2.3, 2.4, 2.5, 2.6