local_date_time.hpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. #ifndef LOCAL_TIME_LOCAL_DATE_TIME_HPP__
  2. #define LOCAL_TIME_LOCAL_DATE_TIME_HPP__
  3. /* Copyright (c) 2003-2005 CrystalClear Software, Inc.
  4. * Subject to the Boost Software License, Version 1.0.
  5. * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
  6. * Author: Jeff Garland, Bart Garst
  7. * $Date: 2008-11-12 14:37:53 -0500 (Wed, 12 Nov 2008) $
  8. */
  9. #include <string>
  10. #include <iomanip>
  11. #include <sstream>
  12. #include <stdexcept>
  13. #include <boost/shared_ptr.hpp>
  14. #include <boost/throw_exception.hpp>
  15. #include <boost/date_time/time.hpp>
  16. #include <boost/date_time/posix_time/posix_time.hpp> //todo remove?
  17. #include <boost/date_time/dst_rules.hpp>
  18. #include <boost/date_time/time_zone_base.hpp>
  19. #include <boost/date_time/special_defs.hpp>
  20. namespace boost {
  21. namespace local_time {
  22. //! simple exception for reporting when STD or DST cannot be determined
  23. struct ambiguous_result : public std::logic_error
  24. {
  25. ambiguous_result (std::string const& msg = std::string()) :
  26. std::logic_error(std::string("Daylight Savings Results are ambiguous: " + msg)) {}
  27. };
  28. //! simple exception for when time label given cannot exist
  29. struct time_label_invalid : public std::logic_error
  30. {
  31. time_label_invalid (std::string const& msg = std::string()) :
  32. std::logic_error(std::string("Time label given is invalid: " + msg)) {}
  33. };
  34. struct dst_not_valid: public std::logic_error
  35. {
  36. dst_not_valid(std::string const& msg = std::string()) :
  37. std::logic_error(std::string("is_dst flag does not match resulting dst for time label given: " + msg)) {}
  38. };
  39. //TODO: I think these should be in local_date_time_base and not
  40. // necessarily brought into the namespace
  41. using date_time::time_is_dst_result;
  42. using date_time::is_in_dst;
  43. using date_time::is_not_in_dst;
  44. using date_time::ambiguous;
  45. using date_time::invalid_time_label;
  46. //! Representation of "wall-clock" time in a particular time zone
  47. /*! Representation of "wall-clock" time in a particular time zone
  48. * Local_date_time_base holds a time value (date and time offset from 00:00)
  49. * along with a time zone. The time value is stored as UTC and conversions
  50. * to wall clock time are made as needed. This approach allows for
  51. * operations between wall-clock times in different time zones, and
  52. * daylight savings time considerations, to be made. Time zones are
  53. * required to be in the form of a boost::shared_ptr<time_zone_base>.
  54. */
  55. template<class utc_time_=posix_time::ptime,
  56. class tz_type=date_time::time_zone_base<utc_time_,char> >
  57. class local_date_time_base : public date_time::base_time<utc_time_,
  58. boost::posix_time::posix_time_system> {
  59. public:
  60. typedef utc_time_ utc_time_type;
  61. typedef typename utc_time_type::time_duration_type time_duration_type;
  62. typedef typename utc_time_type::date_type date_type;
  63. typedef typename date_type::duration_type date_duration_type;
  64. typedef typename utc_time_type::time_system_type time_system_type;
  65. /*! This constructor interprets the passed time as a UTC time.
  66. * So, for example, if the passed timezone is UTC-5 then the
  67. * time will be adjusted back 5 hours. The time zone allows for
  68. * automatic calculation of whether the particular time is adjusted for
  69. * daylight savings, etc.
  70. * If the time zone shared pointer is null then time stays unadjusted.
  71. *@param t A UTC time
  72. *@param tz Timezone for to adjust the UTC time to.
  73. */
  74. local_date_time_base(utc_time_type t,
  75. boost::shared_ptr<tz_type> tz) :
  76. date_time::base_time<utc_time_type, time_system_type>(t),
  77. zone_(tz)
  78. {
  79. // param was already utc so nothing more to do
  80. }
  81. /*! This constructs a local time -- the passed time information
  82. * understood to be in the passed tz. The DST flag must be passed
  83. * to indicate whether the time is in daylight savings or not.
  84. * @throws -- time_label_invalid if the time passed does not exist in
  85. * the given locale. The non-existent case occurs typically
  86. * during the shift-back from daylight savings time. When
  87. * the clock is shifted forward a range of times
  88. * (2 am to 3 am in the US) is skipped and hence is invalid.
  89. * @throws -- dst_not_valid if the DST flag is passed for a period
  90. * where DST is not active.
  91. */
  92. local_date_time_base(date_type d,
  93. time_duration_type td,
  94. boost::shared_ptr<tz_type> tz,
  95. bool dst_flag) : //necessary for constr_adj()
  96. date_time::base_time<utc_time_type,time_system_type>(construction_adjustment(utc_time_type(d, td), tz, dst_flag)),
  97. zone_(tz)
  98. {
  99. if(tz != boost::shared_ptr<tz_type>() && tz->has_dst()){
  100. // d & td are already local so we use them
  101. time_is_dst_result result = check_dst(d, td, tz);
  102. bool in_dst = (result == is_in_dst); // less processing than is_dst()
  103. // ambig occurs at end, invalid at start
  104. if(result == invalid_time_label){
  105. // Ex: 2:15am local on trans-in day in nyc, dst_flag irrelevant
  106. std::ostringstream ss;
  107. ss << "time given: " << d << ' ' << td;
  108. boost::throw_exception(time_label_invalid(ss.str()));
  109. }
  110. else if(result != ambiguous && in_dst != dst_flag){
  111. // is dst_flag accurate?
  112. // Ex: false flag in NYC in June
  113. std::ostringstream ss;
  114. ss.setf(std::ios_base::boolalpha);
  115. ss << "flag given: dst=" << dst_flag << ", dst calculated: dst=" << in_dst;
  116. boost::throw_exception(dst_not_valid(ss.str()));
  117. }
  118. // everything checks out and conversion to utc already done
  119. }
  120. }
  121. //TODO maybe not the right set...Ignore the last 2 for now...
  122. enum DST_CALC_OPTIONS { EXCEPTION_ON_ERROR, NOT_DATE_TIME_ON_ERROR };
  123. //ASSUME_DST_ON_ERROR, ASSUME_NOT_DST_ON_ERROR };
  124. /*! This constructs a local time -- the passed time information
  125. * understood to be in the passed tz. The DST flag is calculated
  126. * according to the specified rule.
  127. */
  128. local_date_time_base(date_type d,
  129. time_duration_type td,
  130. boost::shared_ptr<tz_type> tz,
  131. DST_CALC_OPTIONS calc_option) :
  132. // dummy value - time_ is set in constructor code
  133. date_time::base_time<utc_time_type,time_system_type>(utc_time_type(d,td)),
  134. zone_(tz)
  135. {
  136. time_is_dst_result result = check_dst(d, td, tz);
  137. if(result == ambiguous) {
  138. if(calc_option == EXCEPTION_ON_ERROR){
  139. std::ostringstream ss;
  140. ss << "time given: " << d << ' ' << td;
  141. boost::throw_exception(ambiguous_result(ss.str()));
  142. }
  143. else{ // NADT on error
  144. this->time_ = posix_time::posix_time_system::get_time_rep(date_type(date_time::not_a_date_time), time_duration_type(date_time::not_a_date_time));
  145. }
  146. }
  147. else if(result == invalid_time_label){
  148. if(calc_option == EXCEPTION_ON_ERROR){
  149. std::ostringstream ss;
  150. ss << "time given: " << d << ' ' << td;
  151. boost::throw_exception(time_label_invalid(ss.str()));
  152. }
  153. else{ // NADT on error
  154. this->time_ = posix_time::posix_time_system::get_time_rep(date_type(date_time::not_a_date_time), time_duration_type(date_time::not_a_date_time));
  155. }
  156. }
  157. else if(result == is_in_dst){
  158. utc_time_type t =
  159. construction_adjustment(utc_time_type(d, td), tz, true);
  160. this->time_ = posix_time::posix_time_system::get_time_rep(t.date(),
  161. t.time_of_day());
  162. }
  163. else{
  164. utc_time_type t =
  165. construction_adjustment(utc_time_type(d, td), tz, false);
  166. this->time_ = posix_time::posix_time_system::get_time_rep(t.date(),
  167. t.time_of_day());
  168. }
  169. }
  170. //! Determines if given time label is in daylight savings for given zone
  171. /*! Determines if given time label is in daylight savings for given zone.
  172. * Takes a date and time_duration representing a local time, along
  173. * with time zone, and returns a time_is_dst_result object as result.
  174. */
  175. static time_is_dst_result check_dst(date_type d,
  176. time_duration_type td,
  177. boost::shared_ptr<tz_type> tz)
  178. {
  179. if(tz != boost::shared_ptr<tz_type>() && tz->has_dst()) {
  180. typedef typename date_time::dst_calculator<date_type, time_duration_type> dst_calculator;
  181. return dst_calculator::local_is_dst(
  182. d, td,
  183. tz->dst_local_start_time(d.year()).date(),
  184. tz->dst_local_start_time(d.year()).time_of_day(),
  185. tz->dst_local_end_time(d.year()).date(),
  186. tz->dst_local_end_time(d.year()).time_of_day(),
  187. tz->dst_offset()
  188. );
  189. }
  190. else{
  191. return is_not_in_dst;
  192. }
  193. }
  194. //! Simple destructor, releases time zone if last referrer
  195. ~local_date_time_base() {};
  196. //! Copy constructor
  197. local_date_time_base(const local_date_time_base& rhs) :
  198. date_time::base_time<utc_time_type, time_system_type>(rhs),
  199. zone_(rhs.zone_)
  200. {}
  201. //! Special values constructor
  202. explicit local_date_time_base(const boost::date_time::special_values sv,
  203. boost::shared_ptr<tz_type> tz = boost::shared_ptr<tz_type>()) :
  204. date_time::base_time<utc_time_type, time_system_type>(utc_time_type(sv)),
  205. zone_(tz)
  206. {}
  207. //! returns time zone associated with calling instance
  208. boost::shared_ptr<tz_type> zone() const
  209. {
  210. return zone_;
  211. }
  212. //! returns false is time_zone is NULL and if time value is a special_value
  213. bool is_dst() const
  214. {
  215. if(zone_ != boost::shared_ptr<tz_type>() && zone_->has_dst() && !this->is_special()) {
  216. // check_dst takes a local time, *this is utc
  217. utc_time_type lt(this->time_);
  218. lt += zone_->base_utc_offset();
  219. // dst_offset only needs to be considered with ambiguous time labels
  220. // make that adjustment there
  221. switch(check_dst(lt.date(), lt.time_of_day(), zone_)){
  222. case is_not_in_dst:
  223. return false;
  224. case is_in_dst:
  225. return true;
  226. case ambiguous:
  227. if(lt + zone_->dst_offset() < zone_->dst_local_end_time(lt.date().year())) {
  228. return true;
  229. }
  230. break;
  231. case invalid_time_label:
  232. if(lt >= zone_->dst_local_start_time(lt.date().year())) {
  233. return true;
  234. }
  235. break;
  236. }
  237. }
  238. return false;
  239. }
  240. //! Returns object's time value as a utc representation
  241. utc_time_type utc_time() const
  242. {
  243. return utc_time_type(this->time_);
  244. }
  245. //! Returns object's time value as a local representation
  246. utc_time_type local_time() const
  247. {
  248. if(zone_ != boost::shared_ptr<tz_type>()){
  249. utc_time_type lt = this->utc_time() + zone_->base_utc_offset();
  250. if (is_dst()) {
  251. lt += zone_->dst_offset();
  252. }
  253. return lt;
  254. }
  255. return utc_time_type(this->time_);
  256. }
  257. //! Returns string in the form "2003-Aug-20 05:00:00 EDT"
  258. /*! Returns string in the form "2003-Aug-20 05:00:00 EDT". If
  259. * time_zone is NULL the time zone abbreviation will be "UTC". The time
  260. * zone abbrev will not be included if calling object is a special_value*/
  261. std::string to_string() const
  262. {
  263. //TODO is this a temporary function ???
  264. std::ostringstream ss;
  265. if(this->is_special()){
  266. ss << utc_time();
  267. return ss.str();
  268. }
  269. if(zone_ == boost::shared_ptr<tz_type>()) {
  270. ss << utc_time() << " UTC";
  271. return ss.str();
  272. }
  273. bool is_dst_ = is_dst();
  274. utc_time_type lt = this->utc_time() + zone_->base_utc_offset();
  275. if (is_dst_) {
  276. lt += zone_->dst_offset();
  277. }
  278. ss << local_time() << " ";
  279. if (is_dst()) {
  280. ss << zone_->dst_zone_abbrev();
  281. }
  282. else {
  283. ss << zone_->std_zone_abbrev();
  284. }
  285. return ss.str();
  286. }
  287. /*! returns a local_date_time_base in the given time zone with the
  288. * optional time_duration added. */
  289. local_date_time_base local_time_in(boost::shared_ptr<tz_type> new_tz,
  290. time_duration_type td=time_duration_type(0,0,0)) const
  291. {
  292. return local_date_time_base(utc_time_type(this->time_) + td, new_tz);
  293. }
  294. //! Returns name of associated time zone or "Coordinated Universal Time".
  295. /*! Optional bool parameter will return time zone as an offset
  296. * (ie "+07:00" extended iso format). Empty string is returned for
  297. * classes that do not use a time_zone */
  298. std::string zone_name(bool as_offset=false) const
  299. {
  300. if(zone_ == boost::shared_ptr<tz_type>()) {
  301. if(as_offset) {
  302. return std::string("Z");
  303. }
  304. else {
  305. return std::string("Coordinated Universal Time");
  306. }
  307. }
  308. if (is_dst()) {
  309. if(as_offset) {
  310. time_duration_type td = zone_->base_utc_offset();
  311. td += zone_->dst_offset();
  312. return zone_as_offset(td, ":");
  313. }
  314. else {
  315. return zone_->dst_zone_name();
  316. }
  317. }
  318. else {
  319. if(as_offset) {
  320. time_duration_type td = zone_->base_utc_offset();
  321. return zone_as_offset(td, ":");
  322. }
  323. else {
  324. return zone_->std_zone_name();
  325. }
  326. }
  327. }
  328. //! Returns abbreviation of associated time zone or "UTC".
  329. /*! Optional bool parameter will return time zone as an offset
  330. * (ie "+0700" iso format). Empty string is returned for classes
  331. * that do not use a time_zone */
  332. std::string zone_abbrev(bool as_offset=false) const
  333. {
  334. if(zone_ == boost::shared_ptr<tz_type>()) {
  335. if(as_offset) {
  336. return std::string("Z");
  337. }
  338. else {
  339. return std::string("UTC");
  340. }
  341. }
  342. if (is_dst()) {
  343. if(as_offset) {
  344. time_duration_type td = zone_->base_utc_offset();
  345. td += zone_->dst_offset();
  346. return zone_as_offset(td, "");
  347. }
  348. else {
  349. return zone_->dst_zone_abbrev();
  350. }
  351. }
  352. else {
  353. if(as_offset) {
  354. time_duration_type td = zone_->base_utc_offset();
  355. return zone_as_offset(td, "");
  356. }
  357. else {
  358. return zone_->std_zone_abbrev();
  359. }
  360. }
  361. }
  362. //! returns a posix_time_zone string for the associated time_zone. If no time_zone, "UTC+00" is returned.
  363. std::string zone_as_posix_string() const
  364. {
  365. if(zone_ == shared_ptr<tz_type>()) {
  366. return std::string("UTC+00");
  367. }
  368. return zone_->to_posix_string();
  369. }
  370. //! Equality comparison operator
  371. /*bool operator==(const date_time::base_time<boost::posix_time::ptime,boost::posix_time::posix_time_system>& rhs) const
  372. { // fails due to rhs.time_ being protected
  373. return date_time::base_time<boost::posix_time::ptime,boost::posix_time::posix_time_system>::operator==(rhs);
  374. //return this->time_ == rhs.time_;
  375. }*/
  376. //! Equality comparison operator
  377. bool operator==(const local_date_time_base& rhs) const
  378. {
  379. return time_system_type::is_equal(this->time_, rhs.time_);
  380. }
  381. //! Non-Equality comparison operator
  382. bool operator!=(const local_date_time_base& rhs) const
  383. {
  384. return !(*this == rhs);
  385. }
  386. //! Less than comparison operator
  387. bool operator<(const local_date_time_base& rhs) const
  388. {
  389. return time_system_type::is_less(this->time_, rhs.time_);
  390. }
  391. //! Less than or equal to comparison operator
  392. bool operator<=(const local_date_time_base& rhs) const
  393. {
  394. return (*this < rhs || *this == rhs);
  395. }
  396. //! Greater than comparison operator
  397. bool operator>(const local_date_time_base& rhs) const
  398. {
  399. return !(*this <= rhs);
  400. }
  401. //! Greater than or equal to comparison operator
  402. bool operator>=(const local_date_time_base& rhs) const
  403. {
  404. return (*this > rhs || *this == rhs);
  405. }
  406. //! Local_date_time + date_duration
  407. local_date_time_base operator+(const date_duration_type& dd) const
  408. {
  409. return local_date_time_base(time_system_type::add_days(this->time_,dd), zone_);
  410. }
  411. //! Local_date_time += date_duration
  412. local_date_time_base operator+=(const date_duration_type& dd)
  413. {
  414. this->time_ = time_system_type::add_days(this->time_,dd);
  415. return *this;
  416. }
  417. //! Local_date_time - date_duration
  418. local_date_time_base operator-(const date_duration_type& dd) const
  419. {
  420. return local_date_time_base(time_system_type::subtract_days(this->time_,dd), zone_);
  421. }
  422. //! Local_date_time -= date_duration
  423. local_date_time_base operator-=(const date_duration_type& dd)
  424. {
  425. this->time_ = time_system_type::subtract_days(this->time_,dd);
  426. return *this;
  427. }
  428. //! Local_date_time + time_duration
  429. local_date_time_base operator+(const time_duration_type& td) const
  430. {
  431. return local_date_time_base(time_system_type::add_time_duration(this->time_,td), zone_);
  432. }
  433. //! Local_date_time += time_duration
  434. local_date_time_base operator+=(const time_duration_type& td)
  435. {
  436. this->time_ = time_system_type::add_time_duration(this->time_,td);
  437. return *this;
  438. }
  439. //! Local_date_time - time_duration
  440. local_date_time_base operator-(const time_duration_type& td) const
  441. {
  442. return local_date_time_base(time_system_type::subtract_time_duration(this->time_,td), zone_);
  443. }
  444. //! Local_date_time -= time_duration
  445. local_date_time_base operator-=(const time_duration_type& td)
  446. {
  447. this->time_ = time_system_type::subtract_time_duration(this->time_,td);
  448. return *this;
  449. }
  450. //! local_date_time -= local_date_time --> time_duration_type
  451. time_duration_type operator-(const local_date_time_base& rhs) const
  452. {
  453. return utc_time_type(this->time_) - utc_time_type(rhs.time_);
  454. }
  455. private:
  456. boost::shared_ptr<tz_type> zone_;
  457. //bool is_dst_;
  458. /*! Adjust the passed in time to UTC?
  459. */
  460. utc_time_type construction_adjustment(utc_time_type t,
  461. boost::shared_ptr<tz_type> z,
  462. bool dst_flag)
  463. {
  464. if(z != boost::shared_ptr<tz_type>()) {
  465. if(dst_flag && z->has_dst()) {
  466. t -= z->dst_offset();
  467. } // else no adjust
  468. t -= z->base_utc_offset();
  469. }
  470. return t;
  471. }
  472. /*! Simple formatting code -- todo remove this?
  473. */
  474. std::string zone_as_offset(const time_duration_type& td,
  475. const std::string& separator) const
  476. {
  477. std::ostringstream ss;
  478. if(td.is_negative()) {
  479. // a negative duration is represented as "-[h]h:mm"
  480. // we require two digits for the hour. A positive duration
  481. // with the %H flag will always give two digits
  482. ss << "-";
  483. }
  484. else {
  485. ss << "+";
  486. }
  487. ss << std::setw(2) << std::setfill('0')
  488. << date_time::absolute_value(td.hours())
  489. << separator
  490. << std::setw(2) << std::setfill('0')
  491. << date_time::absolute_value(td.minutes());
  492. return ss.str();
  493. }
  494. };
  495. //!Use the default parameters to define local_date_time
  496. typedef local_date_time_base<> local_date_time;
  497. } }
  498. #endif