gregorian::date

Contents

Description
Construction
date arithmetic
date I/O
Observers
Comparisons
Synopsis

Description

date is a class in namespace gregorian which represents a calendar (Gregorian) date. It can be constructed in a variety of ways using intuitive and type safe syntax. Once constructed, the day, month, year and day of week can be observed. Date arithmetic is fully supported, including simple means to find the nth day-of-week in a month, the last day of a month, and dates plus/minus days, months and years.

The header is date.h, the source is date.cpp, and there is a test in test_date.cpp. The test file also has a function which takes an int representing a year, and returns a date representing Easter for that year.

Construction

The default constructor constructs a date with the current local date.

To construct a date the client supplies day, month and year specifications. This information can be specified with the type int. For example:

date d = day(19) / month(4) / year(2004);

This date corresponds to April 19, 2004. When using this form, the argument to day must be in the range [1, 31], and month in the range [1, 12]. If values are given outside of this range, a bad_date exception will be thrown. The year is represented with an unsigned short which typically has the range [0, 65535]. If the combination of day, month and year specifiers results in an invalid date, a bad_date exception will be thrown.

The day, month and year specifiers can occur in one of three orders:

date d1 = day(19) / month(4) / year(2004);
date d2 = month(4) / day(19) / year(2004);
date d3 = year(2004) / month(4) / day(19);

These dates all refer to the same day. If the day, month and year specifications are given in an order other than one of these three, a compile time error will result.

Furthermore, the day/month/year wrappers are optional in the second and third positions. Once the leading specifier is given, date construction is unambiguous:

date d1 = day(19) / 4 / 2004;
date d2 = month(4) / 19 / 2004;
date d3 = year(2004) / 4 / 19;

And as it turns out, the form month/day/year is special in this respect. For this form only, the month wrapper may be omitted if the day wrapper is present. This may be convenient in some situations:

date d = 4 / day(19) / 2004;

Don't worry, if you try to construct a date in an invalid or ambiguous format, it will fail at compile time, not run time.

Additionally there are const month objects with the names: jan, feb ... dec which can be used in place of the explicit month specifier:

date d = apr/19/2004;

There are also two const detail::spec objects named first and last. These can be used in place of the day specification to indicate you want the first or last day of the month. There is no semantic difference between specifying first and day(1). But last is special:

date d1 = apr/last/2004;  // Apr. 30, 2004
date d2 = last/feb/2004;  // Feb. 29, 2004

This also provides an easy way to get the number of days in a month:

int num_days_in_feb = (last/feb/2004).day();

One can also construct a date by specifying that you want the nth day-of-week for a given month and year. For example:

date d = 2*week_day(0)/apr/2004;  // Apr. 11, 2004, the second Sunday in April

The argument to week_day must be in the range [0, 6] which represents Sun - Sat. If an argument outside this range is given, a bad_date exception will be thrown. The week_day object must be pre-multiplied by an int, even if that int is 1 (you want the first Sunday in April for example). Additionally the client can use the first and last specifiers in place of the int:

date d1 = first*week_day(4)/apr/2004;  // Apr.  1, 2004
date d2 =  last*week_day(4)/apr/2004;  // Apr. 29, 2004

If the multiplier of week_day() specifies a day that does not exist, then a bad_date exception will be thrown:

date d = 5*week_day(6)/apr/2004;  // error, only 4 Saturdays in April

There also exist const week_day objects named sun, mon, tue, wed, thu, fri, sat which are semantically identical to week_day(0) through week_day(6):

date d = last*sat/apr/2004;  // Apr. 24, 2004

Note that if the month/day/year form or the year/month/day form is used for the last example, then parentheses must be used to group the day specification:

date d = apr/(last*sat)/2004;

With the keywords, and the explicit wrappers, the code becomes much more readable, and ambiguous date formats do not occur:

date d(1, 2, 2004);  // does not compile!

Should the above mean Jan. 2 or Feb. 1? Instead one writes:

date d = jan/2/2004;

or:

date d = day(1)/feb/2004;

