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

Detailed Description

Outline

Purpose

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

Classes

See also
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?

// 2 means Monday.
static int ymdToDayOfWeek(int year, int month, int day)
Definition bdlt_prolepticdateimputil.h:757

Then, was the year 2000 a leap year?

// Yes, it was.
static bool isLeapYear(int year)
Definition bdlt_prolepticdateimputil.h:617

Next, was February 29, 1900 a valid date in history?

2,
29));
// No, it was not.
static bool isValidYearMonthDay(int year, int month, int day)
Definition bdlt_prolepticdateimputil.h:639

Then, what was the last day of February in 1600?

// The 29th.
static int lastDayOfMonth(int year, int month)

Next, how many leap years occurred from 1959 to 2012, inclusive?

assert(14 == bdlt::ProlepticDateImpUtil::numLeapYears(1959, 2012));
// There were 14.
static int numLeapYears(int year1, int year2)

Now, on what day of the year will February 29, 2020 fall?

assert(60 == bdlt::ProlepticDateImpUtil::ymdToDayOfYear(2020, 2, 29));
// The 60th one.
static int ymdToDayOfYear(int year, int month, int day)

Finally, in what month did the 120th day of 2011 fall?

assert(4 == bdlt::ProlepticDateImpUtil::ydToMonth(2011, 120));
// 4 means April.
static int ydToMonth(int year, int dayOfYear)
Definition bdlt_prolepticdateimputil.h:727

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:

/// This class represents a valid date, in the proleptic Gregorian
/// calendar, in the range `[0001/01/01 .. 9999/12/31]`.
class MyDate {
// DATA
int d_serialDate; // 1 = 0001/01/01, 2 = 0001/01/02, etc.
// FRIENDS
friend bool operator==(const MyDate&, const MyDate&);
// ...
private:
// PRIVATE CREATORS
/// Create a `MyDate` object initialized with the value indicated by
/// the specified `serialDate`. The behavior is undefined unless
/// `serialDate` represents a valid `MyDate` value.
explicit MyDate(int serialDate);
public:
// CLASS METHODS
/// Return `true` if the specified `year`, `month`, and `day`
/// represent a valid value for a `MyDate` object, and `false`
/// otherwise.
static bool isValid(int year, int month, int day);
// CREATORS
/// Create a `MyDate` object having the earliest supported valid
/// date value, i.e., "0001/01/01".
MyDate();
/// 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`.
MyDate(int year, int month, int day);
// ...
// MANIPULATORS
// ...
/// 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`.
void setYearMonthDay(int year, int month, int day);
// ACCESSORS
/// Load, into the specified `year`, `month`, and `day`, the
/// individual attribute values of this `MyDate` object.
void getYearMonthDay(int *year, int *month, int *day) const;
/// Return the day of the month in the range `[1 .. 31]` of this
/// `MyDate` object.
int day() const;
/// Return the month of the year in the range `[1 .. 12]` of this
/// `MyDate` object.
int month() const;
/// Return the year in the range `[1 .. 9999]` of this `MyDate`
/// object.
int year() const;
// ...
};
// FREE OPERATORS
/// 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.
bool operator==(const MyDate& lhs, const MyDate& rhs);
// ...

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)
{
d_serialDate));
}
// CLASS METHODS
inline
bool MyDate::isValid(int year, int month, int day)
{
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));
month,
day);
}
// ACCESSORS
inline
void MyDate::getYearMonthDay(int *year, int *month, int *day) const
{
month,
day,
d_serialDate);
}
inline
int MyDate::day() const
{
}
inline
int MyDate::month() const
{
}
inline
int MyDate::year() const
{
}
// FREE OPERATORS
inline
bool operator==(const MyDate& lhs, const MyDate& rhs)
{
return lhs.d_serialDate == rhs.d_serialDate;
}
#define BSLS_ASSERT_SAFE(X)
Definition bsls_assert.h:1762
bool operator==(const FileCleanerConfiguration &lhs, const FileCleanerConfiguration &rhs)
Definition bbldc_basicisma30360.h:112
static bool isValidSerial(int serialDay)
Definition bdlt_prolepticdateimputil.h:633
static int serialToYear(int serialDay)
static int serialToMonth(int serialDay)
static void serialToYmd(int *year, int *month, int *day, int serialDay)
static int ymdToSerial(int year, int month, int day)
static int serialToDay(int serialDay)

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.