date_generators.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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 <stdexcept>
  14. #include <sstream>
  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
  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. virtual std::string to_string() const
  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. BOOST_DATE_TIME_DECL const char* nth_as_str(int n);
  149. //! Useful generator functor for finding holidays
  150. /*! Based on the idea in Cal. Calc. for finding holidays that are
  151. * the 'first Monday of September'. When instantiated with
  152. * 'fifth' kday of month, the result will be the last kday of month
  153. * which can be the fourth or fifth depending on the structure of
  154. * the month.
  155. *
  156. * The algorithm here basically guesses for the first
  157. * day of the month. Then finds the first day of the correct
  158. * type. That is, if the first of the month is a Tuesday
  159. * and it needs Wenesday then we simply increment by a day
  160. * and then we can add the length of a week until we get
  161. * to the 'nth kday'. There are probably more efficient
  162. * algorithms based on using a mod 7, but this one works
  163. * reasonably well for basic applications.
  164. * \ingroup date_alg
  165. */
  166. template<class date_type>
  167. class nth_kday_of_month : public year_based_generator<date_type>
  168. {
  169. public:
  170. typedef typename date_type::calendar_type calendar_type;
  171. typedef typename calendar_type::day_of_week_type day_of_week_type;
  172. typedef typename calendar_type::month_type month_type;
  173. typedef typename calendar_type::year_type year_type;
  174. typedef typename date_type::duration_type duration_type;
  175. enum week_num {first=1, second, third, fourth, fifth};
  176. nth_kday_of_month(week_num week_no,
  177. day_of_week_type dow,
  178. month_type m) :
  179. month_(m),
  180. wn_(week_no),
  181. dow_(dow)
  182. {}
  183. //! Return a concrete date when provided with a year specific year.
  184. date_type get_date(year_type y) const
  185. {
  186. date_type d(y, month_, 1); //first day of month
  187. duration_type one_day(1);
  188. duration_type one_week(7);
  189. while (dow_ != d.day_of_week()) {
  190. d = d + one_day;
  191. }
  192. int week = 1;
  193. while (week < wn_) {
  194. d = d + one_week;
  195. week++;
  196. }
  197. // remove wrapping to next month behavior
  198. if(d.month() != month_) {
  199. d = d - one_week;
  200. }
  201. return d;
  202. }
  203. // added for streaming
  204. month_type month() const
  205. {
  206. return month_;
  207. }
  208. week_num nth_week() const
  209. {
  210. return wn_;
  211. }
  212. day_of_week_type day_of_week() const
  213. {
  214. return dow_;
  215. }
  216. const char* nth_week_as_str() const
  217. {
  218. return nth_as_str(wn_);
  219. }
  220. //! Returns string suitable for use in POSIX time zone string
  221. /*! Returns a string formatted as "M4.3.0" ==> 3rd Sunday in April. */
  222. virtual std::string to_string() const
  223. {
  224. std::ostringstream ss;
  225. ss << 'M'
  226. << static_cast<int>(month_) << '.'
  227. << static_cast<int>(wn_) << '.'
  228. << static_cast<int>(dow_);
  229. return ss.str();
  230. }
  231. private:
  232. month_type month_;
  233. week_num wn_;
  234. day_of_week_type dow_;
  235. };
  236. //! Useful generator functor for finding holidays and daylight savings
  237. /*! Similar to nth_kday_of_month, but requires less paramters
  238. * \ingroup date_alg
  239. */
  240. template<class date_type>
  241. class first_kday_of_month : public year_based_generator<date_type>
  242. {
  243. public:
  244. typedef typename date_type::calendar_type calendar_type;
  245. typedef typename calendar_type::day_of_week_type day_of_week_type;
  246. typedef typename calendar_type::month_type month_type;
  247. typedef typename calendar_type::year_type year_type;
  248. typedef typename date_type::duration_type duration_type;
  249. //!Specify the first 'Sunday' in 'April' spec
  250. /*!@param dow The day of week, eg: Sunday, Monday, etc
  251. * @param m The month of the year, eg: Jan, Feb, Mar, etc
  252. */
  253. first_kday_of_month(day_of_week_type dow, month_type m) :
  254. month_(m),
  255. dow_(dow)
  256. {}
  257. //! Return a concrete date when provided with a year specific year.
  258. date_type get_date(year_type year) const
  259. {
  260. date_type d(year, month_,1);
  261. duration_type one_day(1);
  262. while (dow_ != d.day_of_week()) {
  263. d = d + one_day;
  264. }
  265. return d;
  266. }
  267. // added for streaming
  268. month_type month() const
  269. {
  270. return month_;
  271. }
  272. day_of_week_type day_of_week() const
  273. {
  274. return dow_;
  275. }
  276. //! Returns string suitable for use in POSIX time zone string
  277. /*! Returns a string formatted as "M4.1.0" ==> 1st Sunday in April. */
  278. virtual std::string to_string() const
  279. {
  280. std::ostringstream ss;
  281. ss << 'M'
  282. << static_cast<int>(month_) << '.'
  283. << 1 << '.'
  284. << static_cast<int>(dow_);
  285. return ss.str();
  286. }
  287. private:
  288. month_type month_;
  289. day_of_week_type dow_;
  290. };
  291. //! Calculate something like Last Sunday of January
  292. /*! Useful generator functor for finding holidays and daylight savings
  293. * Get the last day of the month and then calculate the difference
  294. * to the last previous day.
  295. * @tparam date_type A date class that exports day_of_week, month_type, etc.
  296. * \ingroup date_alg
  297. */
  298. template<class date_type>
  299. class last_kday_of_month : public year_based_generator<date_type>
  300. {
  301. public:
  302. typedef typename date_type::calendar_type calendar_type;
  303. typedef typename calendar_type::day_of_week_type day_of_week_type;
  304. typedef typename calendar_type::month_type month_type;
  305. typedef typename calendar_type::year_type year_type;
  306. typedef typename date_type::duration_type duration_type;
  307. //!Specify the date spec like last 'Sunday' in 'April' spec
  308. /*!@param dow The day of week, eg: Sunday, Monday, etc
  309. * @param m The month of the year, eg: Jan, Feb, Mar, etc
  310. */
  311. last_kday_of_month(day_of_week_type dow, month_type m) :
  312. month_(m),
  313. dow_(dow)
  314. {}
  315. //! Return a concrete date when provided with a year specific year.
  316. date_type get_date(year_type year) const
  317. {
  318. date_type d(year, month_, calendar_type::end_of_month_day(year,month_));
  319. duration_type one_day(1);
  320. while (dow_ != d.day_of_week()) {
  321. d = d - one_day;
  322. }
  323. return d;
  324. }
  325. // added for streaming
  326. month_type month() const
  327. {
  328. return month_;
  329. }
  330. day_of_week_type day_of_week() const
  331. {
  332. return dow_;
  333. }
  334. //! Returns string suitable for use in POSIX time zone string
  335. /*! Returns a string formatted as "M4.5.0" ==> last Sunday in April. */
  336. virtual std::string to_string() const
  337. {
  338. std::ostringstream ss;
  339. ss << 'M'
  340. << static_cast<int>(month_) << '.'
  341. << 5 << '.'
  342. << static_cast<int>(dow_);
  343. return ss.str();
  344. }
  345. private:
  346. month_type month_;
  347. day_of_week_type dow_;
  348. };
  349. //! Calculate something like "First Sunday after Jan 1,2002
  350. /*! Date generator that takes a date and finds kday after
  351. *@code
  352. typedef boost::date_time::first_kday_after<date> firstkdayafter;
  353. firstkdayafter fkaf(Monday);
  354. fkaf.get_date(date(2002,Feb,1));
  355. @endcode
  356. * \ingroup date_alg
  357. */
  358. template<class date_type>
  359. class first_kday_after
  360. {
  361. public:
  362. typedef typename date_type::calendar_type calendar_type;
  363. typedef typename calendar_type::day_of_week_type day_of_week_type;
  364. typedef typename date_type::duration_type duration_type;
  365. first_kday_after(day_of_week_type dow) :
  366. dow_(dow)
  367. {}
  368. //! Return next kday given.
  369. date_type get_date(date_type start_day) const
  370. {
  371. duration_type one_day(1);
  372. date_type d = start_day + one_day;
  373. while (dow_ != d.day_of_week()) {
  374. d = d + one_day;
  375. }
  376. return d;
  377. }
  378. // added for streaming
  379. day_of_week_type day_of_week() const
  380. {
  381. return dow_;
  382. }
  383. private:
  384. day_of_week_type dow_;
  385. };
  386. //! Calculate something like "First Sunday before Jan 1,2002
  387. /*! Date generator that takes a date and finds kday after
  388. *@code
  389. typedef boost::date_time::first_kday_before<date> firstkdaybefore;
  390. firstkdaybefore fkbf(Monday);
  391. fkbf.get_date(date(2002,Feb,1));
  392. @endcode
  393. * \ingroup date_alg
  394. */
  395. template<class date_type>
  396. class first_kday_before
  397. {
  398. public:
  399. typedef typename date_type::calendar_type calendar_type;
  400. typedef typename calendar_type::day_of_week_type day_of_week_type;
  401. typedef typename date_type::duration_type duration_type;
  402. first_kday_before(day_of_week_type dow) :
  403. dow_(dow)
  404. {}
  405. //! Return next kday given.
  406. date_type get_date(date_type start_day) const
  407. {
  408. duration_type one_day(1);
  409. date_type d = start_day - one_day;
  410. while (dow_ != d.day_of_week()) {
  411. d = d - one_day;
  412. }
  413. return d;
  414. }
  415. // added for streaming
  416. day_of_week_type day_of_week() const
  417. {
  418. return dow_;
  419. }
  420. private:
  421. day_of_week_type dow_;
  422. };
  423. //! Calculates the number of days until the next weekday
  424. /*! Calculates the number of days until the next weekday.
  425. * If the date given falls on a Sunday and the given weekday
  426. * is Tuesday the result will be 2 days */
  427. template<typename date_type, class weekday_type>
  428. inline
  429. typename date_type::duration_type days_until_weekday(const date_type& d, const weekday_type& wd)
  430. {
  431. typedef typename date_type::duration_type duration_type;
  432. duration_type wks(0);
  433. duration_type dd(wd.as_number() - d.day_of_week().as_number());
  434. if(dd.is_negative()){
  435. wks = duration_type(7);
  436. }
  437. return dd + wks;
  438. }
  439. //! Calculates the number of days since the previous weekday
  440. /*! Calculates the number of days since the previous weekday
  441. * If the date given falls on a Sunday and the given weekday
  442. * is Tuesday the result will be 5 days. The answer will be a positive
  443. * number because Tuesday is 5 days before Sunday, not -5 days before. */
  444. template<typename date_type, class weekday_type>
  445. inline
  446. typename date_type::duration_type days_before_weekday(const date_type& d, const weekday_type& wd)
  447. {
  448. typedef typename date_type::duration_type duration_type;
  449. duration_type wks(0);
  450. duration_type dd(wd.as_number() - d.day_of_week().as_number());
  451. if(dd.days() > 0){
  452. wks = duration_type(7);
  453. }
  454. // we want a number of days, not an offset. The value returned must
  455. // be zero or larger.
  456. return (-dd + wks);
  457. }
  458. //! Generates a date object representing the date of the following weekday from the given date
  459. /*! Generates a date object representing the date of the following
  460. * weekday from the given date. If the date given is 2004-May-9
  461. * (a Sunday) and the given weekday is Tuesday then the resulting date
  462. * will be 2004-May-11. */
  463. template<class date_type, class weekday_type>
  464. inline
  465. date_type next_weekday(const date_type& d, const weekday_type& wd)
  466. {
  467. return d + days_until_weekday(d, wd);
  468. }
  469. //! Generates a date object representing the date of the previous weekday from the given date
  470. /*! Generates a date object representing the date of the previous
  471. * weekday from the given date. If the date given is 2004-May-9
  472. * (a Sunday) and the given weekday is Tuesday then the resulting date
  473. * will be 2004-May-4. */
  474. template<class date_type, class weekday_type>
  475. inline
  476. date_type previous_weekday(const date_type& d, const weekday_type& wd)
  477. {
  478. return d - days_before_weekday(d, wd);
  479. }
  480. } } //namespace date_time
  481. #endif