and if you mistakenly write:

date d = 1/2/2004;

then you will get a compile-time error.

date arithmetic

Any two dates can be subtracted and the result is an int representing the number of days between the dates:

date d = oct/15/2004;
int day_number = d - jan/first/d.year();

A date can have an int added to it or subtracted from it. The int represents days. The day wrapper is not to be used in this context to make that explicit. A date may not be subtracted from an int.

date d = apr/19/2004;
++d;        // apr/20/2004
d += 1;     // apr/21/2004
d = d + 1;  // apr/22/2004
d = 1 + d;  // apr/23/2004
d = 1 - d;  // compile time error!
d = d - 1;  // apr/22/2004
d -= 1;     // apr/21/2004
--d;        // apr/20/2004

A month (using the month wrapper) can be added to or subtracted from a date.

date d = apr/19/2004;
d += month(1);     // may/19/2004
d = d + month(1);  // jun/19/2004
d = month(1) + d;  // jul/19/2004
d = month(1) - d;  // compile time error!
d = d - month(1);  // jun/19/2004
d -= month(1);     // may/19/2004

If the resulting month does not have the day specified in the source, then a bad_date exception is thrown:

date d = mar/31/2004;
d += month(1);     // bad_date thrown, there is no apr/31/2004

However, the above example can be modified to mean that d represents the last day of March, and then 1 month can be added to that to find the last day in April:

date d = mar/last/2004;  // mar/31/2004
d += month(1);           // apr/30/2004

The last day specifier is special in this way. Both date(mar/31/2004) and date(mar/last/2004) compare equal. But the latter is tagged with extra information that means: The last day of this month, as opposed to simply Mar. 31, 2004. That tagging will remain in place until the date object is modified by day-oriented arithmetic, or until the date object is assigned the value of a non-last-tagged date. This makes it very easy to do something such as iterate over the last day of the month for a year:

for (date d = jan/last/2004, end = dec/last/2004; d <= end; d += month(1))
    std::cout << d << '\n';
 
01/31/04
02/29/04
03/31/04
04/30/04
05/31/04
06/30/04
07/31/04
08/31/04
09/30/04
10/31/04
11/30/04
12/31/04

The date's constructed with last*week_day(i) follow similar semantics. For example, here is a loop that prints out the last Sat. of every month in 2004:

for (date d = last*sat/jan/2004, end = last*sat/dec/2004; d <= end; d += month(1))
    std::cout << d << '\n';
 
01/31/04
02/28/04
03/27/04
04/24/04
05/29/04
06/26/04
07/31/04
08/28/04
09/25/04
10/30/04
11/27/04
12/25/04

However asking for the fifth Sat. of every month would result in a bad_date exception being thrown during the computation for the first month that does not have five Saturdays (February). And asking for the fourth Sat. of every month would result in a different output for those months with five Saturdays.

for (date d = 4*sat/jan/2004, end = 4*sat/dec/2004; d <= end; d += month(1))
    std::cout << d << '\n';
 
01/24/04
02/28/04
03/27/04
04/24/04
05/22/04
06/26/04
07/24/04
08/28/04
09/25/04
10/23/04
11/27/04
12/25/04

Arithmetic with years can be done analogously with the month-arithmetic:

date d = feb/29/2004;
d += year(1);  // bad_date thrown, feb/29/2005 doesn't exist

But:

date d = feb/last/2004;
d += year(1);  // ok, feb/28/2005

date I/O

I/O is handled in a standard fashion. The date object has standard stream inserters and extractors. The std::time_get and std::time_put facets are employed for formatting and parsing. These use the "%x" specifier from C's strftime function. The behavior can be modified by appropriate locale setting in an implementation-defined manner. The "C" locale I/O's with the format mm/dd/yy (as shown in the examples here). In what century yy is interpreted (for parsing) is implementation-defined.

Observers

The following member functions return information about the date:

int day() const;
int month() const;
int year() const;
int day_of_week() const;
bool is_leap() const;

The day() function will return a result in the range [1, 31].

