1 #ifndef _DATE_TIME_POSIX_TIME_ZONE__
2 #define _DATE_TIME_POSIX_TIME_ZONE__
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.txt or http://www.boost.org/LICENSE_1_0.txt)
7 * Author: Jeff Garland, Bart Garst
14 #include <boost/tokenizer.hpp>
15 #include <boost/throw_exception.hpp>
16 #include <boost/date_time/gregorian/gregorian.hpp>
17 #include <boost/date_time/time_zone_names.hpp>
18 #include <boost/date_time/time_zone_base.hpp>
19 #include <boost/date_time/local_time/dst_transition_day_rules.hpp>
20 #include <boost/date_time/posix_time/posix_time.hpp>
21 #include <boost/date_time/string_convert.hpp>
22 #include <boost/date_time/time_parsing.hpp>
27 //! simple exception for UTC and Daylight savings start/end offsets
28 struct bad_offset
: public std::out_of_range
30 bad_offset(std::string
const& msg
= std::string()) :
31 std::out_of_range(std::string("Offset out of range: " + msg
)) {}
33 //! simple exception for UTC daylight savings adjustment
34 struct bad_adjustment
: public std::out_of_range
36 bad_adjustment(std::string
const& msg
= std::string()) :
37 std::out_of_range(std::string("Adjustment out of range: " + msg
)) {}
40 typedef boost::date_time::dst_adjustment_offsets
<boost::posix_time::time_duration
> dst_adjustment_offsets
;
42 //! A time zone class constructed from a POSIX time zone string
43 /*! A POSIX time zone string takes the form of:<br>
44 * "std offset dst [offset],start[/time],end[/time]" (w/no spaces)
45 * 'std' specifies the abbrev of the time zone.<br>
46 * 'offset' is the offset from UTC.<br>
47 * 'dst' specifies the abbrev of the time zone during daylight savings time.<br>
48 * The second offset is how many hours changed during DST. Default=1<br>
49 * 'start' and'end' are the dates when DST goes into (and out of) effect.<br>
50 * 'offset' takes the form of: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}<br>
51 * 'time' and 'offset' take the same form. Time defaults=02:00:00<br>
52 * 'start' and 'end' can be one of three forms:<br>
53 * Mm.w.d {month=1-12, week=1-5 (5 is always last), day=0-6}<br>
54 * Jn {n=1-365 Feb29 is never counted}<br>
55 * n {n=0-365 Feb29 is counted in leap years}<br>
56 * Example "PST-5PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00"
58 * Exceptions will be thrown under these conditions:<br>
59 * An invalid date spec (see date class)<br>
60 * A boost::local_time::bad_offset exception will be thrown for:<br>
61 * A DST start or end offset that is negative or more than 24 hours<br>
62 * A UTC zone that is greater than +12 or less than -12 hours<br>
63 * A boost::local_time::bad_adjustment exception will be thrown for:<br>
64 * A DST adjustment that is 24 hours or more (positive or negative)<br>
67 class posix_time_zone_base
: public date_time::time_zone_base
<posix_time::ptime
,CharT
> {
69 typedef boost::posix_time::time_duration time_duration_type
;
70 typedef date_time::time_zone_names_base
<CharT
> time_zone_names
;
71 typedef date_time::time_zone_base
<posix_time::ptime
,CharT
> base_type
;
72 typedef typename
base_type::string_type string_type
;
73 typedef CharT char_type
;
74 typedef typename
base_type::stringstream_type stringstream_type
;
75 typedef boost::char_separator
<char_type
, std::char_traits
<char_type
> > char_separator_type
;
76 typedef boost::tokenizer
<char_separator_type
,
77 typename
string_type::const_iterator
,
78 string_type
> tokenizer_type
;
79 typedef typename
boost::tokenizer
<char_separator_type
,
80 typename
string_type::const_iterator
,
81 string_type
>::iterator tokenizer_iterator_type
;
83 //! Construct from a POSIX time zone string
84 posix_time_zone_base(const string_type
& s
) :
85 //zone_names_("std_name","std_abbrev","no-dst","no-dst"),
88 base_utc_offset_(posix_time::hours(0)),
89 dst_offsets_(posix_time::hours(0),posix_time::hours(0),posix_time::hours(0)),
93 // Work around bug in aC++ compiler: see QXCR1000880488 in the
94 // HP bug tracking system
95 const char_type sep_chars
[2] = {',',0};
97 const char_type sep_chars
[2] = {','};
99 char_separator_type
sep(sep_chars
);
100 tokenizer_type
tokens(s
, sep
);
101 tokenizer_iterator_type it
= tokens
.begin();
104 string_type tmp_str
= *it
++;
105 calc_rules(tmp_str
, *it
);
108 virtual ~posix_time_zone_base() {};
109 //!String for the zone when not in daylight savings (eg: EST)
110 virtual string_type
std_zone_abbrev()const
112 return zone_names_
.std_zone_abbrev();
114 //!String for the timezone when in daylight savings (eg: EDT)
115 /*! For those time zones that have no DST, an empty string is used */
116 virtual string_type
dst_zone_abbrev() const
118 return zone_names_
.dst_zone_abbrev();
120 //!String for the zone when not in daylight savings (eg: Eastern Standard Time)
121 /*! The full STD name is not extracted from the posix time zone string.
122 * Therefore, the STD abbreviation is used in it's place */
123 virtual string_type
std_zone_name()const
125 return zone_names_
.std_zone_name();
127 //!String for the timezone when in daylight savings (eg: Eastern Daylight Time)
128 /*! The full DST name is not extracted from the posix time zone string.
129 * Therefore, the STD abbreviation is used in it's place. For time zones
130 * that have no DST, an empty string is used */
131 virtual string_type
dst_zone_name()const
133 return zone_names_
.dst_zone_name();
135 //! True if zone uses daylight savings adjustments otherwise false
136 virtual bool has_dst()const
140 //! Local time that DST starts -- NADT if has_dst is false
141 virtual posix_time::ptime
dst_local_start_time(gregorian::greg_year y
)const
143 gregorian::date
d(gregorian::not_a_date_time
);
146 d
= dst_calc_rules_
->start_day(y
);
148 return posix_time::ptime(d
, dst_offsets_
.dst_start_offset_
);
150 //! Local time that DST ends -- NADT if has_dst is false
151 virtual posix_time::ptime
dst_local_end_time(gregorian::greg_year y
)const
153 gregorian::date
d(gregorian::not_a_date_time
);
156 d
= dst_calc_rules_
->end_day(y
);
158 return posix_time::ptime(d
, dst_offsets_
.dst_end_offset_
);
160 //! Base offset from UTC for zone (eg: -07:30:00)
161 virtual time_duration_type
base_utc_offset()const
163 return base_utc_offset_
;
165 //! Adjustment forward or back made while DST is in effect
166 virtual time_duration_type
dst_offset()const
168 return dst_offsets_
.dst_adjust_
;
171 //! Returns a POSIX time_zone string for this object
172 virtual string_type
to_posix_string() const
174 // std offset dst [offset],start[/time],end[/time] - w/o spaces
175 stringstream_type ss
;
177 boost::shared_ptr
<dst_calc_rule
> no_rules
;
179 ss
<< std_zone_abbrev();
181 if(base_utc_offset().is_negative()) {
182 // inverting the sign guarantees we get two digits
183 ss
<< '-' << std::setw(2) << base_utc_offset().invert_sign().hours();
186 ss
<< '+' << std::setw(2) << base_utc_offset().hours();
188 if(base_utc_offset().minutes() != 0 || base_utc_offset().seconds() != 0) {
189 ss
<< ':' << std::setw(2) << base_utc_offset().minutes();
190 if(base_utc_offset().seconds() != 0) {
191 ss
<< ':' << std::setw(2) << base_utc_offset().seconds();
194 if(dst_calc_rules_
!= no_rules
) {
196 ss
<< dst_zone_abbrev();
198 if(dst_offset().is_negative()) {
199 // inverting the sign guarantees we get two digits
200 ss
<< '-' << std::setw(2) << dst_offset().invert_sign().hours();
203 ss
<< '+' << std::setw(2) << dst_offset().hours();
205 if(dst_offset().minutes() != 0 || dst_offset().seconds() != 0) {
206 ss
<< ':' << std::setw(2) << dst_offset().minutes();
207 if(dst_offset().seconds() != 0) {
208 ss
<< ':' << std::setw(2) << dst_offset().seconds();
212 ss
<< ',' << date_time::convert_string_type
<char, char_type
>(dst_calc_rules_
->start_rule_as_string()) << '/'
213 << std::setw(2) << dst_offsets_
.dst_start_offset_
.hours() << ':'
214 << std::setw(2) << dst_offsets_
.dst_start_offset_
.minutes();
215 if(dst_offsets_
.dst_start_offset_
.seconds() != 0) {
216 ss
<< ':' << std::setw(2) << dst_offsets_
.dst_start_offset_
.seconds();
219 ss
<< ',' << date_time::convert_string_type
<char, char_type
>(dst_calc_rules_
->end_rule_as_string()) << '/'
220 << std::setw(2) << dst_offsets_
.dst_end_offset_
.hours() << ':'
221 << std::setw(2) << dst_offsets_
.dst_end_offset_
.minutes();
222 if(dst_offsets_
.dst_end_offset_
.seconds() != 0) {
223 ss
<< ':' << std::setw(2) << dst_offsets_
.dst_end_offset_
.seconds();
230 time_zone_names zone_names_
;
232 time_duration_type base_utc_offset_
;
233 dst_adjustment_offsets dst_offsets_
;
234 boost::shared_ptr
<dst_calc_rule
> dst_calc_rules_
;
236 /*! Extract time zone abbreviations for STD & DST as well
237 * as the offsets for the time shift that occurs and how
238 * much of a shift. At this time full time zone names are
239 * NOT extracted so the abbreviations are used in their place */
240 void calc_zone(const string_type
& obj
){
241 const char_type empty_string
[2] = {'\0'};
242 stringstream_type
ss(empty_string
);
243 typename
string_type::const_pointer sit
= obj
.c_str(), obj_end
= sit
+ obj
.size();
244 string_type l_std_zone_abbrev
, l_dst_zone_abbrev
;
246 // get 'std' name/abbrev
247 while(std::isalpha(*sit
)){
250 l_std_zone_abbrev
= ss
.str();
251 ss
.str(empty_string
);
256 while(sit
!= obj_end
&& !std::isalpha(*sit
)){
259 base_utc_offset_
= date_time::str_from_delimited_time_duration
<time_duration_type
,char_type
>(ss
.str());
260 ss
.str(empty_string
);
262 // base offset must be within range of -12 hours to +12 hours
263 if(base_utc_offset_
< time_duration_type(-12,0,0) ||
264 base_utc_offset_
> time_duration_type(12,0,0))
266 boost::throw_exception(bad_offset(posix_time::to_simple_string(base_utc_offset_
)));
270 // get DST data if given
274 // get 'dst' name/abbrev
275 while(sit
!= obj_end
&& std::isalpha(*sit
)){
278 l_dst_zone_abbrev
= ss
.str();
279 ss
.str(empty_string
);
281 // get DST offset if given
284 while(sit
!= obj_end
&& !std::isalpha(*sit
)){
287 dst_offsets_
.dst_adjust_
= date_time::str_from_delimited_time_duration
<time_duration_type
,char_type
>(ss
.str());
288 ss
.str(empty_string
);
290 else{ // default DST offset
291 dst_offsets_
.dst_adjust_
= posix_time::hours(1);
294 // adjustment must be within +|- 1 day
295 if(dst_offsets_
.dst_adjust_
<= time_duration_type(-24,0,0) ||
296 dst_offsets_
.dst_adjust_
>= time_duration_type(24,0,0))
298 boost::throw_exception(bad_adjustment(posix_time::to_simple_string(dst_offsets_
.dst_adjust_
)));
301 // full names not extracted so abbrevs used in their place
302 zone_names_
= time_zone_names(l_std_zone_abbrev
, l_std_zone_abbrev
, l_dst_zone_abbrev
, l_dst_zone_abbrev
);
305 void calc_rules(const string_type
& start
, const string_type
& end
){
307 // Work around bug in aC++ compiler: see QXCR1000880488 in the
308 // HP bug tracking system
309 const char_type sep_chars
[2] = {'/',0};
311 const char_type sep_chars
[2] = {'/'};
313 char_separator_type
sep(sep_chars
);
314 tokenizer_type
st_tok(start
, sep
);
315 tokenizer_type
et_tok(end
, sep
);
316 tokenizer_iterator_type sit
= st_tok
.begin();
317 tokenizer_iterator_type eit
= et_tok
.begin();
319 // generate date spec
320 char_type x
= string_type(*sit
).at(0);
325 julian_no_leap(*sit
, *eit
);
328 julian_day(*sit
, *eit
);
333 // generate durations
335 if(sit
!= st_tok
.end()){
336 dst_offsets_
.dst_start_offset_
= date_time::str_from_delimited_time_duration
<time_duration_type
,char_type
>(*sit
);
340 dst_offsets_
.dst_start_offset_
= posix_time::hours(2);
342 // start/end offsets must fall on given date
343 if(dst_offsets_
.dst_start_offset_
< time_duration_type(0,0,0) ||
344 dst_offsets_
.dst_start_offset_
>= time_duration_type(24,0,0))
346 boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_
.dst_start_offset_
)));
350 if(eit
!= et_tok
.end()){
351 dst_offsets_
.dst_end_offset_
= date_time::str_from_delimited_time_duration
<time_duration_type
,char_type
>(*eit
);
355 dst_offsets_
.dst_end_offset_
= posix_time::hours(2);
357 // start/end offsets must fall on given date
358 if(dst_offsets_
.dst_end_offset_
< time_duration_type(0,0,0) ||
359 dst_offsets_
.dst_end_offset_
>= time_duration_type(24,0,0))
361 boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_
.dst_end_offset_
)));
365 /* Parses out a start/end date spec from a posix time zone string.
366 * Date specs come in three possible formats, this function handles
367 * the 'M' spec. Ex "M2.2.4" => 2nd month, 2nd week, 4th day .
369 void M_func(const string_type
& s
, const string_type
& e
){
370 typedef gregorian::nth_kday_of_month nkday
;
371 unsigned short sm
=0,sw
=0,sd
=0,em
=0,ew
=0,ed
=0; // start/end month,week,day
373 // Work around bug in aC++ compiler: see QXCR1000880488 in the
374 // HP bug tracking system
375 const char_type sep_chars
[3] = {'M','.',0};
377 const char_type sep_chars
[3] = {'M','.'};
379 char_separator_type
sep(sep_chars
);
380 tokenizer_type
stok(s
, sep
), etok(e
, sep
);
382 tokenizer_iterator_type it
= stok
.begin();
383 sm
= lexical_cast
<unsigned short>(*it
++);
384 sw
= lexical_cast
<unsigned short>(*it
++);
385 sd
= lexical_cast
<unsigned short>(*it
);
388 em
= lexical_cast
<unsigned short>(*it
++);
389 ew
= lexical_cast
<unsigned short>(*it
++);
390 ed
= lexical_cast
<unsigned short>(*it
);
392 dst_calc_rules_
= shared_ptr
<dst_calc_rule
>(
393 new nth_kday_dst_rule(
394 nth_last_dst_rule::start_rule(
395 static_cast<nkday::week_num
>(sw
),sd
,sm
),
396 nth_last_dst_rule::start_rule(
397 static_cast<nkday::week_num
>(ew
),ed
,em
)
402 //! Julian day. Feb29 is never counted, even in leap years
403 // expects range of 1-365
404 void julian_no_leap(const string_type
& s
, const string_type
& e
){
405 typedef gregorian::gregorian_calendar calendar
;
406 const unsigned short year
= 2001; // Non-leap year
409 sd
= lexical_cast
<int>(s
.substr(1)); // skip 'J'
410 while(sd
>= calendar::end_of_month_day(year
,sm
)){
411 sd
-= calendar::end_of_month_day(year
,sm
++);
415 ed
= lexical_cast
<int>(e
.substr(1)); // skip 'J'
416 while(ed
> calendar::end_of_month_day(year
,em
)){
417 ed
-= calendar::end_of_month_day(year
,em
++);
420 dst_calc_rules_
= shared_ptr
<dst_calc_rule
>(
421 new partial_date_dst_rule(
422 partial_date_dst_rule::start_rule(
423 sd
, static_cast<date_time::months_of_year
>(sm
)),
424 partial_date_dst_rule::end_rule(
425 ed
, static_cast<date_time::months_of_year
>(em
))
430 //! Julian day. Feb29 is always counted, but exception thrown in non-leap years
431 // expects range of 0-365
432 void julian_day(const string_type
& s
, const string_type
& e
){
434 sd
= lexical_cast
<int>(s
);
435 ed
= lexical_cast
<int>(e
);
436 dst_calc_rules_
= shared_ptr
<dst_calc_rule
>(
437 new partial_date_dst_rule(
438 partial_date_dst_rule::start_rule(++sd
),// args are 0-365
439 partial_date_dst_rule::end_rule(++ed
) // pd expects 1-366
444 //! helper function used when throwing exceptions
445 static std::string
td_as_string(const time_duration_type
& td
)
448 #if defined(USE_DATE_TIME_PRE_1_33_FACET_IO)
449 s
= posix_time::to_simple_string(td
);
451 std::stringstream ss
;
459 typedef posix_time_zone_base
<char> posix_time_zone
;
461 } } // namespace boost::local_time
464 #endif // _DATE_TIME_POSIX_TIME_ZONE__