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
8
* $Date: 2010-06-10 13:24:38 -0400 (Thu, 10 Jun 2010) $
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 +14 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>
66
* Note that UTC zone offsets can be greater than +12:
67
* http://www.worldtimezone.com/utc/utc+1200.html
70
class posix_time_zone_base : public date_time::time_zone_base<posix_time::ptime,CharT> {
72
typedef boost::posix_time::time_duration time_duration_type;
73
typedef date_time::time_zone_names_base<CharT> time_zone_names;
74
typedef date_time::time_zone_base<posix_time::ptime,CharT> base_type;
75
typedef typename base_type::string_type string_type;
76
typedef CharT char_type;
77
typedef typename base_type::stringstream_type stringstream_type;
78
typedef boost::char_separator<char_type, std::char_traits<char_type> > char_separator_type;
79
typedef boost::tokenizer<char_separator_type,
80
typename string_type::const_iterator,
81
string_type> tokenizer_type;
82
typedef typename tokenizer_type::iterator tokenizer_iterator_type;
84
//! Construct from a POSIX time zone string
85
posix_time_zone_base(const string_type& s) :
86
//zone_names_("std_name","std_abbrev","no-dst","no-dst"),
89
base_utc_offset_(posix_time::hours(0)),
90
dst_offsets_(posix_time::hours(0),posix_time::hours(0),posix_time::hours(0)),
94
// Work around bug in aC++ compiler: see QXCR1000880488 in the
95
// HP bug tracking system
96
const char_type sep_chars[2] = {',',0};
98
const char_type sep_chars[2] = {','};
100
char_separator_type sep(sep_chars);
101
tokenizer_type tokens(s, sep);
102
tokenizer_iterator_type it = tokens.begin(), end = tokens.end();
104
BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse time zone name"));
109
BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST begin time"));
110
string_type dst_begin = *it++;
113
BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST end time"));
114
string_type dst_end = *it;
115
calc_rules(dst_begin, dst_end);
118
virtual ~posix_time_zone_base() {};
119
//!String for the zone when not in daylight savings (eg: EST)
120
virtual string_type std_zone_abbrev()const
122
return zone_names_.std_zone_abbrev();
124
//!String for the timezone when in daylight savings (eg: EDT)
125
/*! For those time zones that have no DST, an empty string is used */
126
virtual string_type dst_zone_abbrev() const
128
return zone_names_.dst_zone_abbrev();
130
//!String for the zone when not in daylight savings (eg: Eastern Standard Time)
131
/*! The full STD name is not extracted from the posix time zone string.
132
* Therefore, the STD abbreviation is used in it's place */
133
virtual string_type std_zone_name()const
135
return zone_names_.std_zone_name();
137
//!String for the timezone when in daylight savings (eg: Eastern Daylight Time)
138
/*! The full DST name is not extracted from the posix time zone string.
139
* Therefore, the STD abbreviation is used in it's place. For time zones
140
* that have no DST, an empty string is used */
141
virtual string_type dst_zone_name()const
143
return zone_names_.dst_zone_name();
145
//! True if zone uses daylight savings adjustments otherwise false
146
virtual bool has_dst()const
150
//! Local time that DST starts -- NADT if has_dst is false
151
virtual posix_time::ptime dst_local_start_time(gregorian::greg_year y)const
153
gregorian::date d(gregorian::not_a_date_time);
156
d = dst_calc_rules_->start_day(y);
158
return posix_time::ptime(d, dst_offsets_.dst_start_offset_);
160
//! Local time that DST ends -- NADT if has_dst is false
161
virtual posix_time::ptime dst_local_end_time(gregorian::greg_year y)const
163
gregorian::date d(gregorian::not_a_date_time);
166
d = dst_calc_rules_->end_day(y);
168
return posix_time::ptime(d, dst_offsets_.dst_end_offset_);
170
//! Base offset from UTC for zone (eg: -07:30:00)
171
virtual time_duration_type base_utc_offset()const
173
return base_utc_offset_;
175
//! Adjustment forward or back made while DST is in effect
176
virtual time_duration_type dst_offset()const
178
return dst_offsets_.dst_adjust_;
181
//! Returns a POSIX time_zone string for this object
182
virtual string_type to_posix_string() const
184
// std offset dst [offset],start[/time],end[/time] - w/o spaces
185
stringstream_type ss;
187
boost::shared_ptr<dst_calc_rule> no_rules;
189
ss << std_zone_abbrev();
191
if(base_utc_offset().is_negative()) {
192
// inverting the sign guarantees we get two digits
193
ss << '-' << std::setw(2) << base_utc_offset().invert_sign().hours();
196
ss << '+' << std::setw(2) << base_utc_offset().hours();
198
if(base_utc_offset().minutes() != 0 || base_utc_offset().seconds() != 0) {
199
ss << ':' << std::setw(2) << base_utc_offset().minutes();
200
if(base_utc_offset().seconds() != 0) {
201
ss << ':' << std::setw(2) << base_utc_offset().seconds();
204
if(dst_calc_rules_ != no_rules) {
206
ss << dst_zone_abbrev();
208
if(dst_offset().is_negative()) {
209
// inverting the sign guarantees we get two digits
210
ss << '-' << std::setw(2) << dst_offset().invert_sign().hours();
213
ss << '+' << std::setw(2) << dst_offset().hours();
215
if(dst_offset().minutes() != 0 || dst_offset().seconds() != 0) {
216
ss << ':' << std::setw(2) << dst_offset().minutes();
217
if(dst_offset().seconds() != 0) {
218
ss << ':' << std::setw(2) << dst_offset().seconds();
222
ss << ',' << date_time::convert_string_type<char, char_type>(dst_calc_rules_->start_rule_as_string()) << '/'
223
<< std::setw(2) << dst_offsets_.dst_start_offset_.hours() << ':'
224
<< std::setw(2) << dst_offsets_.dst_start_offset_.minutes();
225
if(dst_offsets_.dst_start_offset_.seconds() != 0) {
226
ss << ':' << std::setw(2) << dst_offsets_.dst_start_offset_.seconds();
229
ss << ',' << date_time::convert_string_type<char, char_type>(dst_calc_rules_->end_rule_as_string()) << '/'
230
<< std::setw(2) << dst_offsets_.dst_end_offset_.hours() << ':'
231
<< std::setw(2) << dst_offsets_.dst_end_offset_.minutes();
232
if(dst_offsets_.dst_end_offset_.seconds() != 0) {
233
ss << ':' << std::setw(2) << dst_offsets_.dst_end_offset_.seconds();
240
time_zone_names zone_names_;
242
time_duration_type base_utc_offset_;
243
dst_adjustment_offsets dst_offsets_;
244
boost::shared_ptr<dst_calc_rule> dst_calc_rules_;
246
/*! Extract time zone abbreviations for STD & DST as well
247
* as the offsets for the time shift that occurs and how
248
* much of a shift. At this time full time zone names are
249
* NOT extracted so the abbreviations are used in their place */
250
void calc_zone(const string_type& obj){
251
const char_type empty_string[2] = {'\0'};
252
stringstream_type ss(empty_string);
253
typename string_type::const_pointer sit = obj.c_str(), obj_end = sit + obj.size();
254
string_type l_std_zone_abbrev, l_dst_zone_abbrev;
256
// get 'std' name/abbrev
257
while(std::isalpha(*sit)){
260
l_std_zone_abbrev = ss.str();
261
ss.str(empty_string);
266
while(sit != obj_end && !std::isalpha(*sit)){
269
base_utc_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(ss.str());
270
ss.str(empty_string);
272
// base offset must be within range of -12 hours to +14 hours
273
if(base_utc_offset_ < time_duration_type(-12,0,0) ||
274
base_utc_offset_ > time_duration_type(14,0,0))
276
boost::throw_exception(bad_offset(posix_time::to_simple_string(base_utc_offset_)));
280
// get DST data if given
284
// get 'dst' name/abbrev
285
while(sit != obj_end && std::isalpha(*sit)){
288
l_dst_zone_abbrev = ss.str();
289
ss.str(empty_string);
291
// get DST offset if given
294
while(sit != obj_end && !std::isalpha(*sit)){
297
dst_offsets_.dst_adjust_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(ss.str());
298
ss.str(empty_string);
300
else{ // default DST offset
301
dst_offsets_.dst_adjust_ = posix_time::hours(1);
304
// adjustment must be within +|- 1 day
305
if(dst_offsets_.dst_adjust_ <= time_duration_type(-24,0,0) ||
306
dst_offsets_.dst_adjust_ >= time_duration_type(24,0,0))
308
boost::throw_exception(bad_adjustment(posix_time::to_simple_string(dst_offsets_.dst_adjust_)));
311
// full names not extracted so abbrevs used in their place
312
zone_names_ = time_zone_names(l_std_zone_abbrev, l_std_zone_abbrev, l_dst_zone_abbrev, l_dst_zone_abbrev);
315
void calc_rules(const string_type& start, const string_type& end){
317
// Work around bug in aC++ compiler: see QXCR1000880488 in the
318
// HP bug tracking system
319
const char_type sep_chars[2] = {'/',0};
321
const char_type sep_chars[2] = {'/'};
323
char_separator_type sep(sep_chars);
324
tokenizer_type st_tok(start, sep);
325
tokenizer_type et_tok(end, sep);
326
tokenizer_iterator_type sit = st_tok.begin();
327
tokenizer_iterator_type eit = et_tok.begin();
329
// generate date spec
330
char_type x = string_type(*sit).at(0);
335
julian_no_leap(*sit, *eit);
338
julian_day(*sit, *eit);
343
// generate durations
345
if(sit != st_tok.end()){
346
dst_offsets_.dst_start_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(*sit);
350
dst_offsets_.dst_start_offset_ = posix_time::hours(2);
352
// start/end offsets must fall on given date
353
if(dst_offsets_.dst_start_offset_ < time_duration_type(0,0,0) ||
354
dst_offsets_.dst_start_offset_ >= time_duration_type(24,0,0))
356
boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_start_offset_)));
360
if(eit != et_tok.end()){
361
dst_offsets_.dst_end_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(*eit);
365
dst_offsets_.dst_end_offset_ = posix_time::hours(2);
367
// start/end offsets must fall on given date
368
if(dst_offsets_.dst_end_offset_ < time_duration_type(0,0,0) ||
369
dst_offsets_.dst_end_offset_ >= time_duration_type(24,0,0))
371
boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_end_offset_)));
375
/* Parses out a start/end date spec from a posix time zone string.
376
* Date specs come in three possible formats, this function handles
377
* the 'M' spec. Ex "M2.2.4" => 2nd month, 2nd week, 4th day .
379
void M_func(const string_type& s, const string_type& e){
380
typedef gregorian::nth_kday_of_month nkday;
381
unsigned short sm=0,sw=0,sd=0,em=0,ew=0,ed=0; // start/end month,week,day
383
// Work around bug in aC++ compiler: see QXCR1000880488 in the
384
// HP bug tracking system
385
const char_type sep_chars[3] = {'M','.',0};
387
const char_type sep_chars[3] = {'M','.'};
389
char_separator_type sep(sep_chars);
390
tokenizer_type stok(s, sep), etok(e, sep);
392
tokenizer_iterator_type it = stok.begin();
393
sm = lexical_cast<unsigned short>(*it++);
394
sw = lexical_cast<unsigned short>(*it++);
395
sd = lexical_cast<unsigned short>(*it);
398
em = lexical_cast<unsigned short>(*it++);
399
ew = lexical_cast<unsigned short>(*it++);
400
ed = lexical_cast<unsigned short>(*it);
402
dst_calc_rules_ = shared_ptr<dst_calc_rule>(
403
new nth_kday_dst_rule(
404
nth_last_dst_rule::start_rule(
405
static_cast<nkday::week_num>(sw),sd,sm),
406
nth_last_dst_rule::start_rule(
407
static_cast<nkday::week_num>(ew),ed,em)
412
//! Julian day. Feb29 is never counted, even in leap years
413
// expects range of 1-365
414
void julian_no_leap(const string_type& s, const string_type& e){
415
typedef gregorian::gregorian_calendar calendar;
416
const unsigned short year = 2001; // Non-leap year
419
sd = lexical_cast<int>(s.substr(1)); // skip 'J'
420
while(sd >= calendar::end_of_month_day(year,sm)){
421
sd -= calendar::end_of_month_day(year,sm++);
425
ed = lexical_cast<int>(e.substr(1)); // skip 'J'
426
while(ed > calendar::end_of_month_day(year,em)){
427
ed -= calendar::end_of_month_day(year,em++);
430
dst_calc_rules_ = shared_ptr<dst_calc_rule>(
431
new partial_date_dst_rule(
432
partial_date_dst_rule::start_rule(
433
sd, static_cast<date_time::months_of_year>(sm)),
434
partial_date_dst_rule::end_rule(
435
ed, static_cast<date_time::months_of_year>(em))
440
//! Julian day. Feb29 is always counted, but exception thrown in non-leap years
441
// expects range of 0-365
442
void julian_day(const string_type& s, const string_type& e){
444
sd = lexical_cast<int>(s);
445
ed = lexical_cast<int>(e);
446
dst_calc_rules_ = shared_ptr<dst_calc_rule>(
447
new partial_date_dst_rule(
448
partial_date_dst_rule::start_rule(++sd),// args are 0-365
449
partial_date_dst_rule::end_rule(++ed) // pd expects 1-366
454
//! helper function used when throwing exceptions
455
static std::string td_as_string(const time_duration_type& td)
458
#if defined(USE_DATE_TIME_PRE_1_33_FACET_IO)
459
s = posix_time::to_simple_string(td);
461
std::stringstream ss;
469
typedef posix_time_zone_base<char> posix_time_zone;
471
} } // namespace boost::local_time
474
#endif // _DATE_TIME_POSIX_TIME_ZONE__