BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlt_calendar

Detailed Description

Outline

Purpose

Provide fast repository for accessing weekend/holiday information.

Classes

See also
bdlt_packedcalendar

Description

This component provides a value-semantic class, bdlt::Calendar, that represents weekend and holiday information over a valid range of dates. A bdlt::Calendar is an approximation to the same mathematical type, and is capable of representing the same subset of mathematical values, as a bdlt::PackedCalendar. A bdlt::Calendar object (representing the same mathematical value) can be constructed directly from a reference to a non-modifiable bdlt::PackedCalendar object, and a reference to a non-modifiable bdlt::PackedCalendar is readily accessible from any bdlt::Calendar object.

But unlike bdlt::PackedCalendar, which is optimized for spatial efficiency, bdlt::Calendar is designed to be especially efficient at determining whether a given bdlt::Date value (within the valid range for a particular bdlt::Calendar object) is a business day – i.e., not a weekend day or holiday (see "Usage" below). For example, the cost of determining whether a given bdlt::Date is a business day, as opposed to a weekend or holiday, consists of only a few constant-time operations, compared to a binary search in a bdlt::PackedCalendar representing the same calendar value.

Default-constructed calendars are empty, and have an empty valid range. Calendars can also be constructed with an initial (non-empty) valid range, implying that all dates within that range are business days. The setValidRange and addDay methods modify the valid range of a calendar, and a suite of "add" methods can be used to populate a calendar with weekend days and holidays.

The addHolidayCode method associates an integer "holiday code" with a specific date, and can be called repeatedly with different integers and the same date to build up a set of holiday codes for that date. Note that holiday codes are unique integers that, from the perspective of the calendar, have no particular meaning. Typically, the user will choose holiday code values that are indices into an auxiliary collection (such as a bsl::vector<bsl::string>) to identify holiday names for end-user display.

Once a calendar is populated, a rich set of accessor methods can be used to determine, e.g., if a given date is a business day, or the number of non-business days within some subrange of the calendar. The holidays within a calendar can be obtained in increasing (chronological) order using an iterator identified by the nested HolidayConstIterator typedef. The set of holiday codes associated with an arbitrary date in a bdlt::Calendar (or the current holiday referred to by a HolidayConstIterator) can be obtained in increasing (numerical) order using an iterator identified by the nested HolidayCodeConstIterator typedef (see below).

Calendars are value-semantic objects, and, as such, necessarily support all of the standard value-semantic operations, such as default construction, copy construction and copy assignment, equality comparison, and externalization (BDEX streaming, in particular). Calendars also support the notions of both union and intersection merging operations, whereby a calendar can change its value to contain the union or intersection of its own contained weekend days, holidays, and holiday codes with those of another calendar object. Such merging operations will, in general, also alter the valid range of the resulting calendar. Note that merged calendars can be significantly more efficient for certain repeated "is-common-business-day" determinations among two or more calendars.

Weekend Days and Weekend-Days Transitions

A calendar maintains a set of dates considered to be weekend days. Typically, a calendar's weekend days fall on the same days of the week for the entire range of a calendar. For example, the weekend for United States has consisted of Saturday and Sunday since the year 1940. The addWeekendDay and addWeekendDays methods can be used to specify the weekend days for these calendars.

However, sometimes a calendar's weekend days changes over time. For example, Bangladesh's weekend consisted of Friday until June 1, 1997 when Bangladesh changed its weekends to contain both Friday and Saturday. Later, on October 1, 2001, Bangladesh reverted to a weekend of only Friday, until on September 9, 2009, Bangladesh again changed its weekends to include both Friday and Saturday.

To optimize for space allocation while supporting both consistent and changing weekend days, a calendar represents weekend information using a sequence of "weekend-days transitions", each of which comprises a date and a set of days of the week considered to be the weekend on and after that date. To represent the weekend days of Bangladesh, a calendar can use a sequence of four weekend-days transitions: (1) a transition on January 1, 0001 having a weekend day set containing only Friday, (2) a transition at June 1, 1997 having a weekend day set containing Friday and Saturday, (3) a transition at October 1, 2001 having a weekend day set containing only Friday, and (4) a transition at September 9, 2009 having a weekend day set containing Friday and Saturday. To represent the weekend days of the United States, a calendar having a range after 1940 can use a single weekend-days transition on January 1, 0001 containing Saturday and Sunday.

