date_generators.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. #ifndef DATE_TIME_DATE_GENERATORS_HPP__
  2. #define DATE_TIME_DATE_GENERATORS_HPP__
  3. /* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc.
  4. * Use, modification and distribution is subject to the
  5. * Boost Software License, Version 1.0. (See accompanying
  6. * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
  7. * Author: Jeff Garland, Bart Garst
  8. * $Date$
  9. */
  10. /*! @file date_generators.hpp
  11. Definition and implementation of date algorithm templates
  12. */
  13. #include <sstream>
  14. #include <stdexcept>
  15. #include <boost/throw_exception.hpp>
  16. #include <boost/date_time/date.hpp>
  17. #include <boost/date_time/compiler_config.hpp>
  18. namespace boost {
  19. namespace date_time {
  20. //! Base class for all generators that take a year and produce a date.
  21. /*! This class is a base class for polymorphic function objects that take
  22. a year and produce a concrete date.
  23. @tparam date_type The type representing a date. This type must
  24. export a calender_type which defines a year_type.
  25. */
  26. template<class date_type>
  27. class year_based_generator
  28. {
  29. public:
  30. typedef typename date_type::calendar_type calendar_type;
  31. typedef typename calendar_type::year_type year_type;
  32. year_based_generator() {}
  33. virtual ~year_based_generator() {}
  34. virtual date_type get_date(year_type y) const = 0;
  35. //! Returns a string for use in a POSIX time_zone string
  36. virtual std::string to_string() const = 0;
  37. };
  38. //! Generates a date by applying the year to the given month and day.
  39. /*!
  40. Example usage:
  41. @code
  42. partial_date pd(1, Jan);
  43. partial_date pd2(70);
  44. date d = pd.get_date(2002); //2002-Jan-01
  45. date d2 = pd2.get_date(2002); //2002-Mar-10
  46. @endcode
  47. \ingroup date_alg
  48. */
  49. template<class date_type>
  50. class partial_date : public year_based_generator<date_type>
  51. {
  52. public:
  53. typedef typename date_type::calendar_type calendar_type;
  54. typedef typename calendar_type::day_type day_type;
  55. typedef typename calendar_type::month_type month_type;
  56. typedef typename calendar_type::year_type year_type;
  57. typedef typename date_type::duration_type duration_type;
  58. typedef typename duration_type::duration_rep duration_rep;
  59. partial_date(day_type d, month_type m) :
  60. day_(d),
  61. month_(m)
  62. {}
  63. //! Partial date created from number of days into year. Range 1-366
  64. /*! Allowable values range from 1 to 366. 1=Jan1, 366=Dec31. If argument
  65. * exceeds range, partial_date will be created with closest in-range value.
  66. * 60 will always be Feb29, if get_date() is called with a non-leap year
  67. * an exception will be thrown */
  68. partial_date(duration_rep days) :
  69. day_(1), // default values
  70. month_(1)
  71. {
  72. date_type d1(2000,1,1);
  73. if(days > 1) {
  74. if(days > 366) // prevents wrapping
  75. {
  76. days = 366;
  77. }
  78. days = days - 1;
  79. duration_type dd(days);
  80. d1 = d1 + dd;
  81. }
  82. day_ = d1.day();
  83. month_ = d1.month();
  84. }
  85. //! Return a concrete date when provided with a year specific year.
  86. /*! Will throw an 'invalid_argument' exception if a partial_date object,
  87. * instantiated with Feb-29, has get_date called with a non-leap year.
  88. * Example:
  89. * @code
  90. * partial_date pd(29, Feb);
  91. * pd.get_date(2003); // throws invalid_argument exception
  92. * pg.get_date(2000); // returns 2000-2-29
  93. * @endcode
  94. */
  95. date_type get_date(year_type y) const BOOST_OVERRIDE
  96. {
  97. if((day_ == 29) && (month_ == 2) && !(calendar_type::is_leap_year(y))) {
  98. std::ostringstream ss;
  99. ss << "No Feb 29th in given year of " << y << ".";
  100. boost::throw_exception(std::invalid_argument(ss.str()));
  101. }
  102. return date_type(y, month_, day_);
  103. }
  104. date_type operator()(year_type y) const
  105. {
  106. return get_date(y);
  107. //return date_type(y, month_, day_);
  108. }
  109. bool operator==(const partial_date& rhs) const
  110. {
  111. return (month_ == rhs.month_) && (day_ == rhs.day_);
  112. }
  113. bool operator<(const partial_date& rhs) const
  114. {
  115. if (month_ < rhs.month_) return true;
  116. if (month_ > rhs.month_) return false;
  117. //months are equal
  118. return (day_ < rhs.day_);
  119. }
  120. // added for streaming purposes
  121. month_type month() const
  122. {
  123. return month_;
  124. }
  125. day_type day() const
  126. {
  127. return day_;
  128. }
  129. //! Returns string suitable for use in POSIX time zone string
  130. /*! Returns string formatted with up to 3 digits:
  131. * Jan-01 == "0"
  132. * Feb-29 == "58"
  133. * Dec-31 == "365" */
  134. std::string to_string() const BOOST_OVERRIDE
  135. {
  136. std::ostringstream ss;
  137. date_type d(2004, month_, day_);
  138. unsigned short c = d.day_of_year();
  139. c--; // numbered 0-365 while day_of_year is 1 based...
  140. ss << c;
  141. return ss.str();
  142. }
  143. private:
  144. day_type day_;
  145. month_type month_;
  146. };
  147. //! Returns nth arg as string. 1 -> "first", 2 -> "second", max is 5.
  148. inline const char* nth_as_str(int ele)
  149. {
  150. static const char* const _nth_as_str[] = {"out of range", "first", "second",
  151. "third", "fourth", "fifth"};
  152. if(ele >= 1 && ele <= 5) {
  153. return _nth_as_str[ele];
  154. }
  155. else {
  156. return _nth_as_str[0];
  157. }
  158. }
  159. //! Useful generator functor for finding holidays
  160. /*! Based on the idea in Cal. Calc. for finding holidays that are
  161. * the 'first Monday of September'. When instantiated with
  162. * 'fifth' kday of month, the result will be the last kday of month
  163. * which can be the fourth or fifth depending on the structure of
  164. * the month.
  165. *
  166. * The algorithm here basically guesses for the first
  167. * day of the month. Then finds the first day of the correct
  168. * type. That is, if the first of the month is a Tuesday
  169. * and it needs Wednesday then we simply increment by a day
  170. * and then we can add the length of a week until we get
  171. * to the 'nth kday'. There are probably more efficient
  172. * algorithms based on using a mod 7, but this one works
  173. * reasonably well for basic applications.
  174. * \ingroup date_alg
  175. */
  176. template<class date_type>
  177. class nth_kday_of_month : public year_based_generator<date_type>
  178. {
  179. public:
  180. typedef typename date_type::calendar_type calendar_type;
  181. typedef typename calendar_type::day_of_week_type day_of_week_type;
  182. typedef typename calendar_type::month_type month_type;
  183. typedef typename calendar_type::year_type year_type;
  184. typedef typename date_type::duration_type duration_type;
  185. enum week_num {first=1, second, third, fourth, fifth};
  186. nth_kday_of_month(week_num week_no,
  187. day_of_week_type dow,
  188. month_type m) :
  189. month_(m),
  190. wn_(week_no),
  191. dow_(dow)
  192. {}
  193. //! Return a concrete date when provided with a year specific year.
  194. date_type get_date(year_type y) const BOOST_OVERRIDE
  195. {
  196. date_type d(y, month_, 1); //first day of month
  197. duration_type one_day(1);
  198. duration_type one_week(7);
  199. while (dow_ != d.day_of_week()) {
  200. d = d + one_day;
  201. }
  202. int week = 1;
  203. while (week < wn_) {
  204. d = d + one_week;
  205. week++;
  206. }
  207. // remove wrapping to next month behavior
  208. if(d.month() != month_) {
  209. d = d - one_week;
  210. }
  211. return d;
  212. }
  213. // added for streaming
  214. month_type month() const
  215. {
  216. return month_;
  217. }
  218. week_num nth_week() const
  219. {
  220. return wn_;
  221. }
  222. day_of_week_type day_of_week() const
  223. {
  224. return dow_;
  225. }
  226. const char* nth_week_as_str() const
  227. {
  228. return nth_as_str(wn_);
  229. }
  230. //! Returns string suitable for use in POSIX time zone string
  231. /*! Returns a string formatted as "M4.3.0" ==> 3rd Sunday in April. */
  232. std::string to_string() const BOOST_OVERRIDE
  233. {
  234. std::ostringstream ss;
  235. ss << 'M'
  236. << static_cast<int>(month_) << '.'
  237. << static_cast<int>(wn_) << '.'
  238. << static_cast<int>(dow_);
  239. return ss.str();
  240. }
  241. private:
  242. month_type month_;
  243. week_num wn_;
  244. day_of_week_type dow_;
  245. };
  246. //! Useful generator functor for finding holidays and daylight savings
  247. /*! Similar to nth_kday_of_month, but requires less paramters
  248. * \ingroup date_alg
  249. */
  250. template<class date_type>
  251. class first_kday_of_month : public year_based_generator<date_type>
  252. {
  253. public:
  254. typedef typename date_type::calendar_type calendar_type;
  255. typedef typename calendar_type::day_of_week_type day_of_week_type;
  256. typedef typename calendar_type::month_type month_type;
  257. typedef typename calendar_type::year_type year_type;
  258. typedef typename date_type::duration_type duration_type;
  259. //!Specify the first 'Sunday' in 'April' spec
  260. /*!@param dow The day of week, eg: Sunday, Monday, etc
  261. * @param m The month of the year, eg: Jan, Feb, Mar, etc
  262. */
  263. first_kday_of_month(day_of_week_type dow, month_type m) :
  264. month_(m),
  265. dow_(dow)
  266. {}
  267. //! Return a concrete date when provided with a year specific year.
  268. date_type get_date(year_type year) const BOOST_OVERRIDE
  269. {
  270. date_type d(year, month_,1);
  271. duration_type one_day(1);
  272. while (dow_ != d.day_of_week()) {
  273. d = d + one_day;
  274. }
  275. return d;
  276. }
  277. // added for streaming
  278. month_type month() const
  279. {
  280. return month_;
  281. }
  282. day_of_week_type day_of_week() const
  283. {
  284. return dow_;
  285. }
  286. //! Returns string suitable for use in POSIX time zone string
  287. /*! Returns a string formatted as "M4.1.0" ==> 1st Sunday in April. */
  288. std::string to_string() const BOOST_OVERRIDE
  289. {
  290. std::ostringstream ss;
  291. ss << 'M'
  292. << static_cast<int>(month_) << '.'
  293. << 1 << '.'
  294. << static_cast<int>(dow_);
  295. return ss.str();
  296. }
  297. private:
  298. month_type month_;
  299. day_of_week_type dow_;
  300. };
  301. //! Calculate something like Last Sunday of January
  302. /*! Useful generator functor for finding holidays and daylight savings
  303. * Get the last day of the month and then calculate the difference
  304. * to the last previous day.
  305. * @tparam date_type A date class that exports day_of_week, month_type, etc.
  306. * \ingroup date_alg
  307. */
  308. template<class date_type>
  309. class last_kday_of_month : public year_based_generator<date_type>
  310. {
  311. public:
  312. typedef typename date_type::calendar_type calendar_type;
  313. typedef typename calendar_type::day_of_week_type day_of_week_type;
  314. typedef typename calendar_type::month_type month_type;
  315. typedef typename calendar_type::year_type year_type;
  316. typedef typename date_type::duration_type duration_type;
  317. //!Specify the date spec like last 'Sunday' in 'April' spec
  318. /*!@param dow The day of week, eg: Sunday, Monday, etc
  319. * @param m The month of the year, eg: Jan, Feb, Mar, etc
  320. */
  321. last_kday_of_month(day_of_week_type dow, month_type m) :
  322. month_(m),
  323. dow_(dow)
  324. {}
  325. //! Return a concrete date when provided with a year specific year.
  326. date_type get_date(year_type year) const BOOST_OVERRIDE
  327. {
  328. date_type d(year, month_, calendar_type::end_of_month_day(year,month_));
  329. duration_type one_day(1);
  330. while (dow_ != d.day_of_week()) {
  331. d = d - one_day;
  332. }
  333. return d;
  334. }
  335. // added for streaming
  336. month_type month() const
  337. {
  338. return month_;
  339. }
  340. day_of_week_type day_of_week() const
  341. {
  342. return dow_;
  343. }
  344. //! Returns string suitable for use in POSIX time zone string
  345. /*! Returns a string formatted as "M4.5.0" ==> last Sunday in April. */
  346. std::string to_string() const BOOST_OVERRIDE
  347. {
  348. std::ostringstream ss;
  349. ss << 'M'
  350. << static_cast<int>(month_) << '.'
  351. << 5 << '.'
  352. << static_cast<int>(dow_);
  353. return ss.str();
  354. }
  355. private:
  356. month_type month_;
  357. day_of_week_type dow_;
  358. };
  359. //! Calculate something like "First Sunday after Jan 1,2002
  360. /*! Date generator that takes a date and finds kday after
  361. *@code
  362. typedef boost::date_time::first_kday_after<date> firstkdayafter;
  363. firstkdayafter fkaf(Monday);
  364. fkaf.get_date(date(2002,Feb,1));
  365. @endcode
  366. * \ingroup date_alg
  367. */
  368. template<class date_type>
  369. class first_kday_after
  370. {
  371. public:
  372. typedef typename date_type::calendar_type calendar_type;
  373. typedef typename calendar_type::day_of_week_type day_of_week_type;
  374. typedef typename date_type::duration_type duration_type;
  375. first_kday_after(day_of_week_type dow) :
  376. dow_(dow)
  377. {}
  378. //! Return next kday given.
  379. date_type get_date(date_type start_day) const
  380. {
  381. duration_type one_day(1);
  382. date_type d = start_day + one_day;
  383. while (dow_ != d.day_of_week()) {
  384. d = d + one_day;
  385. }
  386. return d;
  387. }
  388. // added for streaming
  389. day_of_week_type day_of_week() const
  390. {
  391. return dow_;
  392. }
  393. private:
  394. day_of_week_type dow_;
  395. };
  396. //! Calculate something like "First Sunday before Jan 1,2002
  397. /*! Date generator that takes a date and finds kday after
  398. *@code
  399. typedef boost::date_time::first_kday_before<date> firstkdaybefore;
  400. firstkdaybefore fkbf(Monday);
  401. fkbf.get_date(date(2002,Feb,1));
  402. @endcode
  403. * \ingroup date_alg
  404. */
  405. template<class date_type>
  406. class first_kday_before
  407. {
  408. public:
  409. typedef typename date_type::calendar_type calendar_type;
  410. typedef typename calendar_type::day_of_week_type day_of_week_type;
  411. typedef typename date_type::duration_type duration_type;
  412. first_kday_before(day_of_week_type dow) :
  413. dow_(dow)
  414. {}
  415. //! Return next kday given.
  416. date_type get_date(date_type start_day) const
  417. {
  418. duration_type one_day(1);
  419. date_type d = start_day - one_day;
  420. while (dow_ != d.day_of_week()) {
  421. d = d - one_day;
  422. }
  423. return d;
  424. }
  425. // added for streaming
  426. day_of_week_type day_of_week() const
  427. {
  428. return dow_;
  429. }
  430. private:
  431. day_of_week_type dow_;
  432. };
  433. //! Calculates the number of days until the next weekday
  434. /*! Calculates the number of days until the next weekday.
  435. * If the date given falls on a Sunday and the given weekday
  436. * is Tuesday the result will be 2 days */
  437. template<typename date_type, class weekday_type>
  438. inline
  439. typename date_type::duration_type days_until_weekday(const date_type& d, const weekday_type& wd)
  440. {
  441. typedef typename date_type::duration_type duration_type;
  442. duration_type wks(0);
  443. duration_type dd(wd.as_number() - d.day_of_week().as_number());
  444. if(dd.is_negative()){
  445. wks = duration_type(7);
  446. }
  447. return dd + wks;
  448. }
  449. //! Calculates the number of days since the previous weekday
  450. /*! Calculates the number of days since the previous weekday
  451. * If the date given falls on a Sunday and the given weekday
  452. * is Tuesday the result will be 5 days. The answer will be a positive
  453. * number because Tuesday is 5 days before Sunday, not -5 days before. */
  454. template<typename date_type, class weekday_type>
  455. inline
  456. typename date_type::duration_type days_before_weekday(const date_type& d, const weekday_type& wd)
  457. {
  458. typedef typename date_type::duration_type duration_type;
  459. duration_type wks(0);
  460. duration_type dd(wd.as_number() - d.day_of_week().as_number());
  461. if(dd.days() > 0){
  462. wks = duration_type(7);
  463. }
  464. // we want a number of days, not an offset. The value returned must
  465. // be zero or larger.
  466. return (-dd + wks);
  467. }
  468. //! Generates a date object representing the date of the following weekday from the given date
  469. /*! Generates a date object representing the date of the following
  470. * weekday from the given date. If the date given is 2004-May-9
  471. * (a Sunday) and the given weekday is Tuesday then the resulting date
  472. * will be 2004-May-11. */
  473. template<class date_type, class weekday_type>
  474. inline
  475. date_type next_weekday(const date_type& d, const weekday_type& wd)
  476. {
  477. return d + days_until_weekday(d, wd);
  478. }
  479. //! Generates a date object representing the date of the previous weekday from the given date
  480. /*! Generates a date object representing the date of the previous
  481. * weekday from the given date. If the date given is 2004-May-9
  482. * (a Sunday) and the given weekday is Tuesday then the resulting date
  483. * will be 2004-May-4. */
  484. template<class date_type, class weekday_type>
  485. inline
  486. date_type previous_weekday(const date_type& d, const weekday_type& wd)
  487. {
  488. return d - days_before_weekday(d, wd);
  489. }
  490. } } //namespace date_time
  491. #endif