// bdlt_prolepticdateimputil.h -*-C++-*- #ifndef INCLUDED_BDLT_PROLEPTICDATEIMPUTIL #define INCLUDED_BDLT_PROLEPTICDATEIMPUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide low-level support functions for date-value manipulation. // //@CLASSES: // bdlt::ProlepticDateImpUtil: low-level date-related stateless functions // //@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? //.. // 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. #include <bdlscm_version.h> #include <bsls_assert.h> #include <bsls_review.h> namespace BloombergLP { namespace bdlt { // =========================== // struct ProlepticDateImpUtil // =========================== struct ProlepticDateImpUtil { // This 'struct' provides a namespace for a suite of pure functions that // perform low-level operations on date values in a variety of formats: // year/month/day, year/day-of-year, and serial date. Dates in the range // '[0001/01/01 .. 9999/12/31]' that are valid per the proleptic Gregorian // calendar are supported, with serial date 1 (3652059) corresponding to // '0001/01/01' ('9999/12/31'). Note that all of the functions, whether or // not implemented in terms of a static cache, are stateless, and, as such, // are inherently thread-safe. private: // PRIVATE TYPES struct YearMonthDay { short d_year; char d_month; char d_day; }; enum { k_MAX_SERIAL_DATE = 3652059 }; // PRIVATE CLASS DATA static const int s_firstCachedYear; static const int s_lastCachedYear; static const int s_firstCachedSerialDate; static const int s_lastCachedSerialDate; static const int s_cachedSerialDate[][13]; static const YearMonthDay s_cachedYearMonthDay[]; static const char s_cachedDaysInMonth[][13]; public: // CLASS METHODS static bool isLeapYear(int year); // Return 'true' if the specified 'year' is a leap year, and 'false' // otherwise. The behavior is undefined unless '1 <= year <= 9999'. static int lastDayOfMonth(int year, int month); // Return the last day of the specified 'month' in the specified // 'year'. The behavior is undefined unless '1 <= year <= 9999' and // '1 <= month <= 12'. Note that the value returned will be in the // range '[28 .. 31]'. static int numLeapYears(int year1, int year2); // Return the number of leap years occurring between the specified // 'year1' and 'year2', inclusive. The behavior is undefined unless // '1 <= year1 <= 9999', '1 <= year2 <= 9999', and 'year1 <= year2'. // Is Valid Date static bool isValidSerial(int serialDay); // Return 'true' if the specified 'serialDay' represents a valid date // value, and 'false' otherwise. Note that valid date values are (as // fully defined in the component-level documentation) in the range // '[1 .. 3652059]'. static bool isValidYearDay(int year, int dayOfYear); // Return 'true' if the specified 'year' and 'dayOfYear' represents a // valid date value, and 'false' otherwise. Note that valid date // values are (as fully defined in the component-level documentation) // in the range '[0001/01 .. 9999/366]'. static bool isValidYearMonthDay(int year, int month, int day); // Return 'true' if the specified 'year', 'month', and 'day' represents // a valid date value, and 'false' otherwise. Note that valid date // values are (as fully defined in the component-level documentation) // in the range '[0001/01/01 .. 9999/12/31]'. static bool isValidYearMonthDayNoCache(int year, int month, int day); // Return 'true' if the specified 'year', 'month', and 'day' represents // a valid date value, and 'false' otherwise. Note that valid date // values are (as fully defined in the component-level documentation) // in the range '[0001/01/01 .. 9999/12/31]'. Also note that this // function is guaranteed not to use any date-cache optimizations. // To Serial Date (s) static int ydToSerial(int year, int dayOfYear); // Return the serial date representation of the date value indicated by // the specified 'year' and 'dayOfYear'. The behavior is undefined // unless 'isValidYearDay(year, dayOfYear)' returns 'true'. static int ymdToSerial(int year, int month, int day); // Return the serial date representation of the date value indicated by // the specified 'year', 'month', and 'day'. The behavior is undefined // unless 'isValidYearMonthDay(year, month, day)' returns 'true'. static int ymdToSerialNoCache(int year, int month, int day); // Return the serial date representation of the date value indicated by // the specified 'year', 'month', and 'day'. The behavior is undefined // unless 'isValidYearMonthDay(year, month, day)' returns 'true'. Note // that this function is guaranteed not to use any date-cache // optimizations. // To Day-Of-Year Date (yd) static int serialToDayOfYear(int serialDay); // Return the day of the year of the date value indicated by the // specified 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static void serialToYd(int *year, int *dayOfYear, int serialDay); // Load, into the specified 'year' and 'dayOfYear', the year-day // representation of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static int ymdToDayOfYear(int year, int month, int day); // Return the day of the year of the date value indicated by the // specified 'year', 'month', and 'day'. The behavior is undefined // unless 'isValidYearMonthDay(year, month, day)' returns 'true'. // To Calendar Date (ymd) static int serialToDay(int serialDay); // Return the day (of the month) of the date value indicated by the // specified 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static int serialToDayNoCache(int serialDay); // Return the day (of the month) of the date value indicated by the // specified 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. Note that this function // is guaranteed not to use any date-cache optimizations. static int serialToMonth(int serialDay); // Return the month of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static int serialToMonthNoCache(int serialDay); // Return the month of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. Note that this function // is guaranteed not to use any date-cache optimizations. static int serialToYear(int serialDay); // Return the year of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static int serialToYearNoCache(int serialDay); // Return the year of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. Note that this function // is guaranteed not to use any date-cache optimizations. static void serialToYmd(int *year, int *month, int *day, int serialDay); // Load, into the specified 'year', 'month', and 'day', the date value // indicated by the specified 'serialDay'. The behavior is undefined // unless 'isValidSerial(serialDay)' returns 'true'. static void serialToYmdNoCache(int *year, int *month, int *day, int serialDay); // Load, into the specified 'year', 'month', and 'day', the date value // indicated by the specified 'serialDay'. The behavior is undefined // unless 'isValidSerial(serialDay)' returns 'true'. Note that this // function is guaranteed not to use any date-cache-optimizations. static int ydToDay(int year, int dayOfYear); // Return the day (of the month) of the date value indicated by the // specified 'year' and 'dayOfYear'. The behavior is undefined unless // 'isValidYearDay(year, dayOfYear)' returns 'true'. static void ydToMd(int *month, int *day, int year, int dayOfYear); // Load, into the specified 'month' and 'day', the (partial) calendar // date representation of the date value indicated by the specified // 'year' and 'dayOfYear'. The behavior is undefined unless // 'isValidYearDay(year, dayOfYear)' returns 'true'. static int ydToMonth(int year, int dayOfYear); // Return the month of the date value indicated by the specified 'year' // and 'dayOfYear'. The behavior is undefined unless // 'isValidYearDay(year, dayOfYear)' returns 'true'. // To Day of Week '[SUN = 1, MON .. SAT]' static int serialToDayOfWeek(int serialDay); // Return, as an integer (with '1 = SUN', '2 = MON', ..., '7 = SAT'), // the day (of the week) of the date value indicated by the specified // 'serialDay'. The behavior is undefined unless // 'isValidSerial(serialDay)' returns 'true'. static int ydToDayOfWeek(int year, int dayOfYear); // Return, as an integer (with '1 = SUN', '2 = MON', ..., '7 = SAT'), // the day (of the week) of the date value indicated by the specified // 'year' and 'dayOfYear'. The behavior is undefined unless // 'isValidYearDay(year, dayOfYear)' returns 'true'. static int ymdToDayOfWeek(int year, int month, int day); // Return, as an integer (with '1 = SUN', '2 = MON', ..., '7 = SAT'), // the day (of the week) of the date value indicated by the specified // 'year', 'month', and 'day'. The behavior is undefined unless // 'isValidYearMonthDay(year, month, day)' returns 'true'. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // --------------------------- // struct ProlepticDateImpUtil // --------------------------- // CLASS METHODS inline bool ProlepticDateImpUtil::isLeapYear(int year) { BSLS_REVIEW(1 <= year); BSLS_REVIEW( year <= 9999); // Note the relative probabilities, from most likely to least likely: //: o Is not a leap year. //: o Is not the turn of any century (e.g., is not 1900). //: o Is a multiple of 400 (e.g., is 2000). return 0 == year % 4 && (0 != year % 100 || 0 == year % 400); } // Is Valid Date inline bool ProlepticDateImpUtil::isValidSerial(int serialDay) { return static_cast<unsigned>(serialDay) - 1 < k_MAX_SERIAL_DATE; } inline bool ProlepticDateImpUtil::isValidYearMonthDay(int year, int month, int day) { if (s_firstCachedYear <= year && year <= s_lastCachedYear) { // Check 'month' and 'day'; the cache cannot catch out-of-range issues. if (month < 1 || month > 12 || day < 1) { return false; // RETURN } return day <= s_cachedDaysInMonth[year - s_firstCachedYear][month]; // RETURN } else { return isValidYearMonthDayNoCache(year, month, day); // RETURN } } // To Day-Of-Year Date (yd) inline int ProlepticDateImpUtil::serialToDayOfYear(int serialDay) { BSLS_REVIEW(isValidSerial(serialDay)); int dayOfYear, year; serialToYd(&year, &dayOfYear, serialDay); return dayOfYear; } // To Calendar Date (ymd) inline int ProlepticDateImpUtil::serialToDayNoCache(int serialDay) { BSLS_REVIEW(isValidSerial(serialDay)); int year, month, day; serialToYmdNoCache(&year, &month, &day, serialDay); return day; } inline int ProlepticDateImpUtil::serialToMonthNoCache(int serialDay) { BSLS_REVIEW(isValidSerial(serialDay)); int year, month, day; serialToYmdNoCache(&year, &month, &day, serialDay); return month; } inline int ProlepticDateImpUtil::serialToYearNoCache(int serialDay) { BSLS_REVIEW(isValidSerial(serialDay)); int year, dayOfYear; serialToYd(&year, &dayOfYear, serialDay); return year; } inline void ProlepticDateImpUtil::serialToYmdNoCache(int *year, int *month, int *day, int serialDay) { BSLS_REVIEW(year); BSLS_REVIEW(month); BSLS_REVIEW(day); BSLS_REVIEW(isValidSerial(serialDay)); int dayOfYear; serialToYd(year, &dayOfYear, serialDay); ydToMd(month, day, *year, dayOfYear); } inline int ProlepticDateImpUtil::ydToDay(int year, int dayOfYear) { BSLS_ASSERT_SAFE(isValidYearDay(year, dayOfYear)); int month, day; ydToMd(&month, &day, year, dayOfYear); return day; } inline int ProlepticDateImpUtil::ydToMonth(int year, int dayOfYear) { BSLS_ASSERT_SAFE(isValidYearDay(year, dayOfYear)); int month, day; ydToMd(&month, &day, year, dayOfYear); return month; } // To Day of Week '[SUN = 1, MON .. SAT]' inline int ProlepticDateImpUtil::serialToDayOfWeek(int serialDay) { BSLS_REVIEW(isValidSerial(serialDay)); // 0001/01/01 was a Monday (MON == 2). return 1 + serialDay % 7; } inline int ProlepticDateImpUtil::ydToDayOfWeek(int year, int dayOfYear) { BSLS_ASSERT_SAFE(isValidYearDay(year, dayOfYear)); return serialToDayOfWeek(ydToSerial(year, dayOfYear)); } inline int ProlepticDateImpUtil::ymdToDayOfWeek(int year, int month, int day) { BSLS_ASSERT_SAFE(isValidYearMonthDay(year, month, day)); return serialToDayOfWeek(ymdToSerial(year, month, day)); } } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2014 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------