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