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