The month() function will return a result in the range [1, 12].

The year() member function will return an int indicating the year, including century. For example a date with the year 2004 returns 2004.

The day_of_week() function returns a result in the range [0, 6] indicating Sunday through Saturday respectively.

The is_leap() function returns true if the year of the date is a leap year, else false.

Comparisons

Two dates can be compared by any of the 6 relational operators. Two dates are equal if they would return the same day(), month() and year(). It is not possible for a date object to hold an invalid date. One date will compare less than another if it occurs before the other on the Gregorian calendar.

Synopsis

namespace gregorian
{
 
namespace detail
{
 
class spec
{
public:
    spec();
    bool operator == (const spec& y) const {return id_ == y.id_;}
    bool operator != (const spec& y) const {return id_ != y.id_;}
};
 
}  // detail
 
extern const detail::spec last;
extern const detail::spec first;
 
class bad_date
    : public std::exception
{
public:
    virtual const char* what() const throw() {return "bad_date";}
};
 
class day
{
public:
    day(int d);
    day(detail::spec s);
};
 
struct month
{
    month(int m) : value(m) {}
    int value;
};
 
struct year
{
public:
    year(int y) : value(y) {}
    int value;
};
 
class week_day
{
public:
    week_day(int d);
};
 
extern const week_day sun;
extern const week_day mon;
extern const week_day tue;
extern const week_day wed;
extern const week_day thu;
extern const week_day fri;
extern const week_day sat;
 
extern const month jan;
extern const month feb;
extern const month mar;
extern const month apr;
extern const month may;
extern const month jun;
extern const month jul;
extern const month aug;
extern const month sep;
extern const month oct;
extern const month nov;
extern const month dec;
 
namespace detail
{
 
class day_month_spec
{
public:
    day_month_spec(day d, month m);
};
 
class month_year_spec
{
public:
    month_year_spec(month m, year y);
};
 
}  // detail
 
date operator+(const date&, month);
date operator+(const date&, year);
 
class date
{
public:
    date();
    date(detail::day_month_spec dm, year y);
    date(day d, detail::month_year_spec my);
 
    int day() const;
    int month() const;
    int year() const;
    int day_of_week() const;
    bool is_leap() const;
 
    date& operator+=(int d);
    date& operator++();
    date  operator++(int);
    date& operator-=(int d);
    date& operator--();
    date  operator--(int);
    friend date operator+(const date& x, int y);
    friend date operator+(int x, const date& y);
    friend date operator-(const date& x, int y);
 
    date& operator+=(gregorian::month m);
    date& operator-=(gregorian::month m);
    friend date operator+(gregorian::month m, const date& y);
    friend date operator-(const date& x, gregorian::month m);
 
    date& operator+=(gregorian::year y);
    date& operator-=(gregorian::year y);
    friend date operator+(gregorian::year y, const date& x);
    friend date operator-(const date& x, gregorian::year y);
 
    friend long operator-(const date& x, const date& y);
 
    friend bool operator==(const date& x, const date& y);
    friend bool operator!=(const date& x, const date& y);
    friend bool operator< (const date& x, const date& y);
    friend bool operator<=(const date& x, const date& y);
    friend bool operator> (const date& x, const date& y);
    friend bool operator>=(const date& x, const date& y);
};
 
day operator*(detail::spec s, week_day wd);
day operator*(int n, week_day wd);
 
detail::day_month_spec operator/(day d, month m);
detail::day_month_spec operator/(month m, day d);
detail::month_year_spec operator/(year y, month m);
date operator/(detail::day_month_spec dm, year y);
date operator/(detail::month_year_spec my, day d);
 
template<class charT, class traits>
std::basic_istream<charT,traits>&
operator >>(std::basic_istream<charT,traits>& is, date& item);
 
template<class charT, class traits>
std::basic_ostream<charT, traits>&
operator <<(std::basic_ostream<charT, traits>& os, const date& item);
 
}  // gregorian

An interesting part of this interface is that private objects (those in namespace detail) are exposed, but the user creates these objects implicitly during date construction.