Dynalib Utils
ptz.h
Go to the documentation of this file.
1 #ifndef PTZ_H
2 #define PTZ_H
3 
4 // The MIT License (MIT)
5 //
6 // Copyright (c) 2017 Howard Hinnant
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in all
16 // copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 // SOFTWARE.
25 
26 // This header allows Posix-style time zones as specified for TZ here:
27 // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
28 //
29 // Posix::time_zone can be constructed with a posix-style string and then used in
30 // a zoned_time like so:
31 //
32 // zoned_time<system_clock::duration, Posix::time_zone> zt{"EST5EDT,M3.2.0,M11.1.0",
33 // system_clock::now()};
34 // or:
35 //
36 // Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"};
37 // zoned_time<system_clock::duration, Posix::time_zone> zt{tz, system_clock::now()};
38 //
39 // Note, Posix-style time zones are not recommended for all of the reasons described here:
40 // https://stackoverflow.com/tags/timezone/info
41 //
42 // They are provided here as a non-trivial custom time zone example, and if you really
43 // have to have Posix time zones, you're welcome to use this one.
44 
45 #include "date/tz.h"
46 #include <cctype>
47 #include <ostream>
48 #include <string>
49 
50 namespace Posix
51 {
52 
53 namespace detail
54 {
55 
56 #if HAS_STRING_VIEW
57 
58 using string_t = std::string_view;
59 
60 #else // !HAS_STRING_VIEW
61 
62 using string_t = std::string;
63 
64 #endif // !HAS_STRING_VIEW
65 
66 class rule;
67 
68 void throw_invalid(const string_t& s, unsigned i, const string_t& message);
69 unsigned read_date(const string_t& s, unsigned i, rule& r);
70 unsigned read_name(const string_t& s, unsigned i, std::string& name);
71 unsigned read_signed_time(const string_t& s, unsigned i, std::chrono::seconds& t);
72 unsigned read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t);
73 unsigned read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u);
74 
75 class rule
76 {
77  enum {off, J, M, N};
78 
79  date::month m_;
80  date::weekday wd_;
81  unsigned short n_ : 14;
82  unsigned short mode_ : 2;
83  std::chrono::duration<std::int32_t> time_ = std::chrono::hours{2};
84 
85 public:
86  rule() : mode_(off) {}
87 
88  bool ok() const {return mode_ != off;}
90 
91  friend std::ostream& operator<<(std::ostream& os, const rule& r);
92  friend unsigned read_date(const string_t& s, unsigned i, rule& r);
93 };
94 
95 inline
98 {
99  using namespace date;
100  using sec = std::chrono::seconds;
102  switch (mode_)
103  {
104  case J:
105  t = local_days{y/jan/0} + days{n_ + (y.is_leap() && n_ > 59)} + sec{time_};
106  break;
107  case M:
108  t = (n_ == 5 ? local_days{y/m_/wd_[last]} : local_days{y/m_/wd_[n_]}) + sec{time_};
109  break;
110  case N:
111  t = local_days{y/jan/1} + days{n_} + sec{time_};
112  break;
113  default:
114  assert(!"rule called with bad mode");
115  }
116  return t;
117 }
118 
119 inline
120 std::ostream&
121 operator<<(std::ostream& os, const rule& r)
122 {
123  switch (r.mode_)
124  {
125  case rule::J:
126  os << 'J' << r.n_ << date::format(" %T", r.time_);
127  break;
128  case rule::M:
129  if (r.n_ == 5)
130  os << r.m_/r.wd_[date::last];
131  else
132  os << r.m_/r.wd_[r.n_];
133  os << date::format(" %T", r.time_);
134  break;
135  case rule::N:
136  os << r.n_ << date::format(" %T", r.time_);
137  break;
138  default:
139  break;
140  }
141  return os;
142 }
143 
144 } // namespace detail
145 
147 {
148  std::string std_abbrev_;
149  std::string dst_abbrev_ = {};
150  std::chrono::seconds offset_;
151  std::chrono::seconds save_ = std::chrono::hours{1};
152  detail::rule start_rule_;
153  detail::rule end_rule_;
154 
155 public:
156  explicit time_zone(const detail::string_t& name);
157 
158  template <class Duration>
159  date::sys_info get_info(date::sys_time<Duration> st) const;
160  template <class Duration>
161  date::local_info get_info(date::local_time<Duration> tp) const;
162 
163  template <class Duration>
165  to_sys(date::local_time<Duration> tp) const;
166 
167  template <class Duration>
169  to_sys(date::local_time<Duration> tp, date::choose z) const;
170 
171  template <class Duration>
173  to_local(date::sys_time<Duration> tp) const;
174 
175  friend std::ostream& operator<<(std::ostream& os, const time_zone& z);
176 
177  const time_zone* operator->() const {return this;}
178 };
179 
180 inline
182 {
183  using namespace detail;
184  auto i = read_name(s, 0, std_abbrev_);
185  i = read_signed_time(s, i, offset_);
186  offset_ = -offset_;
187  if (i != s.size())
188  {
189  i = read_name(s, i, dst_abbrev_);
190  if (i != s.size())
191  {
192  if (s[i] != ',')
193  i = read_signed_time(s, i, save_);
194  if (i != s.size())
195  {
196  if (s[i] != ',')
197  throw_invalid(s, i, "Expecting end of string or ',' to start rule");
198  ++i;
199  i = read_date(s, i, start_rule_);
200  if (i == s.size() || s[i] != ',')
201  throw_invalid(s, i, "Expecting ',' and then the ending rule");
202  ++i;
203  i = read_date(s, i, end_rule_);
204  if (i != s.size())
205  throw_invalid(s, i, "Found unexpected trailing characters");
206  }
207  }
208  }
209 }
210 
211 template <class Duration>
214 {
215  using namespace date;
216  using namespace std::chrono;
217  sys_info r{};
218  r.offset = offset_;
219  if (start_rule_.ok())
220  {
221  auto y = year_month_day{floor<days>(st)}.year();
222  auto start = sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
223  auto end = sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
224  if (start <= st && st < end)
225  {
226  r.begin = start;
227  r.end = end;
228  r.offset += save_;
229  r.save = ceil<minutes>(save_);
230  r.abbrev = dst_abbrev_;
231  }
232  else if (st < start)
233  {
234  r.begin = sys_seconds{(end_rule_(y-years{1}) -
235  (offset_ + save_)).time_since_epoch()};
236  r.end = start;
237  r.abbrev = std_abbrev_;
238  }
239  else // st >= end
240  {
241  r.begin = end;
242  r.end = sys_seconds{(start_rule_(y+years{1}) - offset_).time_since_epoch()};
243  r.abbrev = std_abbrev_;
244  }
245  }
246  else // constant offset
247  {
248  r.begin = sys_days{year::min()/jan/1};
249  r.end = sys_days{year::max()/dec/last};
250  r.abbrev = std_abbrev_;
251  }
252  return r;
253 }
254 
255 template <class Duration>
258 {
259  using namespace date;
260  using namespace std::chrono;
261  local_info r{};
262  if (start_rule_.ok())
263  {
264  auto y = year_month_day{floor<days>(tp)}.year();
265  auto start = sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
266  auto end = sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
267  auto utcs = sys_seconds{floor<seconds>(tp - offset_).time_since_epoch()};
268  auto utcd = sys_seconds{floor<seconds>(tp - (offset_ + save_)).time_since_epoch()};
269  if ((utcs < start) != (utcd < start))
270  {
271  r.first.begin = sys_seconds{(end_rule_(y-years{1}) -
272  (offset_ + save_)).time_since_epoch()};
273  r.first.end = start;
274  r.first.offset = offset_;
275  r.first.abbrev = std_abbrev_;
276  r.second.begin = start;
277  r.second.end = end;
278  r.second.abbrev = dst_abbrev_;
279  r.second.offset = offset_ + save_;
280  r.second.save = ceil<minutes>(save_);
281  r.result = save_ > seconds{0} ? local_info::nonexistent
282  : local_info::ambiguous;
283  }
284  else if ((utcs < end) != (utcd < end))
285  {
286  r.first.begin = start;
287  r.first.end = end;
288  r.first.offset = offset_ + save_;
289  r.first.save = ceil<minutes>(save_);
290  r.first.abbrev = dst_abbrev_;
291  r.second.begin = end;
292  r.second.end = sys_seconds{(start_rule_(y+years{1}) -
293  offset_).time_since_epoch()};
294  r.second.abbrev = std_abbrev_;
295  r.second.offset = offset_;
296  r.result = save_ > seconds{0} ? local_info::ambiguous
297  : local_info::nonexistent;
298  }
299  else if (utcs < start)
300  {
301  r.first.begin = sys_seconds{(end_rule_(y-years{1}) -
302  (offset_ + save_)).time_since_epoch()};
303  r.first.end = start;
304  r.first.offset = offset_;
305  r.first.abbrev = std_abbrev_;
306  }
307  else if (utcs < end)
308  {
309  r.first.begin = start;
310  r.first.end = end;
311  r.first.offset = offset_ + save_;
312  r.first.save = ceil<minutes>(save_);
313  r.first.abbrev = dst_abbrev_;
314  }
315  else
316  {
317  r.first.begin = end;
318  r.first.end = sys_seconds{(start_rule_(y+years{1}) -
319  offset_).time_since_epoch()};
320  r.first.abbrev = std_abbrev_;
321  r.first.offset = offset_;
322  }
323  }
324  else // constant offset
325  {
326  r.first.begin = sys_days{year::min()/jan/1};
327  r.first.end = sys_days{year::max()/dec/last};
328  r.first.abbrev = std_abbrev_;
329  r.first.offset = offset_;
330  }
331  return r;
332 }
333 
334 template <class Duration>
337 {
338  using namespace date;
339  auto i = get_info(tp);
340  if (i.result == local_info::nonexistent)
341  throw nonexistent_local_time(tp, i);
342  else if (i.result == local_info::ambiguous)
343  throw ambiguous_local_time(tp, i);
344  return sys_time<Duration>{tp.time_since_epoch()} - i.first.offset;
345 }
346 
347 template <class Duration>
350 {
351  using namespace date;
352  auto i = get_info(tp);
353  if (i.result == local_info::nonexistent)
354  {
355  return i.first.end;
356  }
357  else if (i.result == local_info::ambiguous)
358  {
359  if (z == choose::latest)
360  return sys_time<Duration>{tp.time_since_epoch()} - i.second.offset;
361  }
362  return sys_time<Duration>{tp.time_since_epoch()} - i.first.offset;
363 }
364 
365 template <class Duration>
368 {
369  using namespace date;
370  using namespace std::chrono;
372  auto i = get_info(tp);
373  return LT{(tp + i.offset).time_since_epoch()};
374 }
375 
376 inline
377 std::ostream&
378 operator<<(std::ostream& os, const time_zone& z)
379 {
380  using date::operator<<;
381  os << '{';
382  os << z.std_abbrev_ << ", " << z.dst_abbrev_ << date::format(", %T, ", z.offset_)
383  << date::format("%T, [", z.save_) << z.start_rule_ << ", " << z.end_rule_ << ")}";
384  return os;
385 }
386 
387 namespace detail
388 {
389 
390 inline
391 void
392 throw_invalid(const string_t& s, unsigned i, const string_t& message)
393 {
394  throw std::runtime_error(std::string("Invalid time_zone initializer.\n") +
395  std::string(message) + ":\n" +
396  s + '\n' +
397  "\x1b[1;32m" +
398  std::string(i, '~') + '^' +
399  std::string(s.size()-i-1, '~') +
400  "\x1b[0m");
401 }
402 
403 inline
404 unsigned
405 read_date(const string_t& s, unsigned i, rule& r)
406 {
407  using namespace date;
408  if (i == s.size())
409  throw_invalid(s, i, "Expected rule but found end of string");
410  if (s[i] == 'J')
411  {
412  ++i;
413  unsigned n;
414  i = read_unsigned(s, i, 3, n);
415  r.mode_ = rule::J;
416  r.n_ = n;
417  }
418  else if (s[i] == 'M')
419  {
420  ++i;
421  unsigned m;
422  i = read_unsigned(s, i, 2, m);
423  if (i == s.size() || s[i] != '.')
424  throw_invalid(s, i, "Expected '.' after month");
425  ++i;
426  unsigned n;
427  i = read_unsigned(s, i, 1, n);
428  if (i == s.size() || s[i] != '.')
429  throw_invalid(s, i, "Expected '.' after weekday index");
430  ++i;
431  unsigned wd;
432  i = read_unsigned(s, i, 1, wd);
433  r.mode_ = rule::M;
434  r.m_ = month{m};
435  r.wd_ = weekday{wd};
436  r.n_ = n;
437  }
438  else if (std::isdigit(s[i]))
439  {
440  unsigned n;
441  i = read_unsigned(s, i, 3, n);
442  r.mode_ = rule::N;
443  r.n_ = n;
444  }
445  else
446  throw_invalid(s, i, "Expected 'J', 'M', or a digit to start rule");
447  if (i != s.size() && s[i] == '/')
448  {
449  ++i;
450  std::chrono::seconds t;
451  i = read_unsigned_time(s, i, t);
452  r.time_ = t;
453  }
454  return i;
455 }
456 
457 inline
458 unsigned
459 read_name(const string_t& s, unsigned i, std::string& name)
460 {
461  if (i == s.size())
462  throw_invalid(s, i, "Expected a name but found end of string");
463  if (s[i] == '<')
464  {
465  ++i;
466  while (true)
467  {
468  if (i == s.size())
469  throw_invalid(s, i,
470  "Expected to find closing '>', but found end of string");
471  if (s[i] == '>')
472  break;
473  name.push_back(s[i]);
474  ++i;
475  }
476  ++i;
477  }
478  else
479  {
480  while (i != s.size() && std::isalpha(s[i]))
481  {
482  name.push_back(s[i]);
483  ++i;
484  }
485  }
486  if (name.size() < 3)
487  throw_invalid(s, i, "Found name to be shorter than 3 characters");
488  return i;
489 }
490 
491 inline
492 unsigned
493 read_signed_time(const string_t& s, unsigned i,
494  std::chrono::seconds& t)
495 {
496  if (i == s.size())
497  throw_invalid(s, i, "Expected to read signed time, but found end of string");
498  bool negative = false;
499  if (s[i] == '-')
500  {
501  negative = true;
502  ++i;
503  }
504  else if (s[i] == '+')
505  ++i;
506  i = read_unsigned_time(s, i, t);
507  if (negative)
508  t = -t;
509  return i;
510 }
511 
512 inline
513 unsigned
514 read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t)
515 {
516  using namespace std::chrono;
517  if (i == s.size())
518  throw_invalid(s, i, "Expected to read unsigned time, but found end of string");
519  unsigned x;
520  i = read_unsigned(s, i, 2, x);
521  t = hours{x};
522  if (i != s.size() && s[i] == ':')
523  {
524  ++i;
525  i = read_unsigned(s, i, 2, x);
526  t += minutes{x};
527  if (i != s.size() && s[i] == ':')
528  {
529  ++i;
530  i = read_unsigned(s, i, 2, x);
531  t += seconds{x};
532  }
533  }
534  return i;
535 }
536 
537 inline
538 unsigned
539 read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u)
540 {
541  if (i == s.size() || !std::isdigit(s[i]))
542  throw_invalid(s, i, "Expected to find a decimal digit");
543  u = static_cast<unsigned>(s[i] - '0');
544  unsigned count = 1;
545  for (++i; count < limit && i != s.size() && std::isdigit(s[i]); ++i, ++count)
546  u = u * 10 + static_cast<unsigned>(s[i] - '0');
547  return i;
548 }
549 
550 } // namespace detail
551 
552 } // namespace Posix
553 
554 namespace date
555 {
556 
557 template <>
559 {
560 
561 #if HAS_STRING_VIEW
562 
563  static
565  locate_zone(std::string_view name)
566  {
567  return Posix::time_zone{name};
568  }
569 
570 #else // !HAS_STRING_VIEW
571 
572  static
574  locate_zone(const std::string& name)
575  {
576  return Posix::time_zone{name};
577  }
578 
579  static
581  locate_zone(const char* name)
582  {
583  return Posix::time_zone{name};
584  }
585 
586 #endif // !HAS_STRING_VIEW
587 
588 };
589 
590 } // namespace date
591 
592 #endif // PTZ_H
Definition: date.h:638
Definition: tz.h:779
CONSTCD11 bool is_leap() const NOEXCEPT
Definition: date.h:1542
CONSTDATA date::month dec
Definition: date.h:1847
local_time< days > local_days
Definition: date.h:172
date::local_time< typename std::common_type< Duration, std::chrono::seconds >::type > to_local(date::sys_time< Duration > tp) const
Definition: ptz.h:367
static Posix::time_zone locate_zone(const std::string &name)
Definition: ptz.h:574
Definition: tz.h:239
friend unsigned read_date(const string_t &s, unsigned i, rule &r)
Definition: ptz.h:405
rule()
Definition: ptz.h:86
Definition: ptz.h:75
sys_time< std::chrono::seconds > sys_seconds
Definition: date.h:164
unsigned read_signed_time(const string_t &s, unsigned i, std::chrono::seconds &t)
Definition: ptz.h:493
unsigned read_date(const string_t &s, unsigned i, rule &r)
Definition: ptz.h:405
std::chrono::duration< int, std::ratio_multiply< std::ratio< 146097, 400 >, days::period > > years
Definition: date.h:153
Definition: tz.h:156
sys_time< days > sys_days
Definition: date.h:163
Definition: ptz.h:50
Definition: date.h:79
Definition: date.h:365
auto format(const std::locale &loc, const CharT *fmt, const Streamable &tp) -> decltype(to_stream(std::declval< std::basic_ostream< CharT > &>(), fmt, tp), std::basic_string< CharT >
Definition: date.h:5663
Definition: tz.h:201
date::local_seconds operator()(date::year y) const
Definition: ptz.h:97
std::chrono::duration< int, std::ratio_multiply< std::ratio< 24 >, std::chrono::hours::period > > days
Definition: date.h:147
CONSTDATA date::month jan
Definition: date.h:1836
static Posix::time_zone locate_zone(const char *name)
Definition: ptz.h:581
bool ok() const
Definition: ptz.h:88
date::sys_time< typename std::common_type< Duration, std::chrono::seconds >::type > to_sys(date::local_time< Duration > tp) const
Definition: ptz.h:336
friend std::ostream & operator<<(std::ostream &os, const rule &r)
Definition: ptz.h:121
Definition: tz.h:177
const time_zone * locate_zone(const std::string &tz_name)
Definition: tz.cpp:3532
std::chrono::time_point< std::chrono::system_clock, Duration > sys_time
Definition: date.h:161
CONSTDATA date::last_spec last
Definition: date.h:1834
unsigned read_unsigned(const string_t &s, unsigned i, unsigned limit, unsigned &u)
Definition: ptz.h:539
local_time< std::chrono::seconds > local_seconds
Definition: date.h:171
time_zone(const detail::string_t &name)
Definition: ptz.h:181
unsigned read_unsigned_time(const string_t &s, unsigned i, std::chrono::seconds &t)
Definition: ptz.h:514
date::sys_info get_info(date::sys_time< Duration > st) const
Definition: ptz.h:213
choose
Definition: tz.h:149
void throw_invalid(const string_t &s, unsigned i, const string_t &message)
Definition: ptz.h:392
Definition: ptz.h:146
Definition: tz.h:285
Definition: date.h:411
Definition: date.h:327
std::chrono::time_point< local_t, Duration > local_time
Definition: date.h:169
unsigned read_name(const string_t &s, unsigned i, std::string &name)
Definition: ptz.h:459
std::chrono::seconds offset
Definition: tz.h:160
const time_zone * operator->() const
Definition: ptz.h:177
std::string string_t
Definition: ptz.h:62