PrevUpHomeNext

Public Holiday Schedules

Concepts and properties of a schedule
Building the schedule
Complete schedule

One of the finest things you can do with the timepoint generator is to schedule actions for annual events on calendar dates.

Goal

For this example we will create a schedule observing the US Federal Holidays, which halts a program during the holiday until the next morning (from 4AM to 4AM), plus a daily restart schedule.

More detailed information about the federal US holidays can be found on https://www.redcort.com/us-federal-bank-holidays/.

Synkronize features "Halt" schedules additionally to "Duration" schedules, plus scheduling on the "nth day of week in a month", which allows for yet undetermined dates in a year like the "3rd Monday of January".

Let's take a look at a single 'Halt Execution' schedule represented in XML:

<!-- 1. -->
<fds:schedules xmlns:fds="http://xml.firedaemon.com/scheduling/v3" xmlns:fd="http://xml.firedaemon.com">

    <!-- 2. -->
    <schedule name="New Year's Day" fd:fixed_duration_as="downtime">

        <!-- 3. -->
        <interval xsi:type="fds:attributive_interval" granularity="year_interval" length="1" blueprint="fixed_subrange_duration">

            <!-- 4. -->
            <onset name="New Year's Day"
                   minute="0" hour="4" monthday="0" month="0"/>
            <onset minute="0" hour="4" monthday="0" month="0"/>
        </interval>

        <!-- 5. -->
        <activity_boundary/>
    </schedule>
