Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlt_prolepticdateimputil
[Package bdlt]

Provide low-level support functions for date-value manipulation. More...

Namespaces

namespace  bdlt

Detailed Description

Outline
Purpose:
Provide low-level support functions for date-value manipulation.
Classes:
bdlt::ProlepticDateImpUtil low-level date-related stateless functions
See also:
Component bdlt_date
Description:
This component provides a utility struct, bdlt::ProlepticDateImpUtil, that defines a suite of low-level, date-related functions, which can be used to validate, manipulate, and convert among values in three different formats:
  YMD: year/month/day date
   YD: year/day-of-year date
    S: serial date
The supplied functionality can also be used (e.g.) for determining leap years, finding the last day in a given month, and for determining the day of the week for a given date. Note that in this component a "date" is understood to represent a valid day in the (YMD) range 0001/01/01 to 9999/12/31 according to the proleptic Gregorian calendar:
  http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
Representations, Valid Dates, and Leap Years:
The "Calendar Date", or "year-month-day (ymd)", is the canonical representation and is denoted as "YYYY/MM/DD", with valid years being in the range [1 .. 9999]. Within a valid year, valid months and valid days are confined to the respective ranges [1 .. 12] and [1 .. 31]. Valid dates in this representation range from 0001/01/01 to 9999/12/31, governed by the proleptic Gregorian calendar. Specifically, within the 4th, 6th, 9th, and 11th months (respectively, April, June, September, and November) of any year, valid days are in the range [1 .. 30]. Valid days for all other months of any year, with exception of the 2nd month (February), are in the range [1 .. 31].
In a leap year, February has 29 days instead of the usual 28. Thus, the range of valid days for February in a leap year is [1 .. 29]; otherwise, the range of valid days for February is [1 .. 28]. The proleptic Gregorian calendar retroactively applies the leap year rules instituted by the Gregorian Reformation to all years. In particular, a year in the range [1 .. 9999] is a leap year if it is divisible by 4, but not divisible by 100, unless it is also divisible by 400. (Expressed conversely, all years not divisible by 4 are non-leap years, as are all century years not divisible by 400 (e.g., 1900).
The "Day-Of-Year Date", or "year-day (yd)" representation, denoted by "YYYY/DDD", represents dates by their year (again, in the range [1 .. 9999]) and the day of year, in the range [1 .. 366]. Valid date values in this representation range from 0001/001 to 9999/365, with a day-of-year value of 366 permitted for leap years only.
The "Serial Date" representation depicts dates as consecutive integers beginning with 1 (representing 0001/01/01). In this representation, valid date values are in the range [1 .. 3652059], with 3652059 representing 9999/12/31.
Caching:
To achieve maximal runtime performance, several of the functions in this component reserve the right to be implemented using statically cached (i.e., tabulated, pre-calculated) values (which is inherently thread-safe). For all functions where a cache may be used, bdlt::ProlepticDateImpUtil also explicitly provides a NoCache version (e.g., ymdToSerialNoCache) that is guaranteed NOT to use a cache. Although the "normal" (potentially cached) functions typically gain huge performance advantages, the NoCache versions may conceivably be preferred by the performance-minded user who is reasonably certain that the vast majority of date values of interest will miss the cache (thus incurring a small, but unnecessary overhead for the cache-hit tests). Note, however, that the NoCache function variants are provided primarily for testing and for generating the cache in the first place (see this component's test driver).
Usage:
This section illustrates intended use of this component.
Example 1: Use as a General Purpose Utility:
The primary purpose of this component is to support the implementation of a general-purpose, value-semantic (vocabulary) "Date" type. However, it also provides many low-level utility functions suitable for direct use by other clients. In this example we employ several of the functions from this component to ask questions about particular dates in one of the three supported formats.
First, what day of the week was January 3, 2010?
  assert(2 == bdlt::ProlepticDateImpUtil::ymdToDayOfWeek(2010, 3, 1));
                                                           // 2 means Monday.
Then, was the year 2000 a leap year?
  assert(true == bdlt::ProlepticDateImpUtil::isLeapYear(2000));
                                                           // Yes, it was.
Next, was February 29, 1900 a valid date in history?
  assert(false == bdlt::ProlepticDateImpUtil::isValidYearMonthDay(1900,
                                                                     2,
                                                                    29));
                                                           // No, it was not.
Then, what was the last day of February in 1600?
  assert(29 == bdlt::ProlepticDateImpUtil::lastDayOfMonth(1600, 2));
                                                           // The 29th.
Next, how many leap years occurred from 1959 to 2012, inclusive?
  assert(14 == bdlt::ProlepticDateImpUtil::numLeapYears(1959, 2012));
                                                           // There were 14.
Now, on what day of the year will February 29, 2020 fall?
  assert(60 == bdlt::ProlepticDateImpUtil::ymdToDayOfYear(2020, 2, 29));
                                                           // The 60th one.
Finally, in what month did the 120th day of 2011 fall?
  assert(4 == bdlt::ProlepticDateImpUtil::ydToMonth(2011, 120));
                                                           // 4 means April.
Example 2: Implement a Value-Semantic Date Type:
Using the functions supplied in this component, we can easily implement a C++ class that represents abstract (mathematical) date values and performs common operations on them. The internal representation could be any of the three supported by this component. In this example, we choose to represent the date value internally as a "serial date".
First, we define a partial interface of our date class, MyDate, omitting many methods, free operators, and friend declarations that do not contribute substantively to illustrating use of this component:
  class MyDate {
      // This class represents a valid date, in the proleptic Gregorian
      // calendar, in the range '[0001/01/01 .. 9999/12/31]'.

      // DATA
      int d_serialDate;  // 1 = 0001/01/01, 2 = 0001/01/02, etc.

      // FRIENDS
      friend bool operator==(const MyDate&, const MyDate&);
      // ...

    private:
      // PRIVATE CREATORS
      explicit MyDate(int serialDate);
          // Create a 'MyDate' object initialized with the value indicated by
          // the specified 'serialDate'.  The behavior is undefined unless
          // 'serialDate' represents a valid 'MyDate' value.

    public:
      // CLASS METHODS
      static bool isValid(int year, int month, int day);
          // Return 'true' if the specified 'year', 'month', and 'day'
          // represent a valid value for a 'MyDate' object, and 'false'
          // otherwise.

      // CREATORS
      MyDate();
          // Create a 'MyDate' object having the earliest supported valid
          // date value, i.e., "0001/01/01".

      MyDate(int year, int month, int day);
          // Create a 'MyDate' object having the value represented by the
          // specified 'year', 'month', and 'day'.  The behavior is undefined
          // unless 'isValid(year, month, day)' returns 'true'.

      // ...

      // MANIPULATORS

      // ...

      void setYearMonthDay(int year, int month, int day);
          // Set this 'MyDate' object to have the value represented by the
          // specified 'year', 'month', and 'day'.  The behavior is undefined
          // unless 'isValid(year, month, day)' returns 'true'.

      // ACCESSORS
      void getYearMonthDay(int *year, int *month, int *day) const;
          // Load, into the specified 'year', 'month', and 'day', the
          // individual attribute values of this 'MyDate' object.

      int day() const;
          // Return the day of the month in the range '[1 .. 31]' of this
          // 'MyDate' object.

      int month() const;
          // Return the month of the year in the range '[1 .. 12]' of this
          // 'MyDate' object.

      int year() const;
          // Return the year in the range '[1 .. 9999]' of this 'MyDate'
          // object.

      // ...
  };

  // FREE OPERATORS
  bool operator==(const MyDate& lhs, const MyDate& rhs);
      // Return 'true' if the specified 'lhs' and 'rhs' 'MyDate' objects have
      // the same value, and 'false' otherwise.  Two dates have the same
      // value if each of the corresponding 'year', 'month', and 'day'
      // attributes respectively have the same value.

  // ...
Then, we provide an implementation of the MyDate methods and associated free operators declared above, using bsls_assert to identify preconditions and invariants where appropriate. Note the use of various bdlt::ProlepticDateImpUtil functions in the code:
  // PRIVATE CREATORS
  inline
  MyDate::MyDate(int serialDate)
  : d_serialDate(serialDate)
  {
      BSLS_ASSERT_SAFE(bdlt::ProlepticDateImpUtil::isValidSerial(
                                                              d_serialDate));
  }

  // CLASS METHODS
  inline
  bool MyDate::isValid(int year, int month, int day)
  {
      return bdlt::ProlepticDateImpUtil::isValidYearMonthDay(year,
                                                             month,
                                                             day);
  }

  // CREATORS
  inline
  MyDate::MyDate()
  : d_serialDate(1)
  {
  }

  inline
  MyDate::MyDate(int year, int month, int day)
  : d_serialDate(bdlt::ProlepticDateImpUtil::ymdToSerial(year, month, day))
  {
      BSLS_ASSERT_SAFE(isValid(year, month, day));
  }

  // ...

  // MANIPULATORS

  // ...

  inline
  void MyDate::setYearMonthDay(int year, int month, int day)
  {
      BSLS_ASSERT_SAFE(isValid(year, month, day));

      d_serialDate = bdlt::ProlepticDateImpUtil::ymdToSerial(year,
                                                             month,
                                                             day);
  }

  // ACCESSORS
  inline
  void MyDate::getYearMonthDay(int *year, int *month, int *day) const
  {
      BSLS_ASSERT_SAFE(year);
      BSLS_ASSERT_SAFE(month);
      BSLS_ASSERT_SAFE(day);

      bdlt::ProlepticDateImpUtil::serialToYmd(year,
                                              month,
                                              day,
                                              d_serialDate);
  }

  inline
  int MyDate::day() const
  {
      return bdlt::ProlepticDateImpUtil::serialToDay(d_serialDate);
  }

  inline
  int MyDate::month() const
  {
      return bdlt::ProlepticDateImpUtil::serialToMonth(d_serialDate);
  }

  inline
  int MyDate::year() const
  {
      return bdlt::ProlepticDateImpUtil::serialToYear(d_serialDate);
  }

  // FREE OPERATORS
  inline
  bool operator==(const MyDate& lhs, const MyDate& rhs)
  {
      return lhs.d_serialDate == rhs.d_serialDate;
  }
Next, we illustrate basic use of our MyDate class, starting with the creation of a default object, d1:
  MyDate d1;                        assert(   1 == d1.year());
                                    assert(   1 == d1.month());
                                    assert(   1 == d1.day());
Now, we set d1 to July 4, 1776 via the setYearMonthDay method, but we first verify that it is a valid date using isValid:
                                    assert(MyDate::isValid(1776, 7, 4));
  d1.setYearMonthDay(1776, 7, 4);   assert(1776 == d1.year());
                                    assert(   7 == d1.month());
                                    assert(   4 == d1.day());
Finally, using the value constructor, we create d2 to have the same value as d1:
  MyDate d2(1776, 7, 4);            assert(1776 == d2.year());
                                    assert(   7 == d2.month());
                                    assert(   4 == d2.day());
                                    assert(  d1 == d2);
Note that equality comparison of MyDate objects is very efficient, being comprised of a comparison of two int values. Similarly, the MyDate methods and free operators (not shown) that add a (signed) number of days to a date are also very efficient. However, one of the trade-offs of storing a date internally as a serial value is that operations involving conversion among the serial value and one or more of the year, month, and day attributes (e.g., setYearMonthDay, getYearMonthDay) entail considerably more computation.