diff --git a/README.md b/README.md index 9c159c2..265da63 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ This is actually three separate C++11/C++14 libraries: -1. `"date.h"` is a header-only library which builds upon ``. It adds some new `duration` types, and new `time_point` types. It also adds "field" types such as `year_month_day` which is a struct `{year, month, day}`. And it provides convenient means to convert between the "field" types and the `time_point` types. See http://howardhinnant.github.io/date_v2.html for more details. +1. `"date.h"` is a header-only library which builds upon ``. It adds some new `duration` types, and new `time_point` types. It also adds "field" types such as `year_month_day` which is a struct `{year, month, day}`. And it provides convenient means to convert between the "field" types and the `time_point` types. See http://howardhinnant.github.io/date/date.html for more details. Here is the Cppcon 2015 presentation on date.h: https://www.youtube.com/watch?v=tzyGjOm8AKo Here are the Cppcon 2015 slides on date.h: http://schd.ws/hosted_files/cppcon2015/43/hinnant_dates.pdf -2. `"tz.h"` / `"tz.cpp"` are a timezone library built on top of the `"date.h"` library. This timezone library is a complete parser of the IANA timezone database. It provides for an easy way to access all of the data in this database, using the types from `"date.h"` and ``. The IANA database also includes data on leap seconds, and this library provides utilities to compute with that information as well. See http://howardhinnant.github.io/tz.html for more details. +2. `"tz.h"` / `"tz.cpp"` are a timezone library built on top of the `"date.h"` library. This timezone library is a complete parser of the IANA timezone database. It provides for an easy way to access all of the data in this database, using the types from `"date.h"` and ``. The IANA database also includes data on leap seconds, and this library provides utilities to compute with that information as well. See http://howardhinnant.github.io/date/tz.html for more details. -3. `"iso_week.h"` is a header-only library built on top of the `"date.h"` library which implements the ISO week date calendar. See http://howardhinnant.github.io/iso_week.html for more details. +3. `"iso_week.h"` is a header-only library built on top of the `"date.h"` library which implements the ISO week date calendar. See http://howardhinnant.github.io/date/iso_week.html for more details. diff --git a/date.h b/date.h index 7922d57..80766bb 100644 --- a/date.h +++ b/date.h @@ -22,17 +22,23 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. #include #include #if !(__cplusplus >= 201402) # include #endif +#include +#include #include #include #include #include -#include +#include namespace date { @@ -81,7 +87,19 @@ using months = std::chrono::duration // time_point -using day_point = std::chrono::time_point; +template + using sys_time = std::chrono::time_point; + +using sys_days = sys_time; +using sys_seconds = sys_time; + +struct local_t {}; + +template + using local_time = std::chrono::time_point; + +using local_seconds = local_time; +using local_days = local_time; // types @@ -314,7 +332,8 @@ class weekday public: explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; explicit weekday(int) = delete; - CONSTCD11 weekday(const day_point& dp) NOEXCEPT; + CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; + CONSTCD11 weekday(const local_days& dp) NOEXCEPT; weekday& operator++() NOEXCEPT; weekday operator++(int) NOEXCEPT; @@ -525,7 +544,9 @@ public: CONSTCD11 year_month_day(const date::year& y, const date::month& m, const date::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; - CONSTCD14 year_month_day(const day_point& dp) NOEXCEPT; + + CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; + CONSTCD14 year_month_day(local_days dp) NOEXCEPT; year_month_day& operator+=(const months& m) NOEXCEPT; year_month_day& operator-=(const months& m) NOEXCEPT; @@ -536,11 +557,13 @@ public: CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::day day() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: - static CONSTCD14 year_month_day from_day_point(const day_point& dp) NOEXCEPT; + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; @@ -580,7 +603,8 @@ public: CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 date::day day() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; @@ -634,7 +658,8 @@ class year_month_weekday public: CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT; - CONSTCD14 year_month_weekday(const day_point& dp) NOEXCEPT; + CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 year_month_weekday(const local_days& dp) NOEXCEPT; year_month_weekday& operator+=(const months& m) NOEXCEPT; year_month_weekday& operator-=(const months& m) NOEXCEPT; @@ -647,11 +672,13 @@ public: CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: - static CONSTCD14 year_month_weekday from_day_point(const day_point& dp) NOEXCEPT; + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 @@ -707,8 +734,12 @@ public: CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; + +private: + CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 @@ -1292,19 +1323,14 @@ year::is_leap() const NOEXCEPT } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} -CONSTCD11 inline bool year::ok() const NOEXCEPT {return min() <= *this && *this <= max();} +CONSTCD11 inline bool year::ok() const NOEXCEPT {return true;} CONSTCD11 inline year year::min() NOEXCEPT { - using namespace std::chrono; - static_assert(sizeof(seconds)*CHAR_BIT >= 41, "seconds may overflow"); - static_assert(sizeof(hours)*CHAR_BIT >= 30, "hours may overflow"); - return sizeof(minutes)*CHAR_BIT < 34 ? - year{1970} + duration_cast(minutes::min()) : - year{std::numeric_limits::min()}; + return year{std::numeric_limits::min()}; } CONSTCD11 @@ -1312,12 +1338,7 @@ inline year year::max() NOEXCEPT { - using namespace std::chrono; - static_assert(sizeof(seconds)*CHAR_BIT >= 41, "seconds may overflow"); - static_assert(sizeof(hours)*CHAR_BIT >= 30, "hours may overflow"); - return sizeof(minutes)*CHAR_BIT < 34 ? - year{1969} + duration_cast(minutes::max()) : - year{std::numeric_limits::max()}; + return year{std::numeric_limits::max()}; } CONSTCD11 @@ -1431,7 +1452,13 @@ weekday::weekday(unsigned wd) NOEXCEPT CONSTCD11 inline -weekday::weekday(const day_point& dp) NOEXCEPT +weekday::weekday(const sys_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD11 +inline +weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} @@ -2171,6 +2198,20 @@ year_month_day_last::day() const NOEXCEPT d[static_cast(month()) - 1] : date::day{29}; } +CONSTCD14 +inline +year_month_day_last::operator sys_days() const NOEXCEPT +{ + return sys_days(year()/month()/day()); +} + +CONSTCD14 +inline +year_month_day_last::operator local_days() const NOEXCEPT +{ + return local_days(year()/month()/day()); +} + CONSTCD11 inline bool @@ -2305,8 +2346,14 @@ year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT CONSTCD14 inline -year_month_day::year_month_day(const day_point& dp) NOEXCEPT - : year_month_day(from_day_point(dp)) +year_month_day::year_month_day(sys_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(local_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} @@ -2347,7 +2394,8 @@ year_month_day::operator-=(const years& y) NOEXCEPT CONSTCD14 inline -year_month_day::operator day_point() const NOEXCEPT +days +year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); @@ -2360,14 +2408,21 @@ year_month_day::operator day_point() const NOEXCEPT auto const yoe = static_cast(y - era * 400); // [0, 399] auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] - return day_point{days{era * 146097 + static_cast(doe) - 719468}}; + return days{era * 146097 + static_cast(doe) - 719468}; } CONSTCD14 inline -year_month_day_last::operator day_point() const NOEXCEPT +year_month_day::operator sys_days() const NOEXCEPT { - return day_point(year()/month()/day()); + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_day::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; } CONSTCD14 @@ -2449,17 +2504,17 @@ operator<<(std::ostream& os, const year_month_day& ymd) CONSTCD14 inline year_month_day -year_month_day::from_day_point(const day_point& dp) NOEXCEPT +year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - auto const z = dp.time_since_epoch().count() + 719468; + auto const z = dp.count() + 719468; auto const era = (z >= 0 ? z : z - 146096) / 146097; auto const doe = static_cast(z - era * 146097); // [0, 146096] auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] - auto const y = static_cast(yoe) + era * 400; + auto const y = static_cast(yoe) + era * 400; auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] auto const mp = (5*doy + 2)/153; // [0, 11] auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] @@ -2529,8 +2584,14 @@ year_month_weekday::year_month_weekday(const date::year& y, const date::month& m CONSTCD14 inline -year_month_weekday::year_month_weekday(const day_point& dp) NOEXCEPT - : year_month_weekday(from_day_point(dp)) +year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) {} inline @@ -2594,10 +2655,16 @@ year_month_weekday::weekday_indexed() const NOEXCEPT CONSTCD14 inline -year_month_weekday::operator day_point() const NOEXCEPT +year_month_weekday::operator sys_days() const NOEXCEPT { - auto d = day_point(y_/m_/1); - return d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}); + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; } CONSTCD14 @@ -2616,13 +2683,24 @@ year_month_weekday::ok() const NOEXCEPT CONSTCD14 inline year_month_weekday -year_month_weekday::from_day_point(const day_point& dp) NOEXCEPT +year_month_weekday::from_days(days d) NOEXCEPT { + sys_days dp{d}; auto const wd = date::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } +CONSTCD14 +inline +days +year_month_weekday::to_days() const NOEXCEPT +{ + auto d = sys_days(y_/m_/1); + return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) + ).time_since_epoch(); +} + CONSTCD11 inline bool @@ -2761,10 +2839,16 @@ year_month_weekday_last::weekday_last() const NOEXCEPT CONSTCD14 inline -year_month_weekday_last::operator day_point() const NOEXCEPT +year_month_weekday_last::operator sys_days() const NOEXCEPT { - auto const d = day_point(y_/m_/last); - return d - (date::weekday{d} - wdl_.weekday()); + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday_last::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; } CONSTCD11 @@ -2775,6 +2859,15 @@ year_month_weekday_last::ok() const NOEXCEPT return y_.ok() && m_.ok() && wdl_.ok(); } +CONSTCD14 +inline +days +year_month_weekday_last::to_days() const NOEXCEPT +{ + auto const d = sys_days(y_/m_/last); + return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); +} + CONSTCD11 inline bool @@ -3723,8 +3816,7 @@ typename std::enable_if std::ratio_less::value , std::ostream& >::type -operator<<(std::ostream& os, - const std::chrono::time_point& tp) +operator<<(std::ostream& os, const sys_time& tp) { auto const dp = floor(tp); return os << year_month_day(dp) << ' ' << make_time(tp-dp); @@ -3732,11 +3824,19 @@ operator<<(std::ostream& os, inline std::ostream& -operator<<(std::ostream& os, const day_point& dp) +operator<<(std::ostream& os, const sys_days& dp) { return os << year_month_day(dp); } +template +inline +std::ostream& +operator<<(std::ostream& os, const local_time& ut) +{ + return os << sys_time{ut.time_since_epoch()}; +} + } // namespace date #endif // DATE_H diff --git a/iso_week.h b/iso_week.h index 9a84d8f..f6afceb 100644 --- a/iso_week.h +++ b/iso_week.h @@ -38,7 +38,8 @@ using years = date::years; // time_point -using day_point = date::day_point; +using sys_days = date::sys_days; +using local_days = date::local_days; // types @@ -95,7 +96,8 @@ public: explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; CONSTCD11 weekday(date::weekday wd) NOEXCEPT; explicit weekday(int) = delete; - CONSTCD11 weekday(const day_point& dp) NOEXCEPT; + CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; + CONSTCD11 weekday(const local_days& dp) NOEXCEPT; weekday& operator++() NOEXCEPT; weekday operator++(int) NOEXCEPT; @@ -328,7 +330,8 @@ public: CONSTCD11 iso_week::weeknum weeknum() const NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; @@ -357,7 +360,8 @@ public: CONSTCD11 year_weeknum_weekday(const iso_week::year& y, const iso_week::weeknum& wn, const iso_week::weekday& wd) NOEXCEPT; CONSTCD14 year_weeknum_weekday(const year_lastweek_weekday& ylwwd) NOEXCEPT; - CONSTCD14 year_weeknum_weekday(const day_point& dp) NOEXCEPT; + CONSTCD14 year_weeknum_weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 year_weeknum_weekday(const local_days& dp) NOEXCEPT; year_weeknum_weekday& operator+=(const years& y) NOEXCEPT; year_weeknum_weekday& operator-=(const years& y) NOEXCEPT; @@ -366,11 +370,12 @@ public: CONSTCD11 iso_week::weeknum weeknum() const NOEXCEPT; CONSTCD11 iso_week::weekday weekday() const NOEXCEPT; - CONSTCD14 operator day_point() const NOEXCEPT; + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: - static CONSTCD14 year_weeknum_weekday from_day_point(const day_point& dp) NOEXCEPT; + static CONSTCD14 year_weeknum_weekday from_days(days dp) NOEXCEPT; }; CONSTCD11 bool operator==(const year_weeknum_weekday& x, const year_weeknum_weekday& y) NOEXCEPT; @@ -431,7 +436,13 @@ weekday::weekday(date::weekday wd) NOEXCEPT CONSTCD11 inline -weekday::weekday(const day_point& dp) NOEXCEPT +weekday::weekday(const sys_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD11 +inline +weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} @@ -980,8 +991,8 @@ weeknum year_lastweek::weeknum() const NOEXCEPT { const auto y = date::year{int{y_}}; - const auto s0 = day_point{(y-years{1})/12/date::thu[date::last]}; - const auto s1 = day_point{y/12/date::thu[date::last]}; + const auto s0 = sys_days{(y-years{1})/12/date::thu[date::last]}; + const auto s1 = sys_days{y/12/date::thu[date::last]}; return iso_week::weeknum(date::trunc(s1-s0).count()); } @@ -1273,9 +1284,17 @@ CONSTCD11 inline weekday year_lastweek_weekday::weekday() const NOEXCEPT {return CONSTCD14 inline -year_lastweek_weekday::operator day_point() const NOEXCEPT +year_lastweek_weekday::operator sys_days() const NOEXCEPT { - return day_point{date::year{int{y_}}/date::dec/date::thu[date::last]} + (mon - thu) + return sys_days{date::year{int{y_}}/date::dec/date::thu[date::last]} + (mon - thu) + - (mon - wd_); +} + +CONSTCD14 +inline +year_lastweek_weekday::operator local_days() const NOEXCEPT +{ + return local_days{date::year{int{y_}}/date::dec/date::thu[date::last]} + (mon - thu) - (mon - wd_); } @@ -1390,8 +1409,14 @@ year_weeknum_weekday::year_weeknum_weekday(const year_lastweek_weekday& ylwwd) N CONSTCD14 inline -year_weeknum_weekday::year_weeknum_weekday(const day_point& dp) NOEXCEPT - : year_weeknum_weekday(from_day_point(dp)) +year_weeknum_weekday::year_weeknum_weekday(const sys_days& dp) NOEXCEPT + : year_weeknum_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_weeknum_weekday::year_weeknum_weekday(const local_days& dp) NOEXCEPT + : year_weeknum_weekday(from_days(dp.time_since_epoch())) {} inline @@ -1416,9 +1441,17 @@ CONSTCD11 inline weekday year_weeknum_weekday::weekday() const NOEXCEPT {return CONSTCD14 inline -year_weeknum_weekday::operator day_point() const NOEXCEPT +year_weeknum_weekday::operator sys_days() const NOEXCEPT { - return day_point{date::year{int{y_}-1}/date::dec/date::thu[date::last]} + return sys_days{date::year{int{y_}-1}/date::dec/date::thu[date::last]} + + (date::mon - date::thu) + weeks{unsigned{wn_}-1} + (wd_ - mon); +} + +CONSTCD14 +inline +year_weeknum_weekday::operator local_days() const NOEXCEPT +{ + return local_days{date::year{int{y_}-1}/date::dec/date::thu[date::last]} + (date::mon - date::thu) + weeks{unsigned{wn_}-1} + (wd_ - mon); } @@ -1433,15 +1466,16 @@ year_weeknum_weekday::ok() const NOEXCEPT CONSTCD14 inline year_weeknum_weekday -year_weeknum_weekday::from_day_point(const day_point& dp) NOEXCEPT +year_weeknum_weekday::from_days(days d) NOEXCEPT { + const auto dp = sys_days{d}; const auto wd = iso_week::weekday{dp}; auto y = date::year_month_day{dp + days{3}}.year(); - auto start = day_point{(y - date::years{1})/date::dec/date::thu[date::last]} + (mon-thu); + auto start = sys_days{(y - date::years{1})/date::dec/date::thu[date::last]} + (mon-thu); if (dp < start) { --y; - start = day_point{(y - date::years{1})/date::dec/date::thu[date::last]} + (mon-thu); + start = sys_days{(y - date::years{1})/date::dec/date::thu[date::last]} + (mon-thu); } const auto wn = iso_week::weeknum(date::trunc(dp - start).count() + 1); return {iso_week::year(int{y}), wn, wd}; diff --git a/test/date_test/durations.pass.cpp b/test/date_test/durations.pass.cpp index 70a152b..2c35e98 100644 --- a/test/date_test/durations.pass.cpp +++ b/test/date_test/durations.pass.cpp @@ -36,7 +36,7 @@ // // time_point // -// using day_point = std::chrono::time_point; +// using sys_days = std::chrono::time_point; #include "date.h" @@ -54,7 +54,7 @@ static_assert(date::years{400} == date::days{146097}, ""); static_assert(date::days{365} < date::years{1} && date::years{1} < date::days{366}, ""); static_assert(date::weeks{52} < date::years{1} && date::years{1} < date::weeks{53}, ""); -static_assert(std::is_same{}, ""); +static_assert(std::is_same{}, ""); int main() diff --git a/test/date_test/sizeof.pass.cpp b/test/date_test/sizeof.pass.cpp index 104507c..3df45a1 100644 --- a/test/date_test/sizeof.pass.cpp +++ b/test/date_test/sizeof.pass.cpp @@ -34,7 +34,7 @@ main() static_assert(sizeof(months) == 4, ""); static_assert(sizeof(years) == 4, ""); - static_assert(sizeof(day_point) == 4, ""); + static_assert(sizeof(sys_days) == 4, ""); static_assert(sizeof(last_spec) == 1, ""); diff --git a/test/date_test/weekday.pass.cpp b/test/date_test/weekday.pass.cpp index bae513b..ed5da17 100644 --- a/test/date_test/weekday.pass.cpp +++ b/test/date_test/weekday.pass.cpp @@ -25,7 +25,7 @@ // unsigned char wd_; // public: // explicit constexpr weekday(unsigned wd) noexcept; -// constexpr weekday(const day_point& dp) noexcept; +// constexpr weekday(const sys_days& dp) noexcept; // // weekday& operator++() noexcept; // weekday operator++(int) noexcept; @@ -76,10 +76,10 @@ static_assert( std::is_trivially_move_constructible{}, ""); static_assert( std::is_trivially_move_assignable{}, ""); static_assert( std::is_nothrow_constructible{}, ""); -static_assert( std::is_nothrow_constructible{}, ""); +static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_nothrow_constructible{}, ""); static_assert(!std::is_convertible{}, ""); -static_assert( std::is_convertible{}, ""); +static_assert( std::is_convertible{}, ""); static_assert(!std::is_convertible{}, ""); static_assert(static_cast(date::weekday{1u}) == 1, ""); diff --git a/test/date_test/year.pass.cpp b/test/date_test/year.pass.cpp index dfb051b..1b28914 100644 --- a/test/date_test/year.pass.cpp +++ b/test/date_test/year.pass.cpp @@ -108,29 +108,29 @@ main() #if __cplusplus >= 201402 using int64_t = std::int64_t; - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(days::min()), ""); - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(hours::min()), ""); - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(minutes::min()), ""); - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(seconds::min()), ""); - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(milliseconds::min()), ""); - static_assert(day_point(year::min()/jan/1) - day_point(1970_y/jan/1) + static_assert(sys_days(year::min()/jan/1) - sys_days(1970_y/jan/1) >= as(microseconds::min()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(microseconds::max()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(milliseconds::max()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(seconds::max()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(minutes::max()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(hours::max()), ""); - static_assert(day_point(year::max()/dec/31) - day_point(1970_y/jan/1) + static_assert(sys_days(year::max()/dec/31) - sys_days(1970_y/jan/1) <= as(days::max()), ""); #endif diff --git a/test/date_test/year_month_day.pass.cpp b/test/date_test/year_month_day.pass.cpp index ef93ac6..792253e 100644 --- a/test/date_test/year_month_day.pass.cpp +++ b/test/date_test/year_month_day.pass.cpp @@ -26,7 +26,7 @@ // constexpr year_month_day(const date::year& y, const date::month& m, // const date::day& d) noexcept; // constexpr year_month_day(const year_month_day_last& ymdl) noexcept; -// constexpr year_month_day(const day_point& dp) noexcept; +// constexpr year_month_day(const sys_days& dp) noexcept; // // year_month_day& operator+=(const months& m) noexcept; // year_month_day& operator-=(const months& m) noexcept; @@ -37,7 +37,7 @@ // constexpr date::month month() const noexcept; // constexpr date::day day() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // }; @@ -76,10 +76,10 @@ static_assert(std::is_nothrow_constructible{}, ""); static_assert(std::is_convertible{}, ""); -static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); -static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); +static_assert(std::is_nothrow_constructible{}, ""); +static_assert(std::is_convertible{}, ""); +static_assert(std::is_nothrow_constructible{}, ""); +static_assert(std::is_convertible{}, ""); void test_arithmetic() @@ -131,7 +131,7 @@ test_day_point_conversion() using namespace date; year y = year{-1000}; year end = 3000_y; - day_point prev_dp = day_point(year_month_day{y, jan, 1_d}) - days{1}; + sys_days prev_dp = sys_days(year_month_day{y, jan, 1_d}) - days{1}; weekday prev_wd = weekday{prev_dp}; for (; y <= end; ++y) { @@ -143,7 +143,7 @@ test_day_point_conversion() { year_month_day ymd = {y, m, d}; assert(ymd.ok()); - day_point dp = ymd; + sys_days dp = ymd; assert(dp == prev_dp + days{1}); year_month_day ymd2 = dp; assert(ymd2 == ymd); @@ -171,7 +171,7 @@ main() static_assert(ymd1.day() == 9_d, ""); #if __cplusplus >= 201402 - constexpr day_point dp = ymd1; + constexpr sys_days dp = ymd1; static_assert(dp.time_since_epoch() == days{16656}, ""); constexpr year_month_day ymd2 = dp; static_assert(ymd1 == ymd2, ""); @@ -186,7 +186,7 @@ main() static_assert(ymd3.day() == 31_d, ""); #if __cplusplus >= 201402 - constexpr day_point dp3 = ymd3; + constexpr sys_days dp3 = ymd3; static_assert(dp3.time_since_epoch() == days{-1}, ""); constexpr year_month_day ymd4 = dp3; static_assert(ymd3 == ymd4, ""); @@ -211,9 +211,9 @@ main() static_assert( (2100_y/feb/28).ok(), ""); static_assert(!(2100_y/feb/29).ok(), ""); - static_assert(day_point(2100_y/feb/28) + days{1} == day_point(2100_y/mar/1), ""); - static_assert(day_point(2000_y/mar/1) - day_point(2000_y/feb/28) == days{2}, ""); - static_assert(day_point(2100_y/mar/1) - day_point(2100_y/feb/28) == days{1}, ""); + static_assert(sys_days(2100_y/feb/28) + days{1} == sys_days(2100_y/mar/1), ""); + static_assert(sys_days(2000_y/mar/1) - sys_days(2000_y/feb/28) == days{2}, ""); + static_assert(sys_days(2100_y/mar/1) - sys_days(2100_y/feb/28) == days{1}, ""); static_assert(jan/31/2015 == jan/last/2015, ""); static_assert(feb/28/2015 == feb/last/2015, ""); diff --git a/test/date_test/year_month_day_last.pass.cpp b/test/date_test/year_month_day_last.pass.cpp index fdd1df0..6ac800a 100644 --- a/test/date_test/year_month_day_last.pass.cpp +++ b/test/date_test/year_month_day_last.pass.cpp @@ -36,7 +36,7 @@ // constexpr date::month_day_last month_day_last() const noexcept; // constexpr date::day day() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // }; @@ -94,9 +94,9 @@ static_assert( std::is_trivially_move_assignable{}, " static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); +static_assert(std::is_convertible{}, ""); void test_arithmetic() @@ -154,7 +154,7 @@ main() static_assert(ymdl1.month_day_last() == month_day_last{aug}, ""); #if __cplusplus >= 201402 static_assert(ymdl1.day() == 31_d, ""); - constexpr day_point dp = ymdl1; + constexpr sys_days dp = ymdl1; constexpr year_month_day ymd = dp; static_assert(ymd == 2015_y/aug/31, ""); #endif diff --git a/test/date_test/year_month_weekday.pass.cpp b/test/date_test/year_month_weekday.pass.cpp index b3a00de..70fefa0 100644 --- a/test/date_test/year_month_weekday.pass.cpp +++ b/test/date_test/year_month_weekday.pass.cpp @@ -25,7 +25,7 @@ // public: // constexpr year_month_weekday(const date::year& y, const date::month& m, // const date::weekday_indexed& wdi) noexcept; -// constexpr year_month_weekday(const day_point& dp) noexcept; +// constexpr year_month_weekday(const sys_days& dp) noexcept; // // year_month_weekday& operator+=(const months& m) noexcept; // year_month_weekday& operator-=(const months& m) noexcept; @@ -38,11 +38,11 @@ // constexpr unsigned index() const noexcept; // constexpr date::weekday_indexed weekday_indexed() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // // private: -// static constexpr year_month_weekday from_day_point(const day_point& dp) noexcept; +// static constexpr year_month_weekday from_day_point(const sys_days& dp) noexcept; // }; // constexpr @@ -93,11 +93,11 @@ static_assert(std::is_nothrow_constructible{}, ""); static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); -static_assert(std::is_nothrow_constructible{}, ""); +static_assert(std::is_convertible{}, ""); +static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); +static_assert(std::is_convertible{}, ""); void test_arithmetic() @@ -158,7 +158,7 @@ main() static_assert(ymdl1.index() == 2u, ""); static_assert(ymdl1.weekday_indexed() == fri[2], ""); #if __cplusplus >= 201402 - constexpr day_point dp = ymdl1; + constexpr sys_days dp = ymdl1; constexpr year_month_day ymd = dp; static_assert(ymd == 2015_y/aug/14, ""); #endif diff --git a/test/date_test/year_month_weekday_last.pass.cpp b/test/date_test/year_month_weekday_last.pass.cpp index 08a0e44..843df17 100644 --- a/test/date_test/year_month_weekday_last.pass.cpp +++ b/test/date_test/year_month_weekday_last.pass.cpp @@ -36,7 +36,7 @@ // constexpr date::weekday weekday() const noexcept; // constexpr date::weekday_last weekday_last() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // }; @@ -90,9 +90,9 @@ static_assert( std::is_trivially_move_assignable{ static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_nothrow_constructible{}, ""); -static_assert(std::is_convertible{}, ""); +static_assert(std::is_convertible{}, ""); void test_arithmetic() @@ -150,7 +150,7 @@ main() static_assert(ymdl1.weekday() == fri, ""); static_assert(ymdl1.weekday_last() == fri[last], ""); #if __cplusplus >= 201402 - constexpr day_point dp = ymdl1; + constexpr sys_days dp = ymdl1; constexpr year_month_day ymd = dp; static_assert(ymd == 2015_y/aug/28, ""); #endif diff --git a/test/iso_week/weekday.pass.cpp b/test/iso_week/weekday.pass.cpp index 9c84c9b..702cdc4 100644 --- a/test/iso_week/weekday.pass.cpp +++ b/test/iso_week/weekday.pass.cpp @@ -26,7 +26,7 @@ // public: // explicit constexpr weekday(unsigned wd) noexcept; // constexpr weekday(date::weekday wd) noexcept; -// constexpr weekday(const day_point& dp) noexcept; +// constexpr weekday(const sys_days& dp) noexcept; // // weekday& operator++() noexcept; // weekday operator++(int) noexcept; @@ -85,12 +85,12 @@ static_assert(std::is_standard_layout{}, ""); static_assert(std::is_literal_type{}, ""); static_assert( std::is_nothrow_constructible{}, ""); -static_assert( std::is_nothrow_constructible{}, ""); +static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_nothrow_constructible{}, ""); static_assert(!std::is_convertible{}, ""); -static_assert( std::is_convertible{}, ""); +static_assert( std::is_convertible{}, ""); static_assert( std::is_convertible{}, ""); static_assert( std::is_convertible{}, ""); static_assert(!std::is_convertible{}, ""); diff --git a/test/iso_week/year_lastweek_weekday.pass.cpp b/test/iso_week/year_lastweek_weekday.pass.cpp index 59a43aa..e2164a4 100644 --- a/test/iso_week/year_lastweek_weekday.pass.cpp +++ b/test/iso_week/year_lastweek_weekday.pass.cpp @@ -33,7 +33,7 @@ // constexpr iso_week::weeknum weeknum() const noexcept; // constexpr iso_week::weekday weekday() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // }; // @@ -69,10 +69,10 @@ static_assert(std::is_literal_type{}, ""); static_assert( std::is_nothrow_constructible{}, ""); -static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_convertible{}, ""); + iso_week::sys_days>{}, ""); int main() @@ -100,8 +100,8 @@ main() assert(x3.weeknum() == 53_w); assert(x3.weekday() == tue); - constexpr day_point dp = 2015_y/last/wed; - static_assert(dp == day_point{days{16799}}, ""); + constexpr sys_days dp = 2015_y/last/wed; + static_assert(dp == sys_days{days{16799}}, ""); static_assert(x0.ok(), ""); assert(x3.ok()); diff --git a/test/iso_week/year_weeknum_weekday.pass.cpp b/test/iso_week/year_weeknum_weekday.pass.cpp index 4b4a726..02e6420 100644 --- a/test/iso_week/year_weeknum_weekday.pass.cpp +++ b/test/iso_week/year_weeknum_weekday.pass.cpp @@ -28,7 +28,7 @@ // constexpr year_weeknum_weekday(const iso_week::year& y, const iso_week::weeknum& wn, // const iso_week::weekday& wd) noexcept; // constexpr year_weeknum_weekday(const year_lastweek_weekday& ylwwd) noexcept; -// constexpr year_weeknum_weekday(const day_point& dp) noexcept; +// constexpr year_weeknum_weekday(const sys_days& dp) noexcept; // // year_weeknum_weekday& operator+=(const years& y) noexcept; // year_weeknum_weekday& operator-=(const years& y) noexcept; @@ -37,7 +37,7 @@ // constexpr iso_week::weeknum weeknum() const noexcept; // constexpr iso_week::weekday weekday() const noexcept; // -// constexpr operator day_point() const noexcept; +// constexpr operator sys_days() const noexcept; // constexpr bool ok() const noexcept; // }; // @@ -79,13 +79,13 @@ static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_nothrow_constructible{}, ""); -static_assert( std::is_convertible{}, ""); +static_assert( std::is_convertible{}, ""); -static_assert( std::is_nothrow_constructible{}, ""); static_assert( std::is_convertible{}, ""); + iso_week::sys_days>{}, ""); int main() @@ -102,7 +102,7 @@ main() static_assert(x1.weeknum() == 53_w, ""); static_assert(x1.weekday() == tue, ""); - constexpr year_weeknum_weekday x2 = day_point{days{16792}}; + constexpr year_weeknum_weekday x2 = sys_days{days{16792}}; static_assert(x2.year() == 2015_y, ""); static_assert(x2.weeknum() == 52_w, ""); static_assert(x2.weekday() == wed, ""); @@ -118,8 +118,8 @@ main() assert(x3.weeknum() == 52_w); assert(x3.weekday() == wed); - constexpr day_point dp = 2015_y/52_w/wed; - static_assert(dp == day_point{days{16792}}, ""); + constexpr sys_days dp = 2015_y/52_w/wed; + static_assert(dp == sys_days{days{16792}}, ""); static_assert(x0.ok(), ""); static_assert(x1.ok(), ""); diff --git a/test/tz_test/tzdata2016d.txt.zip b/test/tz_test/tzdata2016d.txt.zip new file mode 100644 index 0000000..8af5ea1 Binary files /dev/null and b/test/tz_test/tzdata2016d.txt.zip differ diff --git a/test/tz_test/validate.cpp b/test/tz_test/validate.cpp index 15a0313..c563ab6 100644 --- a/test/tz_test/validate.cpp +++ b/test/tz_test/validate.cpp @@ -2,38 +2,42 @@ #include void -test_info(const date::Zone* zone, const date::Info& info) +test_info(const date::time_zone* zone, const date::Info& info) { using namespace date; using namespace std::chrono; auto begin = info.begin; auto end = info.end - microseconds{1}; - auto mid = begin + (end - begin) /2 ; + auto mid = begin + (end - begin) /2; + using sys_microseconds = sys_time; + using zoned_microseconds = zoned_time; + using local_microseconds = local_time; + zoned_microseconds local{zone}; - if (begin > day_point{jan/1/1700}) + if (begin > sys_days{jan/1/1700}) { - auto local = zone->to_local(begin).first; - auto prev_local = zone->to_local(begin - seconds{1}).first; - if (prev_local < local - seconds{1}) + auto prev_local = local; + local = begin; + prev_local = begin - seconds{1}; + auto slocal = local.get_local_time(); + auto plocal = prev_local.get_local_time(); + if (plocal < slocal - seconds{1}) { - assert(zone->to_sys(local) == begin); - auto imaginary = prev_local + (local - seconds{1} - prev_local) / 2; + assert(sys_microseconds{local} == begin); try { - zone->to_sys(imaginary); + local = plocal + (slocal - seconds{1} - plocal) / 2; assert(false); } catch (const nonexistent_local_time&) { } } - else if (prev_local > local - seconds{1}) + else if (plocal > slocal - seconds{1}) { - auto ambiguous = local - seconds{1} + - (prev_local - (local - seconds{1})) / 2; try { - zone->to_sys(ambiguous); + local = slocal - seconds{1} + (plocal - (slocal - seconds{1})) / 2; assert(false); } catch (const ambiguous_local_time&) @@ -42,33 +46,34 @@ test_info(const date::Zone* zone, const date::Info& info) } } - auto local = zone->to_local(mid).first; - assert(zone->to_sys(local) == mid); + local = mid; + assert(sys_microseconds{local} == mid); - if (end < day_point{jan/1/3000}) + if (end < sys_days{jan/1/3000}) { - auto local = zone->to_local(end).first; - auto next_local = zone->to_local(info.end).first; - if (next_local < local + microseconds{1}) + local = end; + auto next_local = local; + next_local = info.end; + auto slocal = local.get_local_time(); + auto nlocal = next_local.get_local_time(); + if (nlocal < slocal + microseconds{1}) { - auto ambiguous = next_local + (local + microseconds{1} - next_local) / 2; try { - zone->to_sys(ambiguous); + local = nlocal + (slocal + microseconds{1} - nlocal) / 2; assert(false); } catch (const ambiguous_local_time&) { } } - else if (next_local > local + microseconds{1}) + else if (nlocal > slocal + microseconds{1}) { - assert(zone->to_sys(local) == end); - auto imaginary = local + microseconds{1} + - (next_local - (local + microseconds{1})) / 2; + assert(sys_microseconds{local} == end); try { - zone->to_sys(imaginary); + local = slocal + microseconds{1} + + (nlocal - (slocal + microseconds{1})) / 2; assert(false); } catch (const nonexistent_local_time&) @@ -96,9 +101,9 @@ main() { std::cout << name << '\n'; auto z = locate_zone(name); - auto begin = day_point(jan/1/year::min()) + 0s; - auto end = day_point(jan/1/2035) + 0s; - auto info = z->get_info(begin, tz::utc); + auto begin = sys_days(jan/1/year::min()) + 0s; + auto end = sys_days(jan/1/2035) + 0s; + auto info = z->get_info(begin); std::cout << "Initially: "; if (info.offset >= 0s) std::cout << '+'; @@ -114,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) @@ -122,7 +127,7 @@ main() auto dp = floor(begin); auto ymd = year_month_day(dp); auto time = make_time(begin - dp); - std::cout << ymd << 'T' << time << "Z "; + std::cout << ymd << ' ' << time << "Z "; if (info.offset >= 0s) std::cout << '+'; std::cout << make_time(info.offset); diff --git a/test/tz_test/zone.pass.cpp b/test/tz_test/zone.pass.cpp index eed4698..30329e5 100644 --- a/test/tz_test/zone.pass.cpp +++ b/test/tz_test/zone.pass.cpp @@ -6,10 +6,10 @@ main() { using namespace std; using namespace date; - static_assert( is_nothrow_destructible{}, ""); - static_assert(!is_default_constructible{}, ""); - static_assert(!is_copy_constructible{}, ""); - static_assert(!is_copy_assignable{}, ""); - static_assert( is_nothrow_move_constructible{}, ""); - static_assert( is_nothrow_move_assignable{}, ""); + static_assert( is_nothrow_destructible{}, ""); + static_assert(!is_default_constructible{}, ""); + static_assert(!is_copy_constructible{}, ""); + static_assert(!is_copy_assignable{}, ""); + static_assert( is_nothrow_move_constructible{}, ""); + static_assert( is_nothrow_move_assignable{}, ""); } diff --git a/tz.cpp b/tz.cpp index ab4870f..1efadbe 100644 --- a/tz.cpp +++ b/tz.cpp @@ -20,6 +20,10 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. #include "tz_private.h" @@ -115,24 +119,16 @@ 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; // +-------------------+ // | 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 = @@ -284,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; @@ -309,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, @@ -360,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. @@ -377,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()) { @@ -412,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); @@ -549,7 +545,7 @@ parse_signed_time(std::istream& in) // MonthDayTime -MonthDayTime::MonthDayTime(second_point tp, tz timezone) +MonthDayTime::MonthDayTime(local_seconds tp, tz timezone) : zone_(timezone) { using namespace date; @@ -606,8 +602,8 @@ MonthDayTime::compare(date::year y, const MonthDayTime& x, date::year yx, { if (zone_ != x.zone_) { - auto dp0 = to_day_point(y); - auto dp1 = x.to_day_point(yx); + auto dp0 = to_sys_days(y); + auto dp1 = x.to_sys_days(yx); if (std::abs((dp0-dp1).count()) > 1) return dp0 < dp1 ? -1 : 1; if (zone_ == tz::local) @@ -642,7 +638,7 @@ MonthDayTime::compare(date::year y, const MonthDayTime& x, date::year yx, return t0 < t1 ? -1 : t0 == t1 ? 0 : 1; } -second_point +sys_seconds MonthDayTime::to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const { @@ -677,23 +673,23 @@ MonthDayTime::U::operator=(const pair& x) return *this; } -date::day_point -MonthDayTime::to_day_point(date::year y) const +date::sys_days +MonthDayTime::to_sys_days(date::year y) const { using namespace std::chrono; using namespace date; switch (type_) { case month_day: - return day_point(y/u.month_day_); + return sys_days(y/u.month_day_); case month_last_dow: - return day_point(y/u.month_weekday_last_); + return sys_days(y/u.month_weekday_last_); case lteq: { auto const x = y/u.month_day_weekday_.month_day_; auto const wd1 = weekday(x); auto const wd0 = u.month_day_weekday_.weekday_; - return day_point(x) - (wd1-wd0); + return sys_days(x) - (wd1-wd0); } case gteq: break; @@ -701,13 +697,13 @@ MonthDayTime::to_day_point(date::year y) const auto const x = y/u.month_day_weekday_.month_day_; auto const wd1 = u.month_day_weekday_.weekday_; auto const wd0 = weekday(x); - return day_point(x) + (wd1-wd0); + return sys_days(x) + (wd1-wd0); } -second_point +sys_seconds MonthDayTime::to_time_point(date::year y) const { - return to_day_point(y) + h_ + m_ + s_; + return to_sys_days(y) + h_ + m_ + s_; } void @@ -721,7 +717,7 @@ MonthDayTime::canonicalize(date::year y) return; case month_last_dow: { - auto const ymd = year_month_day(y/u.month_weekday_last_); + auto const ymd = year_month_day(sys_days{y/u.month_weekday_last_}); u.month_day_ = ymd.month()/ymd.day(); type_ = month_day; return; @@ -731,7 +727,7 @@ MonthDayTime::canonicalize(date::year y) auto const x = y/u.month_day_weekday_.month_day_; auto const wd1 = weekday(x); auto const wd0 = u.month_day_weekday_.weekday_; - auto const ymd = year_month_day(day_point(x) - (wd1-wd0)); + auto const ymd = year_month_day(sys_days(x) - (wd1-wd0)); u.month_day_ = ymd.month()/ymd.day(); type_ = month_day; return; @@ -741,7 +737,7 @@ MonthDayTime::canonicalize(date::year y) auto const x = y/u.month_day_weekday_.month_day_; auto const wd1 = u.month_day_weekday_.weekday_; auto const wd0 = weekday(x); - auto const ymd = year_month_day(day_point(x) + (wd1-wd0)); + auto const ymd = year_month_day(sys_days(x) + (wd1-wd0)); u.month_day_ = ymd.month()/ymd.day(); type_ = month_day; return; @@ -1218,9 +1214,9 @@ Rule::split_overlaps(std::vector& rules) rules.shrink_to_fit(); } -// Zone +// time_zone -Zone::zonelet::~zonelet() +time_zone::zonelet::~zonelet() { #if !defined(_MSC_VER) || (_MSC_VER >= 1900) using minutes = std::chrono::minutes; @@ -1232,14 +1228,14 @@ Zone::zonelet::~zonelet() #endif } -Zone::zonelet::zonelet() +time_zone::zonelet::zonelet() { #if !defined(_MSC_VER) || (_MSC_VER >= 1900) ::new(&u.rule_) std::string(); #endif } -Zone::zonelet::zonelet(const zonelet& i) +time_zone::zonelet::zonelet(const zonelet& i) : gmtoff_(i.gmtoff_) , tag_(i.tag_) , format_(i.format_) @@ -1266,7 +1262,7 @@ Zone::zonelet::zonelet(const zonelet& i) #endif } -Zone::Zone(const std::string& s) +time_zone::time_zone(const std::string& s) #if LAZY_INIT : adjusted_(new std::once_flag{}) #endif @@ -1290,7 +1286,7 @@ Zone::Zone(const std::string& s) } void -Zone::add(const std::string& s) +time_zone::add(const std::string& s) { try { @@ -1310,7 +1306,7 @@ Zone::add(const std::string& s) } void -Zone::parse_info(std::istream& in) +time_zone::parse_info(std::istream& in) { using namespace date; using namespace std::chrono; @@ -1326,7 +1322,7 @@ Zone::parse_info(std::istream& in) if (in.eof() || in.peek() == '#') { zonelet.until_year_ = year::max(); - zonelet.until_date_ = MonthDayTime(boring_day, tz::utc); + zonelet.until_date_ = MonthDayTime(max_day, tz::utc); } else { @@ -1489,8 +1485,9 @@ find_rule_for_zone(const std::pair& eqr, static std::pair find_rule_for_zone(const std::pair& eqr, - const second_point& tp_utc, const second_point& tp_std, - const second_point& tp_loc) + const sys_seconds& tp_utc, + const local_seconds& tp_std, + const local_seconds& tp_loc) { using namespace std::chrono; using namespace date; @@ -1508,10 +1505,10 @@ find_rule_for_zone(const std::pair& eqr, found = tp_utc < r->mdt().to_time_point(ry); break; case tz::standard: - found = tp_std < r->mdt().to_time_point(ry); + found = sys_seconds{tp_std.time_since_epoch()} < r->mdt().to_time_point(ry); break; case tz::local: - found = tp_loc < r->mdt().to_time_point(ry); + found = sys_seconds{tp_loc.time_since_epoch()} < r->mdt().to_time_point(ry); break; } if (found) @@ -1525,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, @@ -1536,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{day_point(year::min()/boring_day), day_point(year::max()/boring_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); @@ -1569,7 +1566,7 @@ find_rule(const std::pair& first_rule, x.end = r->mdt().to_sys(ry, offset, x.save); } else - x.end = day_point(year::max()/boring_day); + x.end = sys_days(year::max()/max_day); break; } x.save = r->save(); @@ -1580,7 +1577,7 @@ find_rule(const std::pair& first_rule, } void -Zone::adjust_infos(const std::vector& rules) +time_zone::adjust_infos(const std::vector& rules) { using namespace std::chrono; using namespace date; @@ -1640,7 +1637,7 @@ Zone::adjust_infos(const std::vector& rules) final_save = z.last_rule_.first->save(); } z.until_utc_ = z.until_date_.to_sys(z.until_year_, z.gmtoff_, final_save); - z.until_std_ = z.until_utc_ + z.gmtoff_; + z.until_std_ = local_seconds{z.until_utc_.time_since_epoch()} + z.gmtoff_; z.until_loc_ = z.until_std_ + final_save; if (z.tag_ == zonelet::has_rule) @@ -1750,31 +1747,64 @@ format_abbrev(std::string format, const std::string& variable, std::chrono::seco return format; } -Info -Zone::get_info(std::chrono::system_clock::time_point 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) throw std::runtime_error("The year " + std::to_string(static_cast(y)) + " is out of range:[" + std::to_string(static_cast(min_year)) + ", " + std::to_string(static_cast(max_year)) + "]"); - auto tps = floor(tp); #if LAZY_INIT - std::call_once(*adjusted_, [this]() - { - const_cast(this)->adjust_infos(get_tzdb().rules); - }); + std::call_once(*adjusted_, + [this]() + { + const_cast(this)->adjust_infos(get_tzdb().rules); + }); #endif - auto i = std::upper_bound(zonelets_.begin(), zonelets_.end(), tps, - [timezone](second_point t, const zonelet& zl) + auto i = std::upper_bound(zonelets_.begin(), zonelets_.end(), tp, + [timezone](sys_seconds t, const zonelet& zl) { - return timezone == tz::utc ? t < zl.until_utc_ : t < zl.until_loc_; + return timezone == tz::utc ? t < zl.until_utc_ : + t < sys_seconds{zl.until_loc_.time_since_epoch()}; }); - Info r{}; + sys_info r{}; if (i != zonelets_.end()) { if (i->tag_ == zonelet::has_save) @@ -1782,7 +1812,7 @@ Zone::get_info(std::chrono::system_clock::time_point tp, tz timezone) const if (i != zonelets_.begin()) r.begin = i[-1].until_utc_; else - r.begin = day_point(year::min()/boring_day); + r.begin = sys_days(year::min()/min_day); r.end = i->until_utc_; r.offset = i->gmtoff_ + i->u.save_; r.save = i->u.save_; @@ -1792,15 +1822,15 @@ Zone::get_info(std::chrono::system_clock::time_point tp, tz timezone) const if (i != zonelets_.begin()) r.begin = i[-1].until_utc_; else - r.begin = day_point(year::min()/boring_day); + r.begin = sys_days(year::min()/min_day); r.end = i->until_utc_; r.offset = i->gmtoff_; } else { r = find_rule(i->first_rule_, i->last_rule_, y, i->gmtoff_, - MonthDayTime(tps, timezone), i->initial_save_, - i->initial_abbrev_); + MonthDayTime(local_seconds{tp.time_since_epoch()}, timezone), + i->initial_save_, i->initial_abbrev_); r.offset = i->gmtoff_ + r.save; if (i != zonelets_.begin() && r.begin < i[-1].until_utc_) r.begin = i[-1].until_utc_; @@ -1814,7 +1844,7 @@ Zone::get_info(std::chrono::system_clock::time_point tp, tz timezone) const } std::ostream& -operator<<(std::ostream& os, const Zone& z) +operator<<(std::ostream& os, const time_zone& z) { using namespace date; using namespace std::chrono; @@ -1822,10 +1852,11 @@ operator<<(std::ostream& os, const Zone& z) os.fill(' '); os.flags(std::ios::dec | std::ios::left); #if LAZY_INIT - std::call_once(*z.adjusted_, [&z]() - { - const_cast(z).adjust_infos(get_tzdb().rules); - }); + std::call_once(*z.adjusted_, + [&z]() + { + const_cast(z).adjust_infos(get_tzdb().rules); + }); #endif os.width(35); os << z.name_; @@ -1837,7 +1868,7 @@ operator<<(std::ostream& os, const Zone& z) os << ' '; os << make_time(s.gmtoff_) << " "; os.width(15); - if (s.tag_ != Zone::zonelet::has_save) + if (s.tag_ != time_zone::zonelet::has_save) os << s.u.rule_; else { @@ -2120,7 +2151,7 @@ init_tzdb() } else if (word == "Zone") { - db.zones.push_back(Zone(line)); + db.zones.push_back(time_zone(line)); continue_zone = true; } else if (line[0] == '\t' && continue_zone) @@ -2182,12 +2213,12 @@ get_tzdb() return ref; } -const Zone* +const time_zone* locate_zone(const std::string& tz_name) { const auto& db = get_tzdb(); auto zi = std::lower_bound(db.zones.begin(), db.zones.end(), tz_name, - [](const Zone& z, const std::string& nm) + [](const time_zone& z, const std::string& nm) { return z.name() < nm; }); @@ -2201,7 +2232,7 @@ locate_zone(const std::string& tz_name) if (li != db.links.end() && li->name() == tz_name) { zi = std::lower_bound(db.zones.begin(), db.zones.end(), li->target(), - [](const Zone& z, const std::string& nm) + [](const time_zone& z, const std::string& nm) { return z.name() < nm; }); @@ -2215,7 +2246,7 @@ locate_zone(const std::string& tz_name) #ifdef TZ_TEST #ifdef _WIN32 -const Zone* +const time_zone* locate_native_zone(const std::string& native_tz_name) { std::string standard_tz_name; @@ -2292,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"; @@ -2303,9 +2333,25 @@ 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 Zone* +const time_zone* current_zone() { #if TIMEZONE_MAPPING @@ -2346,7 +2392,7 @@ current_zone() #else // ! WIN32 -const Zone* +const time_zone* current_zone() { // On some versions of some linux distro's (e.g. Ubuntu), diff --git a/tz.h b/tz.h index 46e6386..0ebc4a7 100644 --- a/tz.h +++ b/tz.h @@ -22,6 +22,10 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. // Get more recent database at http://www.iana.org/time-zones @@ -93,52 +97,43 @@ static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true, namespace date { -using second_point = std::chrono::time_point; - -enum class tz {utc, local, standard}; enum class choose {earliest, latest}; class nonexistent_local_time : public std::runtime_error { public: - template - nonexistent_local_time(std::chrono::time_point> tp, - second_point first, const std::string& first_abbrev, - second_point last, const std::string& last_abbrev, - second_point time_sys); + template + nonexistent_local_time(local_time tp, local_seconds first, + const std::string& first_abbrev, local_seconds last, + const std::string& last_abbrev, sys_seconds time_sys); private: - template + template static std::string - make_msg(std::chrono::time_point> tp, - second_point first, const std::string& first_abbrev, - second_point last, const std::string& last_abbrev, - second_point time_sys); + make_msg(local_time tp, + local_seconds first, const std::string& first_abbrev, + local_seconds last, const std::string& last_abbrev, + sys_seconds time_sys); }; -template +template inline -nonexistent_local_time::nonexistent_local_time( - std::chrono::time_point> tp, - second_point first, const std::string& first_abbrev, - second_point last, const std::string& last_abbrev, - second_point time_sys) +nonexistent_local_time::nonexistent_local_time(local_time tp, + local_seconds first, + const std::string& first_abbrev, + local_seconds last, + const std::string& last_abbrev, + sys_seconds time_sys) : std::runtime_error(make_msg(tp, first, first_abbrev, last, last_abbrev, time_sys)) {} -template +template std::string -nonexistent_local_time::make_msg(std::chrono::time_point> tp, - second_point first, const std::string& first_abbrev, - second_point last, const std::string& last_abbrev, - second_point time_sys) +nonexistent_local_time::make_msg(local_time tp, local_seconds first, + const std::string& first_abbrev, local_seconds last, + const std::string& last_abbrev, sys_seconds time_sys) { using namespace date; std::ostringstream os; @@ -154,29 +149,25 @@ class ambiguous_local_time : public std::runtime_error { public: - template - ambiguous_local_time(std::chrono::time_point> tp, - std::chrono::seconds first_offset, + template + ambiguous_local_time(local_time tp, std::chrono::seconds first_offset, const std::string& first_abbrev, std::chrono::seconds second_offset, const std::string& second_abbrev); private: - template + template static std::string - make_msg(std::chrono::time_point> tp, + make_msg(local_time tp, std::chrono::seconds first_offset, const std::string& first_abbrev, std::chrono::seconds second_offset, const std::string& second_abbrev); }; -template +template inline ambiguous_local_time::ambiguous_local_time( - std::chrono::time_point> tp, + local_time tp, std::chrono::seconds first_offset, const std::string& first_abbrev, std::chrono::seconds second_offset, @@ -185,10 +176,9 @@ ambiguous_local_time::ambiguous_local_time( second_abbrev)) {} -template +template std::string -ambiguous_local_time::make_msg(std::chrono::time_point> tp, +ambiguous_local_time::make_msg(local_time tp, std::chrono::seconds first_offset, const std::string& first_abbrev, std::chrono::seconds second_offset, @@ -197,26 +187,121 @@ ambiguous_local_time::make_msg(std::chrono::time_point +class zoned_time +{ + const time_zone* zone_; + sys_time tp_; + +public: + zoned_time(sys_time st); + explicit zoned_time(const time_zone* z); + explicit zoned_time(const std::string& name); + + template , + sys_time>::value + >::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); + zoned_time(const time_zone* z, local_time tp, choose c); + zoned_time(const std::string& name, local_time tp, choose c); + + 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); + 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); + + zoned_time& operator=(sys_time st); + zoned_time& operator=(local_time ut); + + 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 + bool + operator==(const zoned_time& x, const zoned_time& y); + + template + friend + std::ostream& + operator<<(std::ostream& os, const zoned_time& t); + +private: + + static_assert(std::is_convertible::value, + "zoned_time must have a precision of seconds or finer"); +}; + +using zoned_seconds = zoned_time; + +template +inline +bool +operator==(const zoned_time& x, const zoned_time& y) +{ + return x.zone_ == y.zone_ && x.tp_ == y.tp_; +} + +template +inline +bool +operator!=(const zoned_time& x, const zoned_time& y) +{ + return !(x == y); +} + +class time_zone { private: struct zonelet; @@ -229,10 +314,58 @@ private: public: #if !defined(_MSC_VER) || (_MSC_VER >= 1900) - Zone(Zone&&) = default; - Zone& operator=(Zone&&) = default; -#else // defined(_MSC_VER) || (_MSC_VER >= 1900) - Zone(Zone&& src) + time_zone(time_zone&&) = default; + time_zone& operator=(time_zone&&) = default; +#else // defined(_MSC_VER) && (_MSC_VER < 1900) + time_zone(time_zone&& src); + time_zone& operator=(time_zone&& src); +#endif // defined(_MSC_VER) && (_MSC_VER < 1900) + + explicit time_zone(const std::string& s); + + const std::string& name() const; + + template sys_info get_info(sys_time st) const; + template local_info get_info(local_time tp) const; + + template + sys_time::type> + to_sys(local_time tp) const; + + template + sys_time::type> + to_sys(local_time tp, choose z) const; + + template + local_time::type> + to_local(sys_time tp) const; + + 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); + + void add(const std::string& s); + void adjust_infos(const std::vector& rules); + +private: + 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 + sys_time::type> + 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) + +inline +time_zone::time_zone(time_zone&& src) : name_(std::move(src.name_)) , zonelets_(std::move(src.zonelets_)) #if LAZY_INIT @@ -240,165 +373,120 @@ public: #endif {} - Zone& operator=(Zone&& src) - { - name_ = std::move(src.name_); - zonelets_ = std::move(src.zonelets_); -#if LAZY_INIT - adjusted_ = std::move(src.adjusted_); -#endif - return *this; - } -#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) - - explicit Zone(const std::string& s); - - const std::string& name() const {return name_;} - Info get_info(std::chrono::system_clock::time_point tp, tz timezone) const; - - template - Info - get_info(std::chrono::time_point> tp, - tz timezone) const - { - using namespace std::chrono; - return get_info(floor(tp), timezone); - } - - template - std::chrono::time_point, - std::chrono::seconds>::type> - to_sys(std::chrono::time_point> tp) const; - - template - std::chrono::time_point, - std::chrono::seconds>::type> - to_sys(std::chrono::time_point> tp, - choose z) const; - - template - std::pair - < - std::chrono::time_point, - std::chrono::seconds>::type>, - std::string - > - to_local(std::chrono::time_point> tp) const; - - friend bool operator==(const Zone& x, const Zone& y); - friend bool operator< (const Zone& x, const Zone& y); - friend std::ostream& operator<<(std::ostream& os, const Zone& z); - - void add(const std::string& s); - void adjust_infos(const std::vector& rules); - -private: - void parse_info(std::istream& in); - - template - std::chrono::time_point, - std::chrono::seconds>::type> - to_sys_impl(std::chrono::time_point> tp, - choose z, std::integral_constant do_throw) const; -}; - -template inline -std::chrono::time_point, - std::chrono::seconds>::type> -Zone::to_sys(std::chrono::time_point> tp) const +time_zone& +time_zone::operator=(time_zone&& src) +{ + name_ = std::move(src.name_); + zonelets_ = std::move(src.zonelets_); +#if LAZY_INIT + adjusted_ = std::move(src.adjusted_); +#endif + return *this; +} + +#endif // defined(_MSC_VER) && (_MSC_VER < 1900) + +inline +const std::string& +time_zone::name() const +{ + return name_; +} + +template +inline +sys_info +time_zone::get_info(sys_time st) const +{ + using namespace std::chrono; + return get_info_impl(floor(st)); +} + +template +inline +local_info +time_zone::get_info(local_time tp) const +{ + using namespace std::chrono; + return get_info_impl(floor(tp)); +} + +template +inline +sys_time::type> +time_zone::to_sys(local_time tp) const { return to_sys_impl(tp, choose{}, std::true_type{}); } -template +template inline -std::chrono::time_point, - std::chrono::seconds>::type> -Zone::to_sys(std::chrono::time_point> tp, choose z) const +sys_time::type> +time_zone::to_sys(local_time tp, choose z) const { return to_sys_impl(tp, z, std::false_type{}); } -template +template inline -std::pair -< - std::chrono::time_point, - std::chrono::seconds>::type>, - std::string -> -Zone::to_local(std::chrono::time_point> tp) const +local_time::type> +time_zone::to_local(sys_time tp) const { - auto const i = get_info(tp, tz::utc); - return {tp + i.offset, i.abbrev}; + using LT = local_time::type>; + auto i = get_info(tp); + return LT{(tp + i.offset).time_since_epoch()}; } -inline bool operator==(const Zone& x, const Zone& y) {return x.name_ == y.name_;} -inline bool operator< (const Zone& x, const Zone& y) {return x.name_ < y.name_;} +inline bool operator==(const time_zone& x, const time_zone& y) {return x.name_ == y.name_;} +inline bool operator< (const time_zone& x, const time_zone& y) {return x.name_ < y.name_;} -inline bool operator!=(const Zone& x, const Zone& y) {return !(x == y);} -inline bool operator> (const Zone& x, const Zone& y) {return y < x;} -inline bool operator<=(const Zone& x, const Zone& y) {return !(y < x);} -inline bool operator>=(const Zone& x, const Zone& y) {return !(x < y);} +inline bool operator!=(const time_zone& x, const time_zone& y) {return !(x == y);} +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 -std::chrono::time_point, - std::chrono::seconds>::type> -Zone::to_sys_impl(std::chrono::time_point> tp, - choose z, std::integral_constant do_throw) const +template +sys_time::type> +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, tz::local); - auto tp_sys = tp - i.offset; - if (floor(tp_sys) - i.begin <= days{1}) + auto i = get_info(tp); + if (i.result == local_info::nonexistent) { - if (floor(tp) < i.begin + i.offset) - { - if (do_throw) - { - auto prev = get_info(i.begin - seconds{1}, tz::utc); - throw nonexistent_local_time(tp, i.begin + prev.offset, prev.abbrev, - i.begin + i.offset, i.abbrev, i.begin); - } - return i.begin; - } - assert(floor(tp) >= - i.begin + get_info(i.begin - seconds{1}, tz::utc).offset); + return i.first.end; } - if (i.end - floor(tp_sys) <= days{1}) + else if (i.result == local_info::ambiguous) { - assert(floor(tp) < i.end + i.offset); - auto next = get_info(i.end, tz::utc); - if (floor(tp) >= i.end + 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 tp - 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 @@ -426,12 +514,12 @@ inline bool operator>=(const Link& x, const Link& y) {return !(x < y);} class Leap { private: - second_point date_; + sys_seconds date_; public: explicit Leap(const std::string& s); - second_point date() const {return date_;} + sys_seconds date() const {return date_;} friend bool operator==(const Leap& x, const Leap& y) {return x.date_ == y.date_;} friend bool operator< (const Leap& x, const Leap& y) {return x.date_ < y.date_;} @@ -439,8 +527,7 @@ public: template friend bool - operator==(const Leap& x, - const std::chrono::time_point& y) + operator==(const Leap& x, const sys_time& y) { return x.date_ == y; } @@ -448,8 +535,7 @@ public: template friend bool - operator< (const Leap& x, - const std::chrono::time_point& y) + operator< (const Leap& x, const sys_time& y) { return x.date_ < y; } @@ -457,8 +543,7 @@ public: template friend bool - operator< (const std::chrono::time_point& x, - const Leap& y) + operator< (const sys_time& x, const Leap& y) { return x < y.date_; } @@ -474,8 +559,7 @@ inline bool operator>=(const Leap& x, const Leap& y) {return !(x < y);} template inline bool -operator==(const std::chrono::time_point& x, - const Leap& y) +operator==(const sys_time& x, const Leap& y) { return y == x; } @@ -483,8 +567,7 @@ operator==(const std::chrono::time_point& x template inline bool -operator!=(const Leap& x, - const std::chrono::time_point& y) +operator!=(const Leap& x, const sys_time& y) { return !(x == y); } @@ -492,8 +575,7 @@ operator!=(const Leap& x, template inline bool -operator!=(const std::chrono::time_point& x, - const Leap& y) +operator!=(const sys_time& x, const Leap& y) { return !(x == y); } @@ -501,8 +583,7 @@ operator!=(const std::chrono::time_point& x template inline bool -operator> (const Leap& x, - const std::chrono::time_point& y) +operator> (const Leap& x, const sys_time& y) { return y < x; } @@ -510,8 +591,7 @@ operator> (const Leap& x, template inline bool -operator> (const std::chrono::time_point& x, - const Leap& y) +operator> (const sys_time& x, const Leap& y) { return y < x; } @@ -519,8 +599,7 @@ operator> (const std::chrono::time_point& x template inline bool -operator<=(const Leap& x, - const std::chrono::time_point& y) +operator<=(const Leap& x, const sys_time& y) { return !(y < x); } @@ -528,8 +607,7 @@ operator<=(const Leap& x, template inline bool -operator<=(const std::chrono::time_point& x, - const Leap& y) +operator<=(const sys_time& x, const Leap& y) { return !(y < x); } @@ -537,8 +615,7 @@ operator<=(const std::chrono::time_point& x template inline bool -operator>=(const Leap& x, - const std::chrono::time_point& y) +operator>=(const Leap& x, const sys_time& y) { return !(x < y); } @@ -546,15 +623,15 @@ operator>=(const Leap& x, template inline bool -operator>=(const std::chrono::time_point& x, - const Leap& y) +operator>=(const sys_time& x, const Leap& y) { return !(x < 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 @@ -592,19 +669,21 @@ struct timezone_info std::string standard_name; }; +} // detail + #endif // TIMEZONE_MAPPING struct TZ_DB { - std::string version; - std::vector zones; - std::vector links; - std::vector leaps; - std::vector rules; + std::string version; + std::vector zones; + std::vector links; + std::vector leaps; + 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; @@ -653,13 +732,273 @@ bool remote_download(const std::string& version); bool remote_install(const std::string& version); #endif -const Zone* locate_zone(const std::string& tz_name); +const time_zone* locate_zone(const std::string& tz_name); #ifdef TZ_TEST #ifdef _WIN32 -const Zone* locate_native_zone(const std::string& native_tz_name); +const time_zone* locate_native_zone(const std::string& native_tz_name); #endif #endif -const Zone* current_zone(); +const time_zone* current_zone(); + +// zoned_time + +template +inline +zoned_time::zoned_time(sys_time st) + : zone_(locate_zone("UTC")) + , tp_(st) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z) + : zone_(z) + {assert(zone_ != nullptr);} + +template +inline +zoned_time::zoned_time(const std::string& name) + : zoned_time(locate_zone(name)) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, local_time t) + : zone_(z) + , tp_(z->to_sys(t)) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, local_time t) + : zoned_time(locate_zone(name), t) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, local_time t, choose c) + : zone_(z) + , tp_(z->to_sys(t, c)) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, local_time t, + choose c) + : zoned_time(locate_zone(name), t, c) + {} + +template +template +inline +zoned_time::zoned_time(const zoned_time& zt) NOEXCEPT + : zone_(zt.zone_) + , tp_(zt.tp_) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const zoned_time& zt) + : zone_(z) + , tp_(zt.tp_) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const zoned_time& zt) + : zoned_time(locate_zone(name), zt) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const zoned_time& zt, choose) + : zoned_time(z, zt) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, + const zoned_time& zt, choose c) + : zoned_time(locate_zone(name), zt, c) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const sys_time& st) + : zone_(z) + , tp_(st) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const sys_time& st) + : zoned_time(locate_zone(name), st) + {} + + +template +inline +zoned_time& +zoned_time::operator=(sys_time st) +{ + tp_ = st; + return *this; +} + +template +inline +zoned_time& +zoned_time::operator=(local_time ut) +{ + tp_ = zone_->to_sys(ut); + return *this; +} + +template +inline +zoned_time::operator local_time() const +{ + return get_local_time(); +} + +template +inline +zoned_time::operator sys_time() const +{ + return get_sys_time(); +} + +template +inline +const time_zone* +zoned_time::get_time_zone() const +{ + return zone_; +} + +template +inline +local_time +zoned_time::get_local_time() const +{ + return zone_->to_local(tp_); +} + +template +inline +sys_time +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 +inline +zoned_time::type> +make_zoned(sys_time tp) +{ + return {tp}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, local_time tp) +{ + return {zone, tp}; +} + +template +inline +zoned_time::type> +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> +make_zoned(const time_zone* zone, const zoned_time& zt) +{ + return {zone, zt}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const zoned_time& zt) +{ + return {name, zt}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const zoned_time& zt, choose c) +{ + return {zone, zt, c}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const zoned_time& zt, choose c) +{ + return {name, zt, c}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const sys_time& st) +{ + return {zone, st}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const sys_time& st) +{ + return {name, st}; +} + +template +inline +std::ostream& +operator<<(std::ostream& os, const zoned_time& t) +{ + auto i = t.zone_->get_info(t.tp_); + auto lt = t.tp_ + i.offset; + return os << lt << ' ' << i.abbrev; +} class utc_clock { @@ -676,15 +1015,19 @@ public: static std::chrono::time_point::type> - sys_to_utc(std::chrono::time_point t); + sys_to_utc(sys_time t); template static - std::chrono::time_point::type> + sys_time::type> utc_to_sys(std::chrono::time_point t); }; +template + using utc_time = std::chrono::time_point; + +using utc_seconds = utc_time; + inline utc_clock::time_point utc_clock::now() NOEXCEPT @@ -694,28 +1037,24 @@ utc_clock::now() NOEXCEPT } template -std::chrono::time_point::type> -utc_clock::sys_to_utc(std::chrono::time_point t) +utc_time::type> +utc_clock::sys_to_utc(sys_time t) { using namespace std::chrono; using duration = typename std::common_type::type; - using time_point = std::chrono::time_point; auto const& leaps = get_tzdb().leaps; auto const lt = std::upper_bound(leaps.begin(), leaps.end(), t); - return time_point{t.time_since_epoch() + seconds{lt-leaps.begin()}}; + return utc_time{t.time_since_epoch() + seconds{lt-leaps.begin()}}; } template -std::chrono::time_point::type> -utc_clock::utc_to_sys(std::chrono::time_point t) +sys_time::type> +utc_clock::utc_to_sys(utc_time t) { using namespace std::chrono; using duration = typename std::common_type::type; - using time_point = std::chrono::time_point; auto const& leaps = get_tzdb().leaps; - auto tp = time_point{t.time_since_epoch()}; + auto tp = sys_time{t.time_since_epoch()}; auto const lt = std::upper_bound(leaps.begin(), leaps.end(), tp); tp -= seconds{lt-leaps.begin()}; if (lt != leaps.begin() && tp + seconds{1} < lt[-1]) @@ -723,6 +1062,22 @@ utc_clock::utc_to_sys(std::chrono::time_point t) return tp; } +template +inline +sys_time::type> +to_sys_time(utc_time ut) +{ + return utc_clock::utc_to_sys(ut); +} + +template +inline +utc_time::type> +to_utc_time(sys_time st) +{ + return utc_clock::sys_to_utc(st); +} + // format namespace detail @@ -731,8 +1086,7 @@ namespace detail template std::string format(const std::locale& loc, std::string format, - std::chrono::time_point tp, - const Zone* zone) + local_time tp, const time_zone* zone = nullptr) { // Handle these specially // %S append fractional seconds if tp has precision finer than seconds @@ -763,13 +1117,10 @@ format(const std::locale& loc, std::string format, break; case 'z': if (zone == nullptr) - { - format.replace(i, 2, "+0000"); - i += 5 - 1; - } + throw std::runtime_error("Can not format local_time with %z"); else { - auto info = zone->get_info(tp, tz::local); + auto info = zone->get_info(tp).first; auto offset = duration_cast(info.offset); ostringstream os; if (offset >= minutes{0}) @@ -783,13 +1134,10 @@ format(const std::locale& loc, std::string format, break; case 'Z': if (zone == nullptr) - { - format.replace(i, 2, "UTC"); - i += 3 - 1; - } + throw std::runtime_error("Can not format local_time with %z"); else { - auto info = zone->get_info(tp, tz::local); + auto info = zone->get_info(tp).first; format.replace(i, 2, info.abbrev); i += info.abbrev.size() - 1; } @@ -799,7 +1147,7 @@ format(const std::locale& loc, std::string format, } auto& f = use_facet>(loc); ostringstream os; - auto tt = system_clock::to_time_t(tp); + auto tt = system_clock::to_time_t(sys_time{tp.time_since_epoch()}); std::tm tm{}; #ifndef _MSC_VER gmtime_r(&tt, &tm); @@ -815,36 +1163,53 @@ format(const std::locale& loc, std::string format, template inline std::string -format(const std::locale& loc, std::string format, - std::chrono::time_point tp, - const Zone* zone = nullptr) +format(const std::locale& loc, std::string format, local_time tp) { - return detail::format(loc, std::move(format), tp, zone); -} - -inline -std::string -format(const std::locale& loc, std::string format, day_point tp, - const Zone* zone = nullptr) -{ - return detail::format(loc, std::move(format), tp, zone); + return detail::format(loc, std::move(format), tp); } template inline std::string -format(std::string format, - std::chrono::time_point tp, - const Zone* zone = nullptr) +format(std::string format, local_time tp) { - return detail::format(std::locale{}, std::move(format), tp, zone); + return detail::format(std::locale{}, std::move(format), tp); } +template inline std::string -format(std::string format, day_point tp, const Zone* zone = nullptr) +format(const std::locale& loc, std::string format, const zoned_time& tp) { - return detail::format(std::locale{}, std::move(format), tp, zone); + return detail::format(loc, std::move(format), tp.get_local_time(), + tp.get_time_zone()); +} + +template +inline +std::string +format(std::string format, const zoned_time& tp) +{ + return detail::format(std::locale{}, std::move(format), tp.get_local_time(), + tp.get_time_zone()); +} + +template +inline +std::string +format(const std::locale& loc, std::string format, sys_time tp) +{ + return detail::format(loc, std::move(format), + local_time{tp.time_since_epoch()}, locate_zone("UTC")); +} + +template +inline +std::string +format(std::string format, sys_time tp) +{ + return detail::format(std::locale{}, std::move(format), + local_time{tp.time_since_epoch()}, locate_zone("UTC")); } // parse @@ -855,8 +1220,7 @@ namespace detail template void parse(std::istream& is, const std::string& format, - std::chrono::time_point& tp, - std::string* abbrev = nullptr) + sys_time& tp, std::string* abbrev = nullptr) { using namespace std; using namespace std::chrono; @@ -894,9 +1258,9 @@ parse(std::istream& is, const std::string& format, double s; is >> s; if (!is.fail()) - subseconds = duration_cast(duration{s}); + subseconds = round(duration{s}); else - err &= ios_base::failbit; + err |= ios_base::failbit; } else { @@ -928,24 +1292,21 @@ parse(std::istream& is, const std::string& format, offset = -offset; } else - err &= ios_base::failbit; + err |= ios_base::failbit; } else - err &= ios_base::failbit; + err |= ios_base::failbit; } break; case 'Z': - if (abbrev != nullptr) + f.get(is, 0, is, err, &tm, b, i); + ++i; + b = i+1; + if ((err & ios_base::failbit) == 0) { - f.get(is, 0, is, err, &tm, b, i); - ++i; - b = i+1; - if ((err & ios_base::failbit) == 0) - { - is >> temp_abbrev; - if (is.fail()) - err &= ios_base::failbit; - } + is >> temp_abbrev; + if (is.fail()) + err |= ios_base::failbit; } break; } @@ -962,10 +1323,11 @@ parse(std::istream& is, const std::string& format, #else auto tt = _mkgmtime(&tm); #endif - tp = floor(system_clock::from_time_t(tt) + - subseconds - offset); + tp = floor(system_clock::from_time_t(tt) + subseconds); if (abbrev != nullptr) *abbrev = std::move(temp_abbrev); + else + tp -= offset; } } is.setstate(err); @@ -977,8 +1339,7 @@ parse(std::istream& is, const std::string& format, template inline void -parse(std::istream& is, const std::string& format, - std::chrono::time_point& tp) +parse(std::istream& is, const std::string& format, sys_time& tp) { detail::parse(is, format, tp); } @@ -986,11 +1347,25 @@ parse(std::istream& is, const std::string& format, template inline void -parse(std::istream& is, const std::string& format, - std::chrono::time_point& tp, +parse(std::istream& is, const std::string& format, local_time& tp, std::string& abbrev) { - detail::parse(is, format, tp, &abbrev); + sys_time st; + detail::parse(is, format, st, &abbrev); + if (!is.fail()) + tp = local_time{st.time_since_epoch()}; +} + +template +inline +void +parse(std::istream& is, const std::string& format, local_time& tp) +{ + sys_time st; + std::string abbrev; + detail::parse(is, format, st, &abbrev); + if (!is.fail()) + tp = local_time{st.time_since_epoch()}; } } // namespace date diff --git a/tz_private.h b/tz_private.h index c706638..f814087 100644 --- a/tz_private.h +++ b/tz_private.h @@ -3,7 +3,7 @@ // The MIT License (MIT) // -// Copyright (c) 2015 Howard Hinnant +// Copyright (c) 2015, 2016 Howard Hinnant // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,12 +22,18 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. #include "tz.h" namespace date { +enum class tz {utc, local, standard}; + class MonthDayTime { private: @@ -80,7 +86,7 @@ private: public: MonthDayTime() = default; - MonthDayTime(second_point tp, tz timezone); + MonthDayTime(local_seconds tp, tz timezone); MonthDayTime(const date::month_day& md, tz timezone); date::day day() const; @@ -89,11 +95,11 @@ public: void canonicalize(date::year y); - second_point + sys_seconds to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const; - date::day_point to_day_point(date::year y) const; + sys_days to_sys_days(date::year y) const; - second_point to_time_point(date::year y) const; + sys_seconds to_time_point(date::year y) const; int compare(date::year y, const MonthDayTime& x, date::year yx, std::chrono::seconds offset, std::chrono::minutes prev_save) const; @@ -181,7 +187,7 @@ inline bool operator> (const std::string& x, const Rule& y) {return y < x;} inline bool operator<=(const std::string& x, const Rule& y) {return !(y < x);} inline bool operator>=(const std::string& x, const Rule& y) {return !(x < y);} -struct Zone::zonelet +struct time_zone::zonelet { enum tag {has_rule, has_save, is_empty}; @@ -203,14 +209,14 @@ struct Zone::zonelet U& operator=(const U&) = delete; } u; - std::string format_; - date::year until_year_{0}; - MonthDayTime until_date_; - second_point until_utc_; - second_point until_std_; - second_point until_loc_; - std::chrono::minutes initial_save_{}; - std::string initial_abbrev_; + std::string format_; + date::year until_year_{0}; + MonthDayTime until_date_; + sys_seconds until_utc_; + local_seconds until_std_; + local_seconds until_loc_; + std::chrono::minutes initial_save_{}; + std::string initial_abbrev_; std::pair first_rule_{nullptr, date::year::min()}; std::pair last_rule_{nullptr, date::year::max()};