@@ -0,0 +1,73 @@
Loading
1 +
// Copyright 2016 Google Inc. All Rights Reserved.
2 +
//
3 +
// Licensed under the Apache License, Version 2.0 (the "License");
4 +
// you may not use this file except in compliance with the License.
5 +
// You may obtain a copy of the License at
6 +
//
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 +
//
9 +
//   Unless required by applicable law or agreed to in writing, software
10 +
//   distributed under the License is distributed on an "AS IS" BASIS,
11 +
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +
//   See the License for the specific language governing permissions and
13 +
//   limitations under the License.
14 +
15 +
#include "cctz/zone_info_source.h"
16 +
17 +
namespace cctz {
18 +
19 +
// Defined out-of-line to avoid emitting a weak vtable in all TUs.
20 +
ZoneInfoSource::~ZoneInfoSource() {}
21 +
std::string ZoneInfoSource::Version() const { return std::string(); }
22 +
23 +
}  // namespace cctz
24 +
25 +
namespace cctz_extension {
26 +
27 +
namespace {
28 +
29 +
// A default for cctz_extension::zone_info_source_factory, which simply
30 +
// defers to the fallback factory.
31 +
std::unique_ptr<cctz::ZoneInfoSource> DefaultFactory(
32 +
    const std::string& name,
33 +
    const std::function<std::unique_ptr<cctz::ZoneInfoSource>(
34 +
        const std::string& name)>& fallback_factory) {
35 +
  return fallback_factory(name);
36 +
}
37 +
38 +
}  // namespace
39 +
40 +
// A "weak" definition for cctz_extension::zone_info_source_factory.
41 +
// The user may override this with their own "strong" definition (see
42 +
// zone_info_source.h).
43 +
#if !defined(__has_attribute)
44 +
#define __has_attribute(x) 0
45 +
#endif
46 +
// MinGW is GCC on Windows, so while it asserts __has_attribute(weak), the
47 +
// Windows linker cannot handle that. Nor does the MinGW compiler know how to
48 +
// pass "#pragma comment(linker, ...)" to the Windows linker.
49 +
#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__)
50 +
ZoneInfoSourceFactory zone_info_source_factory
51 +
    __attribute__((weak)) = DefaultFactory;
52 +
#elif defined(_MSC_VER) && !defined(__MINGW32__) && !defined(_LIBCPP_VERSION)
53 +
extern ZoneInfoSourceFactory zone_info_source_factory;
54 +
extern ZoneInfoSourceFactory default_factory;
55 +
ZoneInfoSourceFactory default_factory = DefaultFactory;
56 +
#if defined(_M_IX86)
57 +
#pragma comment( \
58 +
    linker,      \
59 +
    "/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA")
60 +
#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM) || \
61 +
    defined(_M_ARM64)
62 +
#pragma comment( \
63 +
    linker,      \
64 +
    "/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA")
