diff --git a/test/tz_test/validate.cpp b/test/tz_test/validate.cpp index 59ab6b8..c563ab6 100644 --- a/test/tz_test/validate.cpp +++ b/test/tz_test/validate.cpp @@ -103,7 +103,7 @@ main() auto z = locate_zone(name); auto begin = sys_days(jan/1/year::min()) + 0s; auto end = sys_days(jan/1/2035) + 0s; - auto info = z->get_info(begin, tz::utc); + auto info = z->get_info(begin); std::cout << "Initially: "; if (info.offset >= 0s) std::cout << '+'; @@ -119,7 +119,7 @@ main() auto prev_save = info.save; for (begin = info.end; begin < end; begin = info.end) { - info = z->get_info(begin, tz::utc); + info = z->get_info(begin); test_info(z, info); if (info.offset == prev_offset && info.abbrev == prev_abbrev && info.save == prev_save) diff --git a/tz.cpp b/tz.cpp index c3fa838..1efadbe 100644 --- a/tz.cpp +++ b/tz.cpp @@ -119,9 +119,6 @@ static const std::vector files = CONSTDATA auto min_year = date::year::min(); CONSTDATA auto max_year = date::year::max(); -// Arbitrary day of the year that will be away from any limits. -// Used with year::min() and year::max(). -CONSTDATA auto boring_day = date::aug/18; CONSTDATA auto min_day = date::jan/1; CONSTDATA auto max_day = date::dec/31; @@ -129,16 +126,9 @@ CONSTDATA auto max_day = date::dec/31; // | End Configuration | // +-------------------+ -#if _MSC_VER && ! defined(__clang__) && ! defined( __GNUG__) -// We can't use static_assert here for MSVC (yet) because -// the expression isn't constexpr in MSVC yet. -// FIXME! Remove this when MSVC's constexpr support improves. -#else +#ifndef _MSC_VER static_assert(min_year <= max_year, "Configuration error"); #endif -#if __cplusplus >= 201402 -static_assert(boring_day.ok(), "Configuration error"); -#endif // Until filesystem arrives. static CONSTDATA char folder_delimiter = @@ -290,7 +280,7 @@ static inline size_t countof(T(&arr)[N]) // The routine tries to load as many time zone entries as possible despite errors. // We don't want to fail to load the whole database just because one record can't be read. -static void get_windows_timezone_info(std::vector& tz_list) +static void get_windows_timezone_info(std::vector& tz_list) { tz_list.clear(); LONG result; @@ -315,7 +305,7 @@ static void get_windows_timezone_info(std::vector& tz_list) std::wstring full_zone_key_name; for (DWORD zone_index = 0; ; ++zone_index) { - timezone_info tz; + detail::timezone_info tz; size = (DWORD) sizeof(zone_key_name)/sizeof(zone_key_name[0]); auto status = RegEnumKeyExW(zones_key.handle(), zone_index, zone_key_name, &size, @@ -366,7 +356,7 @@ static void get_windows_timezone_info(std::vector& tz_list) // under the windows registry key Time Zones. // To be clear, standard_name does NOT represent a windows timezone id // or an IANA tzid -static const timezone_info* find_native_timezone_by_standard_name( +static const detail::timezone_info* find_native_timezone_by_standard_name( const std::string& standard_name) { // TODO! we can improve on linear search. @@ -383,11 +373,11 @@ static const timezone_info* find_native_timezone_by_standard_name( // Read CSV file of "other","territory","type". // See timezone_mapping structure for more info. // This function should be kept in sync with the code that writes this file. -static std::vector +static std::vector load_timezone_mappings_from_csv_file(const std::string& input_path) { size_t line = 1; - std::vector mappings; + std::vector mappings; std::ifstream is(input_path, std::ios_base::in | std::ios_base::binary); if (!is.is_open()) { @@ -418,7 +408,7 @@ load_timezone_mappings_from_csv_file(const std::string& input_path) for (;;) { - timezone_mapping zm{}; + detail::timezone_mapping zm{}; char ch; is.read(&ch, 1); @@ -1532,7 +1522,7 @@ find_rule_for_zone(const std::pair& eqr, } static -Info +sys_info find_rule(const std::pair& first_rule, const std::pair& last_rule, const date::year& y, const std::chrono::seconds& offset, @@ -1543,8 +1533,8 @@ find_rule(const std::pair& first_rule, using namespace date; auto r = first_rule.first; auto ry = first_rule.second; - Info x{sys_days(year::min()/min_day), sys_days(year::max()/max_day), - seconds{0}, initial_save, initial_abbrev}; + sys_info x{sys_days(year::min()/min_day), sys_days(year::max()/max_day), + seconds{0}, initial_save, initial_abbrev}; while (r != nullptr) { auto tr = r->mdt().to_sys(ry, offset, x.save); @@ -1757,11 +1747,43 @@ format_abbrev(std::string format, const std::string& variable, std::chrono::seco return format; } -Info -time_zone::get_info_impl(sys_seconds tp, tz timezone) const +sys_info +time_zone::get_info_impl(sys_seconds tp) const +{ + return get_info_impl(tp, static_cast(tz::utc)); +} + +local_info +time_zone::get_info_impl(local_seconds tp) const +{ + using namespace std::chrono; + local_info i{}; + i.first = get_info_impl(sys_seconds{tp.time_since_epoch()}, static_cast(tz::local)); + auto tps = sys_seconds{(tp - i.first.offset).time_since_epoch()}; + if (tps < i.first.begin) + { + i.second = std::move(i.first); + i.first = get_info_impl(i.second.begin - seconds{1}, static_cast(tz::utc)); + i.result = local_info::nonexistent; + } + else if (i.first.end - tps <= days{1}) + { + i.second = get_info_impl(i.first.end, static_cast(tz::utc)); + tps = sys_seconds{(tp - i.second.offset).time_since_epoch()}; + if (tps >= i.second.begin) + i.result = local_info::ambiguous; + else + i.second = {}; + } + return i; +} + +sys_info +time_zone::get_info_impl(sys_seconds tp, int tz_int) const { using namespace std::chrono; using namespace date; + tz timezone = static_cast(tz_int); assert(timezone != tz::standard); auto y = year_month_day(floor(tp)).year(); if (y < min_year || y > max_year) @@ -1782,7 +1804,7 @@ time_zone::get_info_impl(sys_seconds tp, tz timezone) const t < sys_seconds{zl.until_loc_.time_since_epoch()}; }); - Info r{}; + sys_info r{}; if (i != zonelets_.end()) { if (i->tag_ == zonelet::has_save) @@ -2301,9 +2323,8 @@ operator<<(std::ostream& os, const TZ_DB& db) // ----------------------- std::ostream& -operator<<(std::ostream& os, const Info& r) +operator<<(std::ostream& os, const sys_info& r) { - using namespace date; os << r.begin << '\n'; os << r.end << '\n'; os << make_time(r.offset) << "\n"; @@ -2312,6 +2333,22 @@ operator<<(std::ostream& os, const Info& r) return os; } +std::ostream& +operator<<(std::ostream& os, const local_info& r) +{ + if (r.result == local_info::nonexistent) + os << "nonexistent between\n"; + else if (r.result == local_info::ambiguous) + os << "ambiguous between\n"; + os << r.first; + if (r.result != local_info::unique) + { + os << "and\n"; + os << r.second; + } + return os; +} + #ifdef _WIN32 const time_zone* diff --git a/tz.h b/tz.h index 77f2282..1e1d92f 100644 --- a/tz.h +++ b/tz.h @@ -97,7 +97,6 @@ static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true, namespace date { -enum class tz {utc, local, standard}; enum class choose {earliest, latest}; class nonexistent_local_time @@ -197,7 +196,7 @@ ambiguous_local_time::make_msg(local_time tp, class Rule; -struct Info +struct sys_info { sys_seconds begin; sys_seconds end; @@ -207,7 +206,21 @@ struct Info }; std::ostream& -operator<<(std::ostream& os, const Info& r); +operator<<(std::ostream& os, const sys_info& r); + +struct local_info +{ + enum {unique, nonexistent, ambiguous} result; + sys_info first; + sys_info second; +}; + +std::ostream& +operator<<(std::ostream& os, const local_info& r); + +// deprecated: + +using Info = sys_info; class time_zone; // deprecated: @@ -253,12 +266,12 @@ public: explicit zoned_time(const std::string& name); template , sys_time>::value - >> - zoned_time(const zoned_time& zt); + >::type> + zoned_time(const zoned_time& zt) NOEXCEPT; zoned_time(const time_zone* z, local_time tp); zoned_time(const std::string& name, local_time tp); @@ -267,8 +280,8 @@ public: zoned_time(const time_zone* z, const zoned_time& zt); zoned_time(const std::string& name, const zoned_time& zt); - zoned_time(const time_zone* z, const zoned_time& zt, choose c); - zoned_time(const std::string& name, const zoned_time& zt, choose c); + zoned_time(const time_zone* z, const zoned_time& zt, choose); + zoned_time(const std::string& name, const zoned_time& zt, choose); zoned_time(const time_zone* z, const sys_time& st); zoned_time(const std::string& name, const sys_time& st); @@ -276,12 +289,13 @@ public: zoned_time& operator=(sys_time st); zoned_time& operator=(local_time ut); - explicit operator local_time() const; operator sys_time() const; + explicit operator local_time() const; const time_zone* get_time_zone() const; local_time get_local_time() const; sys_time get_sys_time() const; + sys_info get_info() const; template friend @@ -295,21 +309,18 @@ public: private: - static_assert(std::ratio_less_equal::value, + static_assert(std::is_convertible::value, "zoned_time must have a precision of seconds or finer"); }; using zoned_seconds = zoned_time; -// Should equality bother with comparing zones? -// If zones don't matter, add operator< ? template inline bool operator==(const zoned_time& x, const zoned_time& y) { - return x.zone == y.zone && x.tp == y.tp; + return x.zone_ == y.zone_ && x.tp_ == y.tp_; } template @@ -344,10 +355,9 @@ public: const std::string& name() const; - template Info get_info(sys_time st) const; - template Info get_info(local_time tp) const; + template sys_info get_info(sys_time st) const; + template local_info get_info(local_time tp) const; -private: template sys_time::type> to_sys(local_time tp) const; @@ -360,7 +370,6 @@ private: local_time::type> to_local(sys_time tp) const; -public: friend bool operator==(const time_zone& x, const time_zone& y); friend bool operator< (const time_zone& x, const time_zone& y); friend std::ostream& operator<<(std::ostream& os, const time_zone& z); @@ -369,16 +378,18 @@ public: void adjust_infos(const std::vector& rules); private: - Info get_info_impl(sys_seconds tp, tz timezone) const; + sys_info get_info_impl(sys_seconds tp) const; + local_info get_info_impl(local_seconds tp) const; + sys_info get_info_impl(sys_seconds tp, int timezone) const; void parse_info(std::istream& in); - template + template sys_time::type> - to_sys_impl(local_time tp, choose z, - std::integral_constant do_throw) const; - - template friend class zoned_time; + to_sys_impl(local_time tp, choose z, std::false_type) const; + template + sys_time::type> + to_sys_impl(local_time tp, choose, std::true_type) const; }; #if defined(_MSC_VER) && (_MSC_VER < 1900) @@ -415,21 +426,20 @@ time_zone::name() const template inline -Info +sys_info time_zone::get_info(sys_time st) const { using namespace std::chrono; - return get_info_impl(floor(st), tz::utc); + return get_info_impl(floor(st)); } template inline -Info +local_info time_zone::get_info(local_time tp) const { using namespace std::chrono; - return get_info_impl(floor(sys_time{tp.time_since_epoch()}), - tz::local); + return get_info_impl(floor(tp)); } template @@ -466,48 +476,47 @@ inline bool operator> (const time_zone& x, const time_zone& y) {return y < x;} inline bool operator<=(const time_zone& x, const time_zone& y) {return !(y < x);} inline bool operator>=(const time_zone& x, const time_zone& y) {return !(x < y);} -template +template sys_time::type> -time_zone::to_sys_impl(local_time tp, choose z, - std::integral_constant do_throw) const +time_zone::to_sys_impl(local_time tp, choose z, std::false_type) const { using namespace date; using namespace std::chrono; auto i = get_info(tp); - auto tp_sys = sys_time{tp.time_since_epoch()} - i.offset; - if (floor(tp_sys) - i.begin <= days{1}) + if (i.result == local_info::nonexistent) { - auto ut_begin = local_seconds{i.begin.time_since_epoch()} + i.offset; - if (floor(tp) < ut_begin) - { - if (do_throw) - { - auto prev = get_info(i.begin - seconds{1}); - auto ut_prev_begin = local_seconds{i.begin.time_since_epoch()} + prev.offset; - throw nonexistent_local_time(tp, ut_prev_begin, prev.abbrev, - ut_begin, i.abbrev, i.begin); - } - return i.begin; - } - assert(floor(tp) >= - local_seconds{i.begin.time_since_epoch()} + - get_info(i.begin - seconds{1}).offset); + return i.first.end; } - if (i.end - floor(tp_sys) <= days{1}) + else if (i.result == local_info::ambiguous) { - assert(floor(tp) < local_seconds{i.end.time_since_epoch()} + i.offset); - auto next = get_info(i.end); - if (floor(tp) >= local_seconds{i.end.time_since_epoch()} + next.offset) - { - if (do_throw) - throw ambiguous_local_time(tp, i.offset, i.abbrev, - next.offset, next.abbrev); - if (z == choose::earliest) - return tp_sys; - return sys_time{tp.time_since_epoch()} - next.offset; - } + if (z == choose::earliest) + return sys_time{tp.time_since_epoch()} - i.second.offset; } - return tp_sys; + return sys_time{tp.time_since_epoch()} - i.first.offset; +} + +template +sys_time::type> +time_zone::to_sys_impl(local_time tp, choose, std::true_type) const +{ + using namespace date; + using namespace std::chrono; + auto i = get_info(tp); + if (i.result == local_info::nonexistent) + { + auto prev_end = local_seconds{i.first.end.time_since_epoch()} + + i.first.offset; + auto next_begin = local_seconds{i.second.begin.time_since_epoch()} + + i.second.offset; + throw nonexistent_local_time(tp, prev_end, i.first.abbrev, + next_begin, i.second.abbrev, i.first.end); + } + else if (i.result == local_info::ambiguous) + { + throw ambiguous_local_time(tp, i.first.offset, i.first.abbrev, + i.second.offset, i.second.abbrev); + } + return sys_time{tp.time_since_epoch()} - i.first.offset; } class Link @@ -651,7 +660,8 @@ operator>=(const sys_time& x, const Leap& y) #if TIMEZONE_MAPPING -// TODO! Ensure all these types aren't exposed. +namespace detail +{ // The time zone mapping is modelled after this data file: // http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml @@ -689,6 +699,8 @@ struct timezone_info std::string standard_name; }; +} // detail + #endif // TIMEZONE_MAPPING struct TZ_DB @@ -700,8 +712,8 @@ struct TZ_DB std::vector rules; #if TIMEZONE_MAPPING // TODO! These need some protection. - std::vector mappings; - std::vector native_zones; + std::vector mappings; + std::vector native_zones; #endif TZ_DB() = default; @@ -783,7 +795,7 @@ template inline zoned_time::zoned_time(const time_zone* z, local_time t) : zone_(z) - , tp_(floor(z->to_sys(t))) + , tp_(z->to_sys(t)) {} template @@ -796,7 +808,7 @@ template inline zoned_time::zoned_time(const time_zone* z, local_time t, choose c) : zone_(z) - , tp_(floor(z->to_sys(t, c))) + , tp_(z->to_sys(t, c)) {} template @@ -809,7 +821,7 @@ zoned_time::zoned_time(const std::string& name, local_time t template template inline -zoned_time::zoned_time(const zoned_time& zt) +zoned_time::zoned_time(const zoned_time& zt) NOEXCEPT : zone_(zt.zone_) , tp_(zt.tp_) {} @@ -868,7 +880,7 @@ inline zoned_time& zoned_time::operator=(local_time ut) { - tp_ = floor(zone_->to_sys(ut)); + tp_ = zone_->to_sys(ut); return *this; } @@ -899,7 +911,7 @@ inline local_time zoned_time::get_local_time() const { - return floor(zone_->to_local(tp_)); + return zone_->to_local(tp_); } template @@ -910,6 +922,14 @@ zoned_time::get_sys_time() const return tp_; } +template +inline +sys_info +zoned_time::get_info() const +{ + return zone_->get_info(tp_); +} + // make_zoned_time template @@ -936,6 +956,22 @@ make_zoned(const std::string& name, local_time tp) return {name, tp}; } +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, local_time tp, choose c) +{ + return {zone, tp, c}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, local_time tp, choose c) +{ + return {name, tp, c}; +} + template inline zoned_time::type> diff --git a/tz_private.h b/tz_private.h index 497813b..f814087 100644 --- a/tz_private.h +++ b/tz_private.h @@ -32,6 +32,8 @@ namespace date { +enum class tz {utc, local, standard}; + class MonthDayTime { private: