Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlt_packedcalendar
[Package bdlt]

Provide a compact repository for weekend/holiday information. More...

Namespaces

namespace  bdlt

Detailed Description

Outline
Purpose:
Provide a compact repository for weekend/holiday information.
Classes:
bdlt::PackedCalendar compact repository of weekend/holiday information
See also:
Component bdlt_calendar
Description:
This component provides a value-semantic class, bdlt::PackedCalendar, that represents weekend and holiday information over a valid range of dates. A bdlt::PackedCalendar is an approximation to the same mathematical type, and is capable of representing the same subset of mathematical values, as a bdlt::Calendar.
But unlike bdlt::Calendar, which is optimized for runtime efficiency, bdlt::PackedCalendar is designed to minimize the amount of in-process memory required to represent that information. For example, a packed calendar storing 250 holidays and holiday codes can consume as little as approximately 0.75K bytes (e.g., 2 bytes per holiday plus 1 byte per holiday code) to as much as approximately 3K bytes (e.g., 8 bytes per holiday plus 4 bytes per holiday code) depending upon the data of the calendar. For typical calendars having a range of 40 years and 10 holidays per year, the expected size of the packed calendar is about half that of a similar implementation using a non-packed structure.
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::PackedCalendar (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::PackedCalendar 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:
The asymptotic worst-case performance of representative operations is characterized using big-O notation, O[f(N,M,W,V)]. N and M each refer to the combined number (H + C) of holidays H (i.e., method numHolidays) and holiday codes C (i.e., numHolidayCodesTotal) in the respective packed calendars. W and V each refer to the (likely small) number of weekend-days transitions in the respective packed calendars. For clarity, the methods have abbreviated arguments: b, e, and d are dates, c is a holiday code, u is a weekday, and w is a set of weekdays. Here, Best Case complexity, denoted by B[f(N)], is loosely defined (for manipulators) as the worst-case cost, provided that (1) no additional internal capacity is required, (2) the start of the valid range does not change, and (3) that if a holiday (or holiday code) is being added, it is being appended to the end of the current sequence (of the latest holiday).
                                    Worst       Best    Exception-Safety
  Operation                          Case       Case      Guarantee
  ---------                         -----       ----    ----------------
  DEFAULT CTOR                      O[1]                No-Throw
  COPY CTOR(N)                      O[N]                Exception-Safe
  N.DTOR()                          O[1]                No-Throw

  N.OP=(M)                          O[M]                Basic <*>

  N.reserveCapacity(H, C)           O[N]                Strong <*>

  N.setValidRange(b, e)             O[N]        O[1]    Basic <*>
  N.addDay(d)                       O[N]        O[1]    Basic <*>
  N.addHoliday(d)                   O[N]        O[1]    Basic <*>
  N.addHolidayCode(d,c)             O[N]        O[1]    Basic <*>

  N.addWeekendDay(u)                O[1]                No-Throw
  N.addWeekendDaysTransition(d,w)   O[W]                Basic <*>

  N.intersectBusinessDays(M)        O[N+M+W+V]          Basic <*>
  N.intersectNonBusinessDays(M)     O[N+M+W+V]          Basic <*>
  N.unionBusinessDays(M)            O[N+M+W+V]          Basic <*>
  N.unionNonBusinessDays(M)         O[N+M+W+V]          Basic <*>

  N.removeHoliday(d)                O[N]                No-Throw
  N.removeHolidayCode(d, c)         O[N]                No-Throw
  N.removeAll();                    O[1]                No-Throw

  N.swap(M)                         O[1]                No-Throw

  N.firstDate()                     O[1]                No-Throw
  N.lastDate()                      O[1]                No-Throw
  N.length()                        O[1]                No-Throw

  N.numHolidays()                   O[1]                No-Throw

  N.numHolidayCodesTotal()          O[1]                No-Throw
  N.numWeekendDaysInRange()         O[1]                No-Throw

  N.isInRange(d);                   O[1]                No-Throw
  N.isWeekendDay(w);                O[1]                No-Throw
  N.isWeekendDay(d)                 O[log(W)]           No-Throw

  N.isHoliday(d);                   O[log(N)]           No_Throw
  N.isBusinessDay(d);               O[log(N)]           No_Throw
  N.isNonBusinessDay(d);            O[log(N)]           No_Throw

  N.numHolidayCodes(d)              O[log(N)]           No-Throw

  N.numBusinessDays()               O[N]                No-Throw
  N.numNonBusinessDays()            O[N]                No-Throw

  other 'const' methods             O[1] .. O[N]        No-Throw


  OP==(N,M)                         O[min(N,M)+min(W+V) No-Throw
  OP!=(N,M)                         O[min(N,M)+min(W+V) No-Throw

                          <*> No-Throw guarantee when capacity is sufficient.
Note that all of the non-creator methods of bdlt::PackedCalendar provide the No-Throw guarantee whenever sufficient capacity is already available. Also note that these are largely the same as bdlt::Calendar except that the accessors isBusinessDay and isNonBusinessDay are logarithmic in the number of holidays in bdlt::PackedCalendar.
Usage:
The two subsections below illustrate various aspects of populating and using packed calendars.
Example 1: Populating Packed Calendars:
Packed calendars will typically be populated from a database or flat file. The user should employ an appropriate population mechanism that provides the desired holiday dates and associated holiday codes within some desired range. For example, suppose we have created the following flat-file format that encodes calendar information, including holidays and holiday codes (we assume, for the simplicity of this example, that "Weekend Days" (i.e., recurring non-business days) are always just Saturdays and Sundays):
  // HOLIDAY DATE   HOLIDAY CODES
  // ------------   -------------
  // Year Mon Day    #    Codes     Comments, separated by Semicolons (;)
  // ---- --- ---   --- ---------   -------------------------------------
     2010  1  18     1   57         ;Martin Luther King, Jr. Day
     2010  2  15     1   51         ;Presidents Day
     2010  4   2     2   9 105      ;Easter Sunday (Observed); Good Friday
     2010  5  31     1   16         ;Memorial Day
     2010  7   5     1   28         ;Independence Day (Observed)
     2010  9   6     1   44         ;Labor Day
     2010 10  11     1   19         ;Columbus Day
     2010 11   2     0              ;Election Day
     2010 11  25     1   14         ;Thanksgiving Day
     2010 12  25     1    4         ;Christmas Day (Observed)
     2010 12  31     1   22         ;New Year's Day (Observed)
Let's now create a couple of primitive helper functions to extract holiday and holiday-code counts from a given input stream.
First, we'll create a helper function to get a holiday record:
  int getNextHoliday(bsl::istream& input, bdlt::Date *holiday, int *numCodes)
      // Load into the specified 'holiday' the date of the next holiday, and
      // into the specified 'numCodes' the associated number of holiday codes
      // for the holiday read from the specified 'input' stream.  Return 0 on
      // success, and a non-zero value (with no effect on '*holiday' and
      // '*numCodes') otherwise.
  {
      enum { SUCCESS = 0, FAILURE = 1 };

      int year, month, day, codes;

      if (input.good()) {
          input >> year;
      }
      if (input.good()) {
          input >> month;
      }
      if (input.good()) {
          input >> day;
      }
      if (input.good()) {
          input >> codes;
      }

      if (input.good()
       && bdlt::Date::isValidYearMonthDay(year, month, day)) {
          *holiday  = bdlt::Date(year, month, day);
          *numCodes = codes;
          return SUCCESS;                                           // RETURN
      }

      return FAILURE;                                               // RETURN
  }
Then, we'll write a function that gets us an integer holiday code, or invalidates the stream if it cannot (note that negative holiday codes are not supported by this function, but negative holiday codes are supported by bdlt::PackedCalendar):
  void getNextHolidayCode(bsl::istream& input, int *result)
      // Load, into the specified 'result', the value read from the specified
      // 'input' stream.  If the next token is not an integer, invalidate the
      // stream with no effect on 'result'.
  {
      int holidayCode;

      if (input.good()) {
          input >> holidayCode;
      }

      if (input.good()) {
          *result = holidayCode;
      }
  }
Now, with these helper functions, it is a simple matter to write a calendar loader function, load, that populates a given calendar with data in this "proprietary" format:
  void load(bsl::istream& input, bdlt::PackedCalendar *calendar)
      // Populate the specified 'calendar' with holidays and corresponding
      // codes read from the specified 'input' stream in our "proprietary"
      // format (see above).  On success, 'input' will be empty, but valid;
      // otherwise 'input' will be invalid.
  {
      bdlt::Date holiday;
      int        numCodes;

      while (0 == getNextHoliday(input, &holiday, &numCodes)) {
          calendar->addHoliday(holiday);                       // add date
          for (int i = 0; i < numCodes; ++i) {
              int holidayCode;
              getNextHolidayCode(input, &holidayCode);
              if (input.good()) {
                  // add codes

                  calendar->addHolidayCode(holiday, holidayCode);
              }
          }
          input.ignore(256, '\n');  // skip comments
      }
  }
Finally, we load a bdlt::PackedCalendar and verify some values from the calendar.
  bsl::stringstream stream;
  {
      stream << "2010  9   6     1   44         ;Labor Day\n"
             << "2010 10  11     1   19         ;Columbus Day\n"
             << "2010 11   2     0              ;Election Day\n"
             << "2010 11  25     1   14         ;Thanksgiving Day\n";
  }

  bdlt::PackedCalendar calendar;
  load(stream, &calendar);

  assert(bdlt::Date(2010,  9,  6) == calendar.firstDate());
  assert(bdlt::Date(2010, 11, 25) == calendar.lastDate());
  assert(true  == calendar.isBusinessDay(bdlt::Date(2010, 10, 12)));
  assert(false == calendar.isBusinessDay(bdlt::Date(2010, 11,  2)));
Note that different formats can easily be accommodated, while still using the same basic population strategy. Also note that it may be substantially more efficient to populate calendars in increasing date order, compared to either reverse or random order.
Example 2: Using Packed Calendars:
Higher-level clients (e.g., a GUI) may need to extract the holiday codes for a particular date, use them to look up their corresponding string names in a separate repository (e.g., a vector of strings), and to display these names to end users.
First, let's create a function that prints the names of holidays for a given date:
  void
  printHolidayNamesForGivenDate(bsl::ostream&                   output,
                                const bdlt::PackedCalendar&     calendar,
                                const bdlt::Date&               date,
                                const bsl::vector<bsl::string>& holidayNames)
      // Write, to the specified 'output' stream, the elements in the
      // specified 'holidayNames' associated, via holiday codes in the
      // specified 'calendar', to the specified 'date'.  Each holiday name
      // emitted is followed by a newline ('\n').  The behavior is undefined
      // unless 'date' is within the valid range of 'calendar'.
  {
      for (bdlt::PackedCalendar::HolidayCodeConstIterator
                                       it = calendar.beginHolidayCodes(date);
                                       it != calendar.endHolidayCodes(date);
                                     ++it) {
          output << holidayNames[*it] << bsl::endl;
      }
  }
Then, since we can write the names of holidays for a given date, let's write a function that can write out all of the names associated with each holiday in the calendar:
  void
  printHolidayDatesAndNames(bsl::ostream&                   output,
                            const bdlt::PackedCalendar&     calendar,
                            const bsl::vector<bsl::string>& holidayNames)
      // Write, to the specified 'output' stream, each date associated with
      // a holiday in the specified 'calendar' followed by any elements in
      // the specified 'holidayNames' (associated via holiday codes in
      // 'calendar') corresponding to that date.  Each date emitted is
      // preceded and followed by a newline ('\n').  Each holiday name
      // emitted is followed by a newline ('\n').
  {
      for (bdlt::PackedCalendar::HolidayConstIterator
                        it = calendar.beginHolidays();
                                      it != calendar.endHolidays(); ++it) {
          output << '\n' << *it << '\n';
          printHolidayNamesForGivenDate(output,
                                        calendar,
                                        *it,
                                        holidayNames);
      }
  }
Next, we populate the holidayNames vector:
  bsl::vector<bsl::string> holidayNames;
  {
      holidayNames.resize(45);

      holidayNames[44] = "Labor Day";         // holiday code 44 is for
                                              // Labor Day

      holidayNames[14] = "Thanksgiving Day";  // holiday code 14 is for
                                              // Thanksgiving Day
  }
Now, using the calendar populated in the previous example, we print the holiday information to a new bsl::stringstream:
  bsl::stringstream printStream;

  printHolidayDatesAndNames(printStream, calendar, holidayNames);
Finally, we verify the output:
  assert(printStream.str() == "\n06SEP2010\nLabor Day\n\n11OCT2010\n\n\n"
                               "02NOV2010\n\n25NOV2010\nThanksgiving Day\n");