</fds:schedules>
  1. There can be one or multiple schedules.
  2. A schedule is defined by time points, denoting either restart, execution duration (uptime) or halt of execution (downtime).

    A schedule has a name, which is optional, but highly recommended, as it is used for display in various places, for identity and makes things much more understandable.

    • The difference between a "Duration" and a "Halt" schedule is simply the attribute fd:fixed_duration_as="downtime" (→ "Halt") or fd:fixed_duration_as="uptime" (or its absence) (→ "Duration").

      [Note] Note

      The fd:fixed_duration_as is a foreign attribute. It's up to the programmer to handle it.

  3. Time points recur in intervals.
    • The scheduling model is designed for different intervals: by the second, by the minute, hourly, daily, weekly, monthly, yearly, by the leapyear.

      Since the goal is to define a holiday schedule, consisting of certain periods in a year, a 'yearly' interval schedule is what we need in this example. The interval's resolution is called "granularity", which is set to granularity="year_interval" for a yearly repeating schedule.

    • You will find there's a blueprint attribute at the interval element; it tells the scheduling engine how to treat the list of time points (onset elements).
      1. evenly_clocked lets you freely specify the day of year for each time point in an interval, however the time of (i.e. hour, minute, second) of all time points will be equalized to the first time point. This rule also applies to the 'duration' blueprint values.
      2. fixed_duration tells it to expect onsets in pairs and consider them as begin and completion of two time points when the application should execute or halt.
      3. fixed_subrange_duration tells it to expect theonsets in pairs and consider them as begin and completion of a day when the application should execute or halt.
  4. A time point is called onset, in order to express that some action is associated with the time point (which is similar to music theory).

    An onset's attributes (second, minute, ...) configure when the action should happen, counted from the beginning of the interval (i.e. the beginning of year for a yearly schedule).

    This simply means that all offset values are 0-based; i.e. the 5th hour (4 o'clock) is expressed as hour="4", the 11th month (November) is expressed as month="10".

    • To express restarts, only a single onset is needed.
    • To express uptime or downtime, the onsets come in pairs, defining begin and completion (exclusive).
      1. For an uptime (duration) schedule the first onset element marks the start of uptime (the process will be started), the second onset element marks the end of uptime (the process will be terminated).
      2. Conversely, for a downtime (halt) schedule the first onset element marks the start of downtime (the process will be terminated), the second onset element marks the end of downtime (the process will be started).

  5. The activity_boundary fixes a schedule to a certain period.

    This can be useful for various reasons:

    • In regard to a 'holiday' schedule some days must be observed during the week, like "Independence Day". Those holidays may fall on a weekend, so it doesn't make sense to repeat them indefinitely, but fix them to a specific year.
    • Some holidays may occur every 4 years, which requires a fixed start year.

[Note] Note

All 'until time point' values (i.e. the second onset element of onset/completion pairs) [found at activity boundary, completion time point, end of range] denote an exclusive time point (this makes sense when doing calculations internally, plus it let's you specify periods covering the whole interval easily).

Difference and usefulness of "Halt" and "Duration"

Remember: The timepoint generator knows nothing about actions, but only timepoints marking the onset of some action or pairs of timepoints marking onset and completion for an action. In this sense, a "fixed duration" schedule is nothing more than pairs of timepoints marking the begin and completion of whatever you find applicable for your program.

However, differentiating "Halt" from "Duration" is useful when you mix "Duration" (start/stop) and "Halt" (stop/start) schedules and you want to treat both in a uniform way. The timepoint generator has a mode setting that flips stop/start timepoint pairs into start/stop pairs, hence effectively turning the schedule into a regular "Duration" schedule. See fd::timepiece_settings::fixed_duration_as.

The whole schedule consists of multiple independent 'sub' schedules, each addressing different attributes of holidays.

We need 3 Halt schedules, plus 1 Restart schedule as follows.

Regular Annual holidays
  1. New Year's Day occurs on the 1st of January.
  2. Martin Luther King, Jr. Day occurs on the 3rd Monday in January.
  3. George Washington’s Birthday occurs on the 3rd Monday in February.
  4. Memorial Day occurs on the 5th Monday in February.
  5. Labor Day occurs on the 1st Monday in September.
  6. Columbus Day occurs on the 2nd Monday in October.
  7. Thanksgiving Day occurs on the 4th Thursday in November.
  8. Christmas Day occurs on the 25th of December.

These are the 8 holidays, which can be simply repeated every year. This schedule doesn't need to be fixed to a certain year.

XML fragment
<fds:schedules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fds="http://xml.firedaemon.com/scheduling/v3" xmlns:fd="http://xml.firedaemon.com">
    <schedule name="Annual US Federal Holidays" fd:fixed_duration_as="downtime">
        <interval xsi:type="fds:attributive_interval" granularity="year_interval" length="1" blueprint="fixed_subrange_duration">

            <onset name=" Day"
                   minute="0" hour="4" monthday="0" month="0"/>
            <onset minute="0" hour="4" monthday="0" month="0"/>

            <onset name="Martin Luther King, Jr. Day"
                   minute="0" hour="4" month="0" weekday="0" first_dow="0" nth_kday_of_month="3"/>
            <onset minute="0" hour="4" month="0" weekday="0" first_dow="0" nth_kday_of_month="3"/>

            <onset name="George Washington’s Birthday"
                   minute="0" hour="4" month="1" weekday="0" first_dow="0" nth_kday_of_month="3"/>
            <onset minute="0" hour="4" month="1" weekday="0" first_dow="0" nth_kday_of_month="3"/>

            <onset name="Memorial Day"
                   minute="0" hour="4" month="1" weekday="0" first_dow="0" nth_kday_of_month="5"/>
            <onset minute="0" hour="4" month="1" weekday="0" first_dow="0" nth_kday_of_month="5"/>

            <onset name="Labor Day"
                   minute="0" hour="4" month="8" weekday="0" first_dow="0" nth_kday_of_month="1"/>
            <onset minute="0" hour="4" month="8" weekday="0" first_dow="0" nth_kday_of_month="1"/>

            <onset name="Columbus Day"
                   minute="0" hour="4" month="9" weekday="0" first_dow="0" nth_kday_of_month="2"/>
            <onset minute="0" hour="4" month="9" weekday="0" first_dow="0" nth_kday_of_month="2"/>

            <onset name="Thanksgiving Day"
                   minute="0" hour="4" month="10" weekday="4" first_dow="0" nth_kday_of_month="4"/>
            <onset minute="0" hour="4" month="10" weekday="4" first_dow="0" nth_kday_of_month="4"/>

            <onset name="Christmas Day"
                   minute="0" hour="4" monthday="24" month="11"/>
            <onset minute="0" hour="4" monthday="24" month="11"/>
        </interval>
    </schedule>
</fds:schedules>

2020 US Federal Holidays.xml

C++ fragment
interval_schedule_data sd1{ year_interval, L"Regular Annual holidays" };
sd1.appdata = fixed_duration_as::inverse_fixed_duration;
sd1.blueprint = onset_series_blueprint::fixed_subrange_duration;
// 8 onset/completion pairs
sd1.cycle.resize(8 * 2);
sd1.cycle[ 0] = { 0, 0,  4,  0,  0, 0 };                          // New Year's Day
sd1.cycle[ 1] = { 0, 0,  4,  0,  0, 0 };                          // 
sd1.cycle[ 2] = { 0, 0,  4,  0,  0, 0, Monday,   iso_sunday, 3 }; // Martin Luther King, Jr. Day
sd1.cycle[ 3] = { 0, 0,  4,  0,  0, 0, Monday,   iso_sunday, 3 }; // 
sd1.cycle[ 4] = { 0, 0,  4,  0,  1, 0, Monday,   iso_sunday, 3 }; // George Washington’s Birthday
sd1.cycle[ 5] = { 0, 0,  4,  0,  1, 0, Monday,   iso_sunday, 3 }; // 
sd1.cycle[ 6] = { 0, 0,  4,  0,  4, 0, Monday,   iso_sunday, 5 }; // Memorial Day
sd1.cycle[ 7] = { 0, 0,  4,  0,  4, 0, Monday,   iso_sunday, 5 }; // 
sd1.cycle[ 8] = { 0, 0,  4,  0,  8, 0, Monday,   iso_sunday, 1 }; // Labor Day
sd1.cycle[ 9] = { 0, 0,  4,  0,  8, 0, Monday,   iso_sunday, 1 }; // 
sd1.cycle[10] = { 0, 0,  4,  0,  9, 0, Monday,   iso_sunday, 2 }; // Columbus Day
sd1.cycle[11] = { 0, 0,  4,  0,  9, 0, Monday,   iso_sunday, 2 }; // 
sd1.cycle[12] = { 0, 0,  4,  0, 10, 0, Thursday, iso_sunday, 4 }; // Thanksgiving Day
sd1.cycle[13] = { 0, 0,  4,  0, 10, 0, Thursday, iso_sunday, 4 }; // 
sd1.cycle[14] = { 0, 0,  4, 24, 11, 0 };                          // Christmas Day
sd1.cycle[15] = { 0, 0,  4, 24, 11, 0 };                          //
Annual holidays on certain days
  1. Independence Day occurs on the 4th of July.
  2. Veterans Day occurs on the 11th of November.

When a federal holiday falls on a Saturday, it is usually observed on the preceding Friday. When the holiday falls on a Sunday, it is usually observed on the following Monday.

Due to this rule, those days are observed potentially on different dates each year. Synkronize's timepoint generator doesn't know about those special rules, hence it is necessary to fix the schedule to a certain year.

XML fragment
<fds:schedules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fds="http://xml.firedaemon.com/scheduling/v3" xmlns:fd="http://xml.firedaemon.com">
    <schedule name="2020 US Federal Holidays" fd:fixed_duration_as="downtime">
        <activity_boundary from="2020-01-01T00:00:00" until="2021-01-01T00:00:00"/>
        <interval xsi:type="fds:attributive_interval" granularity="year_interval" length="1" blueprint="fixed_subrange_duration">

            <onset name="Independence Day"
                   minute="0" hour="4" monthday="2" month="6"/>
            <onset minute="0" hour="4" monthday="2" month="6"/>

            <onset name="Veterans Day"
                   minute="0" hour="4" monthday="10" month="10"/>
            <onset minute="0" hour="4" monthday="10" month="10"/>
        </interval>
    </schedule>
</fds:schedules>

Annual, Regular US Federal Holidays.xml

C++ fragment
interval_schedule_data sd2{ year_interval, L"2020 US Federal Holidays" };
sd2.appdata = fixed_duration_as::inverse_fixed_duration;
sd2.active_from = { date(2020, 1, 1), time_duration(0, 0, 0) };
sd2.active_until = { date(2021, 1, 1), time_duration(0, 0, 0) };
sd2.blueprint = onset_series_blueprint::fixed_subrange_duration;
// 2 onset/completion pairs
sd2.cycle.resize(2 * 2);
sd2.cycle[0] = { 0, 0,  4,  2,  6, 0 };                           // Independence Day
sd2.cycle[1] = { 0, 0,  4,  2,  6, 0 };                           // 
sd2.cycle[2] = { 0, 0,  4, 10, 10, 0 };                           // Veterans Day
sd2.cycle[3] = { 0, 0,  4, 10, 10, 0 };                           //
Inauguration Day

This holiday repeats every 4 years following a U.S. presidential election. Hence it is necessary to fix the schedule's start period at a certain year.

Additionally, the same rule for a federal holiday falling on a weekend applies (on a Saturday it is usually observed on the preceding Friday; on a Sunday it is usually observed on the following Monday). That's why we fix it at a specific year or at the end of a sequence of years where we know the dates are the same.

[Note] Note

The requirement to repeat the holiday "every 4 years" is represented with length="4" in XML, and with interval_schedule_data::factor=4 in C++.

XML fragment
<fds:schedules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fds="http://xml.firedaemon.com/scheduling/v3" xmlns:fd="http://xml.firedaemon.com">
    <schedule name="2021, 2025 Inauguration Day" fd:fixed_duration_as="downtime">
        <activity_boundary from="2021-01-01T00:00:00" until="2029-01-01T00:00:00"/>
        <interval xsi:type="fds:attributive_interval" granularity="year_interval" length="4" blueprint="fixed_subrange_duration">

            <onset name="Inauguration Day"
                   minute="0" hour="4" monthday="19" month="0"/>
            <onset minute="0" hour="4" monthday="19" month="0"/>
        </interval>
    </schedule>
</fds:schedules>

2021, 2025 Inauguration Day.xml

C++ fragment
interval_schedule_data sd3{ year_interval, L"2021, 2025 Inauguration Day" };
sd3.appdata = fixed_duration_as::inverse_fixed_duration;
// must fix the schedule at its beginning because it repeats every 4 years
sd3.active_from = { date(2020, 1, 1), time_duration(0, 0, 0) };
// must fix the schedule at its end because Inauguration Day date changes in 2029
sd3.active_until = { date(2029, 1, 1), time_duration(0, 0, 0) };
sd3.interval.factor = 4;
sd3.blueprint = onset_series_blueprint::fixed_subrange_duration;
// 1 onset/completion pair
sd3.cycle.resize(1 * 2);
sd3.cycle[0] = { 0, 0,  4, 19,  0, 0 };                           // Inauguration Day
sd3.cycle[1] = { 0, 0,  4, 19,  0, 0 };                           //
Restart schedule

It may be useful to restart the program running as a service on a daily basis. This schedule restarts the program daily at 4AM.

When you want to restart the program periodically, a single Restart schedule is sufficient - Restarts only occur within uptime (reversely they are ignored during downtime).

XML fragment
<fds:schedules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fds="http://xml.firedaemon.com/scheduling/v3" xmlns:fd="http://xml.firedaemon.com">
    <schedule name="Restart daily at 4AM">
        <interval xsi:type="fds:attributive_interval" granularity="day_interval" length="1" blueprint="evenly_clocked">
            <onset minute="0" hour="4"/>
        </interval>
    </schedule>
</fds:schedules>
C++ fragment
interval_schedule_data sd4{ year_interval, L"Restart daily at 4AM" };
sd4.blueprint = onset_series_blueprint::evenly_clocked;
// A single onset
sd4.cycle.resize(1 * 1);
sd4.cycle[0] = { 0, 0,  4 };
XML

Complete US Federal Holidays + Restart.xml

C++

The following listing shows how to:

  1. Construct timepoint generators from the various schedule data objects defined above.
using fixed_duration_as = timepiece_settings::fixed_duration_as;

schedule_timepiece schedule1{ sd1, timepiece_move::rewind, any_cast<fixed_duration_as>(sd1.appdata) };
schedule_timepiece schedule2{ sd2, timepiece_move::rewind, any_cast<fixed_duration_as>(sd2.appdata) };
schedule_timepiece schedule3{ sd3, timepiece_move::rewind, any_cast<fixed_duration_as>(sd3.appdata) };
schedule_timepiece restartSchedule{ sd4 };

Complete US Federal Holidays + Restart.cpp

C++ from XML

The following listing shows how to:

  1. Read a schedule from a (validated) XML file.
  2. Handle reading the fd:fixed_duration_as attribute.
  3. Construct timepoint generators from the various schedule data objects defined in the xml file.
using fixed_duration_as = timepiece_settings::fixed_duration_as;

constexpr auto determineDowntime = [](const boost::property_tree::ptree& scheduleItem, interval_schedule_data* data)
{
    if (is_category(data->blueprint, onset_series_duration_blueprint))
    {
        string durAs = scheduleItem.get<string>("fd:fixed_duration_as", "uptime");
        data->appdata = fixed_duration_as(durAs == "downtime");
    }
};

ifstream is{ L"Complete US Federal Holidays + Restart.xml" };
vector<interval_schedule_definition> schedules = read_xml_interval_schedule(is, determineDowntime);
is.close();

// filter valid schedules
auto validSchedules = schedules | std::views::filter([](const interval_schedule_definition& schedule)
{
    return !schedule.inactive() && !schedule.data().cycle.empty();
});

// construct timepoint generators
list<schedule_timepiece> schedules_;
// start at currently active interval,
// (except if activity boundary is today, which is taken care of by the interval schedule)
for (const interval_schedule_definition& def : validSchedules)
{
    const fixed_duration_as* durAs = any_cast<fixed_duration_as>(&def.app_data());

    schedules_.emplace_back(
        def,
        def.is_duration_blueprint() ?
            timepiece_move::rewind :
            timepiece_move::to_adjacent_end,
        timepiece_settings{ def.is_duration_blueprint() ? *durAs : fixed_duration_as::fixed_duration }
    );
}

Complete US Federal Holidays + Restart_fromXML.cpp


PrevUpHomeNext