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