1 #ifndef DATE_TIME_DST_RULES_HPP__
2 #define DATE_TIME_DST_RULES_HPP__
3 
4 /* Copyright (c) 2002,2003, 2007 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 dst_rules.hpp
13   Contains template class to provide static dst rule calculations
14 */
15 
16 #include "boost/date_time/date_generators.hpp"
17 #include "boost/date_time/period.hpp"
18 #include "boost/date_time/date_defs.hpp"
19 #include <stdexcept>
20 
21 namespace boost {
22   namespace date_time {
23 
24     enum time_is_dst_result {is_not_in_dst, is_in_dst,
25                              ambiguous, invalid_time_label};
26 
27 
28     //! Dynamic class used to caluclate dst transition information
29     template<class date_type_,
30              class time_duration_type_>
31     class dst_calculator
32     {
33     public:
34       typedef time_duration_type_ time_duration_type;
35       typedef date_type_ date_type;
36 
37       //! Check the local time offset when on dst start day
38       /*! On this dst transition, the time label between
39        *  the transition boundary and the boudary + the offset
40        *  are invalid times.  If before the boundary then still
41        *  not in dst.
42        *@param time_of_day Time offset in the day for the local time
43        *@param dst_start_offset_minutes Local day offset for start of dst
44        *@param dst_length_minutes Number of minutes to adjust clock forward
45        *@retval status of time label w.r.t. dst
46        */
47       static time_is_dst_result
process_local_dst_start_day(const time_duration_type & time_of_day,unsigned int dst_start_offset_minutes,long dst_length_minutes)48       process_local_dst_start_day(const time_duration_type& time_of_day,
49                                   unsigned int dst_start_offset_minutes,
50                                   long dst_length_minutes)
51       {
52         //std::cout << "here" << std::endl;
53         if (time_of_day < time_duration_type(0,dst_start_offset_minutes,0)) {
54           return is_not_in_dst;
55         }
56         long offset = dst_start_offset_minutes + dst_length_minutes;
57         if (time_of_day >= time_duration_type(0,offset,0)) {
58           return is_in_dst;
59         }
60         return invalid_time_label;
61       }
62 
63       //! Check the local time offset when on the last day of dst
64       /*! This is the calculation for the DST end day.  On that day times
65        *  prior to the conversion time - dst_length (1 am in US) are still
66        *  in dst.  Times between the above and the switch time are
67        *  ambiguous.  Times after the start_offset are not in dst.
68        *@param time_of_day Time offset in the day for the local time
69        *@param dst_end_offset_minutes Local time of day for end of dst
70        *@retval status of time label w.r.t. dst
71        */
72       static time_is_dst_result
process_local_dst_end_day(const time_duration_type & time_of_day,unsigned int dst_end_offset_minutes,long dst_length_minutes)73       process_local_dst_end_day(const time_duration_type& time_of_day,
74                                 unsigned int dst_end_offset_minutes,
75                                 long dst_length_minutes)
76       {
77         //in US this will be 60 so offset in day is 1,0,0
78         int offset = dst_end_offset_minutes-dst_length_minutes;
79         if (time_of_day < time_duration_type(0,offset,0)) {
80           return is_in_dst;
81         }
82         if (time_of_day >= time_duration_type(0,dst_end_offset_minutes,0)) {
83           return is_not_in_dst;
84         }
85         return ambiguous;
86       }
87 
88       //! Calculates if the given local time is dst or not
89       /*! Determines if the time is really in DST or not.  Also checks for
90        *  invalid and ambiguous.
91        *  @param current_day The day to check for dst
92        *  @param time_of_day Time offset within the day to check
93        *  @param dst_start_day  Starting day of dst for the given locality
94        *  @param dst_start_offset Time offset within day for dst boundary
95        *  @param dst_end_day    Ending day of dst for the given locality
96        *  @param dst_end_offset Time offset within day given in dst for dst boundary
97        *  @param dst_length_minutes length of dst adjusment
98        *  @retval The time is either ambiguous, invalid, in dst, or not in dst
99        */
100       static time_is_dst_result
local_is_dst(const date_type & current_day,const time_duration_type & time_of_day,const date_type & dst_start_day,const time_duration_type & dst_start_offset,const date_type & dst_end_day,const time_duration_type & dst_end_offset,const time_duration_type & dst_length)101       local_is_dst(const date_type& current_day,
102                    const time_duration_type& time_of_day,
103                    const date_type& dst_start_day,
104                    const time_duration_type& dst_start_offset,
105                    const date_type& dst_end_day,
106                    const time_duration_type& dst_end_offset,
107                    const time_duration_type& dst_length)
108       {
109         unsigned int start_minutes = static_cast<unsigned>(
110           dst_start_offset.hours() * 60 + dst_start_offset.minutes());
111         unsigned int end_minutes = static_cast<unsigned>(
112           dst_end_offset.hours() * 60 + dst_end_offset.minutes());
113         long length_minutes = static_cast<long>(
114           dst_length.hours() * 60 + dst_length.minutes());
115 
116         return local_is_dst(current_day, time_of_day,
117                             dst_start_day, start_minutes,
118                             dst_end_day, end_minutes,
119                             length_minutes);
120       }
121 
122       //! Calculates if the given local time is dst or not
123       /*! Determines if the time is really in DST or not.  Also checks for
124        *  invalid and ambiguous.
125        *  @param current_day The day to check for dst
126        *  @param time_of_day Time offset within the day to check
127        *  @param dst_start_day  Starting day of dst for the given locality
128        *  @param dst_start_offset_minutes Offset within day for dst
129        *         boundary (eg 120 for US which is 02:00:00)
130        *  @param dst_end_day    Ending day of dst for the given locality
131        *  @param dst_end_offset_minutes Offset within day given in dst for dst
132        *         boundary (eg 120 for US which is 02:00:00)
133        *  @param dst_length_minutes Length of dst adjusment (eg: 60 for US)
134        *  @retval The time is either ambiguous, invalid, in dst, or not in dst
135        */
136       static time_is_dst_result
local_is_dst(const date_type & current_day,const time_duration_type & time_of_day,const date_type & dst_start_day,unsigned int dst_start_offset_minutes,const date_type & dst_end_day,unsigned int dst_end_offset_minutes,long dst_length_minutes)137       local_is_dst(const date_type& current_day,
138                    const time_duration_type& time_of_day,
139                    const date_type& dst_start_day,
140                    unsigned int dst_start_offset_minutes,
141                    const date_type& dst_end_day,
142                    unsigned int dst_end_offset_minutes,
143                    long dst_length_minutes)
144       {
145         //in northern hemisphere dst is in the middle of the year
146         if (dst_start_day < dst_end_day) {
147           if ((current_day > dst_start_day) && (current_day < dst_end_day)) {
148             return is_in_dst;
149           }
150           if ((current_day < dst_start_day) || (current_day > dst_end_day)) {
151             return is_not_in_dst;
152           }
153         }
154         else {//southern hemisphere dst is at begining /end of year
155           if ((current_day < dst_start_day) && (current_day > dst_end_day)) {
156             return is_not_in_dst;
157           }
158           if ((current_day > dst_start_day) || (current_day < dst_end_day)) {
159             return is_in_dst;
160           }
161         }
162 
163         if (current_day == dst_start_day) {
164           return process_local_dst_start_day(time_of_day,
165                                              dst_start_offset_minutes,
166                                              dst_length_minutes);
167         }
168 
169         if (current_day == dst_end_day) {
170           return process_local_dst_end_day(time_of_day,
171                                            dst_end_offset_minutes,
172                                            dst_length_minutes);
173         }
174         //you should never reach this statement
175         return invalid_time_label;
176       }
177 
178     };
179 
180 
181     //! Compile-time configurable daylight savings time calculation engine
182     /* This template provides the ability to configure a daylight savings
183      * calculation at compile time covering all the cases.  Unfortunately
184      * because of the number of dimensions related to daylight savings
185      * calculation the number of parameters is high.  In addition, the
186      * start and end transition rules are complex types that specify
187      * an algorithm for calculation of the starting day and ending
188      * day of daylight savings time including the month and day
189      * specifications (eg: last sunday in October).
190      *
191      * @param date_type A type that represents dates, typically gregorian::date
192      * @param time_duration_type Used for the offset in the day calculations
193      * @param dst_traits A set of traits that define the rules of dst
194      *        calculation.  The dst_trait must include the following:
195      * start_rule_functor - Rule to calculate the starting date of a
196      *                      dst transition (eg: last_kday_of_month).
197      * start_day - static function that returns month of dst start for
198      *             start_rule_functor
199      * start_month -static function that returns day or day of week for
200      *              dst start of dst
201      * end_rule_functor - Rule to calculate the end of dst day.
202      * end_day - static fucntion that returns end day for end_rule_functor
203      * end_month - static function that returns end month for end_rule_functor
204      * dst_start_offset_minutes - number of minutes from start of day to transition to dst -- 120 (or 2:00 am) is typical for the U.S. and E.U.
205      * dst_start_offset_minutes - number of minutes from start of day to transition off of dst -- 180 (or 3:00 am) is typical for E.U.
206      * dst_length_minutes - number of minutes that dst shifts clock
207      */
208     template<class date_type,
209              class time_duration_type,
210              class dst_traits>
211     class dst_calc_engine
212     {
213     public:
214       typedef typename date_type::year_type year_type;
215       typedef typename date_type::calendar_type calendar_type;
216       typedef dst_calculator<date_type, time_duration_type> dstcalc;
217 
218       //! Calculates if the given local time is dst or not
219       /*! Determines if the time is really in DST or not.  Also checks for
220        *  invalid and ambiguous.
221        *  @retval The time is either ambiguous, invalid, in dst, or not in dst
222        */
local_is_dst(const date_type & d,const time_duration_type & td)223       static time_is_dst_result local_is_dst(const date_type& d,
224                                              const time_duration_type& td)
225       {
226 
227         year_type y = d.year();
228         date_type dst_start = local_dst_start_day(y);
229         date_type dst_end   = local_dst_end_day(y);
230         return dstcalc::local_is_dst(d,td,
231                                      dst_start,
232                                      dst_traits::dst_start_offset_minutes(),
233                                      dst_end,
234                                      dst_traits::dst_end_offset_minutes(),
235                                      dst_traits::dst_shift_length_minutes());
236 
237       }
238 
is_dst_boundary_day(date_type d)239       static bool is_dst_boundary_day(date_type d)
240       {
241         year_type y = d.year();
242         return ((d == local_dst_start_day(y)) ||
243                 (d == local_dst_end_day(y)));
244       }
245 
246       //! The time of day for the dst transition (eg: typically 01:00:00 or 02:00:00)
dst_offset()247       static time_duration_type dst_offset()
248       {
249         return time_duration_type(0,dst_traits::dst_shift_length_minutes(),0);
250       }
251 
local_dst_start_day(year_type year)252       static date_type local_dst_start_day(year_type year)
253       {
254         return dst_traits::local_dst_start_day(year);
255       }
256 
local_dst_end_day(year_type year)257       static date_type local_dst_end_day(year_type year)
258       {
259         return dst_traits::local_dst_end_day(year);
260       }
261 
262 
263     };
264 
265     //! Depricated: Class to calculate dst boundaries for US time zones
266     /* Use dst_calc_engine instead.
267      * In 2007 US/Canada DST rules changed
268      * (http://en.wikipedia.org/wiki/Energy_Policy_Act_of_2005#Change_to_daylight_saving_time).
269      */
270     template<class date_type_,
271              class time_duration_type_,
272              unsigned int dst_start_offset_minutes=120, //from start of day
273              short dst_length_minutes=60>  //1 hour == 60 min in US
274     class us_dst_rules
275     {
276     public:
277       typedef time_duration_type_ time_duration_type;
278       typedef date_type_ date_type;
279       typedef typename date_type::year_type year_type;
280       typedef typename date_type::calendar_type calendar_type;
281       typedef date_time::last_kday_of_month<date_type> lkday;
282       typedef date_time::first_kday_of_month<date_type> fkday;
283       typedef date_time::nth_kday_of_month<date_type> nkday;
284       typedef dst_calculator<date_type, time_duration_type> dstcalc;
285 
286       //! Calculates if the given local time is dst or not
287       /*! Determines if the time is really in DST or not.  Also checks for
288        *  invalid and ambiguous.
289        *  @retval The time is either ambiguous, invalid, in dst, or not in dst
290        */
local_is_dst(const date_type & d,const time_duration_type & td)291       static time_is_dst_result local_is_dst(const date_type& d,
292                                              const time_duration_type& td)
293       {
294 
295         year_type y = d.year();
296         date_type dst_start = local_dst_start_day(y);
297         date_type dst_end   = local_dst_end_day(y);
298         return dstcalc::local_is_dst(d,td,
299                                      dst_start,dst_start_offset_minutes,
300                                      dst_end, dst_start_offset_minutes,
301                                      dst_length_minutes);
302 
303       }
304 
305 
is_dst_boundary_day(date_type d)306       static bool is_dst_boundary_day(date_type d)
307       {
308         year_type y = d.year();
309         return ((d == local_dst_start_day(y)) ||
310                 (d == local_dst_end_day(y)));
311       }
312 
local_dst_start_day(year_type year)313       static date_type local_dst_start_day(year_type year)
314       {
315         if (year >= year_type(2007)) {
316           //second sunday in march
317           nkday ssim(nkday::second, Sunday, date_time::Mar);
318           return ssim.get_date(year);
319         } else {
320           //first sunday in april
321           fkday fsia(Sunday, date_time::Apr);
322           return fsia.get_date(year);
323         }
324       }
325 
local_dst_end_day(year_type year)326       static date_type local_dst_end_day(year_type year)
327       {
328         if (year >= year_type(2007)) {
329           //first sunday in november
330           fkday fsin(Sunday, date_time::Nov);
331           return fsin.get_date(year);
332         } else {
333           //last sunday in october
334           lkday lsio(Sunday, date_time::Oct);
335           return lsio.get_date(year);
336         }
337       }
338 
dst_offset()339       static time_duration_type dst_offset()
340       {
341         return time_duration_type(0,dst_length_minutes,0);
342       }
343 
344      private:
345 
346 
347     };
348 
349     //! Used for local time adjustments in places that don't use dst
350     template<class date_type_, class time_duration_type_>
351     class null_dst_rules
352     {
353     public:
354       typedef time_duration_type_ time_duration_type;
355       typedef date_type_ date_type;
356 
357 
358       //! Calculates if the given local time is dst or not
359       /*! @retval Always is_not_in_dst since this is for zones without dst
360        */
local_is_dst(const date_type &,const time_duration_type &)361       static time_is_dst_result local_is_dst(const date_type&,
362                                              const time_duration_type&)
363       {
364         return is_not_in_dst;
365       }
366 
367       //! Calculates if the given utc time is in dst
utc_is_dst(const date_type &,const time_duration_type &)368       static time_is_dst_result utc_is_dst(const date_type&,
369                                            const time_duration_type&)
370       {
371         return is_not_in_dst;
372       }
373 
is_dst_boundary_day(date_type)374       static bool is_dst_boundary_day(date_type /*d*/)
375       {
376         return false;
377       }
378 
dst_offset()379       static time_duration_type dst_offset()
380       {
381         return time_duration_type(0,0,0);
382       }
383 
384     };
385 
386 
387   } } //namespace date_time
388 
389 
390 
391 #endif
392