On construction, a calendar does not contain any weekend-days transitions. The addWeekendDaysTransition method adds a new weekend-days transition or replaces an existing weekend-days transition. The addWeekendDay and addWeekendDays methods create a weekend-days transition at January 1, 0001, if one doesn't already exist, and update the set of weekend days for that transition. addWeekendDay and addWeekendDays should only be used for calendars having a consistent set of weekend days throughout their entire range. The use of addWeekendDay and addWeekendDays is intended to be mutually exclusive to the use of addWeekendDaysTransition. As such, the behavior of using these two methods together with addWeekendDaysTransition is undefined.

Nested Iterators

Also provided are several STL-style const bidirectional iterators accessible as nested typedefs. HolidayConstIterator, HolidayCodeConstIterator, WeekendDaysTransitionConstIterator, and BusinessDayConstIterator, respectively, iterate over a chronologically ordered sequence of holidays, a numerically ordered sequence of holiday codes, a sequence of chronologically ordered weekend-days transitions, and a sequence of chronologically ordered business days. Reverse iterators are also provided for each of these (forward) iterators. As a general rule, calling a const method will not invalidate any iterators, and calling a non-const method might invalidate all of them; it is, however, guaranteed that attempting to add duplicate holidays or holiday codes will have no effect, and therefore will not invalidate any iterators. It is also guaranteed that adding a new code for an existing holiday will not invalidate any HolidayConstIterator objects.

Note that these iterators do not meet the requirements for a bsl::forward_iterator and should not be used in standard algorithms (e.g., bsl::lower_bound).

Iterator Invalidation

The modification of a bdlt::Calendar will invalidate iterators referring to the calendar. The following table shows the relationship between a calendar manipulator and the types of iterators it will invalidate if the invocation of the manipulator modified the calendar (e.g., using addHoliday with a date that is not currently a holiday in the calendar):

Manipulator Invalidates
-------------------------- --------------------
'operator=' H HC WDT BD
'addHoliday' H HC BD
'addHolidayCode' HC
'addHolidayCodeIfInRange' HC
'addHolidayIfInRange' H HC BD
'addWeekendDay' WDT BD
'addWeekendDays' WDT BD
'addWeekendDaysTransition' WDT BD
'intersectBusinessDays' H HC WDT BD
'intersectNonBusinessDays' H HC WDT BD
'removeAll' H HC WDT BD
'removeHoliday' H HC BD
'removeHolidayCode' HC
'setValidRange' H HC BD
'unionBusinessDays' H HC WDT BD
'unionNonBusinessDays' H HC WDT BD
where "H" represents the holiday iterators ('HolidayConstIterator' and
'HolidayConstReverseIterator'), "HC" represents the holiday code iterators
('HolidayCodeConstIterator' and 'HolidayCodeConstReverseIterator'), "WDT"
represents the weekend-days transition iterators
('WeekendDaysTransitionConstIterator' and
'WeekendDaysTransitionConstReverseIterator'), and "BD" represents the
business day iterators ('BusinessDayConstIterator' and
'BusinessDayConstReverseIterator').

Performance and Exception-Safety Guarantees

bdlt::Calendar supports O[1] (i.e., constant-time) determination of whether a given bdlt::Date value is or is not a business day, which is accomplished by augmenting the implementation of a packed calendar with a supplementary cache. The invariant that this cache and the data represented in the underlying bdlt::PackedCalendar be maintained in a consistent state may add significantly to the cost of performing many manipulator operations, especially those that affect the calendar's valid range and add a new weekend-days transition. Moreover, the cost of many of these operations will now be proportional to the length(s) of the valid range(s), as well as their respective numbers of holidays and associated holiday codes and weekend-days transitions. Hence, when populating a calendar, it is recommended that the desired value be captured first as a bdlt::PackedCalendar, which can then be used efficiently to value-construct the desired bdlt::Calendar object. See the component-level doc for bdlt_packedcalendar for its performance guarantees.

All methods of the bdlt::Calendar are exception-safe, but in general provide only the basic guarantee (i.e., no guarantee of rollback): If an exception occurs (i.e., while attempting to allocate memory), the calendar object is left in a coherent state, but (unless otherwise specified) its value is undefined.

Usage

The two subsections below illustrate various aspects of populating and using calendars.

Example 1: Populating Calendars

bdlt::Calendars can be populated directly, but are often more efficiently created by first creating a corresponding bdlt::PackedCalendar, and then using that object to construct the calendar. As an example, suppose we want to provide efficient access to a (high-performance) bdlt::Calendar for a variety of locales, whose raw information comes from, say, a database. The latency associated with fetching data for individual calendars on demand from a typical database can be prohibitively expensive, so it may make sense to acquire data for all calendars in a single query at start-up.

First, we declare a MyPackedCalendarCache that, internally, is just a mapping from (typically short) character string names (such as "NYB", representing New York Bank settlement days) to bdlt::PackedCalendar objects, containing densely packed calendar data:

/// This class maintains a space-efficient repository of calendar data
/// associated with a (typically short) name.
class MyPackedCalendarCache {
// DATA
public:
// CREATORS
/// Create an empty `MyPackedCalendarCache`. Optionally specify a
/// `basicAllocator` used to supply memory. If `basicAllocator` is
/// 0, the currently installed default allocator is used.
MyPackedCalendarCache(bslma::Allocator *basicAllocator = 0);
// MANIPULATORS
/// Associate the value of the specified `calendar` with the
/// specified `name`.
void assign(const bsl::string& name,
const bdlt::PackedCalendar& calendar);
// ACCESSORS
/// Return the address of calendar data associated with the
/// specified `name`, or 0 if no such association exists.
const bdlt::PackedCalendar *lookup(const bsl::string& name) const;
};
// CREATORS
MyPackedCalendarCache::MyPackedCalendarCache(
bslma::Allocator *basicAllocator)
: d_map(basicAllocator)
{
}
// MANIPULATORS
void MyPackedCalendarCache::assign(const bsl::string& name,
const bdlt::PackedCalendar& calendar)
{
d_map[name] = calendar;
}
// ACCESSORS
const bdlt::PackedCalendar *MyPackedCalendarCache::lookup(
const bsl::string& name) const
{
Cache::const_iterator iter = d_map.find(name);
if (iter == d_map.end()) {
return 0; // RETURN
}
return &iter->second;
}
Definition bdlt_packedcalendar.h:592
Definition bslstl_string.h:1281
Definition bslstl_unorderedmap.h:1089
enable_if< BloombergLP::bslmf::IsTransparentPredicate< HASH, LOOKUP_KEY >::value &&BloombergLP::bslmf::IsTransparentPredicate< EQUAL, LOOKUP_KEY >::value, iterator >::type find(const LOOKUP_KEY &key)
Definition bslstl_unorderedmap.h:1559
Definition bslma_allocator.h:457

Then, we define an application function, loadMyPackedCalendarCache, that takes the address of a MyPackedCalendarCache and populates it with up-to-date calendar data for all known locales (which, in the future, will be from a well-known database location):

/// Load, into the specified `result`, up-to-date calendar information
/// for every known locale. Return 0 on success, and a non-zero value
/// otherwise.
int loadMyPackedCalendarCache(MyPackedCalendarCache *result)
{
calendar.setValidRange(bdlt::Date(2000, 1, 1),
bdlt::Date(2020, 12, 31));
result->assign("NYB", calendar);
return 0;
}
Definition bdlt_date.h:294
void setValidRange(const Date &firstDate, const Date &lastDate)

We can imagine that there might be dozens, even hundreds of different locales, and that most applications will not need efficient access to calendar data from many, let alone every locale; however, many long-running applications may well need to obtain efficient access to the same calendar data repeatedly.

Next, we create a second-level of cache, MyCalendarCache, that maintains a repository of the more runtime-efficient, but also more space-intensive, bdlt::Calendar objects, which are instantiated on demand from a packed-calendar-based data source:

/// This class maintains a cache of runtime-efficient calendar objects
/// created on demand from a compact packed-calendar-based data source,
/// whose address is supplied at construction.
class MyCalendarCache {
// DATA
MyPackedCalendarCache *d_datasource_p;
public:
// CREATORS
/// Create an empty `MyCalendarCache` associated with the specified
/// `dataSource`. Optionally specify a `basicAllocator` used to
/// supply memory. If `basicAllocator` is 0, the currently
/// installed default allocator is used.
MyCalendarCache(MyPackedCalendarCache *dataSource,
bslma::Allocator *basicAllocator = 0);
// MANIPULATORS
/// Return the address of calendar data associated with the
/// specified `name`, or 0 if no such association exists in the data
/// source whose address was supplied at construction. Note that
/// this method may alter the physical state of this object (and is
/// therefore deliberately declared non-`const`).
const bdlt::Calendar *lookup(const bsl::string& name);
};
MyCalendarCache::MyCalendarCache(MyPackedCalendarCache *dataSource,
bslma::Allocator *basicAllocator)
: d_datasource_p(dataSource)
, d_map(basicAllocator)
{
}
const bdlt::Calendar *MyCalendarCache::lookup(const bsl::string& name)
{
Cache::const_iterator iter = d_map.find(name);
if (iter == d_map.end()) {
const bdlt::PackedCalendar *pc = d_datasource_p->lookup(name);
if (!pc) {
// No such name in the data source.
return 0; // RETURN
}
// Create new entry in calendar cache.
iter = d_map.insert(bsl::make_pair(name, *pc)).first;
}
// An efficient calendar either already existed or was created.
return &iter->second;
}
Definition bdlt_calendar.h:569

