#ifndef _DATE_TIME_POSIX_TIME_ZONE__ #define _DATE_TIME_POSIX_TIME_ZONE__ /* Copyright (c) 2003-2005 CrystalClear Software, Inc. * Subject to the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) * Author: Jeff Garland, Bart Garst * $Date: 2010-06-10 13:24:38 -0400 (Thu, 10 Jun 2010) $ */ #include #include #include #include #include #include #include #include #include #include #include #include namespace boost{ namespace local_time{ //! simple exception for UTC and Daylight savings start/end offsets struct bad_offset : public std::out_of_range { bad_offset(std::string const& msg = std::string()) : std::out_of_range(std::string("Offset out of range: " + msg)) {} }; //! simple exception for UTC daylight savings adjustment struct bad_adjustment : public std::out_of_range { bad_adjustment(std::string const& msg = std::string()) : std::out_of_range(std::string("Adjustment out of range: " + msg)) {} }; typedef boost::date_time::dst_adjustment_offsets dst_adjustment_offsets; //! A time zone class constructed from a POSIX time zone string /*! A POSIX time zone string takes the form of:
* "std offset dst [offset],start[/time],end[/time]" (w/no spaces) * 'std' specifies the abbrev of the time zone.
* 'offset' is the offset from UTC.
* 'dst' specifies the abbrev of the time zone during daylight savings time.
* The second offset is how many hours changed during DST. Default=1
* 'start' and'end' are the dates when DST goes into (and out of) effect.
* 'offset' takes the form of: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}
* 'time' and 'offset' take the same form. Time defaults=02:00:00
* 'start' and 'end' can be one of three forms:
* Mm.w.d {month=1-12, week=1-5 (5 is always last), day=0-6}
* Jn {n=1-365 Feb29 is never counted}
* n {n=0-365 Feb29 is counted in leap years}
* Example "PST-5PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00" *
* Exceptions will be thrown under these conditions:
* An invalid date spec (see date class)
* A boost::local_time::bad_offset exception will be thrown for:
* A DST start or end offset that is negative or more than 24 hours
* A UTC zone that is greater than +14 or less than -12 hours
* A boost::local_time::bad_adjustment exception will be thrown for:
* A DST adjustment that is 24 hours or more (positive or negative)
* * Note that UTC zone offsets can be greater than +12: * http://www.worldtimezone.com/utc/utc+1200.html */ template class posix_time_zone_base : public date_time::time_zone_base { public: typedef boost::posix_time::time_duration time_duration_type; typedef date_time::time_zone_names_base time_zone_names; typedef date_time::time_zone_base base_type; typedef typename base_type::string_type string_type; typedef CharT char_type; typedef typename base_type::stringstream_type stringstream_type; typedef boost::char_separator > char_separator_type; typedef boost::tokenizer tokenizer_type; typedef typename tokenizer_type::iterator tokenizer_iterator_type; //! Construct from a POSIX time zone string posix_time_zone_base(const string_type& s) : //zone_names_("std_name","std_abbrev","no-dst","no-dst"), zone_names_(), has_dst_(false), base_utc_offset_(posix_time::hours(0)), dst_offsets_(posix_time::hours(0),posix_time::hours(0),posix_time::hours(0)), dst_calc_rules_() { #ifdef __HP_aCC // Work around bug in aC++ compiler: see QXCR1000880488 in the // HP bug tracking system const char_type sep_chars[2] = {',',0}; #else const char_type sep_chars[2] = {','}; #endif char_separator_type sep(sep_chars); tokenizer_type tokens(s, sep); tokenizer_iterator_type it = tokens.begin(), end = tokens.end(); if (it == end) BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse time zone name")); calc_zone(*it++); if(has_dst_) { if (it == end) BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST begin time")); string_type dst_begin = *it++; if (it == end) BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST end time")); string_type dst_end = *it; calc_rules(dst_begin, dst_end); } } virtual ~posix_time_zone_base() {}; //!String for the zone when not in daylight savings (eg: EST) virtual string_type std_zone_abbrev()const { return zone_names_.std_zone_abbrev(); } //!String for the timezone when in daylight savings (eg: EDT) /*! For those time zones that have no DST, an empty string is used */ virtual string_type dst_zone_abbrev() const { return zone_names_.dst_zone_abbrev(); } //!String for the zone when not in daylight savings (eg: Eastern Standard Time) /*! The full STD name is not extracted from the posix time zone string. * Therefore, the STD abbreviation is used in it's place */ virtual string_type std_zone_name()const { return zone_names_.std_zone_name(); } //!String for the timezone when in daylight savings (eg: Eastern Daylight Time) /*! The full DST name is not extracted from the posix time zone string. * Therefore, the STD abbreviation is used in it's place. For time zones * that have no DST, an empty string is used */ virtual string_type dst_zone_name()const { return zone_names_.dst_zone_name(); } //! True if zone uses daylight savings adjustments otherwise false virtual bool has_dst()const { return has_dst_; } //! Local time that DST starts -- NADT if has_dst is false virtual posix_time::ptime dst_local_start_time(gregorian::greg_year y)const { gregorian::date d(gregorian::not_a_date_time); if(has_dst_) { d = dst_calc_rules_->start_day(y); } return posix_time::ptime(d, dst_offsets_.dst_start_offset_); } //! Local time that DST ends -- NADT if has_dst is false virtual posix_time::ptime dst_local_end_time(gregorian::greg_year y)const { gregorian::date d(gregorian::not_a_date_time); if(has_dst_) { d = dst_calc_rules_->end_day(y); } return posix_time::ptime(d, dst_offsets_.dst_end_offset_); } //! Base offset from UTC for zone (eg: -07:30:00) virtual time_duration_type base_utc_offset()const { return base_utc_offset_; } //! Adjustment forward or back made while DST is in effect virtual time_duration_type dst_offset()const { return dst_offsets_.dst_adjust_; } //! Returns a POSIX time_zone string for this object virtual string_type to_posix_string() const { // std offset dst [offset],start[/time],end[/time] - w/o spaces stringstream_type ss; ss.fill('0'); boost::shared_ptr no_rules; // std ss << std_zone_abbrev(); // offset if(base_utc_offset().is_negative()) { // inverting the sign guarantees we get two digits ss << '-' << std::setw(2) << base_utc_offset().invert_sign().hours(); } else { ss << '+' << std::setw(2) << base_utc_offset().hours(); } if(base_utc_offset().minutes() != 0 || base_utc_offset().seconds() != 0) { ss << ':' << std::setw(2) << base_utc_offset().minutes(); if(base_utc_offset().seconds() != 0) { ss << ':' << std::setw(2) << base_utc_offset().seconds(); } } if(dst_calc_rules_ != no_rules) { // dst ss << dst_zone_abbrev(); // dst offset if(dst_offset().is_negative()) { // inverting the sign guarantees we get two digits ss << '-' << std::setw(2) << dst_offset().invert_sign().hours(); } else { ss << '+' << std::setw(2) << dst_offset().hours(); } if(dst_offset().minutes() != 0 || dst_offset().seconds() != 0) { ss << ':' << std::setw(2) << dst_offset().minutes(); if(dst_offset().seconds() != 0) { ss << ':' << std::setw(2) << dst_offset().seconds(); } } // start/time ss << ',' << date_time::convert_string_type(dst_calc_rules_->start_rule_as_string()) << '/' << std::setw(2) << dst_offsets_.dst_start_offset_.hours() << ':' << std::setw(2) << dst_offsets_.dst_start_offset_.minutes(); if(dst_offsets_.dst_start_offset_.seconds() != 0) { ss << ':' << std::setw(2) << dst_offsets_.dst_start_offset_.seconds(); } // end/time ss << ',' << date_time::convert_string_type(dst_calc_rules_->end_rule_as_string()) << '/' << std::setw(2) << dst_offsets_.dst_end_offset_.hours() << ':' << std::setw(2) << dst_offsets_.dst_end_offset_.minutes(); if(dst_offsets_.dst_end_offset_.seconds() != 0) { ss << ':' << std::setw(2) << dst_offsets_.dst_end_offset_.seconds(); } } return ss.str(); } private: time_zone_names zone_names_; bool has_dst_; time_duration_type base_utc_offset_; dst_adjustment_offsets dst_offsets_; boost::shared_ptr dst_calc_rules_; /*! Extract time zone abbreviations for STD & DST as well * as the offsets for the time shift that occurs and how * much of a shift. At this time full time zone names are * NOT extracted so the abbreviations are used in their place */ void calc_zone(const string_type& obj){ const char_type empty_string[2] = {'\0'}; stringstream_type ss(empty_string); typename string_type::const_pointer sit = obj.c_str(), obj_end = sit + obj.size(); string_type l_std_zone_abbrev, l_dst_zone_abbrev; // get 'std' name/abbrev while(std::isalpha(*sit)){ ss << *sit++; } l_std_zone_abbrev = ss.str(); ss.str(empty_string); // get UTC offset if(sit != obj_end){ // get duration while(sit != obj_end && !std::isalpha(*sit)){ ss << *sit++; } base_utc_offset_ = date_time::str_from_delimited_time_duration(ss.str()); ss.str(empty_string); // base offset must be within range of -12 hours to +14 hours if(base_utc_offset_ < time_duration_type(-12,0,0) || base_utc_offset_ > time_duration_type(14,0,0)) { boost::throw_exception(bad_offset(posix_time::to_simple_string(base_utc_offset_))); } } // get DST data if given if(sit != obj_end){ has_dst_ = true; // get 'dst' name/abbrev while(sit != obj_end && std::isalpha(*sit)){ ss << *sit++; } l_dst_zone_abbrev = ss.str(); ss.str(empty_string); // get DST offset if given if(sit != obj_end){ // get duration while(sit != obj_end && !std::isalpha(*sit)){ ss << *sit++; } dst_offsets_.dst_adjust_ = date_time::str_from_delimited_time_duration(ss.str()); ss.str(empty_string); } else{ // default DST offset dst_offsets_.dst_adjust_ = posix_time::hours(1); } // adjustment must be within +|- 1 day if(dst_offsets_.dst_adjust_ <= time_duration_type(-24,0,0) || dst_offsets_.dst_adjust_ >= time_duration_type(24,0,0)) { boost::throw_exception(bad_adjustment(posix_time::to_simple_string(dst_offsets_.dst_adjust_))); } } // full names not extracted so abbrevs used in their place zone_names_ = time_zone_names(l_std_zone_abbrev, l_std_zone_abbrev, l_dst_zone_abbrev, l_dst_zone_abbrev); } void calc_rules(const string_type& start, const string_type& end){ #ifdef __HP_aCC // Work around bug in aC++ compiler: see QXCR1000880488 in the // HP bug tracking system const char_type sep_chars[2] = {'/',0}; #else const char_type sep_chars[2] = {'/'}; #endif char_separator_type sep(sep_chars); tokenizer_type st_tok(start, sep); tokenizer_type et_tok(end, sep); tokenizer_iterator_type sit = st_tok.begin(); tokenizer_iterator_type eit = et_tok.begin(); // generate date spec char_type x = string_type(*sit).at(0); if(x == 'M'){ M_func(*sit, *eit); } else if(x == 'J'){ julian_no_leap(*sit, *eit); } else{ julian_day(*sit, *eit); } ++sit; ++eit; // generate durations // starting offset if(sit != st_tok.end()){ dst_offsets_.dst_start_offset_ = date_time::str_from_delimited_time_duration(*sit); } else{ // default dst_offsets_.dst_start_offset_ = posix_time::hours(2); } // start/end offsets must fall on given date if(dst_offsets_.dst_start_offset_ < time_duration_type(0,0,0) || dst_offsets_.dst_start_offset_ >= time_duration_type(24,0,0)) { boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_start_offset_))); } // ending offset if(eit != et_tok.end()){ dst_offsets_.dst_end_offset_ = date_time::str_from_delimited_time_duration(*eit); } else{ // default dst_offsets_.dst_end_offset_ = posix_time::hours(2); } // start/end offsets must fall on given date if(dst_offsets_.dst_end_offset_ < time_duration_type(0,0,0) || dst_offsets_.dst_end_offset_ >= time_duration_type(24,0,0)) { boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_end_offset_))); } } /* Parses out a start/end date spec from a posix time zone string. * Date specs come in three possible formats, this function handles * the 'M' spec. Ex "M2.2.4" => 2nd month, 2nd week, 4th day . */ void M_func(const string_type& s, const string_type& e){ typedef gregorian::nth_kday_of_month nkday; unsigned short sm=0,sw=0,sd=0,em=0,ew=0,ed=0; // start/end month,week,day #ifdef __HP_aCC // Work around bug in aC++ compiler: see QXCR1000880488 in the // HP bug tracking system const char_type sep_chars[3] = {'M','.',0}; #else const char_type sep_chars[3] = {'M','.'}; #endif char_separator_type sep(sep_chars); tokenizer_type stok(s, sep), etok(e, sep); tokenizer_iterator_type it = stok.begin(); sm = lexical_cast(*it++); sw = lexical_cast(*it++); sd = lexical_cast(*it); it = etok.begin(); em = lexical_cast(*it++); ew = lexical_cast(*it++); ed = lexical_cast(*it); dst_calc_rules_ = shared_ptr( new nth_kday_dst_rule( nth_last_dst_rule::start_rule( static_cast(sw),sd,sm), nth_last_dst_rule::start_rule( static_cast(ew),ed,em) ) ); } //! Julian day. Feb29 is never counted, even in leap years // expects range of 1-365 void julian_no_leap(const string_type& s, const string_type& e){ typedef gregorian::gregorian_calendar calendar; const unsigned short year = 2001; // Non-leap year unsigned short sm=1; int sd=0; sd = lexical_cast(s.substr(1)); // skip 'J' while(sd >= calendar::end_of_month_day(year,sm)){ sd -= calendar::end_of_month_day(year,sm++); } unsigned short em=1; int ed=0; ed = lexical_cast(e.substr(1)); // skip 'J' while(ed > calendar::end_of_month_day(year,em)){ ed -= calendar::end_of_month_day(year,em++); } dst_calc_rules_ = shared_ptr( new partial_date_dst_rule( partial_date_dst_rule::start_rule( sd, static_cast(sm)), partial_date_dst_rule::end_rule( ed, static_cast(em)) ) ); } //! Julian day. Feb29 is always counted, but exception thrown in non-leap years // expects range of 0-365 void julian_day(const string_type& s, const string_type& e){ int sd=0, ed=0; sd = lexical_cast(s); ed = lexical_cast(e); dst_calc_rules_ = shared_ptr( new partial_date_dst_rule( partial_date_dst_rule::start_rule(++sd),// args are 0-365 partial_date_dst_rule::end_rule(++ed) // pd expects 1-366 ) ); } //! helper function used when throwing exceptions static std::string td_as_string(const time_duration_type& td) { std::string s; #if defined(USE_DATE_TIME_PRE_1_33_FACET_IO) s = posix_time::to_simple_string(td); #else std::stringstream ss; ss << td; s = ss.str(); #endif return s; } }; typedef posix_time_zone_base posix_time_zone; } } // namespace boost::local_time #endif // _DATE_TIME_POSIX_TIME_ZONE__