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 lenght 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_minutes) |

108 | { |

109 | unsigned int start_minutes = |

110 | dst_start_offset.hours() * 60 + dst_start_offset.minutes(); |

111 | unsigned int end_minutes = |

112 | dst_end_offset.hours() * 60 + dst_end_offset.minutes(); |

113 | long length_minutes = |

114 | dst_length_minutes.hours() * 60 + dst_length_minutes.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(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 | |

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) |

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(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 | |

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 | |

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, gregorian::Mar); |

318 | return ssim.get_date(year); |

319 | } else { |

320 | //first sunday in april |

321 | fkday fsia(Sunday, gregorian::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, gregorian::Nov); |

331 | return fsin.get_date(year); |

332 | } else { |

333 | //last sunday in october |

334 | lkday lsio(Sunday, gregorian::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 |