65 +
#else
66 +
#error Unsupported MSVC platform
67 +
#endif  // _M_<PLATFORM>
68 +
#else
69 +
// Make it a "strong" definition if we have no other choice.
70 +
ZoneInfoSourceFactory zone_info_source_factory = DefaultFactory;
71 +
#endif
72 +
73 +
}  // namespace cctz_extension
0 74
imilarity index 70%
1 75
ename from src/cctz/include/time_zone.h
2 76
ename to src/cctz/time_zone.h

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -66,25 +66,35 @@
Loading
66 66
// it would take us to another day, and perhaps week, or even month.
67 67
struct PosixTransition {
68 68
  enum DateFormat { J, N, M };
69 -
  struct {
69 +
70 +
  struct Date {
71 +
    struct NonLeapDay {
72 +
      std::int_fast16_t day;  // day of non-leap year [1:365]
73 +
    };
74 +
    struct Day {
75 +
      std::int_fast16_t day;  // day of year [0:365]
76 +
    };
77 +
    struct MonthWeekWeekday {
78 +
      std::int_fast8_t month;    // month of year [1:12]
79 +
      std::int_fast8_t week;     // week of month [1:5] (5==last)
80 +
      std::int_fast8_t weekday;  // 0==Sun, ..., 6=Sat
81 +
    };
82 +
70 83
    DateFormat fmt;
84 +
71 85
    union {
72 -
      struct {
73 -
        std::int_fast16_t day;  // day of non-leap year [1:365]
74 -
      } j;
75 -
      struct {
76 -
        std::int_fast16_t day;  // day of year [0:365]
77 -
      } n;
78 -
      struct {
79 -
        std::int_fast8_t month;    // month of year [1:12]
80 -
        std::int_fast8_t week;     // week of month [1:5] (5==last)
81 -
        std::int_fast8_t weekday;  // 0==Sun, ..., 6=Sat
82 -
      } m;
86 +
      NonLeapDay j;
87 +
      Day n;
88 +
      MonthWeekWeekday m;
83 89
    };
84 -
  } date;
85 -
  struct {
90 +
  };
91 +
92 +
  struct Time {
86 93
    std::int_fast32_t offset;  // seconds before/after 00:00:00
87 -
  } time;
94 +
  };
95 +
96 +
  Date date;
97 +
  Time time;
88 98
};
89 99
90 100
// The entirety of a POSIX-string specified time-zone rule. The standard

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,7 +12,19 @@
Loading
12 12
//   See the License for the specific language governing permissions and
13 13
//   limitations under the License.
14 14
15 -
#include "time_zone.h"
15 +
#include "cctz/time_zone.h"
16 +
17 +
#if defined(__ANDROID__)
18 +
#include <sys/system_properties.h>
19 +
#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21
20 +
#include <dlfcn.h>
21 +
#endif
22 +
#endif
23 +
24 +
#if defined(__APPLE__)
25 +
#include <CoreFoundation/CFTimeZone.h>
26 +
#include <vector>
27 +
#endif
16 28
17 29
#include <cstdlib>
18 30
#include <cstring>
@@ -23,21 +35,73 @@
Loading
23 35
24 36
namespace cctz {
25 37
38 +
#if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 21
39 +
namespace {
40 +
// Android 'L' removes __system_property_get() from the NDK, however
41 +
// it is still a hidden symbol in libc so we use dlsym() to access it.
42 +
// See Chromium's base/sys_info_android.cc for a similar example.
43 +
44 +
using property_get_func = int (*)(const char*, char*);
45 +
46 +
property_get_func LoadSystemPropertyGet() {
47 +
  int flag = RTLD_LAZY | RTLD_GLOBAL;
48 +
#if defined(RTLD_NOLOAD)
49 +
  flag |= RTLD_NOLOAD;  // libc.so should already be resident
50 +
#endif
51 +
  if (void* handle = dlopen("libc.so", flag)) {
52 +
    void* sym = dlsym(handle, "__system_property_get");
53 +
    dlclose(handle);
54 +
    return reinterpret_cast<property_get_func>(sym);
55 +
  }
56 +
  return nullptr;
57 +
}
58 +
59 +
int __system_property_get(const char* name, char* value) {
60 +
  static property_get_func system_property_get = LoadSystemPropertyGet();
61 +
  return system_property_get ? system_property_get(name, value) : -1;
62 +
}
63 +
64 +
}  // namespace
65 +
#endif
66 +
26 67
std::string time_zone::name() const {
27 -
  return time_zone::Impl::get(*this).name();
68 +
  return effective_impl().Name();
28 69
}
29 70
30 71
time_zone::absolute_lookup time_zone::lookup(
31 -
    const time_point<sys_seconds>& tp) const {
32 -
  return time_zone::Impl::get(*this).BreakTime(tp);
72 +
    const time_point<seconds>& tp) const {
73 +
  return effective_impl().BreakTime(tp);
33 74
}
34 75
35 76
time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const {
36 -
  return time_zone::Impl::get(*this).MakeTime(cs);
77 +
  return effective_impl().MakeTime(cs);
78 +
}
79 +
80 +
bool time_zone::next_transition(const time_point<seconds>& tp,
81 +
                                civil_transition* trans) const {
82 +
  return effective_impl().NextTransition(tp, trans);
83 +
}
84 +
85 +
bool time_zone::prev_transition(const time_point<seconds>& tp,
86 +
                                civil_transition* trans) const {
87 +
  return effective_impl().PrevTransition(tp, trans);
88 +
}
89 +
90 +
std::string time_zone::version() const {
91 +
  return effective_impl().Version();
37 92
}
38 93
39 -
bool operator==(time_zone lhs, time_zone rhs) {
40 -
  return &time_zone::Impl::get(lhs) == &time_zone::Impl::get(rhs);
94 +
std::string time_zone::description() const {
95 +
  return effective_impl().Description();
96 +
}
97 +
98 +
const time_zone::Impl& time_zone::effective_impl() const {
99 +
  if (impl_ == nullptr) {
100 +
    // Dereferencing an implicit-UTC time_zone is expected to be
101 +
    // rare, so we don't mind paying a small synchronization cost.
102 +
    return *time_zone::Impl::UTC().impl_;
103 +
  }
104 +
  return *impl_;
41 105
}
42 106
43 107
bool load_time_zone(const std::string& name, time_zone* tz) {
@@ -48,7 +112,7 @@
Loading
48 112
  return time_zone::Impl::UTC();  // avoid name lookup
49 113
}
50 114
51 -
time_zone fixed_time_zone(const sys_seconds& offset) {
115 +
time_zone fixed_time_zone(const seconds& offset) {
52 116
  time_zone tz;
53 117
  load_time_zone(FixedOffsetToName(offset), &tz);
54 118
  return tz;
@@ -56,6 +120,25 @@
Loading
56 120
57 121
time_zone local_time_zone() {
58 122
  const char* zone = ":localtime";
123 +
#if defined(__ANDROID__)
124 +
  char sysprop[PROP_VALUE_MAX];
125 +
  if (__system_property_get("persist.sys.timezone", sysprop) > 0) {
126 +
    zone = sysprop;
127 +
  }
128 +
#endif
129 +
#if defined(__APPLE__)
130 +
  std::vector<char> buffer;
131 +
  CFTimeZoneRef tz_default = CFTimeZoneCopyDefault();
132 +
  if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) {
133 +
    CFStringEncoding encoding = kCFStringEncodingUTF8;
134 +
    CFIndex length = CFStringGetLength(tz_name);
135 +
    buffer.resize(CFStringGetMaximumSizeForEncoding(length, encoding) + 1);
136 +
    if (CFStringGetCString(tz_name, &buffer[0], buffer.size(), encoding)) {
137 +
      zone = &buffer[0];
138 +
    }
139 +
  }
140 +
  CFRelease(tz_default);
141 +
#endif
59 142
60 143
  // Allow ${TZ} to override to default zone.
61 144
  char* tz_env = nullptr;
@@ -91,6 +174,9 @@
Loading
91 174
92 175
  time_zone tz;
93 176
  load_time_zone(name, &tz);  // Falls back to UTC.
177 +
  // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and
178 +
  // arrange for %z to generate "-0000" when we don't know the local
179 +
  // offset because the load_time_zone() failed and we're using UTC.
94 180
  return tz;
95 181
}
96 182

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,8 @@
Loading
14 14
15 15
#include "time_zone_impl.h"
16 16
17 +
#include <deque>
18 +
#include <memory>
17 19
#include <mutex>
18 20
#include <string>
19 21
#include <unordered_map>
@@ -31,7 +33,12 @@
Loading
31 33
TimeZoneImplByName* time_zone_map = nullptr;
32 34
33 35
// Mutual exclusion for time_zone_map.
34 -
std::mutex time_zone_mutex;
36 +
std::mutex& TimeZoneMutex() {
37 +
  // This mutex is intentionally "leaked" to avoid the static deinitialization
38 +
  // order fiasco (std::mutex's destructor is not trivial on many platforms).
39 +
  static std::mutex* time_zone_mutex = new std::mutex;
40 +
  return *time_zone_mutex;
41 +
}
35 42
36 43
}  // namespace
37 44
@@ -40,19 +47,18 @@
Loading
40 47
}
41 48
42 49
bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) {
43 -
  const time_zone::Impl* const utc_impl = UTCImpl();
50 +
  const Impl* const utc_impl = UTCImpl();
44 51
45 -
  // First check for UTC (which is never a key in time_zone_map).
46 -
  auto offset = sys_seconds::zero();
47 -
  if (FixedOffsetFromName(name, &offset) && offset == sys_seconds::zero()) {
52 +
  // Check for UTC (which is never a key in time_zone_map).
53 +
  auto offset = seconds::zero();
54 +
  if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) {
48 55
    *tz = time_zone(utc_impl);
49 56
    return true;
50 57
  }
51 58
52 -
  // Then check, under a shared lock, whether the time zone has already
53 -
  // been loaded. This is the common path. TODO: Move to shared_mutex.
59 +
  // Check whether the time zone has already been loaded.
54 60
  {
55 -
    std::lock_guard<std::mutex> lock(time_zone_mutex);
61 +
    std::lock_guard<std::mutex> lock(TimeZoneMutex());
56 62
    if (time_zone_map != nullptr) {
57 63
      TimeZoneImplByName::const_iterator itr = time_zone_map->find(name);
58 64
      if (itr != time_zone_map->end()) {
@@ -62,42 +68,40 @@
Loading
62 68
    }
63 69
  }
64 70
65 -
  // Now check again, under an exclusive lock.
66 -
  std::lock_guard<std::mutex> lock(time_zone_mutex);
71 +
  // Load the new time zone (outside the lock).
72 +
  std::unique_ptr<const Impl> new_impl(new Impl(name));
73 +
74 +
  // Add the new time zone to the map.
75 +
  std::lock_guard<std::mutex> lock(TimeZoneMutex());
67 76
  if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName;
68 77
  const Impl*& impl = (*time_zone_map)[name];
69 -
  if (impl == nullptr) {
70 -
    // The first thread in loads the new time zone.
71 -
    Impl* new_impl = new Impl(name);
72 -
    new_impl->zone_ = TimeZoneIf::Load(new_impl->name_);
73 -
    if (new_impl->zone_ == nullptr) {
74 -
      delete new_impl;  // free the nascent Impl
75 -
      impl = utc_impl;  // and fallback to UTC
76 -
    } else {
77 -
      impl = new_impl;  // install new time zone
78 -
    }
78 +
  if (impl == nullptr) {  // this thread won any load race
79 +
    impl = new_impl->zone_ ? new_impl.release() : utc_impl;
79 80
  }
80 81
  *tz = time_zone(impl);
81 82
  return impl != utc_impl;
82 83
}
83 84
84 -
const time_zone::Impl& time_zone::Impl::get(const time_zone& tz) {
85 -
  if (tz.impl_ == nullptr) {
86 -
    // Dereferencing an implicit-UTC time_zone is expected to be
87 -
    // rare, so we don't mind paying a small synchronization cost.
88 -
    return *UTCImpl();
85 +
void time_zone::Impl::ClearTimeZoneMapTestOnly() {
86 +
  std::lock_guard<std::mutex> lock(TimeZoneMutex());
87 +
  if (time_zone_map != nullptr) {
88 +
    // Existing time_zone::Impl* entries are in the wild, so we can't delete
89 +
    // them. Instead, we move them to a private container, where they are
90 +
    // logically unreachable but not "leaked".  Future requests will result
91 +
    // in reloading the data.
92 +
    static auto* cleared = new std::deque<const time_zone::Impl*>;
93 +
    for (const auto& element : *time_zone_map) {
94 +
      cleared->push_back(element.second);
95 +
    }
96 +
    time_zone_map->clear();
89 97
  }
90 -
  return *tz.impl_;
91 98
}
92 99
93 -
time_zone::Impl::Impl(const std::string& name) : name_(name) {}
100 +
time_zone::Impl::Impl(const std::string& name)
101 +
    : name_(name), zone_(TimeZoneIf::Load(name_)) {}
94 102
95 103
const time_zone::Impl* time_zone::Impl::UTCImpl() {
96 -
  static Impl* utc_impl = [] {
97 -
    Impl* impl = new Impl("UTC");
98 -
    impl->zone_ = TimeZoneIf::Load(impl->name_);  // never fails
99 -
    return impl;
100 -
  }();
104 +
  static const Impl* utc_impl = new Impl("UTC");  // never fails
101 105
  return utc_impl;
102 106
}
103 107

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,30 +25,31 @@
Loading
25 25
#include <string>
26 26
#include <utility>
27 27
28 -
#include "civil_time.h"
28 +
#include "cctz/civil_time.h"
29 29
30 30
namespace cctz {
31 31
32 32
// Convenience aliases. Not intended as public API points.
33 33
template <typename D>
34 34
using time_point = std::chrono::time_point<std::chrono::system_clock, D>;
35 -
using sys_seconds = std::chrono::duration<std::int_fast64_t>;
35 +
using seconds = std::chrono::duration<std::int_fast64_t>;
36 +
using sys_seconds = seconds;  // Deprecated.  Use cctz::seconds instead.
36 37
37 38
namespace detail {
38 39
template <typename D>
39 -
inline std::pair<time_point<sys_seconds>, D>
40 +
inline std::pair<time_point<seconds>, D>
40 41
split_seconds(const time_point<D>& tp) {
41 -
  auto sec = std::chrono::time_point_cast<sys_seconds>(tp);
42 +
  auto sec = std::chrono::time_point_cast<seconds>(tp);
42 43
  auto sub = tp - sec;
43 44
  if (sub.count() < 0) {
44 -
    sec -= sys_seconds(1);
45 -
    sub += sys_seconds(1);
45 +
    sec -= seconds(1);
46 +
    sub += seconds(1);
46 47
  }
47 48
  return {sec, std::chrono::duration_cast<D>(sub)};
48 49
}
49 -
inline std::pair<time_point<sys_seconds>, sys_seconds>
50 -
split_seconds(const time_point<sys_seconds>& tp) {
51 -
  return {tp, sys_seconds(0)};
50 +
inline std::pair<time_point<seconds>, seconds>
51 +
split_seconds(const time_point<seconds>& tp) {
52 +
  return {tp, seconds::zero()};
52 53
}
53 54
}  // namespace detail
54 55
@@ -69,10 +70,10 @@
Loading
69 70
//
70 71
// See also:
71 72
// - http://www.iana.org/time-zones
72 -
// - http://en.wikipedia.org/wiki/Zoneinfo
73 +
// - https://en.wikipedia.org/wiki/Zoneinfo
73 74
class time_zone {
74 75
 public:
75 -
  time_zone() = default;  // Equivalent to UTC
76 +
  time_zone() : time_zone(nullptr) {}  // Equivalent to UTC
76 77
  time_zone(const time_zone&) = default;
77 78
  time_zone& operator=(const time_zone&) = default;
78 79
@@ -97,7 +98,7 @@
Loading
97 98
    bool is_dst;       // is offset non-standard?
98 99
    const char* abbr;  // time-zone abbreviation (e.g., "PST")
99 100
  };
100 -
  absolute_lookup lookup(const time_point<sys_seconds>& tp) const;
101 +
  absolute_lookup lookup(const time_point<seconds>& tp) const;
101 102
  template <typename D>
102 103
  absolute_lookup lookup(const time_point<D>& tp) const {
103 104
    return lookup(detail::split_seconds(tp).first);
@@ -116,9 +117,9 @@
Loading
116 117
  // of the given civil-time argument, and the pre, trans, and post
117 118
  // members will give the absolute time answers using the pre-transition
118 119
  // offset, the transition point itself, and the post-transition offset,
119 -
  // respectively (all three times are equal if kind == UNIQUE).  If any
120 +
  // respectively (all three times are equal if kind == UNIQUE). If any
120 121
  // of these three absolute times is outside the representable range of a
121 -
  // time_point<sys_seconds> the field is set to its maximum/minimum value.
122 +
  // time_point<seconds> the field is set to its maximum/minimum value.
122 123
  //
123 124
  // Example:
124 125
  //   cctz::time_zone lax;
@@ -150,23 +151,85 @@
Loading
150 151
      SKIPPED,   // the civil time did not exist (pre >= trans > post)
151 152
      REPEATED,  // the civil time was ambiguous (pre < trans <= post)
152 153
    } kind;
153 -
    time_point<sys_seconds> pre;    // uses the pre-transition offset
154 -
    time_point<sys_seconds> trans;  // instant of civil-offset change
155 -
    time_point<sys_seconds> post;   // uses the post-transition offset
154 +
    time_point<seconds> pre;    // uses the pre-transition offset
155 +
    time_point<seconds> trans;  // instant of civil-offset change
156 +
    time_point<seconds> post;   // uses the post-transition offset
156 157
  };
157 158
  civil_lookup lookup(const civil_second& cs) const;
158 159
160 +
  // Finds the time of the next/previous offset change in this time zone.
161 +
  //
162 +
  // By definition, next_transition(tp, &trans) returns false when tp has
163 +
  // its maximum value, and prev_transition(tp, &trans) returns false
164 +
  // when tp has its minimum value. If the zone has no transitions, the
165 +
  // result will also be false no matter what the argument.
166 +
  //
167 +
  // Otherwise, when tp has its minimum value, next_transition(tp, &trans)
168 +
  // returns true and sets trans to the first recorded transition. Chains
169 +
  // of calls to next_transition()/prev_transition() will eventually return
170 +
  // false, but it is unspecified exactly when next_transition(tp, &trans)
171 +
  // jumps to false, or what time is set by prev_transition(tp, &trans) for
172 +
  // a very distant tp.
173 +
  //
174 +
  // Note: Enumeration of time-zone transitions is for informational purposes
175 +
  // only. Modern time-related code should not care about when offset changes
176 +
  // occur.
177 +
  //
178 +
  // Example:
179 +
  //   cctz::time_zone nyc;
180 +
  //   if (!cctz::load_time_zone("America/New_York", &nyc)) { ... }
181 +
  //   const auto now = std::chrono::system_clock::now();
182 +
  //   auto tp = cctz::time_point<cctz::seconds>::min();
183 +
  //   cctz::time_zone::civil_transition trans;
184 +
  //   while (tp <= now && nyc.next_transition(tp, &trans)) {
185 +
  //     // transition: trans.from -> trans.to
186 +
  //     tp = nyc.lookup(trans.to).trans;
187 +
  //   }
188 +
  struct civil_transition {
189 +
    civil_second from;  // the civil time we jump from
190 +
    civil_second to;    // the civil time we jump to
191 +
  };
192 +
  bool next_transition(const time_point<seconds>& tp,
193 +
                       civil_transition* trans) const;
194 +
  template <typename D>
195 +
  bool next_transition(const time_point<D>& tp,
196 +
                       civil_transition* trans) const {
197 +
    return next_transition(detail::split_seconds(tp).first, trans);
198 +
  }
199 +
  bool prev_transition(const time_point<seconds>& tp,
200 +
                       civil_transition* trans) const;
201 +
  template <typename D>
202 +
  bool prev_transition(const time_point<D>& tp,
203 +
                       civil_transition* trans) const {
204 +
    return prev_transition(detail::split_seconds(tp).first, trans);
205 +
  }
206 +
207 +
  // version() and description() provide additional information about the
208 +
  // time zone. The content of each of the returned strings is unspecified,
209 +
  // however, when the IANA Time Zone Database is the underlying data source
210 +
  // the version() string will be in the familar form (e.g, "2018e") or
211 +
  // empty when unavailable.
212 +
  //
213 +
  // Note: These functions are for informational or testing purposes only.
214 +
  std::string version() const;  // empty when unknown
215 +
  std::string description() const;
216 +
217 +
  // Relational operators.
218 +
  friend bool operator==(time_zone lhs, time_zone rhs) {
219 +
    return &lhs.effective_impl() == &rhs.effective_impl();
220 +
  }
221 +
  friend bool operator!=(time_zone lhs, time_zone rhs) {
222 +
    return !(lhs == rhs);
223 +
  }
224 +
159 225
  class Impl;
160 226
161 227
 private:
162 228
  explicit time_zone(const Impl* impl) : impl_(impl) {}
163 -
  const Impl* impl_ = nullptr;
229 +
  const Impl& effective_impl() const;  // handles implicit UTC
230 +
  const Impl* impl_;
164 231
};
165 232
166 -
// Relational operators.
167 -
bool operator==(time_zone lhs, time_zone rhs);
168 -
inline bool operator!=(time_zone lhs, time_zone rhs) { return !(lhs == rhs); }
169 -
170 233
// Loads the named time zone. May perform I/O on the initial load.
171 234
// If the name is invalid, or some other kind of error occurs, returns
172 235
// false and "*tz" is set to the UTC time zone.
@@ -178,9 +241,10 @@
Loading
178 241
// Returns a time zone that is a fixed offset (seconds east) from UTC.
179 242
// Note: If the absolute value of the offset is greater than 24 hours
180 243
// you'll get UTC (i.e., zero offset) instead.
181 -
time_zone fixed_time_zone(const sys_seconds& offset);
244 +
time_zone fixed_time_zone(const seconds& offset);
182 245
183 246
// Returns a time zone representing the local time zone. Falls back to UTC.
247 +
// Note: local_time_zone.name() may only be something like "localtime".
184 248
time_zone local_time_zone();
185 249
186 250
// Returns the civil time (cctz::civil_second) within the given time zone at
@@ -197,8 +261,8 @@
Loading
197 261
// it was either repeated or non-existent), then the returned time_point is
198 262
// the best estimate that preserves relative order. That is, this function
199 263
// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
200 -
inline time_point<sys_seconds> convert(const civil_second& cs,
201 -
                                       const time_zone& tz) {
264 +
inline time_point<seconds> convert(const civil_second& cs,
265 +
                                   const time_zone& tz) {
202 266
  const time_zone::civil_lookup cl = tz.lookup(cs);
203 267
  if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
204 268
  return cl.pre;
@@ -206,32 +270,34 @@
Loading
206 270
207 271
namespace detail {
208 272
using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>;
209 -
std::string format(const std::string&, const time_point<sys_seconds>&,
273 +
std::string format(const std::string&, const time_point<seconds>&,
210 274
                   const femtoseconds&, const time_zone&);
211 275
bool parse(const std::string&, const std::string&, const time_zone&,
212 -
           time_point<sys_seconds>*, femtoseconds*, std::string* err = nullptr);
276 +
           time_point<seconds>*, femtoseconds*, std::string* err = nullptr);
213 277
}  // namespace detail
214 278
215 279
// Formats the given time_point in the given cctz::time_zone according to
216 280
// the provided format string. Uses strftime()-like formatting options,
217 281
// with the following extensions:
218 282
//
219 -
//   - %Ez  - RFC3339-compatible numeric time zone (+hh:mm or -hh:mm)
283 +
//   - %Ez  - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm)
284 +
//   - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss)
220 285
//   - %E#S - Seconds with # digits of fractional precision
221 286
//   - %E*S - Seconds with full fractional precision (a literal '*')
222 287
//   - %E#f - Fractional seconds with # digits of precision
223 288
//   - %E*f - Fractional seconds with full precision (a literal '*')
224 289
//   - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
290 +
//   - %ET  - The RFC3339 "date-time" separator "T"
225 291
//
226 -
// Note that %E0S behaves like %S, and %E0f produces no characters.  In
292 +
// Note that %E0S behaves like %S, and %E0f produces no characters. In
227 293
// contrast %E*f always produces at least one digit, which may be '0'.
228 294
//
229 295
// Note that %Y produces as many characters as it takes to fully render the
230 296
// year. A year outside of [-999:9999] when formatted with %E4Y will produce
231 297
// more than four characters, just like %Y.
232 298
//
233 -
// Tip: Format strings should include the UTC offset (e.g., %z or %Ez) so that
234 -
// the resultng string uniquely identifies an absolute time.
299 +
// Tip: Format strings should include the UTC offset (e.g., %z, %Ez, or %E*z)
300 +
// so that the resulting string uniquely identifies an absolute time.
235 301
//
236 302
// Example:
237 303
//   cctz::time_zone lax;
@@ -250,7 +316,9 @@
Loading
250 316
// Parses an input string according to the provided format string and
251 317
// returns the corresponding time_point. Uses strftime()-like formatting
252 318
// options, with the same extensions as cctz::format(), but with the
253 -
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f.
319 +
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez
320 +
// and %E*z also accept the same inputs, which (along with %z) includes
321 +
// 'z' and 'Z' as synonyms for +00:00.  %ET accepts either 'T' or 't'.
254 322
//
255 323
// %Y consumes as many numeric characters as it can, so the matching data
256 324
// should always be terminated with a non-numeric. %E4Y always consumes
@@ -264,7 +332,8 @@
Loading
264 332
// that represents "1970-01-01 15:45:00.0 +0000".
265 333
//
266 334
// Note that parse() returns time instants, so it makes most sense to parse
267 -
// fully-specified date/time strings that include a UTC offset (%z or %Ez).
335 +
// fully-specified date/time strings that include a UTC offset (%z, %Ez, or
336 +
// %E*z).
268 337
//
269 338
// Note also that parse() only heeds the fields year, month, day, hour,
270 339
// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a
@@ -293,7 +362,7 @@
Loading
293 362
template <typename D>
294 363
inline bool parse(const std::string& fmt, const std::string& input,
295 364
                  const time_zone& tz, time_point<D>* tpp) {
296 -
  time_point<sys_seconds> sec;
365 +
  time_point<seconds> sec;
297 366
  detail::femtoseconds fs;
298 367
  const bool b = detail::parse(fmt, input, tz, &sec, &fs);
299 368
  if (b) {
300 369
imilarity index 90%
301 370
ename from src/cctz/include/zone_info_source.h
302 371
ename to src/cctz/zone_info_source.h

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,10 +25,15 @@
Loading
25 25
// A stdio-like interface for providing zoneinfo data for a particular zone.
26 26
class ZoneInfoSource {
27 27
 public:
28 -
  virtual ~ZoneInfoSource() {}
28 +
  virtual ~ZoneInfoSource();
29 29
30 30
  virtual std::size_t Read(void* ptr, std::size_t size) = 0;  // like fread()
31 31
  virtual int Skip(std::size_t offset) = 0;  // like fseek()
32 +
33 +
  // Until the zoneinfo data supports versioning information, we provide
34 +
  // a way for a ZoneInfoSource to indicate it out-of-band.  The default
35 +
  // implementation returns an empty string.
36 +
  virtual std::string Version() const;
32 37
};
33 38
34 39
}  // namespace cctz

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -98,9 +98,9 @@
Loading
98 98
          int weekday = 0;
99 99
          if ((p = ParseInt(p + 1, 0, 6, &weekday)) != nullptr) {
100 100
            res->date.fmt = PosixTransition::M;
101 -
            res->date.m.month = static_cast<int_fast8_t>(month);
102 -
            res->date.m.week = static_cast<int_fast8_t>(week);
103 -
            res->date.m.weekday = static_cast<int_fast8_t>(weekday);
101 +
            res->date.m.month = static_cast<std::int_fast8_t>(month);
102 +
            res->date.m.week = static_cast<std::int_fast8_t>(week);
103 +
            res->date.m.weekday = static_cast<std::int_fast8_t>(weekday);
104 104
          }
105 105
        }
106 106
      }
@@ -108,13 +108,13 @@
Loading
108 108
      int day = 0;
109 109
      if ((p = ParseInt(p + 1, 1, 365, &day)) != nullptr) {
110 110
        res->date.fmt = PosixTransition::J;
111 -
        res->date.j.day = static_cast<int_fast16_t>(day);
111 +
        res->date.j.day = static_cast<std::int_fast16_t>(day);
112 112
      }
113 113
    } else {
114 114
      int day = 0;
115 115
      if ((p = ParseInt(p, 0, 365, &day)) != nullptr) {
116 116
        res->date.fmt = PosixTransition::N;
117 -
        res->date.j.day = static_cast<int_fast16_t>(day);
117 +
        res->date.n.day = static_cast<std::int_fast16_t>(day);
118 118
      }
119 119
    }
120 120
  }

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,8 +20,8 @@
Loading
20 20
#include <ostream>
21 21
#include <type_traits>
22 22
23 -
// Disable constexpr support unless we are using clang in C++14 mode.
24 -
#if __clang__ && __cpp_constexpr >= 201304
23 +
// Disable constexpr support unless we are in C++14 mode.
24 +
#if __cpp_constexpr >= 201304 || (defined(_MSC_VER) && _MSC_VER >= 1910)
25 25
#define CONSTEXPR_D constexpr  // data
26 26
#define CONSTEXPR_F constexpr  // function
27 27
#define CONSTEXPR_M constexpr  // member
@@ -93,53 +93,72 @@
Loading
93 93
  return is_leap_year(y + (m > 2)) ? 366 : 365;
94 94
}
95 95
CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept {
96 -
  CONSTEXPR_D signed char k_days_per_month[12] = {
97 -
      31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31  // non leap year
96 +
  CONSTEXPR_D int k_days_per_month[1 + 12] = {
97 +
      -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31  // non leap year
98 98
  };
99 -
  return k_days_per_month[m - 1] + (m == 2 && is_leap_year(y));
99 +
  return k_days_per_month[m] + (m == 2 && is_leap_year(y));
100 100
}
101 101
102 102
CONSTEXPR_F fields n_day(year_t y, month_t m, diff_t d, diff_t cd,
103 103
                         hour_t hh, minute_t mm, second_t ss) noexcept {
104 -
  y += (cd / 146097) * 400;
104 +
  year_t ey = y % 400;
105 +
  const year_t oey = ey;
106 +
  ey += (cd / 146097) * 400;
105 107
  cd %= 146097;
106 108
  if (cd < 0) {
107 -
    y -= 400;
109 +
    ey -= 400;
108 110
    cd += 146097;
109 111
  }
110 -
  y += (d / 146097) * 400;
112 +
  ey += (d / 146097) * 400;
111 113
  d = d % 146097 + cd;
112 -
  if (d <= 0) {
113 -
    y -= 400;
114 -
    d += 146097;
115 -
  } else if (d > 146097) {
116 -
    y += 400;
117 -
    d -= 146097;
114 +
  if (d > 0) {
115 +
    if (d > 146097) {
116 +
      ey += 400;
117 +
      d -= 146097;
118 +
    }
119 +
  } else {
120 +
    if (d > -365) {
121 +
      // We often hit the previous year when stepping a civil time backwards,
122 +
      // so special case it to avoid counting up by 100/4/1-year chunks.
123 +
      ey -= 1;
124 +
      d += days_per_year(ey, m);
125 +
    } else {
126 +
      ey -= 400;
127 +
      d += 146097;
128 +
    }
118 129
  }
119 130
  if (d > 365) {
120 -
    for (int n = days_per_century(y, m); d > n; n = days_per_century(y, m)) {
131 +
    for (;;) {
132 +
      int n = days_per_century(ey, m);
133 +
      if (d <= n) break;
121 134
      d -= n;
122 -
      y += 100;
135 +
      ey += 100;
123 136
    }
124 -
    for (int n = days_per_4years(y, m); d > n; n = days_per_4years(y, m)) {
137 +
    for (;;) {
138 +
      int n = days_per_4years(ey, m);
139 +
      if (d <= n) break;
125 140
      d -= n;
126 -
      y += 4;
141 +
      ey += 4;
127 142
    }
128 -
    for (int n = days_per_year(y, m); d > n; n = days_per_year(y, m)) {
143 +
    for (;;) {
144 +
      int n = days_per_year(ey, m);
145 +
      if (d <= n) break;
129 146
      d -= n;
130 -
      ++y;
147 +
      ++ey;
131 148
    }
132 149
  }
133 150
  if (d > 28) {
134 -
    for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) {
151 +
    for (;;) {
152 +
      int n = days_per_month(ey, m);
153 +
      if (d <= n) break;
135 154
      d -= n;
136 155
      if (++m > 12) {
137 -
        ++y;
156 +
        ++ey;
138 157
        m = 1;
139 158
      }
140 159
    }
141 160
  }
142 -
  return fields(y, m, static_cast<day_t>(d), hh, mm, ss);
161 +
  return fields(y + (ey - oey), m, static_cast<day_t>(d), hh, mm, ss);
143 162
}
144 163
CONSTEXPR_F fields n_mon(year_t y, diff_t m, diff_t d, diff_t cd,
145 164
                         hour_t hh, minute_t mm, second_t ss) noexcept {
@@ -344,12 +363,12 @@
Loading
344 363
      : civil_time(ct.f_) {}
345 364
346 365
  // Factories for the maximum/minimum representable civil_time.
347 -
  static civil_time max() {
348 -
    const auto max_year = std::numeric_limits<std::int_least64_t>::max();
366 +
  static CONSTEXPR_F civil_time (max)() {
367 +
    const auto max_year = (std::numeric_limits<std::int_least64_t>::max)();
349 368
    return civil_time(max_year, 12, 31, 23, 59, 59);
350 369
  }
351 -
  static civil_time min() {
352 -
    const auto min_year = std::numeric_limits<std::int_least64_t>::min();
370 +
  static CONSTEXPR_F civil_time (min)() {
371 +
    const auto min_year = (std::numeric_limits<std::int_least64_t>::min)();
353 372
    return civil_time(min_year, 1, 1, 0, 0, 0);
354 373
  }
355 374
@@ -367,7 +386,7 @@
Loading
367 386
    return *this;
368 387
  }
369 388
  CONSTEXPR_M civil_time& operator-=(diff_t n) noexcept {
370 -
    if (n != std::numeric_limits<diff_t>::min()) {
389 +
    if (n != (std::numeric_limits<diff_t>::min)()) {
371 390
      f_ = step(T{}, f_, -n);
372 391
    } else {
373 392
      f_ = step(T{}, step(T{}, f_, -(n + 1)), 1);
@@ -392,20 +411,16 @@
Loading
392 411
  }
393 412
394 413
  // Binary arithmetic operators.
395 -
  inline friend CONSTEXPR_M civil_time operator+(civil_time a,
396 -
                                                 diff_t n) noexcept {
414 +
  friend CONSTEXPR_F civil_time operator+(civil_time a, diff_t n) noexcept {
397 415
    return a += n;
398 416
  }
399 -
  inline friend CONSTEXPR_M civil_time operator+(diff_t n,
400 -
                                                 civil_time a) noexcept {
417 +
  friend CONSTEXPR_F civil_time operator+(diff_t n, civil_time a) noexcept {
401 418
    return a += n;
402 419
  }
403 -
  inline friend CONSTEXPR_M civil_time operator-(civil_time a,
404 -
                                                 diff_t n) noexcept {
420 +
  friend CONSTEXPR_F civil_time operator-(civil_time a, diff_t n) noexcept {
405 421
    return a -= n;
406 422
  }
407 -
  inline friend CONSTEXPR_M diff_t operator-(const civil_time& lhs,
408 -
                                             const civil_time& rhs) noexcept {
423 +
  friend CONSTEXPR_F diff_t operator-(civil_time lhs, civil_time rhs) noexcept {
409 424
    return difference(T{}, lhs.f_, rhs.f_);
410 425
  }
411 426
@@ -423,8 +438,8 @@
Loading
423 438
424 439
// Disallows difference between differently aligned types.
425 440
// auto n = civil_day(...) - civil_hour(...);  // would be confusing.
426 -
template <typename Tag1, typename Tag2>
427 -
CONSTEXPR_F diff_t operator-(civil_time<Tag1>, civil_time<Tag2>) = delete;
441 +
template <typename T, typename U>
442 +
CONSTEXPR_F diff_t operator-(civil_time<T>, civil_time<U>) = delete;
428 443
429 444
using civil_year = civil_time<year_tag>;
430 445
using civil_month = civil_time<month_tag>;
@@ -492,29 +507,71 @@
Loading
492 507
  sunday,
493 508
};
494 509
495 -
CONSTEXPR_F weekday get_weekday(const civil_day& cd) noexcept {
496 -
  CONSTEXPR_D weekday k_weekday_by_thu_off[] = {
497 -
      weekday::thursday,  weekday::friday,  weekday::saturday,
498 -
      weekday::sunday,    weekday::monday,  weekday::tuesday,
499 -
      weekday::wednesday,
510 +
CONSTEXPR_F weekday get_weekday(const civil_second& cs) noexcept {
511 +
  CONSTEXPR_D weekday k_weekday_by_mon_off[13] = {
512 +
      weekday::monday,    weekday::tuesday,  weekday::wednesday,
513 +
      weekday::thursday,  weekday::friday,   weekday::saturday,
514 +
      weekday::sunday,    weekday::monday,   weekday::tuesday,
515 +
      weekday::wednesday, weekday::thursday, weekday::friday,
516 +
      weekday::saturday,
517 +
  };
518 +
  CONSTEXPR_D int k_weekday_offsets[1 + 12] = {
519 +
      -1, 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4,
500 520
  };
501 -
  return k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7];
521 +
  year_t wd = 2400 + (cs.year() % 400) - (cs.month() < 3);
522 +
  wd += wd / 4 - wd / 100 + wd / 400;
523 +
  wd += k_weekday_offsets[cs.month()] + cs.day();
524 +
  return k_weekday_by_mon_off[wd % 7 + 6];
502 525
}
503 526
504 527
////////////////////////////////////////////////////////////////////////
505 528
506 529
CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) noexcept {
507 -
  do { cd += 1; } while (get_weekday(cd) != wd);
508 -
  return cd;
530 +
  CONSTEXPR_D weekday k_weekdays_forw[14] = {
531 +
      weekday::monday,    weekday::tuesday,  weekday::wednesday,
532 +
      weekday::thursday,  weekday::friday,   weekday::saturday,
533 +
      weekday::sunday,    weekday::monday,   weekday::tuesday,
534 +
      weekday::wednesday, weekday::thursday, weekday::friday,
535 +
      weekday::saturday,  weekday::sunday,
536 +
  };
537 +
  weekday base = get_weekday(cd);
538 +
  for (int i = 0;; ++i) {
539 +
    if (base == k_weekdays_forw[i]) {
540 +
      for (int j = i + 1;; ++j) {
541 +
        if (wd == k_weekdays_forw[j]) {
542 +
          return cd + (j - i);
543 +
        }
544 +
      }
545 +
    }
546 +
  }
509 547
}
510 548
511 549
CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) noexcept {
512 -
  do { cd -= 1; } while (get_weekday(cd) != wd);
513 -
  return cd;
550 +
  CONSTEXPR_D weekday k_weekdays_back[14] = {
551 +
      weekday::sunday,   weekday::saturday,  weekday::friday,
552 +
      weekday::thursday, weekday::wednesday, weekday::tuesday,
553 +
      weekday::monday,   weekday::sunday,    weekday::saturday,
554 +
      weekday::friday,   weekday::thursday,  weekday::wednesday,
555 +
      weekday::tuesday,  weekday::monday,
556 +
  };
557 +
  weekday base = get_weekday(cd);
558 +
  for (int i = 0;; ++i) {
559 +
    if (base == k_weekdays_back[i]) {
560 +
      for (int j = i + 1;; ++j) {
561 +
        if (wd == k_weekdays_back[j]) {
562 +
          return cd - (j - i);
563 +
        }
564 +
      }
565 +
    }
566 +
  }
514 567
}
515 568
516 -
CONSTEXPR_F int get_yearday(const civil_day& cd) noexcept {
517 -
  return static_cast<int>(cd - civil_day(civil_year(cd))) + 1;
569 +
CONSTEXPR_F int get_yearday(const civil_second& cs) noexcept {
570 +
  CONSTEXPR_D int k_month_offsets[1 + 12] = {
571 +
      -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
572 +
  };
573 +
  const int feb29 = (cs.month() > 2 && impl::is_leap_year(cs.year()));
574 +
  return k_month_offsets[cs.month()] + feb29 + cs.day();
518 575
}
519 576
520 577
////////////////////////////////////////////////////////////////////////

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,8 +15,8 @@
Loading
15 15
#include "time_zone_fixed.h"
16 16
17 17
#include <algorithm>
18 +
#include <cassert>
18 19
#include <chrono>
19 -
#include <cstdio>
20 20
#include <cstring>
21 21
#include <string>
22 22
@@ -25,10 +25,17 @@
Loading
25 25
namespace {
26 26
27 27
// The prefix used for the internal names of fixed-offset zones.
28 -
const char kFixedOffsetPrefix[] = "Fixed/";
28 +
const char kFixedZonePrefix[] = "Fixed/UTC";
29 +
30 +
const char kDigits[] = "0123456789";
31 +
32 +
char* Format02d(char* p, int v) {
33 +
  *p++ = kDigits[(v / 10) % 10];
34 +
  *p++ = kDigits[v % 10];
35 +
  return p;
36 +
}
29 37
30 38
int Parse02d(const char* p) {
31 -
  const char kDigits[] = "0123456789";
32 39
  if (const char* ap = std::strchr(kDigits, *p)) {
33 40
    int v = static_cast<int>(ap - kDigits);
34 41
    if (const char* bp = std::strchr(kDigits, *++p)) {
@@ -40,21 +47,19 @@
Loading
40 47
41 48
}  // namespace
42 49
43 -
bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) {
50 +
bool FixedOffsetFromName(const std::string& name, seconds* offset) {
44 51
  if (name.compare(0, std::string::npos, "UTC", 3) == 0) {
45 -
    *offset = sys_seconds::zero();
52 +
    *offset = seconds::zero();
46 53
    return true;
47 54
  }
48 55
49 -
  const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1;
50 -
  const char* const ep = kFixedOffsetPrefix + prefix_len;
51 -
  if (name.size() != prefix_len + 12)  // "<prefix>UTC+99:99:99"
56 +
  const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1;
57 +
  const char* const ep = kFixedZonePrefix + prefix_len;
58 +
  if (name.size() != prefix_len + 9)  // <prefix>+99:99:99
52 59
    return false;
53 -
  if (!std::equal(kFixedOffsetPrefix, ep, name.begin()))
60 +
  if (!std::equal(kFixedZonePrefix, ep, name.begin()))
54 61
    return false;
55 62
  const char* np = name.data() + prefix_len;
56 -
  if (*np++ != 'U' || *np++ != 'T' || *np++ != 'C')
57 -
    return false;
58 63
  if (np[0] != '+' && np[0] != '-')
59 64
    return false;
60 65
  if (np[3] != ':' || np[6] != ':')  // see note below about large offsets
@@ -69,57 +74,57 @@
Loading
69 74
70 75
  secs += ((hours * 60) + mins) * 60;
71 76
  if (secs > 24 * 60 * 60) return false;  // outside supported offset range
72 -
  *offset = sys_seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
77 +
  *offset = seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
73 78
  return true;
74 79
}
75 80
76 -
std::string FixedOffsetToName(const sys_seconds& offset) {
77 -
  if (offset == sys_seconds::zero()) return "UTC";
81 +
std::string FixedOffsetToName(const seconds& offset) {
82 +
  if (offset == seconds::zero()) return "UTC";
78 83
  if (offset < std::chrono::hours(-24) || offset > std::chrono::hours(24)) {
79 84
    // We don't support fixed-offset zones more than 24 hours
80 85
    // away from UTC to avoid complications in rendering such
81 86
    // offsets and to (somewhat) limit the total number of zones.
82 87
    return "UTC";
83 88
  }
84 -
  int seconds = static_cast<int>(offset.count());
85 -
  const char sign = (seconds < 0 ? '-' : '+');
86 -
  int minutes = seconds / 60;
87 -
  seconds %= 60;
89 +
  int offset_seconds = static_cast<int>(offset.count());
90 +
  const char sign = (offset_seconds < 0 ? '-' : '+');
91 +
  int offset_minutes = offset_seconds / 60;
92 +
  offset_seconds %= 60;
88 93
  if (sign == '-') {
89 -
    if (seconds > 0) {
90 -
      seconds -= 60;
91 -
      minutes += 1;
94 +
    if (offset_seconds > 0) {
95 +
      offset_seconds -= 60;
96 +
      offset_minutes += 1;
92 97
    }
93 -
    seconds = -seconds;
94 -
    minutes = -minutes;
98 +
    offset_seconds = -offset_seconds;
99 +
    offset_minutes = -offset_minutes;
95 100
  }
96 -
  int hours = minutes / 60;
97 -
  minutes %= 60;
98 -
  char buf[sizeof(kFixedOffsetPrefix) + sizeof("UTC-24:00:00")];
99 -
  snprintf(buf, sizeof(buf), "%sUTC%c%02d:%02d:%02d",
100 -
           kFixedOffsetPrefix, sign, hours, minutes, seconds);
101 +
  int offset_hours = offset_minutes / 60;
102 +
  offset_minutes %= 60;
103 +
  const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1;
104 +
  char buf[prefix_len + sizeof("-24:00:00")];
105 +
  char* ep = std::copy(kFixedZonePrefix, kFixedZonePrefix + prefix_len, buf);
106 +
  *ep++ = sign;
107 +
  ep = Format02d(ep, offset_hours);
108 +
  *ep++ = ':';
109 +
  ep = Format02d(ep, offset_minutes);
110 +
  *ep++ = ':';
111 +
  ep = Format02d(ep, offset_seconds);
112 +
  *ep++ = '\0';
113 +
  assert(ep == buf + sizeof(buf));
101 114
  return buf;
102 115
}
103 116
104 -
std::string FixedOffsetToAbbr(const sys_seconds& offset) {
117 +
std::string FixedOffsetToAbbr(const seconds& offset) {
105 118
  std::string abbr = FixedOffsetToName(offset);
106 -
  const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1;
107 -
  const char* const ep = kFixedOffsetPrefix + prefix_len;
108 -
  if (abbr.size() >= prefix_len) {
109 -
    if (std::equal(kFixedOffsetPrefix, ep, abbr.begin())) {
110 -
      abbr.erase(0, prefix_len);
111 -
      if (abbr.size() == 12) {                     // UTC+99:99:99
112 -
        abbr.erase(9, 1);                          // UTC+99:9999
113 -
        abbr.erase(6, 1);                          // UTC+999999
114 -
        if (abbr[8] == '0' && abbr[9] == '0') {    // UTC+999900
115 -
          abbr.erase(8, 2);                        // UTC+9999
116 -
          if (abbr[6] == '0' && abbr[7] == '0') {  // UTC+9900
117 -
            abbr.erase(6, 2);                      // UTC+99
118 -
            if (abbr[4] == '0') {                  // UTC+09
119 -
              abbr.erase(4, 1);                    // UTC+9
120 -
            }
121 -
          }
122 -
        }
119 +
  const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1;
120 +
  if (abbr.size() == prefix_len + 9) {         // <prefix>+99:99:99
121 +
    abbr.erase(0, prefix_len);                 // +99:99:99
122 +
    abbr.erase(6, 1);                          // +99:9999
123 +
    abbr.erase(3, 1);                          // +999999
124 +
    if (abbr[5] == '0' && abbr[6] == '0') {    // +999900
125 +
      abbr.erase(5, 2);                        // +9999
126 +
      if (abbr[3] == '0' && abbr[4] == '0') {  // +9900
127 +
        abbr.erase(3, 2);                      // +99
123 128
      }
124 129
    }
125 130
  }

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,7 +25,7 @@
Loading
25 25
// a grain of salt.
26 26
//
27 27
// For more information see tzfile(5), http://www.iana.org/time-zones, or
28 -
// http://en.wikipedia.org/wiki/Zoneinfo.
28 +
// https://en.wikipedia.org/wiki/Zoneinfo.
29 29
//
30 30
// Note that we assume the proleptic Gregorian calendar and 60-second
31 31
// minutes throughout.
@@ -40,59 +40,19 @@
Loading
40 40
#include <cstdlib>
41 41
#include <cstring>
42 42
#include <functional>
43 -
#include <iostream>
44 43
#include <memory>
45 44
#include <sstream>
46 45
#include <string>
47 46
48 -
#include "civil_time.h"
47 +
#include "cctz/civil_time.h"
49 48
#include "time_zone_fixed.h"
50 49
#include "time_zone_posix.h"
51 50
52 -
namespace cctz_extension {
53 -
54 -
namespace {
55 -
56 -
// A default for cctz_extension::zone_info_source_factory, which simply
57 -
// defers to the fallback factory.
58 -
std::unique_ptr<cctz::ZoneInfoSource> DefaultFactory(
59 -
    const std::string& name,
60 -
    const std::function<std::unique_ptr<cctz::ZoneInfoSource>(
61 -
        const std::string& name)>& fallback_factory) {
62 -
  return fallback_factory(name);
63 -
}
64 -
65 -
}  // namespace
66 -
67 -
// A "weak" definition for cctz_extension::zone_info_source_factory.
68 -
// The user may override this with their own "strong" definition (see
69 -
// zone_info_source.h).
70 -
#if defined(_MSC_VER)
71 -
extern ZoneInfoSourceFactory zone_info_source_factory;
72 -
extern ZoneInfoSourceFactory default_factory = DefaultFactory;
73 -
#if defined(_M_IX86)
74 -
#pragma comment( \
75 -
    linker,      \
76 -
    "/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA")
77 -
#elif defined(_M_IA_64) || defined(_M_AMD64)
78 -
#pragma comment( \
79 -
    linker,      \
80 -
    "/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA")
81 -
#else
82 -
#error Unsupported MSVC platform
83 -
#endif  // _MSC_VER
84 -
#else
85 -
ZoneInfoSourceFactory zone_info_source_factory
86 -
    __attribute__((weak)) = DefaultFactory;
87 -
#endif
88 -
89 -
}  // namespace cctz_extension
90 -
91 51
namespace cctz {
92 52
93 53
namespace {
94 54
95 -
inline bool IsLeap(cctz::year_t year) {
55 +
inline bool IsLeap(year_t year) {
96 56
  return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
97 57
}
98 58
@@ -118,6 +78,27 @@
Loading
118 78
  366 * kSecsPerDay,
119 79
};
120 80
81 +
// Convert a cctz::weekday to a POSIX TZ weekday number (0==Sun, ..., 6=Sat).
82 +
inline int ToPosixWeekday(weekday wd) {
83 +
  switch (wd) {
84 +
    case weekday::sunday:
85 +
      return 0;
86 +
    case weekday::monday:
87 +
      return 1;
88 +
    case weekday::tuesday:
89 +
      return 2;
90 +
    case weekday::wednesday:
91 +
      return 3;
92 +
    case weekday::thursday:
93 +
      return 4;
94 +
    case weekday::friday:
95 +
      return 5;
96 +
    case weekday::saturday:
97 +
      return 6;
98 +
  }
99 +
  return 0; /*NOTREACHED*/
100 +
}
101 +
121 102
// Single-byte, unsigned numeric values are encoded directly.
122 103
inline std::uint_fast8_t Decode8(const char* cp) {
123 104
  return static_cast<std::uint_fast8_t>(*cp) & 0xff;
@@ -177,7 +158,7 @@
Loading
177 158
  return (days * kSecsPerDay) + pt.time.offset;
178 159
}
179 160
180 -
inline time_zone::civil_lookup MakeUnique(const time_point<sys_seconds>& tp) {
161 +
inline time_zone::civil_lookup MakeUnique(const time_point<seconds>& tp) {
181 162
  time_zone::civil_lookup cl;
182 163
  cl.kind = time_zone::civil_lookup::UNIQUE;
183 164
  cl.pre = cl.trans = cl.post = tp;
@@ -208,7 +189,7 @@
Loading
208 189
  return cl;
209 190
}
210 191
211 -
inline civil_second YearShift(const civil_second& cs, cctz::year_t shift) {
192 +
inline civil_second YearShift(const civil_second& cs, year_t shift) {
212 193
  return civil_second(cs.year() + shift, cs.month(), cs.day(),
213 194
                      cs.hour(), cs.minute(), cs.second());
214 195
}
@@ -216,16 +197,32 @@
Loading
216 197
}  // namespace
217 198
218 199
// What (no leap-seconds) UTC+seconds zoneinfo would look like.
219 -
bool TimeZoneInfo::ResetToBuiltinUTC(const sys_seconds& offset) {
200 +
bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
220 201
  transition_types_.resize(1);
221 202
  TransitionType& tt(transition_types_.back());
222 203
  tt.utc_offset = static_cast<std::int_least32_t>(offset.count());
223 204
  tt.is_dst = false;
224 205
  tt.abbr_index = 0;
225 206
207 +
  // We temporarily add some redundant, contemporary (2015 through 2025)
208 +
  // transitions for performance reasons.  See TimeZoneInfo::LocalTime().
209 +
  // TODO: Fix the performance issue and remove the extra transitions.
226 210
  transitions_.clear();
227 -
  transitions_.reserve(2);
228 -
  for (const std::int_fast64_t unix_time : {-(1LL << 59), 2147483647LL}) {
211 +
  transitions_.reserve(12);
212 +
  for (const std::int_fast64_t unix_time : {
213 +
           -(1LL << 59),  // a "first half" transition
214 +
           1420070400LL,  // 2015-01-01T00:00:00+00:00
215 +
           1451606400LL,  // 2016-01-01T00:00:00+00:00
216 +
           1483228800LL,  // 2017-01-01T00:00:00+00:00
217 +
           1514764800LL,  // 2018-01-01T00:00:00+00:00
218 +
           1546300800LL,  // 2019-01-01T00:00:00+00:00
219 +
           1577836800LL,  // 2020-01-01T00:00:00+00:00
220 +
           1609459200LL,  // 2021-01-01T00:00:00+00:00
221 +
           1640995200LL,  // 2022-01-01T00:00:00+00:00
222 +
           1672531200LL,  // 2023-01-01T00:00:00+00:00
223 +
           1704067200LL,  // 2024-01-01T00:00:00+00:00
224 +
           1735689600LL,  // 2025-01-01T00:00:00+00:00
225 +
       }) {
229 226
    Transition& tr(*transitions_.emplace(transitions_.end()));
230 227
    tr.unix_time = unix_time;
231 228
    tr.type_index = 0;
@@ -235,12 +232,12 @@
Loading
235 232
236 233
  default_transition_type_ = 0;
237 234
  abbreviations_ = FixedOffsetToAbbr(offset);
238 -
  abbreviations_.append(1, '\0');  // add NUL
235 +
  abbreviations_.append(1, '\0');
239 236
  future_spec_.clear();  // never needed for a fixed-offset zone
240 237
  extended_ = false;
241 238
242 -
  tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs;
243 -
  tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs;
239 +
  tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
240 +
  tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
244 241
245 242
  transitions_.shrink_to_fit();
246 243
  return true;
@@ -259,8 +256,8 @@
Loading
259 256
  leapcnt = static_cast<std::size_t>(v);
260 257
  if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false;
261 258
  ttisstdcnt = static_cast<std::size_t>(v);
262 -
  if ((v = Decode32(tzh.tzh_ttisgmtcnt)) < 0) return false;
263 -
  ttisgmtcnt = static_cast<std::size_t>(v);
259 +
  if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false;
260 +
  ttisutcnt = static_cast<std::size_t>(v);
264 261
  return true;
265 262
}
266 263
@@ -273,25 +270,10 @@
Loading
273 270
  len += 1 * charcnt;               // abbreviations
274 271
  len += (time_len + 4) * leapcnt;  // leap-time + TAI-UTC
275 272
  len += 1 * ttisstdcnt;            // UTC/local indicators
276 -
  len += 1 * ttisgmtcnt;            // standard/wall indicators
273 +
  len += 1 * ttisutcnt;             // standard/wall indicators
277 274
  return len;
278 275
}
279 276
280 -
// Check that the TransitionType has the expected offset/is_dst/abbreviation.
281 -
void TimeZoneInfo::CheckTransition(const std::string& name,
282 -
                                   const TransitionType& tt,
283 -
                                   std::int_fast32_t offset, bool is_dst,
284 -
                                   const std::string& abbr) const {
285 -
  if (tt.utc_offset != offset || tt.is_dst != is_dst ||
286 -
      &abbreviations_[tt.abbr_index] != abbr) {
287 -
    std::clog << name << ": Transition"
288 -
              << " offset=" << tt.utc_offset << "/"
289 -
              << (tt.is_dst ? "DST" : "STD")
290 -
              << "/abbr=" << &abbreviations_[tt.abbr_index]
291 -
              << " does not match POSIX spec '" << future_spec_ << "'\n";
292 -
  }
293 -
}
294 -
295 277
// zic(8) can generate no-op transitions when a zone changes rules at an
296 278
// instant when there is actually no discontinuity.  So we check whether
297 279
// two transitions have equivalent types (same offset/is_dst/abbr).
@@ -300,114 +282,108 @@
Loading
300 282
  if (tt1_index == tt2_index) return true;
301 283
  const TransitionType& tt1(transition_types_[tt1_index]);
302 284
  const TransitionType& tt2(transition_types_[tt2_index]);
303 -
  if (tt1.is_dst != tt2.is_dst) return false;
304 285
  if (tt1.utc_offset != tt2.utc_offset) return false;
286 +
  if (tt1.is_dst != tt2.is_dst) return false;
305 287
  if (tt1.abbr_index != tt2.abbr_index) return false;
306 288
  return true;
307 289
}
308 290
291 +
// Find/make a transition type with these attributes.
292 +
bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
293 +
                                     const std::string& abbr,
294 +
                                     std::uint_least8_t* index) {
295 +
  std::size_t type_index = 0;
296 +
  std::size_t abbr_index = abbreviations_.size();
297 +
  for (; type_index != transition_types_.size(); ++type_index) {
298 +
    const TransitionType& tt(transition_types_[type_index]);
299 +
    const char* tt_abbr = &abbreviations_[tt.abbr_index];
300 +
    if (tt_abbr == abbr) abbr_index = tt.abbr_index;
301 +
    if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) {
302 +
      if (abbr_index == tt.abbr_index) break;  // reuse
303 +
    }
304 +
  }
305 +
  if (type_index > 255 || abbr_index > 255) {
306 +
    // No index space (8 bits) available for a new type or abbreviation.
307 +
    return false;
308 +
  }
309 +
  if (type_index == transition_types_.size()) {
310 +
    TransitionType& tt(*transition_types_.emplace(transition_types_.end()));
311 +
    tt.utc_offset = static_cast<std::int_least32_t>(utc_offset);
312 +
    tt.is_dst = is_dst;
313 +
    if (abbr_index == abbreviations_.size()) {
314 +
      abbreviations_.append(abbr);
315 +
      abbreviations_.append(1, '\0');
316 +
    }
317 +
    tt.abbr_index = static_cast<std::uint_least8_t>(abbr_index);
318 +
  }
319 +
  *index = static_cast<std::uint_least8_t>(type_index);
320 +
  return true;
321 +
}
322 +
309 323
// Use the POSIX-TZ-environment-variable-style string to handle times
310 324
// in years after the last transition stored in the zoneinfo data.
311 -
void TimeZoneInfo::ExtendTransitions(const std::string& name,
312 -
                                     const Header& hdr) {
325 +
bool TimeZoneInfo::ExtendTransitions() {
313 326
  extended_ = false;
314 -
  bool extending = !future_spec_.empty();
327 +
  if (future_spec_.empty()) return true;  // last transition prevails
315 328
316 329
  PosixTimeZone posix;
317 -
  if (extending && !ParsePosixSpec(future_spec_, &posix)) {
318 -
    std::clog << name << ": Failed to parse '" << future_spec_ << "'\n";
319 -
    extending = false;
320 -
  }
321 -
322 -
  if (extending && posix.dst_abbr.empty()) {  // std only
323 -
    // The future specification should match the last/default transition,
324 -
    // and that means that handling the future will fall out naturally.
325 -
    std::uint_fast8_t index = default_transition_type_;
326 -
    if (hdr.timecnt != 0) index = transitions_[hdr.timecnt - 1].type_index;
327 -
    const TransitionType& tt(transition_types_[index]);
328 -
    CheckTransition(name, tt, posix.std_offset, false, posix.std_abbr);
329 -
    extending = false;
330 -
  }
331 -
332 -
  if (extending && hdr.timecnt < 2) {
333 -
    std::clog << name << ": Too few transitions for POSIX spec\n";
334 -
    extending = false;
335 -
  }
336 -
337 -
  if (!extending) {
338 -
    // Ensure that there is always a transition in the second half of the
339 -
    // time line (the BIG_BANG transition is in the first half) so that the
340 -
    // signed difference between a civil_second and the civil_second of its
341 -
    // previous transition is always representable, without overflow.
342 -
    const Transition& last(transitions_.back());
343 -
    if (last.unix_time < 0) {
344 -
      const std::uint_fast8_t type_index = last.type_index;
345 -
      Transition& tr(*transitions_.emplace(transitions_.end()));
346 -
      tr.unix_time = 2147483647;  // 2038-01-19T03:14:07+00:00
347 -
      tr.type_index = type_index;
348 -
    }
349 -
    return;  // last transition wins
330 +
  if (!ParsePosixSpec(future_spec_, &posix)) return false;
331 +
332 +
  // Find transition type for the future std specification.
333 +
  std::uint_least8_t std_ti;
334 +
  if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti))
335 +
    return false;
336 +
337 +
  if (posix.dst_abbr.empty()) {  // std only
338 +
    // The future specification should match the last transition, and
339 +
    // that means that handling the future will fall out naturally.
340 +
    return EquivTransitions(transitions_.back().type_index, std_ti);
350 341
  }
351 342
343 +
  // Find transition type for the future dst specification.
344 +
  std::uint_least8_t dst_ti;
345 +
  if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti))
346 +
    return false;
347 +
352 348
  // Extend the transitions for an additional 400 years using the
353 349
  // future specification. Years beyond those can be handled by
354 350
  // mapping back to a cycle-equivalent year within that range.
355 -
  // zic(8) should probably do this so that we don't have to.
356 -
  // TODO: Reduce the extension by the number of compatible
357 -
  // transitions already in place.
358 -
  transitions_.reserve(hdr.timecnt + 400 * 2 + 1);
359 -
  transitions_.resize(hdr.timecnt + 400 * 2);
351 +
  // We may need two additional transitions for the current year.
352 +
  transitions_.reserve(transitions_.size() + 400 * 2 + 2);
360 353
  extended_ = true;
361 354
362 -
  // The future specification should match the last two transitions,
363 -
  // and those transitions should have different is_dst flags.
364 -
  const Transition* tr0 = &transitions_[hdr.timecnt - 1];
365 -
  const Transition* tr1 = &transitions_[hdr.timecnt - 2];
366 -
  const TransitionType* tt0 = &transition_types_[tr0->type_index];
367 -
  const TransitionType* tt1 = &transition_types_[tr1->type_index];
368 -
  const TransitionType& spring(tt0->is_dst ? *tt0 : *tt1);
369 -
  const TransitionType& autumn(tt0->is_dst ? *tt1 : *tt0);
370 -
  CheckTransition(name, spring, posix.dst_offset, true, posix.dst_abbr);
371 -
  CheckTransition(name, autumn, posix.std_offset, false, posix.std_abbr);
372 -
373 -
  // Add the transitions to tr1 and back to tr0 for each extra year.
374 -
  last_year_ = LocalTime(tr0->unix_time, *tt0).cs.year();
355 +
  const Transition& last(transitions_.back());
356 +
  const std::int_fast64_t last_time = last.unix_time;
357 +
  const TransitionType& last_tt(transition_types_[last.type_index]);
358 +
  last_year_ = LocalTime(last_time, last_tt).cs.year();
375 359
  bool leap_year = IsLeap(last_year_);
376 -
  const civil_day jan1(last_year_, 1, 1);
377 -
  std::int_fast64_t jan1_time = civil_second(jan1) - civil_second();
378 -
  int jan1_weekday = (static_cast<int>(get_weekday(jan1)) + 1) % 7;
379 -
  Transition* tr = &transitions_[hdr.timecnt];  // next trans to fill
380 -
  if (LocalTime(tr1->unix_time, *tt1).cs.year() != last_year_) {
381 -
    // Add a single extra transition to align to a calendar year.
382 -
    transitions_.resize(transitions_.size() + 1);
383 -
    assert(tr == &transitions_[hdr.timecnt]);  // no reallocation
384 -
    const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
385 -
    std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
386 -
    tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
387 -
    tr++->type_index = tr1->type_index;
388 -
    tr0 = &transitions_[hdr.timecnt];
389 -
    tr1 = &transitions_[hdr.timecnt - 1];
390 -
    tt0 = &transition_types_[tr0->type_index];
391 -
    tt1 = &transition_types_[tr1->type_index];
392 -
  }
393 -
  const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
394 -
  const PosixTransition& pt0(tt0->is_dst ? posix.dst_start : posix.dst_end);
395 -
  for (const cctz::year_t limit = last_year_ + 400; last_year_ < limit;) {
396 -
    last_year_ += 1;  // an additional year of generated transitions
360 +
  const civil_second jan1(last_year_);
361 +
  std::int_fast64_t jan1_time = jan1 - civil_second();
362 +
  int jan1_weekday = ToPosixWeekday(get_weekday(jan1));
363 +
364 +
  Transition dst = {0, dst_ti, civil_second(), civil_second()};
365 +
  Transition std = {0, std_ti, civil_second(), civil_second()};
366 +
  for (const year_t limit = last_year_ + 400;; ++last_year_) {
367 +
    auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start);
368 +
    auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end);
369 +
    dst.unix_time = jan1_time + dst_trans_off - posix.std_offset;
370 +
    std.unix_time = jan1_time + std_trans_off - posix.dst_offset;
371 +
    const auto* ta = dst.unix_time < std.unix_time ? &dst : &std;
372 +
    const auto* tb = dst.unix_time < std.unix_time ? &std : &dst;
373 +
    if (last_time < tb->unix_time) {
374 +
      if (last_time < ta->unix_time) transitions_.push_back(*ta);
375 +
      transitions_.push_back(*tb);
376 +
    }
377 +
    if (last_year_ == limit) break;
397 378
    jan1_time += kSecsPerYear[leap_year];
398 379
    jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7;
399 -
    leap_year = !leap_year && IsLeap(last_year_);
400 -
    std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
401 -
    tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
402 -
    tr++->type_index = tr1->type_index;
403 -
    std::int_fast64_t tr0_offset = TransOffset(leap_year, jan1_weekday, pt0);
404 -
    tr->unix_time = jan1_time + tr0_offset - tt1->utc_offset;
405 -
    tr++->type_index = tr0->type_index;
406 -
  }
407 -
  assert(tr == &transitions_[0] + transitions_.size());
380 +
    leap_year = !leap_year && IsLeap(last_year_ + 1);
381 +
  }
382 +
383 +
  return true;
408 384
}
409 385
410 -
bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
386 +
bool TimeZoneInfo::Load(ZoneInfoSource* zip) {
411 387
  // Read and validate the header.
412 388
  tzhead tzh;
413 389
  if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh))
@@ -444,7 +420,7 @@
Loading
444 420
  }
445 421
  if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt)
446 422
    return false;
447 -
  if (hdr.ttisgmtcnt != 0 && hdr.ttisgmtcnt != hdr.typecnt)
423 +
  if (hdr.ttisutcnt != 0 && hdr.ttisutcnt != hdr.typecnt)
448 424
    return false;
449 425
450 426
  // Read the data into a local buffer.
@@ -455,7 +431,7 @@
Loading
455 431
  const char* bp = tbuf.data();
456 432
457 433
  // Decode and validate the transitions.
458 -
  transitions_.reserve(hdr.timecnt + 2);  // We might add a couple.
434 +
  transitions_.reserve(hdr.timecnt + 2);
459 435
  transitions_.resize(hdr.timecnt);
460 436
  for (std::size_t i = 0; i != hdr.timecnt; ++i) {
461 437
    transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp);
@@ -476,6 +452,7 @@
Loading
476 452
  }
477 453
478 454
  // Decode and validate the transition types.
455 +
  transition_types_.reserve(hdr.typecnt + 2);
479 456
  transition_types_.resize(hdr.typecnt);
480 457
  for (std::size_t i = 0; i != hdr.typecnt; ++i) {
481 458
    transition_types_[i].utc_offset =
@@ -506,6 +483,7 @@
Loading
506 483
  }
507 484
508 485
  // Copy all the abbreviations.
486 +
  abbreviations_.reserve(hdr.charcnt + 10);
509 487
  abbreviations_.assign(bp, hdr.charcnt);
510 488
  bp += hdr.charcnt;
511 489
@@ -515,16 +493,16 @@
Loading
515 493
  // that isn't the case here (see "zic -p").
516 494
  bp += (8 + 4) * hdr.leapcnt;  // leap-time + TAI-UTC
517 495
  bp += 1 * hdr.ttisstdcnt;     // UTC/local indicators
518 -
  bp += 1 * hdr.ttisgmtcnt;     // standard/wall indicators
496 +
  bp += 1 * hdr.ttisutcnt;      // standard/wall indicators
519 497
  assert(bp == tbuf.data() + tbuf.size());
520 498
521 499
  future_spec_.clear();
522 500
  if (tzh.tzh_version[0] != '\0') {
523 501
    // Snarf up the NL-enclosed future POSIX spec. Note
524 502
    // that version '3' files utilize an extended format.
525 -
    auto get_char = [](ZoneInfoSource* zip) -> int {
503 +
    auto get_char = [](ZoneInfoSource* azip) -> int {
526 504
      unsigned char ch;  // all non-EOF results are positive
527 -
      return (zip->Read(&ch, 1) == 1) ? ch : EOF;
505 +
      return (azip->Read(&ch, 1) == 1) ? ch : EOF;
528 506
    };
529 507
    if (get_char(zip) != '\n')
530 508
      return false;
@@ -537,6 +515,13 @@
Loading
537 515
538 516
  // We don't check for EOF so that we're forwards compatible.
539 517
518 +
  // If we did not find version information during the standard loading
519 +
  // process (as of tzh_version '3' that is unsupported), then ask the
520 +
  // ZoneInfoSource for any out-of-bound version string it may be privy to.
521 +
  if (version_.empty()) {
522 +
    version_ = zip->Version();
523 +
  }
524 +
540 525
  // Trim redundant transitions. zic may have added these to work around
541 526
  // differences between the glibc and reference implementations (see
542 527
  // zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071).
@@ -551,21 +536,31 @@
Loading
551 536
  transitions_.resize(hdr.timecnt);
552 537
553 538
  // Ensure that there is always a transition in the first half of the
554 -
  // time line (the second half is handled in ExtendTransitions()) so that
555 -
  // the signed difference between a civil_second and the civil_second of
556 -
  // its previous transition is always representable, without overflow.
557 -
  // A contemporary zic will usually have already done this for us.
539 +
  // time line (the second half is handled below) so that the signed
540 +
  // difference between a civil_second and the civil_second of its
541 +
  // previous transition is always representable, without overflow.
558 542
  if (transitions_.empty() || transitions_.front().unix_time >= 0) {
559 543
    Transition& tr(*transitions_.emplace(transitions_.begin()));
560 -
    tr.unix_time = -(1LL << 59);  // see tz/zic.c "BIG_BANG"
544 +
    tr.unix_time = -(1LL << 59);  // -18267312070-10-26T17:01:52+00:00
561 545
    tr.type_index = default_transition_type_;
562 -
    hdr.timecnt += 1;
563 546
  }
564 547
565 548
  // Extend the transitions using the future specification.
566 -
  ExtendTransitions(name, hdr);
549 +
  if (!ExtendTransitions()) return false;
550 +
551 +
  // Ensure that there is always a transition in the second half of the
552 +
  // time line (the first half is handled above) so that the signed
553 +
  // difference between a civil_second and the civil_second of its
554 +
  // previous transition is always representable, without overflow.
555 +
  const Transition& last(transitions_.back());
556 +
  if (last.unix_time < 0) {
557 +
    const std::uint_fast8_t type_index = last.type_index;
558 +
    Transition& tr(*transitions_.emplace(transitions_.end()));
559 +
    tr.unix_time = 2147483647;  // 2038-01-19T03:14:07+00:00
560 +
    tr.type_index = type_index;
561 +
  }
567 562
568 -
  // Compute the local civil time for each transition and the preceeding
563 +
  // Compute the local civil time for each transition and the preceding
569 564
  // second. These will be used for reverse conversions in MakeTime().
570 565
  const TransitionType* ttp = &transition_types_[default_transition_type_];
571 566
  for (std::size_t i = 0; i != transitions_.size(); ++i) {
@@ -583,10 +578,10 @@
Loading
583 578
  }
584 579
585 580
  // Compute the maximum/minimum civil times that can be converted to a
586 -
  // time_point<sys_seconds> for each of the zone's transition types.
581 +
  // time_point<seconds> for each of the zone's transition types.
587 582
  for (auto& tt : transition_types_) {
588 -
    tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs;
589 -
    tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs;
583 +
    tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
584 +
    tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
590 585
  }
591 586
592 587
  transitions_.shrink_to_fit();
@@ -595,33 +590,57 @@
Loading
595 590
596 591
namespace {
597 592
593 +
// fopen(3) adaptor.
594 +
inline FILE* FOpen(const char* path, const char* mode) {
595 +
#if defined(_MSC_VER)
596 +
  FILE* fp;
597 +
  if (fopen_s(&fp, path, mode) != 0) fp = nullptr;
598 +
  return fp;
599 +
#else
600 +
  return fopen(path, mode);  // TODO: Enable the close-on-exec flag.
601 +
#endif
602 +
}
603 +
598 604
// A stdio(3)-backed implementation of ZoneInfoSource.
599 605
class FileZoneInfoSource : public ZoneInfoSource {
600 606
 public:
601 607
  static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
602 608
603 609
  std::size_t Read(void* ptr, std::size_t size) override {
604 -
    return fread(ptr, 1, size, fp_);
610 +
    size = std::min(size, len_);
611 +
    std::size_t nread = fread(ptr, 1, size, fp_.get());
612 +
    len_ -= nread;
613 +
    return nread;
605 614
  }
606 615
  int Skip(std::size_t offset) override {
607 -
    return fseek(fp_, static_cast<long>(offset), SEEK_CUR);
616 +
    offset = std::min(offset, len_);
617 +
    int rc = fseek(fp_.get(), static_cast<long>(offset), SEEK_CUR);
618 +
    if (rc == 0) len_ -= offset;
619 +
    return rc;
620 +
  }
621 +
  std::string Version() const override {
622 +
    // TODO: It would nice if the zoneinfo data included the tzdb version.
623 +
    return std::string();
608 624
  }
609 625
610 -
 private:
611 -
  explicit FileZoneInfoSource(FILE* fp) : fp_(fp) {}
612 -
  ~FileZoneInfoSource() { fclose(fp_); }
626 +
 protected:
627 +
  explicit FileZoneInfoSource(
628 +
      FILE* fp, std::size_t len = std::numeric_limits<std::size_t>::max())
629 +
      : fp_(fp, fclose), len_(len) {}
613 630
614 -
  FILE* const fp_;
631 +
 private:
632 +
  std::unique_ptr<FILE, int(*)(FILE*)> fp_;
633 +
  std::size_t len_;
615 634
};
616 635
617 636
std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open(
618 637
    const std::string& name) {
619 638
  // Use of the "file:" prefix is intended for testing purposes only.
620 -
  if (name.compare(0, 5, "file:") == 0) return Open(name.substr(5));
639 +
  const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
621 640
622 641
  // Map the time-zone name to a path name.
623 642
  std::string path;
624 -
  if (name.empty() || name[0] != '/') {
643 +
  if (pos == name.size() || name[pos] != '/') {
625 644
    const char* tzdir = "/usr/share/zoneinfo";
626 645
    char* tzdir_env = nullptr;
627 646
#if defined(_MSC_VER)
@@ -636,17 +655,74 @@
Loading
636 655
    free(tzdir_env);
637 656
#endif
638 657
  }
639 -
  path += name;
658 +
  path.append(name, pos, std::string::npos);
640 659
641 660
  // Open the zoneinfo file.
642 -
#if defined(_MSC_VER)
643 -
  FILE* fp;
644 -
  if (fopen_s(&fp, path.c_str(), "rb") != 0) fp = nullptr;
645 -
#else
646 -
  FILE* fp = fopen(path.c_str(), "rb");
647 -
#endif
661 +
  FILE* fp = FOpen(path.c_str(), "rb");
648 662
  if (fp == nullptr) return nullptr;
649 -
  return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(fp));
663 +
  std::size_t length = 0;
664 +
  if (fseek(fp, 0, SEEK_END) == 0) {
665 +
    long offset = ftell(fp);
666 +
    if (offset >= 0) {
667 +
      length = static_cast<std::size_t>(offset);
668 +
    }
669 +
    rewind(fp);
670 +
  }
671 +
  return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(fp, length));
672 +
}
673 +
674 +
class AndroidZoneInfoSource : public FileZoneInfoSource {
675 +
 public:
676 +
  static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
677 +
  std::string Version() const override { return version_; }
678 +
679 +
 private:
680 +
  explicit AndroidZoneInfoSource(FILE* fp, std::size_t len, const char* vers)
681 +
      : FileZoneInfoSource(fp, len), version_(vers) {}
682 +
  std::string version_;
683 +
};
684 +
685 +
std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open(
686 +
    const std::string& name) {
687 +
  // Use of the "file:" prefix is intended for testing purposes only.
688 +
  const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
689 +
690 +
  // See Android's libc/tzcode/bionic.cpp for additional information.
691 +
  for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata",
692 +
                             "/system/usr/share/zoneinfo/tzdata"}) {
693 +
    std::unique_ptr<FILE, int (*)(FILE*)> fp(FOpen(tzdata, "rb"), fclose);
694 +
    if (fp.get() == nullptr) continue;
695 +
696 +
    char hbuf[24];  // covers header.zonetab_offset too
697 +
    if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue;
698 +
    if (strncmp(hbuf, "tzdata", 6) != 0) continue;
699 +
    const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : "";
700 +
    const std::int_fast32_t index_offset = Decode32(hbuf + 12);
701 +
    const std::int_fast32_t data_offset = Decode32(hbuf + 16);
702 +
    if (index_offset < 0 || data_offset < index_offset) continue;
703 +
    if (fseek(fp.get(), static_cast<long>(index_offset), SEEK_SET) != 0)
704 +
      continue;
705 +
706 +
    char ebuf[52];  // covers entry.unused too
707 +
    const std::size_t index_size =
708 +
        static_cast<std::size_t>(data_offset - index_offset);
709 +
    const std::size_t zonecnt = index_size / sizeof(ebuf);
710 +
    if (zonecnt * sizeof(ebuf) != index_size) continue;
711 +
    for (std::size_t i = 0; i != zonecnt; ++i) {
712 +
      if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break;
713 +
      const std::int_fast32_t start = data_offset + Decode32(ebuf + 40);
714 +
      const std::int_fast32_t length = Decode32(ebuf + 44);
715 +
      if (start < 0 || length < 0) break;
716 +
      ebuf[40] = '\0';  // ensure zone name is NUL terminated
717 +
      if (strcmp(name.c_str() + pos, ebuf) == 0) {
718 +
        if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break;
719 +
        return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource(
720 +
            fp.release(), static_cast<std::size_t>(length), vers));
721 +
      }
722 +
    }
723 +
  }
724 +
725 +
  return nullptr;
650 726
}
651 727
652 728
}  // namespace
@@ -656,50 +732,54 @@
Loading
656 732
  // zone never fails because the simple, fixed-offset state can be
657 733
  // internally generated. Note that this depends on our choice to not
658 734
  // accept leap-second encoded ("right") zoneinfo.
659 -
  auto offset = sys_seconds::zero();
735 +
  auto offset = seconds::zero();
660 736
  if (FixedOffsetFromName(name, &offset)) {
661 737
    return ResetToBuiltinUTC(offset);
662 738
  }
663 739
664 740
  // Find and use a ZoneInfoSource to load the named zone.
665 741
  auto zip = cctz_extension::zone_info_source_factory(
666 -
      name, [](const std::string& name) {
667 -
        return FileZoneInfoSource::Open(name);  // fallback factory
742 +
      name, [](const std::string& n) -> std::unique_ptr<ZoneInfoSource> {
743 +
        if (auto z = FileZoneInfoSource::Open(n)) return z;
744 +
        if (auto z = AndroidZoneInfoSource::Open(n)) return z;
745 +
        return nullptr;
668 746
      });
669 -
  return zip != nullptr && Load(name, zip.get());
747 +
  return zip != nullptr && Load(zip.get());
670 748
}
671 749
672 750
// BreakTime() translation for a particular transition type.
673 751
time_zone::absolute_lookup TimeZoneInfo::LocalTime(
674 752
    std::int_fast64_t unix_time, const TransitionType& tt) const {
675 -
  time_zone::absolute_lookup al;
676 -
677 753
  // A civil time in "+offset" looks like (time+offset) in UTC.
678 754
  // Note: We perform two additions in the civil_second domain to
679 755
  // sidestep the chance of overflow in (unix_time + tt.utc_offset).
680 -
  al.cs = (civil_second() + unix_time) + tt.utc_offset;
681 -
682 -
  // Handle offset, is_dst, and abbreviation.
683 -
  al.offset = tt.utc_offset;
684 -
  al.is_dst = tt.is_dst;
685 -
  al.abbr = &abbreviations_[tt.abbr_index];
756 +
  return {(civil_second() + unix_time) + tt.utc_offset,
757 +
          tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]};
758 +
}
686 759
687 -
  return al;
760 +
// BreakTime() translation for a particular transition.
761 +
time_zone::absolute_lookup TimeZoneInfo::LocalTime(
762 +
    std::int_fast64_t unix_time, const Transition& tr) const {
763 +
  const TransitionType& tt = transition_types_[tr.type_index];
764 +
  // Note: (unix_time - tr.unix_time) will never overflow as we
765 +
  // have ensured that there is always a "nearby" transition.
766 +
  return {tr.civil_sec + (unix_time - tr.unix_time),  // TODO: Optimize.
767 +
          tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]};
688 768
}
689 769
690 770
// MakeTime() translation with a conversion-preserving +N * 400-year shift.
691 771
time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs,
692 -
                                                cctz::year_t c4_shift) const {
772 +
                                                year_t c4_shift) const {
693 773
  assert(last_year_ - 400 < cs.year() && cs.year() <= last_year_);
694 774
  time_zone::civil_lookup cl = MakeTime(cs);
695 -
  if (c4_shift > sys_seconds::max().count() / kSecsPer400Years) {
696 -
    cl.pre = cl.trans = cl.post = time_point<sys_seconds>::max();
775 +
  if (c4_shift > seconds::max().count() / kSecsPer400Years) {
776 +
    cl.pre = cl.trans = cl.post = time_point<seconds>::max();
697 777
  } else {
698 -
    const auto offset = sys_seconds(c4_shift * kSecsPer400Years);
699 -
    const auto limit = time_point<sys_seconds>::max() - offset;
778 +
    const auto offset = seconds(c4_shift * kSecsPer400Years);
779 +
    const auto limit = time_point<seconds>::max() - offset;
700 780
    for (auto* tp : {&cl.pre, &cl.trans, &cl.post}) {
701 781
      if (*tp > limit) {
702 -
        *tp = time_point<sys_seconds>::max();
782 +
        *tp = time_point<seconds>::max();
703 783
      } else {
704 784
        *tp += offset;
705 785
      }
@@ -709,12 +789,13 @@
Loading
709 789
}
710 790
711 791
time_zone::absolute_lookup TimeZoneInfo::BreakTime(
712 -
    const time_point<sys_seconds>& tp) const {
792 +
    const time_point<seconds>& tp) const {
713 793
  std::int_fast64_t unix_time = ToUnixSeconds(tp);
714 794
  const std::size_t timecnt = transitions_.size();
715 -
  if (timecnt == 0 || unix_time < transitions_[0].unix_time) {
716 -
    const std::uint_fast8_t type_index = default_transition_type_;
717 -
    return LocalTime(unix_time, transition_types_[type_index]);
795 +
  assert(timecnt != 0);  // We always add a transition.
796 +
797 +
  if (unix_time < transitions_[0].unix_time) {
798 +
    return LocalTime(unix_time, transition_types_[default_transition_type_]);
718 799
  }
719 800
  if (unix_time >= transitions_[timecnt - 1].unix_time) {
720 801
    // After the last transition. If we extended the transitions using
@@ -723,22 +804,20 @@
Loading
723 804
    if (extended_) {
724 805
      const std::int_fast64_t diff =
725 806
          unix_time - transitions_[timecnt - 1].unix_time;
726 -
      const cctz::year_t shift = diff / kSecsPer400Years + 1;
727 -
      const auto d = sys_seconds(shift * kSecsPer400Years);
807 +
      const year_t shift = diff / kSecsPer400Years + 1;
808 +
      const auto d = seconds(shift * kSecsPer400Years);
728 809
      time_zone::absolute_lookup al = BreakTime(tp - d);
729 810
      al.cs = YearShift(al.cs, shift * 400);
730 811
      return al;
731 812
    }
732 -
    const std::uint_fast8_t type_index = transitions_[timecnt - 1].type_index;
733 -
    return LocalTime(unix_time, transition_types_[type_index]);
813 +
    return LocalTime(unix_time, transitions_[timecnt - 1]);
734 814
  }
735 815
736 816
  const std::size_t hint = local_time_hint_.load(std::memory_order_relaxed);
737 817
  if (0 < hint && hint < timecnt) {
738 -
    if (unix_time < transitions_[hint].unix_time) {
739 -
      if (!(unix_time < transitions_[hint - 1].unix_time)) {
740 -
        const std::uint_fast8_t type_index = transitions_[hint - 1].type_index;
741 -
        return LocalTime(unix_time, transition_types_[type_index]);
818 +
    if (transitions_[hint - 1].unix_time <= unix_time) {
819 +
      if (unix_time < transitions_[hint].unix_time) {
820 +
        return LocalTime(unix_time, transitions_[hint - 1]);
742 821
      }
743 822
    }
744 823
  }
@@ -749,8 +828,7 @@
Loading
749 828
                                          Transition::ByUnixTime());
750 829
  local_time_hint_.store(static_cast<std::size_t>(tr - begin),
751 830
                         std::memory_order_relaxed);
752 -
  const std::uint_fast8_t type_index = (--tr)->type_index;
753 -
  return LocalTime(unix_time, transition_types_[type_index]);
831 +
  return LocalTime(unix_time, *--tr);
754 832
}
755 833
756 834
time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const {
@@ -763,13 +841,13 @@
Loading
763 841
  const Transition* end = begin + timecnt;
764 842
  if (cs < begin->civil_sec) {
765 843
    tr = begin;
766 -
  } else if (!(cs < transitions_[timecnt - 1].civil_sec)) {
844 +
  } else if (cs >= transitions_[timecnt - 1].civil_sec) {
767 845
    tr = end;
768 846
  } else {
769 847
    const std::size_t hint = time_local_hint_.load(std::memory_order_relaxed);
770 848
    if (0 < hint && hint < timecnt) {
771 -
      if (cs < transitions_[hint].civil_sec) {
772 -
        if (!(cs < transitions_[hint - 1].civil_sec)) {
849 +
      if (transitions_[hint - 1].civil_sec <= cs) {
850 +
        if (cs < transitions_[hint].civil_sec) {
773 851
          tr = begin + hint;
774 852
        }
775 853
      }
@@ -783,10 +861,10 @@
Loading
783 861
  }
784 862
785 863
  if (tr == begin) {
786 -
    if (!(tr->prev_civil_sec < cs)) {
864 +
    if (tr->prev_civil_sec >= cs) {
787 865
      // Before first transition, so use the default offset.
788 866
      const TransitionType& tt(transition_types_[default_transition_type_]);
789 -
      if (cs < tt.civil_min) return MakeUnique(time_point<sys_seconds>::min());
867 +
      if (cs < tt.civil_min) return MakeUnique(time_point<seconds>::min());
790 868
      return MakeUnique(cs - (civil_second() + tt.utc_offset));
791 869
    }
792 870
    // tr->prev_civil_sec < cs < tr->civil_sec
@@ -794,16 +872,16 @@
Loading
794 872
  }
795 873
796 874
  if (tr == end) {
797 -
    if ((--tr)->prev_civil_sec < cs) {
875 +
    if (cs > (--tr)->prev_civil_sec) {
798 876
      // After the last transition. If we extended the transitions using
799 877
      // future_spec_, shift back to a supported year using the 400-year
800 878
      // cycle of calendaric equivalence and then compensate accordingly.
801 879
      if (extended_ && cs.year() > last_year_) {
802 -
        const cctz::year_t shift = (cs.year() - last_year_ - 1) / 400 + 1;
880 +
        const year_t shift = (cs.year() - last_year_ - 1) / 400 + 1;
803 881
        return TimeLocal(YearShift(cs, shift * -400), shift);
804 882
      }
805 883
      const TransitionType& tt(transition_types_[tr->type_index]);
806 -
      if (cs > tt.civil_max) return MakeUnique(time_point<sys_seconds>::max());
884 +
      if (cs > tt.civil_max) return MakeUnique(time_point<seconds>::max());
807 885
      return MakeUnique(tr->unix_time + (cs - tr->civil_sec));
808 886
    }
809 887
    // tr->civil_sec <= cs <= tr->prev_civil_sec
@@ -815,7 +893,7 @@
Loading
815 893
    return MakeSkipped(*tr, cs);
816 894
  }
817 895
818 -
  if (!((--tr)->prev_civil_sec < cs)) {
896 +
  if (cs <= (--tr)->prev_civil_sec) {
819 897
    // tr->civil_sec <= cs <= tr->prev_civil_sec
820 898
    return MakeRepeated(*tr, cs);
821 899
  }
@@ -824,69 +902,76 @@
Loading
824 902
  return MakeUnique(tr->unix_time + (cs - tr->civil_sec));
825 903
}
826 904
905 +
std::string TimeZoneInfo::Version() const {
906 +
  return version_;
907 +
}
908 +
827 909
std::string TimeZoneInfo::Description() const {
828 910
  std::ostringstream oss;
829 -
  // TODO: It would nice if the zoneinfo data included the zone name.
830 -
  // TODO: It would nice if the zoneinfo data included the tzdb version.
831 911
  oss << "#trans=" << transitions_.size();
832 912
  oss << " #types=" << transition_types_.size();
833 913
  oss << " spec='" << future_spec_ << "'";
834 914
  return oss.str();
835 915
}
836 916
837 -
bool TimeZoneInfo::NextTransition(time_point<sys_seconds>* tp) const {
917 +
bool TimeZoneInfo::NextTransition(const time_point<seconds>& tp,
918 +
                                  time_zone::civil_transition* trans) const {
838 919
  if (transitions_.empty()) return false;
839 920
  const Transition* begin = &transitions_[0];
840 921
  const Transition* end = begin + transitions_.size();
841 922
  if (begin->unix_time <= -(1LL << 59)) {
842 -
    // Do not report the BIG_BANG found in recent zoneinfo data as it is
843 -
    // really a sentinel, not a transition.  See third_party/tz/zic.c.
923 +
    // Do not report the BIG_BANG found in some zoneinfo data as it is
924 +
    // really a sentinel, not a transition.  See pre-2018f tz/zic.c.
844 925
    ++begin;
845 926
  }
846 -
  std::int_fast64_t unix_time = ToUnixSeconds(*tp);
847 -
  const Transition target = { unix_time };
927 +
  std::int_fast64_t unix_time = ToUnixSeconds(tp);
928 +
  const Transition target = {unix_time, 0, civil_second(), civil_second()};
848 929
  const Transition* tr = std::upper_bound(begin, end, target,
849 930
                                          Transition::ByUnixTime());
850 -
  if (tr != begin) {  // skip no-op transitions
851 -
    for (; tr != end; ++tr) {
852 -
      if (!EquivTransitions(tr[-1].type_index, tr[0].type_index)) break;
853 -
    }
931 +
  for (; tr != end; ++tr) {  // skip no-op transitions
932 +
    std::uint_fast8_t prev_type_index =
933 +
        (tr == begin) ? default_transition_type_ : tr[-1].type_index;
934 +
    if (!EquivTransitions(prev_type_index, tr[0].type_index)) break;
854 935
  }
855 936
  // When tr == end we return false, ignoring future_spec_.
856 937
  if (tr == end) return false;
857 -
  *tp = FromUnixSeconds(tr->unix_time);
938 +
  trans->from = tr->prev_civil_sec + 1;
939 +
  trans->to = tr->civil_sec;
858 940
  return true;
859 941
}
860 942
861 -
bool TimeZoneInfo::PrevTransition(time_point<sys_seconds>* tp) const {
943 +
bool TimeZoneInfo::PrevTransition(const time_point<seconds>& tp,
944 +
                                  time_zone::civil_transition* trans) const {
862 945
  if (transitions_.empty()) return false;
863 946
  const Transition* begin = &transitions_[0];
864 947
  const Transition* end = begin + transitions_.size();
865 948
  if (begin->unix_time <= -(1LL << 59)) {
866 -
    // Do not report the BIG_BANG found in recent zoneinfo data as it is
867 -
    // really a sentinel, not a transition.  See third_party/tz/zic.c.
949 +
    // Do not report the BIG_BANG found in some zoneinfo data as it is
950 +
    // really a sentinel, not a transition.  See pre-2018f tz/zic.c.
868 951
    ++begin;
869 952
  }
870 -
  std::int_fast64_t unix_time = ToUnixSeconds(*tp);
871 -
  if (FromUnixSeconds(unix_time) != *tp) {
953 +
  std::int_fast64_t unix_time = ToUnixSeconds(tp);
954 +
  if (FromUnixSeconds(unix_time) != tp) {
872 955
    if (unix_time == std::numeric_limits<std::int_fast64_t>::max()) {
873 956
      if (end == begin) return false;  // Ignore future_spec_.
874 -
      *tp = FromUnixSeconds((--end)->unix_time);
957 +
      trans->from = (--end)->prev_civil_sec + 1;
958 +
      trans->to = end->civil_sec;
875 959
      return true;
876 960
    }
877 961
    unix_time += 1;  // ceils
878 962
  }
879 -
  const Transition target = { unix_time };
963 +
  const Transition target = {unix_time, 0, civil_second(), civil_second()};
880 964
  const Transition* tr = std::lower_bound(begin, end, target,
881 965
                                          Transition::ByUnixTime());
882 -
  if (tr != begin) {  // skip no-op transitions
883 -
    for (; tr - 1 != begin; --tr) {
884 -
      if (!EquivTransitions(tr[-2].type_index, tr[-1].type_index)) break;
885 -
    }
966 +
  for (; tr != begin; --tr) {  // skip no-op transitions
967 +
    std::uint_fast8_t prev_type_index =
968 +
        (tr - 1 == begin) ? default_transition_type_ : tr[-2].type_index;
969 +
    if (!EquivTransitions(prev_type_index, tr[-1].type_index)) break;
886 970
  }
887 971
  // When tr == end we return the "last" transition, ignoring future_spec_.
888 972
  if (tr == begin) return false;
889 -
  *tp = FromUnixSeconds((--tr)->unix_time);
973 +
  trans->from = (--tr)->prev_civil_sec + 1;
974 +
  trans->to = tr->civil_sec;
890 975
  return true;
891 976
}
892 977

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,11 +21,11 @@
Loading
21 21
#include <string>
22 22
#include <vector>
23 23
24 -
#include "civil_time.h"
25 -
#include "time_zone.h"
24 +
#include "cctz/civil_time.h"
25 +
#include "cctz/time_zone.h"
26 +
#include "cctz/zone_info_source.h"
26 27
#include "time_zone_if.h"
27 28
#include "tzfile.h"
28 -
#include "zone_info_source.h"
29 29
30 30
namespace cctz {
31 31
@@ -69,12 +69,15 @@
Loading
69 69
70 70
  // TimeZoneIf implementations.
71 71
  time_zone::absolute_lookup BreakTime(
72 -
      const time_point<sys_seconds>& tp) const override;
72 +
      const time_point<seconds>& tp) const override;
73 73
  time_zone::civil_lookup MakeTime(
74 74
      const civil_second& cs) const override;
75 +
  bool NextTransition(const time_point<seconds>& tp,
76 +
                      time_zone::civil_transition* trans) const override;
77 +
  bool PrevTransition(const time_point<seconds>& tp,
78 +
                      time_zone::civil_transition* trans) const override;
79 +
  std::string Version() const override;
75 80
  std::string Description() const override;
76 -
  bool NextTransition(time_point<sys_seconds>* tp) const override;
77 -
  bool PrevTransition(time_point<sys_seconds>* tp) const override;
78 81
79 82
 private:
80 83
  struct Header {  // counts of:
@@ -83,36 +86,38 @@
Loading
83 86
    std::size_t charcnt;     // zone abbreviation characters
84 87
    std::size_t leapcnt;     // leap seconds (we expect none)
85 88
    std::size_t ttisstdcnt;  // UTC/local indicators (unused)
86 -
    std::size_t ttisgmtcnt;  // standard/wall indicators (unused)
89 +
    std::size_t ttisutcnt;   // standard/wall indicators (unused)
87 90
88 91
    bool Build(const tzhead& tzh);
89 92
    std::size_t DataLength(std::size_t time_len) const;
90 93
  };
91 94
92 -
  void CheckTransition(const std::string& name, const TransitionType& tt,
93 -
                       std::int_fast32_t offset, bool is_dst,
94 -
                       const std::string& abbr) const;
95 +
  bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
96 +
                         const std::string& abbr, std::uint_least8_t* index);
95 97
  bool EquivTransitions(std::uint_fast8_t tt1_index,
96 98
                        std::uint_fast8_t tt2_index) const;
97 -
  void ExtendTransitions(const std::string& name, const Header& hdr);
99 +
  bool ExtendTransitions();
98 100
99 -
  bool ResetToBuiltinUTC(const sys_seconds& offset);
100 -
  bool Load(const std::string& name, ZoneInfoSource* zip);
101 +
  bool ResetToBuiltinUTC(const seconds& offset);
102 +
  bool Load(ZoneInfoSource* zip);
101 103
102 -
  // Helpers for BreakTime() and MakeTime() respectively.
104 +
  // Helpers for BreakTime() and MakeTime().
103 105
  time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time,
104 106
                                       const TransitionType& tt) const;
107 +
  time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time,
108 +
                                       const Transition& tr) const;
105 109
  time_zone::civil_lookup TimeLocal(const civil_second& cs,
106 -
                                    cctz::year_t c4_shift) const;
110 +
                                    year_t c4_shift) const;
107 111
108 112
  std::vector<Transition> transitions_;  // ordered by unix_time and civil_sec
109 113
  std::vector<TransitionType> transition_types_;  // distinct transition types
110 114
  std::uint_fast8_t default_transition_type_;  // for before first transition
111 115
  std::string abbreviations_;  // all the NUL-terminated abbreviations
112 116
117 +
  std::string version_;      // the tzdata version if available
113 118
  std::string future_spec_;  // for after the last zic transition
114 119
  bool extended_;            // future_spec_ was used to generate transitions
115 -
  cctz::year_t last_year_;   // the final year of the generated transitions
120 +
  year_t last_year_;         // the final year of the generated transitions
116 121
117 122
  // We remember the transitions found during the last BreakTime() and
118 123
  // MakeTime() calls. If the next request is for the same transition we

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,139 +13,293 @@
Loading
13 13
//   limitations under the License.
14 14
15 15
#if defined(_WIN32) || defined(_WIN64)
16 -
#define _CRT_SECURE_NO_WARNINGS
16 +
#define _CRT_SECURE_NO_WARNINGS 1
17 17
#endif
18 18
19 19
#include "time_zone_libc.h"
20 20
21 21
#include <chrono>
22 22
#include <ctime>
23 -
#include <tuple>
23 +
#include <limits>
24 24
#include <utility>
25 25
26 -
#include "civil_time.h"
27 -
#include "time_zone.h"
26 +
#include "cctz/civil_time.h"
27 +
#include "cctz/time_zone.h"
28 28
29 29
namespace cctz {
30 30
31 31
namespace {
32 32
33 -
// .first is seconds east of UTC; .second is the time-zone abbreviation.
34 -
using OffsetAbbr = std::pair<int, const char*>;
35 -
36 -
// Defines a function that can be called as follows:
37 -
//
38 -
//   std::tm tm = ...;
39 -
//   OffsetAbbr off_abbr = get_offset_abbr(tm);
40 -
//
41 33
#if defined(_WIN32) || defined(_WIN64)
42 34
// Uses the globals: '_timezone', '_dstbias' and '_tzname'.
43 -
OffsetAbbr get_offset_abbr(const std::tm& tm) {
35 +
auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + _dstbias) {
36 +
  const bool is_dst = tm.tm_isdst > 0;
37 +
  return _timezone + (is_dst ? _dstbias : 0);
38 +
}
39 +
auto tm_zone(const std::tm& tm) -> decltype(_tzname[0]) {
44 40
  const bool is_dst = tm.tm_isdst > 0;
45 -
  const int off = _timezone + (is_dst ? _dstbias : 0);
46 -
  const char* abbr = _tzname[is_dst];
47 -
  return {off, abbr};
41 +
  return _tzname[is_dst];
48 42
}
49 43
#elif defined(__sun)
50 44
// Uses the globals: 'timezone', 'altzone' and 'tzname'.
51 -
OffsetAbbr get_offset_abbr(const std::tm& tm) {
45 +
auto tm_gmtoff(const std::tm& tm) -> decltype(timezone) {
52 46
  const bool is_dst = tm.tm_isdst > 0;
53 -
  const int off = is_dst ? altzone : timezone;
54 -
  const char* abbr = tzname[is_dst];
55 -
  return {off, abbr};
47 +
  return is_dst ? altzone : timezone;
56 48
}
57 -
#elif defined(__native_client__) || defined(__myriad2__) || defined(__asmjs__)
49 +
auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) {
50 +
  const bool is_dst = tm.tm_isdst > 0;
51 +
  return tzname[is_dst];
52 +
}
53 +
#elif defined(__native_client__) || defined(__myriad2__) || \
54 +
    defined(__EMSCRIPTEN__)
58 55
// Uses the globals: 'timezone' and 'tzname'.
59 -
OffsetAbbr get_offset_abbr(const std::tm& tm) {
56 +
auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) {
57 +
  const bool is_dst = tm.tm_isdst > 0;
58 +
  return _timezone + (is_dst ? 60 * 60 : 0);
59 +
}
60 +
auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) {
60 61
  const bool is_dst = tm.tm_isdst > 0;
61 -
  const int off = _timezone + (is_dst ? 60 * 60 : 0);
62 -
  const char* abbr = tzname[is_dst];
63 -
  return {off, abbr};
62 +
  return tzname[is_dst];
63 +
}
64 +
#else
65 +
// Adapt to different spellings of the struct std::tm extension fields.
66 +
#if defined(tm_gmtoff)
67 +
auto tm_gmtoff(const std::tm& tm) -> decltype(tm.tm_gmtoff) {
68 +
  return tm.tm_gmtoff;
69 +
}
70 +
#elif defined(__tm_gmtoff)
71 +
auto tm_gmtoff(const std::tm& tm) -> decltype(tm.__tm_gmtoff) {
72 +
  return tm.__tm_gmtoff;
64 73
}
65 74
#else
66 -
//
67 -
// Returns an OffsetAbbr using std::tm fields with various spellings.
68 -
//
69 -
#if !defined(tm_gmtoff) && !defined(tm_zone)
70 75
template <typename T>
71 -
OffsetAbbr get_offset_abbr(const T& tm, decltype(&T::tm_gmtoff) = nullptr,
72 -
                           decltype(&T::tm_zone) = nullptr) {
73 -
  return {tm.tm_gmtoff, tm.tm_zone};
76 +
auto tm_gmtoff(const T& tm) -> decltype(tm.tm_gmtoff) {
77 +
  return tm.tm_gmtoff;
74 78
}
75 -
#endif  // !defined(tm_gmtoff) && !defined(tm_zone)
76 -
#if !defined(__tm_gmtoff) && !defined(__tm_zone)
77 79
template <typename T>
78 -
OffsetAbbr get_offset_abbr(const T& tm, decltype(&T::__tm_gmtoff) = nullptr,
79 -
                           decltype(&T::__tm_zone) = nullptr) {
80 -
  return {tm.__tm_gmtoff, tm.__tm_zone};
80 +
auto tm_gmtoff(const T& tm) -> decltype(tm.__tm_gmtoff) {
81 +
  return tm.__tm_gmtoff;
81 82
}
82 -
#endif  // !defined(__tm_gmtoff) && !defined(__tm_zone)
83 +
#endif  // tm_gmtoff
84 +
#if defined(tm_zone)
85 +
auto tm_zone(const std::tm& tm) -> decltype(tm.tm_zone) {
86 +
  return tm.tm_zone;
87 +
}
88 +
#elif defined(__tm_zone)
89 +
auto tm_zone(const std::tm& tm) -> decltype(tm.__tm_zone) {
90 +
  return tm.__tm_zone;
91 +
}
92 +
#else
93 +
template <typename T>
94 +
auto tm_zone(const T& tm) -> decltype(tm.tm_zone) {
95 +
  return tm.tm_zone;
96 +
}
97 +
template <typename T>
98 +
auto tm_zone(const T& tm) -> decltype(tm.__tm_zone) {
99 +
  return tm.__tm_zone;
100 +
}
101 +
#endif  // tm_zone
83 102
#endif
84 103
104 +
inline std::tm* gm_time(const std::time_t *timep, std::tm *result) {
105 +
#if defined(_WIN32) || defined(_WIN64)
106 +
    return gmtime_s(result, timep) ? nullptr : result;
107 +
#else
108 +
    return gmtime_r(timep, result);
109 +
#endif
110 +
}
111 +
112 +
inline std::tm* local_time(const std::time_t *timep, std::tm *result) {
113 +
#if defined(_WIN32) || defined(_WIN64)
114 +
    return localtime_s(result, timep) ? nullptr : result;
115 +
#else
116 +
    return localtime_r(timep, result);
117 +
#endif
118 +
}
119 +
120 +
// Converts a civil second and "dst" flag into a time_t and UTC offset.
121 +
// Returns false if time_t cannot represent the requested civil second.
122 +
// Caller must have already checked that cs.year() will fit into a tm_year.
123 +
bool make_time(const civil_second& cs, int is_dst, std::time_t* t, int* off) {
124 +
  std::tm tm;
125 +
  tm.tm_year = static_cast<int>(cs.year() - year_t{1900});
126 +
  tm.tm_mon = cs.month() - 1;
127 +
  tm.tm_mday = cs.day();
128 +
  tm.tm_hour = cs.hour();
129 +
  tm.tm_min = cs.minute();
130 +
  tm.tm_sec = cs.second();
131 +
  tm.tm_isdst = is_dst;
132 +
  *t = std::mktime(&tm);
133 +
  if (*t == std::time_t{-1}) {
134 +
    std::tm tm2;
135 +
    const std::tm* tmp = local_time(t, &tm2);
136 +
    if (tmp == nullptr || tmp->tm_year != tm.tm_year ||
137 +
        tmp->tm_mon != tm.tm_mon || tmp->tm_mday != tm.tm_mday ||
138 +
        tmp->tm_hour != tm.tm_hour || tmp->tm_min != tm.tm_min ||
139 +
        tmp->tm_sec != tm.tm_sec) {
140 +
      // A true error (not just one second before the epoch).
141 +
      return false;
142 +
    }
143 +
  }
144 +
  *off = static_cast<int>(tm_gmtoff(tm));
145 +
  return true;
146 +
}
147 +
148 +
// Find the least time_t in [lo:hi] where local time matches offset, given:
149 +
// (1) lo doesn't match, (2) hi does, and (3) there is only one transition.
150 +
std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) {
151 +
  std::tm tm;
152 +
  while (lo + 1 != hi) {
153 +
    const std::time_t mid = lo + (hi - lo) / 2;
154 +
    std::tm* tmp = local_time(&mid, &tm);
155 +
    if (tmp != nullptr) {
156 +
      if (tm_gmtoff(*tmp) == offset) {
157 +
        hi = mid;
158 +
      } else {
159 +
        lo = mid;
160 +
      }
161 +
    } else {
162 +
      // If std::tm cannot hold some result we resort to a linear search,
163 +
      // ignoring all failed conversions.  Slow, but never really happens.
164 +
      while (++lo != hi) {
165 +
        tmp = local_time(&lo, &tm);
166 +
        if (tmp != nullptr) {
167 +
          if (tm_gmtoff(*tmp) == offset) break;
168 +
        }
169 +
      }
170 +
      return lo;
171 +
    }
172 +
  }
173 +
  return hi;
174 +
}
175 +
85 176
}  // namespace
86 177
87 178
TimeZoneLibC::TimeZoneLibC(const std::string& name)
88 179
    : local_(name == "localtime") {}
89 180
90 181
time_zone::absolute_lookup TimeZoneLibC::BreakTime(
91 -
    const time_point<sys_seconds>& tp) const {
182 +
    const time_point<seconds>& tp) const {
92 183
  time_zone::absolute_lookup al;
93 -
  std::time_t t = ToUnixSeconds(tp);
184 +
  al.offset = 0;
185 +
  al.is_dst = false;
186 +
  al.abbr = "-00";
187 +
188 +
  const std::int_fast64_t s = ToUnixSeconds(tp);
189 +
190 +
  // If std::time_t cannot hold the input we saturate the output.
191 +
  if (s < std::numeric_limits<std::time_t>::min()) {
192 +
    al.cs = civil_second::min();
193 +
    return al;
194 +
  }
195 +
  if (s > std::numeric_limits<std::time_t>::max()) {
196 +
    al.cs = civil_second::max();
197 +
    return al;
198 +
  }
199 +
200 +
  const std::time_t t = static_cast<std::time_t>(s);
94 201
  std::tm tm;
95 -
  if (local_) {
96 -
#if defined(_WIN32) || defined(_WIN64)
97 -
    localtime_s(&tm, &t);
98 -
#else
99 -
    localtime_r(&t, &tm);
100 -
#endif
101 -
    std::tie(al.offset, al.abbr) = get_offset_abbr(tm);
102 -
  } else {
103 -
#if defined(_WIN32) || defined(_WIN64)
104 -
    gmtime_s(&tm, &t);
105 -
#else
106 -
    gmtime_r(&t, &tm);
107 -
#endif
108 -
    al.offset = 0;
109 -
    al.abbr = "UTC";
202 +
  std::tm* tmp = local_ ? local_time(&t, &tm) : gm_time(&t, &tm);
203 +
204 +
  // If std::tm cannot hold the result we saturate the output.
205 +
  if (tmp == nullptr) {
206 +
    al.cs = (s < 0) ? civil_second::min() : civil_second::max();
207 +
    return al;
110 208
  }
111 -
  al.cs = civil_second(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
112 -
                       tm.tm_hour, tm.tm_min, tm.tm_sec);
113 -
  al.is_dst = tm.tm_isdst > 0;
209 +
210 +
  const year_t year = tmp->tm_year + year_t{1900};
211 +
  al.cs = civil_second(year, tmp->tm_mon + 1, tmp->tm_mday,
212 +
                       tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
213 +
  al.offset = static_cast<int>(tm_gmtoff(*tmp));
214 +
  al.abbr = local_ ? tm_zone(*tmp) : "UTC";  // as expected by cctz
215 +
  al.is_dst = tmp->tm_isdst > 0;
114 216
  return al;
115 217
}
116 218
117 219
time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const {
118 -
  time_zone::civil_lookup cl;
119 -
  std::time_t t;
120 -
  if (local_) {
121 -
    // Does not handle SKIPPED/AMBIGUOUS or huge years.
122 -
    std::tm tm;
123 -
    tm.tm_year = static_cast<int>(cs.year() - 1900);
124 -
    tm.tm_mon = cs.month() - 1;
125 -
    tm.tm_mday = cs.day();
126 -
    tm.tm_hour = cs.hour();
127 -
    tm.tm_min = cs.minute();
128 -
    tm.tm_sec = cs.second();
129 -
    tm.tm_isdst = -1;
130 -
    t = std::mktime(&tm);
220 +
  if (!local_) {
221 +
    // If time_point<seconds> cannot hold the result we saturate.
222 +
    static const civil_second min_tp_cs =
223 +
        civil_second() + ToUnixSeconds(time_point<seconds>::min());
224 +
    static const civil_second max_tp_cs =
225 +
        civil_second() + ToUnixSeconds(time_point<seconds>::max());
226 +
    const time_point<seconds> tp =
227 +
        (cs < min_tp_cs)
228 +
            ? time_point<seconds>::min()
229 +
            : (cs > max_tp_cs) ? time_point<seconds>::max()
230 +
                               : FromUnixSeconds(cs - civil_second());
231 +
    return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
232 +
  }
233 +
234 +
  // If tm_year cannot hold the requested year we saturate the result.
235 +
  if (cs.year() < 0) {
236 +
    if (cs.year() < std::numeric_limits<int>::min() + year_t{1900}) {
237 +
      const time_point<seconds> tp = time_point<seconds>::min();
238 +
      return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
239 +
    }
131 240
  } else {
132 -
    t = cs - civil_second();
241 +
    if (cs.year() - year_t{1900} > std::numeric_limits<int>::max()) {
242 +
      const time_point<seconds> tp = time_point<seconds>::max();
243 +
      return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
244 +
    }
133 245
  }
134 -
  cl.kind = time_zone::civil_lookup::UNIQUE;
135 -
  cl.pre = cl.trans = cl.post = FromUnixSeconds(t);
136 -
  return cl;
137 -
}
138 246
139 -
std::string TimeZoneLibC::Description() const {
140 -
  return local_ ? "localtime" : "UTC";
247 +
  // We probe with "is_dst" values of 0 and 1 to try to distinguish unique
248 +
  // civil seconds from skipped or repeated ones.  This is not always possible
249 +
  // however, as the "dst" flag does not change over some offset transitions.
250 +
  // We are also subject to the vagaries of mktime() implementations.
251 +
  std::time_t t0, t1;
252 +
  int offset0, offset1;
253 +
  if (make_time(cs, 0, &t0, &offset0) && make_time(cs, 1, &t1, &offset1)) {
254 +
    if (t0 == t1) {
255 +
      // The civil time was singular (pre == trans == post).
256 +
      const time_point<seconds> tp = FromUnixSeconds(t0);
257 +
      return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
258 +
    }
259 +
260 +
    if (t0 > t1) {
261 +
      std::swap(t0, t1);
262 +
      std::swap(offset0, offset1);
263 +
    }
264 +
    const std::time_t tt = find_trans(t0, t1, offset1);
265 +
    const time_point<seconds> trans = FromUnixSeconds(tt);
266 +
267 +
    if (offset0 < offset1) {
268 +
      // The civil time did not exist (pre >= trans > post).
269 +
      const time_point<seconds> pre = FromUnixSeconds(t1);
270 +
      const time_point<seconds> post = FromUnixSeconds(t0);
271 +
      return {time_zone::civil_lookup::SKIPPED, pre, trans, post};
272 +
    }
273 +
274 +
    // The civil time was ambiguous (pre < trans <= post).
275 +
    const time_point<seconds> pre = FromUnixSeconds(t0);
276 +
    const time_point<seconds> post = FromUnixSeconds(t1);
277 +
    return {time_zone::civil_lookup::REPEATED, pre, trans, post};
278 +
  }
279 +
280 +
  // make_time() failed somehow so we saturate the result.
281 +
  const time_point<seconds> tp = (cs < civil_second())
282 +
                                     ? time_point<seconds>::min()
283 +
                                     : time_point<seconds>::max();
284 +
  return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
141 285
}
142 286
143 -
bool TimeZoneLibC::NextTransition(time_point<sys_seconds>* tp) const {
287 +
bool TimeZoneLibC::NextTransition(const time_point<seconds>&,
288 +
                                  time_zone::civil_transition*) const {
144 289
  return false;
145 290
}
146 291
147 -
bool TimeZoneLibC::PrevTransition(time_point<sys_seconds>* tp) const {
292 +
bool TimeZoneLibC::PrevTransition(const time_point<seconds>&,
293 +
                                  time_zone::civil_transition*) const {
148 294
  return false;
149 295
}
150 296
297 +
std::string TimeZoneLibC::Version() const {
298 +
  return std::string();  // unknown
299 +
}
300 +
301 +
std::string TimeZoneLibC::Description() const {
302 +
  return local_ ? "localtime" : "UTC";
303 +
}
304 +
151 305
}  // namespace cctz

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -31,4 +31,7 @@
Loading
31 31
  return std::unique_ptr<TimeZoneIf>(tz.release());
32 32
}
33 33
34 +
// Defined out-of-line to avoid emitting a weak vtable in all TUs.
35 +
TimeZoneIf::~TimeZoneIf() {}
36 +
34 37
}  // namespace cctz

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,8 +20,8 @@
Loading
20 20
#include <memory>
21 21
#include <string>
22 22
23 -
#include "civil_time.h"
24 -
#include "time_zone.h"
23 +
#include "cctz/civil_time.h"
24 +
#include "cctz/time_zone.h"
25 25
26 26
namespace cctz {
27 27
@@ -32,33 +32,35 @@
Loading
32 32
  // A factory function for TimeZoneIf implementations.
33 33
  static std::unique_ptr<TimeZoneIf> Load(const std::string& name);
34 34
35 -
  virtual ~TimeZoneIf() {}
35 +
  virtual ~TimeZoneIf();
36 36
37 37
  virtual time_zone::absolute_lookup BreakTime(
38 -
      const time_point<sys_seconds>& tp) const = 0;
38 +
      const time_point<seconds>& tp) const = 0;
39 39
  virtual time_zone::civil_lookup MakeTime(
40 40
      const civil_second& cs) const = 0;
41 41
42 +
  virtual bool NextTransition(const time_point<seconds>& tp,
43 +
                              time_zone::civil_transition* trans) const = 0;
44 +
  virtual bool PrevTransition(const time_point<seconds>& tp,
45 +
                              time_zone::civil_transition* trans) const = 0;
46 +
47 +
  virtual std::string Version() const = 0;
42 48
  virtual std::string Description() const = 0;
43 -
  virtual bool NextTransition(time_point<sys_seconds>* tp) const = 0;
44 -
  virtual bool PrevTransition(time_point<sys_seconds>* tp) const = 0;
45 49
46 50
 protected:
47 51
  TimeZoneIf() {}
48 52
};
49 53
50 -
// Convert between time_point<sys_seconds> and a count of seconds since
51 -
// the Unix epoch.  We assume that the std::chrono::system_clock and the
54 +
// Convert between time_point<seconds> and a count of seconds since the
55 +
// Unix epoch.  We assume that the std::chrono::system_clock and the
52 56
// Unix clock are second aligned, but not that they share an epoch.
53 -
inline std::int_fast64_t ToUnixSeconds(const time_point<sys_seconds>& tp) {
54 -
  return (tp - std::chrono::time_point_cast<sys_seconds>(
55 -
                   std::chrono::system_clock::from_time_t(0)))
56 -
      .count();
57 +
inline std::int_fast64_t ToUnixSeconds(const time_point<seconds>& tp) {
58 +
  return (tp - std::chrono::time_point_cast<seconds>(
59 +
                   std::chrono::system_clock::from_time_t(0))).count();
57 60
}
58 -
inline time_point<sys_seconds> FromUnixSeconds(std::int_fast64_t t) {
59 -
  return std::chrono::time_point_cast<sys_seconds>(
60 -
             std::chrono::system_clock::from_time_t(0)) +
61 -
         sys_seconds(t);
61 +
inline time_point<seconds> FromUnixSeconds(std::int_fast64_t t) {
62 +
  return std::chrono::time_point_cast<seconds>(
63 +
             std::chrono::system_clock::from_time_t(0)) + seconds(t);
62 64
}
63 65
64 66
}  // namespace cctz

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -30,12 +30,15 @@
Loading
30 30
31 31
  // TimeZoneIf implementations.
32 32
  time_zone::absolute_lookup BreakTime(
33 -
      const time_point<sys_seconds>& tp) const override;
33 +
      const time_point<seconds>& tp) const override;
34 34
  time_zone::civil_lookup MakeTime(
35 35
      const civil_second& cs) const override;
36 +
  bool NextTransition(const time_point<seconds>& tp,
37 +
                      time_zone::civil_transition* trans) const override;
38 +
  bool PrevTransition(const time_point<seconds>& tp,
39 +
                      time_zone::civil_transition* trans) const override;
40 +
  std::string Version() const override;
36 41
  std::string Description() const override;
37 -
  bool NextTransition(time_point<sys_seconds>* tp) const override;
38 -
  bool PrevTransition(time_point<sys_seconds>* tp) const override;
39 42
40 43
 private:
41 44
  const bool local_;  // localtime or UTC

@@ -10,18 +10,17 @@
Loading
10 10
##' stamps for dates and times (MHS). These function might be useful when the
11 11
##' input template is unambiguous and matches both a time and a date format.
12 12
##'
13 -
##' Lubridate tries its best to figure our the formats, but often a given
14 -
##' format can be interpreted in several ways. One way to deal with the
15 -
##' situation is to provide unambiguous formats like 22/05/81 instead of
16 -
##' 10/05/81 if you want d/m/y format. Another option is to use a more
17 -
##' specialized stamp_date and stamp_time. The core function `stamp()` give
18 -
##' priority to longer date-time formats.
13 +
##' Lubridate tries hard to guess the formats, but often a given format can be
14 +
##' interpreted in multiple ways. One way to deal with such cases is to provide
15 +
##' unambiguous formats like 22/05/81 instead of 10/05/81 for d/m/y
16 +
##' format. Another way is to use a more specialized [`stamp_date`] and
17 +
##' [`stamp_time`]. The core function `stamp()` prioritizes longer date-time
18 +
##' formats.
19 19
##'
20 -
##' Another option is to provide a vector of several values as `x`
21 -
##' parameter. Then \pkg{lubridate} will choose the format which fits `x` the
22 -
##' best. Note that longer formats are preferred. If you have "22:23:00 PM" then
23 -
##' "HMSp" format will be given priority to shorter "HMS" order which also fits
24 -
##' the supplied string.
20 +
##' If `x` is a vector of values \pkg{lubridate} will choose the format which
21 +
##' "fits" `x` the best. Note that longer formats are preferred. If you have
22 +
##' "22:23:00 PM" then "HMSp" format will be given priority to shorter "HMS"
23 +
##' order which also fits the supplied string.
25 24
##'
26 25
##' Finally, you can give desired format order directly as `orders`
27 26
##' argument.

@@ -4,7 +4,7 @@
Loading
4 4
// you may not use this file except in compliance with the License.
5 5
// You may obtain a copy of the License at
6 6
//
7 -
//   http://www.apache.org/licenses/LICENSE-2.0
7 +
//   https://www.apache.org/licenses/LICENSE-2.0
8 8
//
9 9
//   Unless required by applicable law or agreed to in writing, software
10 10
//   distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,8 +18,8 @@
Loading
18 18
#include <memory>
19 19
#include <string>
20 20
21 -
#include "civil_time.h"
22 -
#include "time_zone.h"
21 +
#include "cctz/civil_time.h"
22 +
#include "cctz/time_zone.h"
23 23
#include "time_zone_if.h"
24 24
#include "time_zone_info.h"
25 25
@@ -35,15 +35,18 @@
Loading
35 35
  // some other kind of error occurs. Note that loading "UTC" never fails.
36 36
  static bool LoadTimeZone(const std::string& name, time_zone* tz);
37 37
38 -
  // Dereferences the time_zone to obtain its Impl.
39 -
  static const time_zone::Impl& get(const time_zone& tz);
38 +
  // Clears the map of cached time zones.  Primarily for use in benchmarks
39 +
  // that gauge the performance of loading/parsing the time-zone data.
40 +
  static void ClearTimeZoneMapTestOnly();
40 41
41 42
  // The primary key is the time-zone ID (e.g., "America/New_York").
42 -
  const std::string& name() const { return name_; }
43 +
  const std::string& Name() const {
44 +
    // TODO: It would nice if the zoneinfo data included the zone name.
45 +
    return name_;
46 +
  }
43 47
44 48
  // Breaks a time_point down to civil-time components in this time zone.
45 -
  time_zone::absolute_lookup BreakTime(
46 -
      const time_point<sys_seconds>& tp) const {
49 +
  time_zone::absolute_lookup BreakTime(const time_point<seconds>& tp) const {
47 50
    return zone_->BreakTime(tp);
48 51
  }
49 52
@@ -54,28 +57,22 @@
Loading
54 57
    return zone_->MakeTime(cs);
55 58
  }
56 59
57 -
  // Returns an implementation-specific description of this time zone.
58 -
  std::string Description() const { return zone_->Description(); }
59 -
60 60
  // Finds the time of the next/previous offset change in this time zone.
61 -
  //
62 -
  // By definition, NextTransition(&tp) returns false when tp has its
63 -
  // maximum value, and PrevTransition(&tp) returns false when tp has its
64 -
  // mimimum value.  If the zone has no transitions, the result will also
65 -
  // be false no matter what the argument.
66 -
  //
67 -
  // Otherwise, when tp has its mimimum value, NextTransition(&tp) returns
68 -
  // true and sets tp to the first recorded transition.  Chains of calls
69 -
  // to NextTransition()/PrevTransition() will eventually return false,
70 -
  // but it is unspecified exactly when NextTransition(&tp) jumps to false,
71 -
  // or what time is set by PrevTransition(&tp) for a very distant tp.
72 -
  bool NextTransition(time_point<sys_seconds>* tp) const {
73 -
    return zone_->NextTransition(tp);
61 +
  bool NextTransition(const time_point<seconds>& tp,
62 +
                      time_zone::civil_transition* trans) const {
63 +
    return zone_->NextTransition(tp, trans);
74 64
  }
75 -
  bool PrevTransition(time_point<sys_seconds>* tp) const {
76 -
    return zone_->PrevTransition(tp);
65 +
  bool PrevTransition(const time_point<seconds>& tp,
66 +
                      time_zone::civil_transition* trans) const {
67 +
    return zone_->PrevTransition(tp, trans);
77 68
  }
78 69
70 +
  // Returns an implementation-defined version string for this time zone.
71 +
  std::string Version() const { return zone_->Version(); }
72 +
73 +
  // Returns an implementation-defined description of this time zone.
74 +
  std::string Description() const { return zone_->Description(); }
75 +
79 76
 private:
80 77
  explicit Impl(const std::string& name);
81 78
  static const Impl* UTCImpl();

@@ -1,8 +1,8 @@
Loading
1 1
#include <cstdint>
2 2
#include <limits>
3 3
#include <unordered_map>
4 -
#include "civil_time.h"
5 -
#include "time_zone.h"
4 +
#include "cctz/civil_time.h"
5 +
#include "cctz/time_zone.h"
6 6
#include "utils.h"
7 7
#include <Rcpp.h>
8 8
Files Coverage
R 79.06%
src 71.99%
Project Totals (64 files) 75.88%
1
comment: false
2

3
coverage:
4
  status:
5
    project:
6
      default:
7
        target: auto
8
        threshold: 1%
9
        informational: true
10
    patch:
11
      default:
12
        target: auto
13
        threshold: 1%
14
        informational: true
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.