summaryrefslogtreecommitdiffstats
blob: 20cb40b676b51c2f984f501358163c649ee0c1e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#ifndef DATE_TIME_DST_RULES_HPP__
#define DATE_TIME_DST_RULES_HPP__

/* Copyright (c) 2002,2003, 2007 CrystalClear Software, Inc.
 * Use, modification and distribution is subject to the 
 * Boost Software License, Version 1.0. (See accompanying
 * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 * Author: Jeff Garland, Bart Garst
 * $Date: 2008-02-27 15:00:24 -0500 (Wed, 27 Feb 2008) $
 */

/*! @file dst_rules.hpp
  Contains template class to provide static dst rule calculations
*/

#include "boost/date_time/date_generators.hpp"
#include "boost/date_time/period.hpp"
#include "boost/date_time/date_defs.hpp"
#include <stdexcept>

namespace boost {
  namespace date_time {

    enum time_is_dst_result {is_not_in_dst, is_in_dst, 
                             ambiguous, invalid_time_label};


    //! Dynamic class used to caluclate dst transition information
    template<class date_type_, 
             class time_duration_type_>
    class dst_calculator
    {
    public:
      typedef time_duration_type_ time_duration_type;
      typedef date_type_ date_type;

      //! Check the local time offset when on dst start day
      /*! On this dst transition, the time label between
       *  the transition boundary and the boudary + the offset
       *  are invalid times.  If before the boundary then still 
       *  not in dst.  
       *@param time_of_day Time offset in the day for the local time
       *@param dst_start_offset_minutes Local day offset for start of dst
       *@param dst_length_minutes Number of minutes to adjust clock forward
       *@retval status of time label w.r.t. dst
       */
      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)
      {
        //std::cout << "here" << std::endl;
        if (time_of_day < time_duration_type(0,dst_start_offset_minutes,0)) {
          return is_not_in_dst;
        }
        long offset = dst_start_offset_minutes + dst_length_minutes;
        if (time_of_day >= time_duration_type(0,offset,0)) {
          return is_in_dst;
        }
        return invalid_time_label; 
      }

      //! Check the local time offset when on the last day of dst
      /*! This is the calculation for the DST end day.  On that day times
       *  prior to the conversion time - dst_length (1 am in US) are still 
       *  in dst.  Times between the above and the switch time are 
       *  ambiguous.  Times after the start_offset are not in dst.
       *@param time_of_day Time offset in the day for the local time
       *@param dst_end_offset_minutes Local time of day for end of dst
       *@retval status of time label w.r.t. dst
       */
      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)
      {
        //in US this will be 60 so offset in day is 1,0,0
        int offset = dst_end_offset_minutes-dst_length_minutes;
        if (time_of_day < time_duration_type(0,offset,0)) {
          return is_in_dst;
        }
        if (time_of_day >= time_duration_type(0,dst_end_offset_minutes,0)) {
          return is_not_in_dst;
        }
        return ambiguous;
      }

