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 |
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 |
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 |
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 |
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 | */ |
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(year: y); |
229 | date_type dst_end = local_dst_end_day(year: 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 | |
239 | static bool is_dst_boundary_day(date_type d) |
240 | { |
241 | year_type y = d.year(); |
242 | return ((d == local_dst_start_day(year: y)) || |
243 | (d == local_dst_end_day(year: y))); |
244 | } |
245 | |
246 | //! The time of day for the dst transition (eg: typically 01:00:00 or 02:00:00) |
247 | static time_duration_type dst_offset() |
248 | { |
249 | return time_duration_type(0,dst_traits::dst_shift_length_minutes(),0); |
250 | } |
251 | |
252 | static date_type local_dst_start_day(year_type year) |
253 | { |
254 | return dst_traits::local_dst_start_day(year); |
255 | } |
256 | |
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 | */ |
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(year: y); |
297 | date_type dst_end = local_dst_end_day(year: 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 | |
306 | static bool is_dst_boundary_day(date_type d) |
307 | { |
308 | year_type y = d.year(); |
309 | return ((d == local_dst_start_day(year: y)) || |
310 | (d == local_dst_end_day(year: y))); |
311 | } |
312 | |
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 | |
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 | |
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 | */ |
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 |
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 | |
374 | static bool is_dst_boundary_day(date_type /*d*/) |
375 | { |
376 | return false; |
377 | } |
378 | |
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 | |