Now, we can create and populate the cache:

MyPackedCalendarCache packedCalendarCache;
MyCalendarCache calendarCache(&packedCalendarCache);
loadMyPackedCalendarCache(&packedCalendarCache);

Finally, we request the "NYB" calendar and verify the returned value:

const bdlt::Calendar *calendarPtr = calendarCache.lookup("NYB");
assert(calendarPtr->firstDate() == bdlt::Date(2000, 1, 1));
assert(calendarPtr->lastDate() == bdlt::Date(2020, 12, 31));
const Date & firstDate() const
Definition bdlt_calendar.h:1772
const Date & lastDate() const
Definition bdlt_calendar.h:1852

Example 2: Using Calendars

What makes a bdlt::Calendar substantially different from a bdlt::PackedCalendar is the speed with which the bdlt::Calendar can report whether a given date is or is not a business day. An important use of high-performance calendar objects in financial applications is to quickly determine the settlement date of a financial instrument. In some applications (e.g., those that explore the cross product of various portfolios over several horizons and scenarios), the settlement date may need to be calculated literally billions of times. The settlement date will often be determined from a periodic target date, such as the 15th or 30th of the month, which is then perturbed in some way to arrive at a valid settlement date.

One very common algorithm a security may prescribe for finding a valid settlement date is known as modified following: Given a target day, the settlement date for that month is defined as the first valid business day at or after the given target day in the same month; if no such date exists, then the settlement date is the closest valid business day before the target day in that month.

First, we create a struct, MyCalendarUtil, that provides the modifiedFollowing method:

struct MyCalendarUtil {
// CLASS METHODS
// Return the date of the first business day at or after the
// specified `targetDay` in the specified `month` and `year`
// according to the specified `calendar`, unless the resulting
// date would not fall within `month`, in which case return
// instead the date of the first business day before `targetDay`
// in `month`. The behavior is undefined unless all candidate
// dates applied to `calendar` are within its valid range and
// there exists at least one business day within `month`.
static bdlt::Date modifiedFollowing(int targetDay,
int month,
int year,
const bdlt::Calendar& calendar)
{
month,
targetDay));
// Efficiency is important so we will minimize the number of
// conversions between year/month/day and 'bdlt::Date' objects.
bdlt::Date date(year, month, targetDay);
if (0 == calendar.getNextBusinessDay(&date, date - 1)
&& month == date.month()) {
return date; // RETURN
}
while (calendar.isNonBusinessDay(--date)) {
// empty
}
return date;
}
};
int getNextBusinessDay(Date *nextBusinessDay, const Date &date) const
Definition bdlt_calendar.h:1778
bool isNonBusinessDay(const Date &date) const
Definition bdlt_calendar.h:1830
static bool isValidYearMonthDay(int year, int month, int day)
Definition bdlt_date.h:759
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804

Then, we create and populate two calendars, cal1 and cal2, for testing the modifiedFollowing method:

bdlt::Calendar cal1(bdlt::Date(2015, 1, 1), bdlt::Date(2015,12, 31));
cal1.addWeekendDay(bdlt::DayOfWeek::e_SUN);
cal1.addWeekendDay(bdlt::DayOfWeek::e_SAT);
cal1.addHoliday(bdlt::Date(2015, 7, 3));
bdlt::Calendar cal2(cal1);
cal2.addHoliday(bdlt::Date(2015, 7, 31));
@ e_SUN
Definition bdlt_dayofweek.h:125
@ e_SAT
Definition bdlt_dayofweek.h:131

Finally, we verify the modifiedFollowing functionality:

assert(bdlt::Date(2015, 7, 2) ==
MyCalendarUtil::modifiedFollowing( 2, 7, 2015, cal1));
assert(bdlt::Date(2015, 7, 6) ==
MyCalendarUtil::modifiedFollowing( 3, 7, 2015, cal1));
assert(bdlt::Date(2015, 7, 31) ==
MyCalendarUtil::modifiedFollowing(31, 7, 2015, cal1));
assert(bdlt::Date(2015, 7, 2) ==
MyCalendarUtil::modifiedFollowing( 2, 7, 2015, cal2));
assert(bdlt::Date(2015, 7, 6) ==
MyCalendarUtil::modifiedFollowing( 3, 7, 2015, cal2));
assert(bdlt::Date(2015, 7, 30) ==
MyCalendarUtil::modifiedFollowing(31, 7, 2015, cal2));