      //! Calculates if the given local time is dst or not
      /*! Determines if the time is really in DST or not.  Also checks for 
       *  invalid and ambiguous.
       *  @param current_day The day to check for dst
       *  @param time_of_day Time offset within the day to check 
       *  @param dst_start_day  Starting day of dst for the given locality
       *  @param dst_start_offset Time offset within day for dst boundary
       *  @param dst_end_day    Ending day of dst for the given locality
       *  @param dst_end_offset Time offset within day given in dst for dst boundary
       *  @param dst_length lenght of dst adjusment
       *  @retval The time is either ambiguous, invalid, in dst, or not in dst
       */
      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_minutes)
      {
        unsigned int start_minutes = 
          dst_start_offset.hours() * 60 + dst_start_offset.minutes();
        unsigned int end_minutes = 
          dst_end_offset.hours() * 60 + dst_end_offset.minutes();
        long length_minutes =  
          dst_length_minutes.hours() * 60 + dst_length_minutes.minutes();

        return local_is_dst(current_day, time_of_day,
                            dst_start_day, start_minutes,
                            dst_end_day, end_minutes,
                            length_minutes);
      }

      //! Calculates if the given local time is dst or not
      /*! Determines if the time is really in DST or not.  Also checks for 
       *  invalid and ambiguous.
       *  @param current_day The day to check for dst
       *  @param time_of_day Time offset within the day to check 
       *  @param dst_start_day  Starting day of dst for the given locality
       *  @param dst_start_offset_minutes Offset within day for dst 
       *         boundary (eg 120 for US which is 02:00:00)
       *  @param dst_end_day    Ending day of dst for the given locality
       *  @param dst_end_offset_minutes Offset within day given in dst for dst 
       *         boundary (eg 120 for US which is 02:00:00)
       *  @param dst_length_minutes Length of dst adjusment (eg: 60 for US)
       *  @retval The time is either ambiguous, invalid, in dst, or not in dst
       */
      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)
      {
        //in northern hemisphere dst is in the middle of the year
        if (dst_start_day < dst_end_day) {
          if ((current_day > dst_start_day) && (current_day < dst_end_day)) {
            return is_in_dst;
          }
          if ((current_day < dst_start_day) || (current_day > dst_end_day)) {
            return is_not_in_dst;
          }
        }
        else {//southern hemisphere dst is at begining /end of year
          if ((current_day < dst_start_day) && (current_day > dst_end_day)) {
            return is_not_in_dst;
          }
          if ((current_day > dst_start_day) || (current_day < dst_end_day)) {
            return is_in_dst;
          }
        }

        if (current_day == dst_start_day) {
          return process_local_dst_start_day(time_of_day,
                                             dst_start_offset_minutes,
                                             dst_length_minutes);
        }
      
        if (current_day == dst_end_day) {
          return process_local_dst_end_day(time_of_day,
                                           dst_end_offset_minutes,
                                           dst_length_minutes);
        }
        //you should never reach this statement
        return invalid_time_label;
      }

    };


    //! Compile-time configurable daylight savings time calculation engine
    /* This template provides the ability to configure a daylight savings
     * calculation at compile time covering all the cases.  Unfortunately
     * because of the number of dimensions related to daylight savings
     * calculation the number of parameters is high.  In addition, the
     * start and end transition rules are complex types that specify 
     * an algorithm for calculation of the starting day and ending
     * day of daylight savings time including the month and day 
     * specifications (eg: last sunday in October).
     *
     * @param date_type A type that represents dates, typically gregorian::date
     * @param time_duration_type Used for the offset in the day calculations
     * @param dst_traits A set of traits that define the rules of dst 
     *        calculation.  The dst_trait must include the following:
     * start_rule_functor - Rule to calculate the starting date of a
     *                      dst transition (eg: last_kday_of_month).
     * start_day - static function that returns month of dst start for 
     *             start_rule_functor
     * start_month -static function that returns day or day of week for 
     *              dst start of dst
     * end_rule_functor - Rule to calculate the end of dst day.
     * end_day - static fucntion that returns end day for end_rule_functor
     * end_month - static function that returns end month for end_rule_functor
     * 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.
     * 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. 
     * dst_length_minutes - number of minutes that dst shifts clock
     */
    template<class date_type, 
             class time_duration_type,
             class dst_traits>
    class dst_calc_engine
    {
    public:
      typedef typename date_type::year_type year_type;
      typedef typename date_type::calendar_type calendar_type;
      typedef dst_calculator<date_type, time_duration_type> dstcalc;

      //! Calculates if the given local time is dst or not
      /*! Determines if the time is really in DST or not.  Also checks for 
       *  invalid and ambiguous.
       *  @retval The time is either ambiguous, invalid, in dst, or not in dst
       */
      static time_is_dst_result local_is_dst(const date_type& d,
                                             const time_duration_type& td) 
      {

        year_type y = d.year();
        date_type dst_start = local_dst_start_day(y);
        date_type dst_end   = local_dst_end_day(y);
        return dstcalc::local_is_dst(d,td,
                                     dst_start,
                                     dst_traits::dst_start_offset_minutes(),
                                     dst_end, 
                                     dst_traits::dst_end_offset_minutes(), 
                                     dst_traits::dst_shift_length_minutes());
      
      }

      static bool is_dst_boundary_day(date_type d)
      {
        year_type y = d.year();
        return ((d == local_dst_start_day(y)) ||
                (d == local_dst_end_day(y)));
      }

      //! The time of day for the dst transition (eg: typically 01:00:00 or 02:00:00)
      static time_duration_type dst_offset() 
      {
        return time_duration_type(0,dst_traits::dst_shift_length_minutes(),0);
      }

      static date_type local_dst_start_day(year_type year)
      {
        return dst_traits::local_dst_start_day(year);      
      }

      static date_type local_dst_end_day(year_type year)
      {
        return dst_traits::local_dst_end_day(year);
      }


    };

    //! Depricated: Class to calculate dst boundaries for US time zones
    /* Use dst_calc_engine instead.
     * In 2007 US/Canada DST rules changed
     * (http://en.wikipedia.org/wiki/Energy_Policy_Act_of_2005#Change_to_daylight_saving_time).
     */
    template<class date_type_, 
             class time_duration_type_,
             unsigned int dst_start_offset_minutes=120, //from start of day 
             short dst_length_minutes=60>  //1 hour == 60 min in US
    class us_dst_rules 
    {
    public:
      typedef time_duration_type_ time_duration_type;
      typedef date_type_ date_type;
      typedef typename date_type::year_type year_type;
      typedef typename date_type::calendar_type calendar_type;
      typedef date_time::last_kday_of_month<date_type> lkday;
      typedef date_time::first_kday_of_month<date_type> fkday;
      typedef date_time::nth_kday_of_month<date_type> nkday;
      typedef dst_calculator<date_type, time_duration_type> dstcalc;

      //! Calculates if the given local time is dst or not
      /*! Determines if the time is really in DST or not.  Also checks for 
       *  invalid and ambiguous.
       *  @retval The time is either ambiguous, invalid, in dst, or not in dst
       */
      static time_is_dst_result local_is_dst(const date_type& d,
                                             const time_duration_type& td) 
      {

        year_type y = d.year();
        date_type dst_start = local_dst_start_day(y);
        date_type dst_end   = local_dst_end_day(y);
        return dstcalc::local_is_dst(d,td,
                                     dst_start,dst_start_offset_minutes,
                                     dst_end, dst_start_offset_minutes, 
                                     dst_length_minutes);
      
      }


      static bool is_dst_boundary_day(date_type d)
      {
        year_type y = d.year();
        return ((d == local_dst_start_day(y)) ||
                (d == local_dst_end_day(y)));
      }

      static date_type local_dst_start_day(year_type year)
      {
        if (year >= year_type(2007)) {
          //second sunday in march
          nkday ssim(nkday::second, Sunday, gregorian::Mar);
          return ssim.get_date(year);      
        } else {
          //first sunday in april
          fkday fsia(Sunday, gregorian::Apr);
          return fsia.get_date(year);      
        }
      }

      static date_type local_dst_end_day(year_type year)
      {
        if (year >= year_type(2007)) {
          //first sunday in november
          fkday fsin(Sunday, gregorian::Nov);
          return fsin.get_date(year);      
        } else {
          //last sunday in october
          lkday lsio(Sunday, gregorian::Oct);
          return lsio.get_date(year);
        }
      }

      static time_duration_type dst_offset()
      {
        return time_duration_type(0,dst_length_minutes,0);
      }

     private:


    };

    //! Used for local time adjustments in places that don't use dst
    template<class date_type_, class time_duration_type_>
    class null_dst_rules
    {
    public:
      typedef time_duration_type_ time_duration_type;
      typedef date_type_ date_type;


      //! Calculates if the given local time is dst or not
      /*! @retval Always is_not_in_dst since this is for zones without dst
       */
      static time_is_dst_result local_is_dst(const date_type&, 
                                             const time_duration_type&) 
      {
        return is_not_in_dst;
      }
    
      //! Calculates if the given utc time is in dst
      static time_is_dst_result utc_is_dst(const date_type&, 
                                           const time_duration_type&) 
      {
        return is_not_in_dst;
      }

      static bool is_dst_boundary_day(date_type d)
      {
        return false;
      }

      static time_duration_type dst_offset() 
      {
        return time_duration_type(0,0,0);
      }

    };


  } } //namespace date_time



#endif