1 | #ifndef _DATE_TIME_POSIX_TIME_ZONE__
|
---|
2 | #define _DATE_TIME_POSIX_TIME_ZONE__
|
---|
3 |
|
---|
4 | /* Copyright (c) 2003-2005 CrystalClear Software, Inc.
|
---|
5 | * Subject to the Boost Software License, Version 1.0. (See accompanying
|
---|
6 | * file LICENSE-1.0 or http://www.boost.org/LICENSE-1.0)
|
---|
7 | * Author: Jeff Garland, Bart Garst
|
---|
8 | * $Date: 2005/05/25 14:15:41 $
|
---|
9 | */
|
---|
10 |
|
---|
11 | #include <string>
|
---|
12 | #include <sstream>
|
---|
13 | #include "boost/date_time/gregorian/gregorian.hpp"
|
---|
14 | #include "boost/date_time/time_zone_names.hpp"
|
---|
15 | #include "boost/date_time/time_zone_base.hpp"
|
---|
16 | #include "boost/date_time/local_time/dst_transition_day_rules.hpp"
|
---|
17 | #include "boost/date_time/posix_time/posix_time.hpp"
|
---|
18 | #include "boost/tokenizer.hpp"
|
---|
19 | #include <stdexcept>
|
---|
20 |
|
---|
21 | namespace boost{
|
---|
22 | namespace local_time{
|
---|
23 |
|
---|
24 | //! simple exception for UTC and Daylight savings start/end offsets
|
---|
25 | struct bad_offset : public std::out_of_range
|
---|
26 | {
|
---|
27 | bad_offset(std::string _msg="") : std::out_of_range(std::string("Offset out of range: " + _msg)) {}
|
---|
28 | };
|
---|
29 | //! simple exception for UTC daylight savings adjustment
|
---|
30 | struct bad_adjustment : public std::out_of_range
|
---|
31 | {
|
---|
32 | bad_adjustment(std::string _msg="") : std::out_of_range(std::string("Adjustment out of range: " + _msg)) {}
|
---|
33 | };
|
---|
34 |
|
---|
35 | typedef boost::date_time::time_zone_names time_zone_names;
|
---|
36 | typedef boost::date_time::dst_adjustment_offsets<boost::posix_time::time_duration> dst_adjustment_offsets;
|
---|
37 | typedef boost::date_time::time_zone_base<boost::posix_time::ptime> time_zone;
|
---|
38 |
|
---|
39 | //! A time zone class constructed from a POSIX time zone string
|
---|
40 | /*! A POSIX time zone string takes the form of:<br>
|
---|
41 | * "std offset dst [offset],start[/time],end[/time]" (w/no spaces)
|
---|
42 | * 'std' specifies the abbrev of the time zone.<br>
|
---|
43 | * 'offset' is the offset from UTC.<br>
|
---|
44 | * 'dst' specifies the abbrev of the time zone during daylight savings time.<br>
|
---|
45 | * The second offset is how many hours changed during DST. Default=1<br>
|
---|
46 | * 'start' and'end' are the dates when DST goes into (and out of) effect.<br>
|
---|
47 | * 'offset' takes the form of: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}<br>
|
---|
48 | * 'time' and 'offset' take the same form. Time defaults=02:00:00<br>
|
---|
49 | * 'start' and 'end' can be one of three forms:<br>
|
---|
50 | * Mm.w.d {month=1-12, week=1-5 (5 is always last), day=0-6}<br>
|
---|
51 | * Jn {n=1-365 Feb29 is never counted}<br>
|
---|
52 | * n {n=0-365 Feb29 is counted in leap years}<br>
|
---|
53 | * Example "PST-5PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00"
|
---|
54 | * <br>
|
---|
55 | * Exceptions will be thrown under these conditions:<br>
|
---|
56 | * An invalid date spec (see date class)<br>
|
---|
57 | * A boost::local_time::bad_offset exception will be thrown for:<br>
|
---|
58 | * A DST start or end offset that is negative or more than 24 hours<br>
|
---|
59 | * A UTC zone that is greater than +12 or less than -12 hours<br>
|
---|
60 | * A boost::local_time::bad_adjustment exception will be thrown for:<br>
|
---|
61 | * A DST adjustment that is 24 hours or more (positive or negative)<br>
|
---|
62 | */
|
---|
63 | class posix_time_zone : public time_zone {
|
---|
64 | public:
|
---|
65 | typedef boost::posix_time::time_duration time_duration_type;
|
---|
66 | typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
---|
67 | typedef time_zone base_type;
|
---|
68 | typedef base_type::string_type string_type;
|
---|
69 | typedef base_type::stringstream_type stringstream_type;
|
---|
70 |
|
---|
71 | //! Construct from a POSIX time zone string
|
---|
72 | posix_time_zone(const std::string& s) :
|
---|
73 | zone_names_("std_name","std_abbrev","no-dst","no-dst"),
|
---|
74 | has_dst_(false),
|
---|
75 | base_utc_offset_(posix_time::hours(0)),
|
---|
76 | dst_offsets_(posix_time::hours(0),posix_time::hours(0),posix_time::hours(0)),
|
---|
77 | dst_calc_rules_()
|
---|
78 | {
|
---|
79 | boost::char_separator<char> sep(",");
|
---|
80 | tokenizer tokens(s, sep);
|
---|
81 | tokenizer::iterator it = tokens.begin();
|
---|
82 | calc_zone(*it++);
|
---|
83 | if(has_dst_){
|
---|
84 | std::string tmp_str = *it++;
|
---|
85 | calc_rules(tmp_str, *it);
|
---|
86 | }
|
---|
87 | }
|
---|
88 | virtual ~posix_time_zone() {};
|
---|
89 | //!String for the zone when not in daylight savings (eg: EST)
|
---|
90 | virtual std::string std_zone_abbrev()const
|
---|
91 | {
|
---|
92 | return zone_names_.std_zone_abbrev();
|
---|
93 | }
|
---|
94 | //!String for the timezone when in daylight savings (eg: EDT)
|
---|
95 | /*! For those time zones that have no DST, an empty string is used */
|
---|
96 | virtual std::string dst_zone_abbrev() const
|
---|
97 | {
|
---|
98 | return zone_names_.dst_zone_abbrev();
|
---|
99 | }
|
---|
100 | //!String for the zone when not in daylight savings (eg: Eastern Standard Time)
|
---|
101 | /*! The full STD name is not extracted from the posix time zone string.
|
---|
102 | * Therefore, the STD abbreviation is used in it's place */
|
---|
103 | virtual std::string std_zone_name()const
|
---|
104 | {
|
---|
105 | return zone_names_.std_zone_name();
|
---|
106 | }
|
---|
107 | //!String for the timezone when in daylight savings (eg: Eastern Daylight Time)
|
---|
108 | /*! The full DST name is not extracted from the posix time zone string.
|
---|
109 | * Therefore, the STD abbreviation is used in it's place. For time zones
|
---|
110 | * that have no DST, an empty string is used */
|
---|
111 | virtual std::string dst_zone_name()const
|
---|
112 | {
|
---|
113 | return zone_names_.dst_zone_name();
|
---|
114 | }
|
---|
115 | //! True if zone uses daylight savings adjustments otherwise false
|
---|
116 | virtual bool has_dst()const
|
---|
117 | {
|
---|
118 | return has_dst_;
|
---|
119 | }
|
---|
120 | //! Local time that DST starts -- NADT if has_dst is false
|
---|
121 | virtual posix_time::ptime dst_local_start_time(gregorian::greg_year y)const
|
---|
122 | {
|
---|
123 | gregorian::date d(gregorian::not_a_date_time);
|
---|
124 | if(has_dst_)
|
---|
125 | {
|
---|
126 | d = dst_calc_rules_->start_day(y);
|
---|
127 | }
|
---|
128 | return posix_time::ptime(d, dst_offsets_.dst_start_offset_);
|
---|
129 | }
|
---|
130 | //! Local time that DST ends -- NADT if has_dst is false
|
---|
131 | virtual posix_time::ptime dst_local_end_time(gregorian::greg_year y)const
|
---|
132 | {
|
---|
133 | gregorian::date d(gregorian::not_a_date_time);
|
---|
134 | if(has_dst_)
|
---|
135 | {
|
---|
136 | d = dst_calc_rules_->end_day(y);
|
---|
137 | }
|
---|
138 | return posix_time::ptime(d, dst_offsets_.dst_end_offset_);
|
---|
139 | }
|
---|
140 | //! Base offset from UTC for zone (eg: -07:30:00)
|
---|
141 | virtual time_duration_type base_utc_offset()const
|
---|
142 | {
|
---|
143 | return base_utc_offset_;
|
---|
144 | }
|
---|
145 | //! Adjustment forward or back made while DST is in effect
|
---|
146 | virtual time_duration_type dst_offset()const
|
---|
147 | {
|
---|
148 | return dst_offsets_.dst_adjust_;
|
---|
149 | }
|
---|
150 |
|
---|
151 | //! Returns a POSIX time_zone string for this object
|
---|
152 | virtual string_type to_posix_string() const
|
---|
153 | {
|
---|
154 | // std offset dst [offset],start[/time],end[/time] - w/o spaces
|
---|
155 | stringstream_type ss;
|
---|
156 | ss.fill('0');
|
---|
157 | boost::shared_ptr<dst_calc_rule> no_rules;
|
---|
158 | // std
|
---|
159 | ss << std_zone_abbrev();
|
---|
160 | // offset
|
---|
161 | if(base_utc_offset().is_negative()) {
|
---|
162 | // inverting the sign guarantees we get two digits
|
---|
163 | ss << '-' << std::setw(2) << base_utc_offset().invert_sign().hours();
|
---|
164 | }
|
---|
165 | else {
|
---|
166 | ss << '+' << std::setw(2) << base_utc_offset().hours();
|
---|
167 | }
|
---|
168 | if(base_utc_offset().minutes() != 0 || base_utc_offset().seconds() != 0) {
|
---|
169 | ss << ':' << std::setw(2) << base_utc_offset().minutes();
|
---|
170 | if(base_utc_offset().seconds() != 0) {
|
---|
171 | ss << ':' << std::setw(2) << base_utc_offset().seconds();
|
---|
172 | }
|
---|
173 | }
|
---|
174 | if(dst_calc_rules_ != no_rules) {
|
---|
175 | // dst
|
---|
176 | ss << dst_zone_abbrev();
|
---|
177 | // dst offset
|
---|
178 | if(dst_offset().is_negative()) {
|
---|
179 | // inverting the sign guarantees we get two digits
|
---|
180 | ss << '-' << std::setw(2) << dst_offset().invert_sign().hours();
|
---|
181 | }
|
---|
182 | else {
|
---|
183 | ss << '+' << std::setw(2) << dst_offset().hours();
|
---|
184 | }
|
---|
185 | if(dst_offset().minutes() != 0 || dst_offset().seconds() != 0) {
|
---|
186 | ss << ':' << std::setw(2) << dst_offset().minutes();
|
---|
187 | if(dst_offset().seconds() != 0) {
|
---|
188 | ss << ':' << std::setw(2) << dst_offset().seconds();
|
---|
189 | }
|
---|
190 | }
|
---|
191 | // start/time
|
---|
192 | ss << ',' << dst_calc_rules_->start_rule_as_string() << '/'
|
---|
193 | << std::setw(2) << dst_offsets_.dst_start_offset_.hours() << ':'
|
---|
194 | << std::setw(2) << dst_offsets_.dst_start_offset_.minutes();
|
---|
195 | if(dst_offsets_.dst_start_offset_.seconds() != 0) {
|
---|
196 | ss << ':' << std::setw(2) << dst_offsets_.dst_start_offset_.seconds();
|
---|
197 | }
|
---|
198 | // end/time
|
---|
199 | ss << ',' << dst_calc_rules_->end_rule_as_string() << '/'
|
---|
200 | << std::setw(2) << dst_offsets_.dst_end_offset_.hours() << ':'
|
---|
201 | << std::setw(2) << dst_offsets_.dst_end_offset_.minutes();
|
---|
202 | if(dst_offsets_.dst_end_offset_.seconds() != 0) {
|
---|
203 | ss << ':' << std::setw(2) << dst_offsets_.dst_end_offset_.seconds();
|
---|
204 | }
|
---|
205 | }
|
---|
206 |
|
---|
207 | return ss.str();
|
---|
208 | }
|
---|
209 | private:
|
---|
210 | time_zone_names zone_names_;
|
---|
211 | bool has_dst_;
|
---|
212 | time_duration_type base_utc_offset_;
|
---|
213 | dst_adjustment_offsets dst_offsets_;
|
---|
214 | boost::shared_ptr<dst_calc_rule> dst_calc_rules_;
|
---|
215 |
|
---|
216 | /*! Extract time zone abbreviations for STD & DST as well
|
---|
217 | * as the offsets for the time the shift occurs and how
|
---|
218 | * much of a shift. At this time full time zone names are
|
---|
219 | * NOT extracted so the abbreviations are used in their place */
|
---|
220 | void calc_zone(const std::string& obj){
|
---|
221 | std::stringstream ss("");
|
---|
222 | std::string::const_iterator sit = obj.begin();
|
---|
223 | std::string std_zone_abbrev("std_abbrev"), dst_zone_abbrev("");
|
---|
224 |
|
---|
225 | // get 'std' name/abbrev
|
---|
226 | while(std::isalpha(*sit)){
|
---|
227 | ss << *sit++;
|
---|
228 | }
|
---|
229 | std_zone_abbrev = ss.str();
|
---|
230 | ss.str("");
|
---|
231 |
|
---|
232 | // get UTC offset
|
---|
233 | if(sit != obj.end()){
|
---|
234 | // get duration
|
---|
235 | while(sit != obj.end() && !std::isalpha(*sit)){
|
---|
236 | ss << *sit++;
|
---|
237 | }
|
---|
238 | base_utc_offset_ = posix_time::duration_from_string(ss.str());
|
---|
239 | ss.str("");
|
---|
240 |
|
---|
241 | // base offset must be within range of -12 hours to +12 hours
|
---|
242 | if(base_utc_offset_ < time_duration_type(-12,0,0) ||
|
---|
243 | base_utc_offset_ > time_duration_type(12,0,0))
|
---|
244 | {
|
---|
245 | throw bad_offset(posix_time::to_simple_string(base_utc_offset_));
|
---|
246 | }
|
---|
247 | }
|
---|
248 |
|
---|
249 | // get DST data if given
|
---|
250 | if(sit != obj.end()){
|
---|
251 | has_dst_ = true;
|
---|
252 |
|
---|
253 | // get 'dst' name/abbrev
|
---|
254 | while(sit != obj.end() && std::isalpha(*sit)){
|
---|
255 | ss << *sit++;
|
---|
256 | }
|
---|
257 | dst_zone_abbrev = ss.str();
|
---|
258 | ss.str("");
|
---|
259 |
|
---|
260 | // get DST offset if given
|
---|
261 | if(sit != obj.end()){
|
---|
262 | // get duration
|
---|
263 | while(sit != obj.end() && !std::isalpha(*sit)){
|
---|
264 | ss << *sit++;
|
---|
265 | }
|
---|
266 | dst_offsets_.dst_adjust_ =
|
---|
267 | posix_time::duration_from_string(ss.str());
|
---|
268 | ss.str("");
|
---|
269 | }
|
---|
270 | else{ // default DST offset
|
---|
271 | dst_offsets_.dst_adjust_ = posix_time::hours(1);
|
---|
272 | }
|
---|
273 |
|
---|
274 | // adjustment must be within +|- 1 day
|
---|
275 | if(dst_offsets_.dst_adjust_ <= time_duration_type(-24,0,0) ||
|
---|
276 | dst_offsets_.dst_adjust_ >= time_duration_type(24,0,0))
|
---|
277 | {
|
---|
278 | throw bad_adjustment(posix_time::to_simple_string(dst_offsets_.dst_adjust_));
|
---|
279 | }
|
---|
280 | }
|
---|
281 | // full names not extracted so abbrevs used in their place
|
---|
282 | zone_names_ = time_zone_names(std_zone_abbrev, std_zone_abbrev, dst_zone_abbrev, dst_zone_abbrev);
|
---|
283 | }
|
---|
284 |
|
---|
285 | void calc_rules(const std::string& start, const std::string& end){
|
---|
286 | boost::char_separator<char> sep("/");
|
---|
287 | tokenizer st_tok(start, sep);
|
---|
288 | tokenizer et_tok(end, sep);
|
---|
289 | tokenizer::iterator sit = st_tok.begin();
|
---|
290 | tokenizer::iterator eit = et_tok.begin();
|
---|
291 |
|
---|
292 | // generate date spec
|
---|
293 | char x = std::string(*sit).at(0);
|
---|
294 | if(x == 'M'){
|
---|
295 | M_func(*sit, *eit);
|
---|
296 | }
|
---|
297 | else if(x == 'J'){
|
---|
298 | julian_no_leap(*sit, *eit);
|
---|
299 | }
|
---|
300 | else{
|
---|
301 | julian_day(*sit, *eit);
|
---|
302 | }
|
---|
303 |
|
---|
304 | ++sit;
|
---|
305 | ++eit;
|
---|
306 | // generate durations
|
---|
307 | // starting offset
|
---|
308 | if(sit != st_tok.end()){
|
---|
309 | dst_offsets_.dst_start_offset_ = posix_time::duration_from_string(*sit);
|
---|
310 | }
|
---|
311 | else{
|
---|
312 | // default
|
---|
313 | dst_offsets_.dst_start_offset_ = posix_time::hours(2);
|
---|
314 | }
|
---|
315 | // start/end offsets must fall on given date
|
---|
316 | if(dst_offsets_.dst_start_offset_ < time_duration_type(0,0,0) ||
|
---|
317 | dst_offsets_.dst_start_offset_ >= time_duration_type(24,0,0))
|
---|
318 | {
|
---|
319 | throw bad_offset(posix_time::to_simple_string(dst_offsets_.dst_start_offset_));
|
---|
320 | }
|
---|
321 |
|
---|
322 | // ending offset
|
---|
323 | if(eit != et_tok.end()){
|
---|
324 | dst_offsets_.dst_end_offset_ = posix_time::duration_from_string(*eit);
|
---|
325 | }
|
---|
326 | else{
|
---|
327 | // default
|
---|
328 | dst_offsets_.dst_end_offset_ = posix_time::hours(2);
|
---|
329 | }
|
---|
330 | // start/end offsets must fall on given date
|
---|
331 | if(dst_offsets_.dst_end_offset_ < time_duration_type(0,0,0) ||
|
---|
332 | dst_offsets_.dst_end_offset_ >= time_duration_type(24,0,0))
|
---|
333 | {
|
---|
334 | throw bad_offset(posix_time::to_simple_string(dst_offsets_.dst_end_offset_));
|
---|
335 | }
|
---|
336 | }
|
---|
337 |
|
---|
338 | /* Parses out a start/end date spec from a posix time zone string.
|
---|
339 | * Date specs come in three possible formats, this function handles
|
---|
340 | * the 'M' spec. Ex "M2.2.4" => 2nd month, 2nd week, 4th day .
|
---|
341 | */
|
---|
342 | void M_func(const std::string& s, const std::string& e){
|
---|
343 | typedef gregorian::nth_kday_of_month nkday;
|
---|
344 | unsigned short sm=0,sw=0,sd=0,em=0,ew=0,ed=0; // start/end month,week,day
|
---|
345 | char_separator<char> sep("M.");
|
---|
346 | tokenizer stok(s, sep), etok(e, sep);
|
---|
347 |
|
---|
348 | tokenizer::iterator it = stok.begin();
|
---|
349 | sm = lexical_cast<unsigned short>(*it++);
|
---|
350 | sw = lexical_cast<unsigned short>(*it++);
|
---|
351 | sd = lexical_cast<unsigned short>(*it);
|
---|
352 |
|
---|
353 | it = etok.begin();
|
---|
354 | em = lexical_cast<unsigned short>(*it++);
|
---|
355 | ew = lexical_cast<unsigned short>(*it++);
|
---|
356 | ed = lexical_cast<unsigned short>(*it);
|
---|
357 |
|
---|
358 | dst_calc_rules_ = shared_ptr<dst_calc_rule>(
|
---|
359 | new nth_kday_dst_rule(
|
---|
360 | nth_last_dst_rule::start_rule(
|
---|
361 | static_cast<nkday::week_num>(sw),sd,sm),
|
---|
362 | nth_last_dst_rule::start_rule(
|
---|
363 | static_cast<nkday::week_num>(ew),ed,em)
|
---|
364 | )
|
---|
365 | );
|
---|
366 | }
|
---|
367 |
|
---|
368 | //! Julian day. Feb29 is never counted, even in leap years
|
---|
369 | // expects range of 1-365
|
---|
370 | void julian_no_leap(const std::string& s, const std::string& e){
|
---|
371 | typedef gregorian::gregorian_calendar calendar;
|
---|
372 | const unsigned short year = 2001; // Non-leap year
|
---|
373 | unsigned short sm=1;
|
---|
374 | int sd=0;
|
---|
375 | sd = lexical_cast<int>(s.substr(1)); // skip 'J'
|
---|
376 | while(sd >= calendar::end_of_month_day(year,sm)){
|
---|
377 | sd -= calendar::end_of_month_day(year,sm++);
|
---|
378 | }
|
---|
379 | unsigned short em=1;
|
---|
380 | int ed=0;
|
---|
381 | ed = lexical_cast<int>(e.substr(1)); // skip 'J'
|
---|
382 | while(ed > calendar::end_of_month_day(year,em)){
|
---|
383 | ed -= calendar::end_of_month_day(year,em++);
|
---|
384 | }
|
---|
385 |
|
---|
386 | dst_calc_rules_ = shared_ptr<dst_calc_rule>(
|
---|
387 | new partial_date_dst_rule(
|
---|
388 | partial_date_dst_rule::start_rule(
|
---|
389 | sd, static_cast<date_time::months_of_year>(sm)),
|
---|
390 | partial_date_dst_rule::end_rule(
|
---|
391 | ed, static_cast<date_time::months_of_year>(em))
|
---|
392 | )
|
---|
393 | );
|
---|
394 | }
|
---|
395 |
|
---|
396 | //! Julian day. Feb29 is always counted, but exception thrown in non-leap years
|
---|
397 | // expects range of 0-365
|
---|
398 | void julian_day(const std::string& s, const std::string& e){
|
---|
399 | int sd=0, ed=0;
|
---|
400 | sd = lexical_cast<int>(s);
|
---|
401 | ed = lexical_cast<int>(e);
|
---|
402 | dst_calc_rules_ = shared_ptr<dst_calc_rule>(
|
---|
403 | new partial_date_dst_rule(
|
---|
404 | partial_date_dst_rule::start_rule(++sd),// args are 0-365
|
---|
405 | partial_date_dst_rule::end_rule(++ed) // pd expects 1-366
|
---|
406 | )
|
---|
407 | );
|
---|
408 | }
|
---|
409 |
|
---|
410 | //! helper function used when throwing exceptions
|
---|
411 | static std::string td_as_string(const time_duration_type& td)
|
---|
412 | {
|
---|
413 | std::string s;
|
---|
414 | #if defined(USE_DATE_TIME_PRE_1_33_FACET_IO)
|
---|
415 | s = posix_time::to_simple_string(td);
|
---|
416 | #else
|
---|
417 | std::stringstream ss;
|
---|
418 | ss << td;
|
---|
419 | s = ss.str();
|
---|
420 | #endif
|
---|
421 | return s;
|
---|
422 | }
|
---|
423 | };
|
---|
424 |
|
---|
425 | } } // namespace boost::local_time
|
---|
426 |
|
---|
427 |
|
---|
428 | #endif // _DATE_TIME_POSIX_TIME_ZONE__
|
---|