From 7e62dad6dc12701d5633d1509451947e575f5f41 Mon Sep 17 00:00:00 2001 From: blockparty Date: Thu, 21 Nov 2019 05:51:31 -0600 Subject: [PATCH 01/11] Check if features are defined --- toml/datetime.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index dc52396..371f93b 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -20,7 +20,7 @@ namespace toml namespace detail { // TODO: find more sophisticated way to handle this -#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE || _POSIX_SOURCE +#if _POSIX_C_SOURCE >= 1 || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) inline std::tm localtime_s(const std::time_t* src) { std::tm dst; From 6d41a1adb9a34d81c63945d49c568102d4906ef8 Mon Sep 17 00:00:00 2001 From: blockparty Date: Fri, 22 Nov 2019 05:59:55 -0600 Subject: [PATCH 02/11] Suppress unused variable warning --- toml/combinator.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/toml/combinator.hpp b/toml/combinator.hpp index 1c6ce25..aa82012 100644 --- a/toml/combinator.hpp +++ b/toml/combinator.hpp @@ -45,6 +45,7 @@ inline std::string show_char(const char c) buf.fill('\0'); const auto r = std::snprintf( buf.data(), buf.size(), "0x%02x", static_cast(c) & 0xFF); + (void) r; // Unused variable warning assert(r == static_cast(buf.size()) - 1); return std::string(buf.data()); } From 5a8d368927fab570a68f768ff027285f7c6403c1 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Fri, 6 Dec 2019 20:33:15 +0900 Subject: [PATCH 03/11] feat: add thread-safe detail::gmtime_s --- toml/datetime.hpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index 371f93b..ba241b1 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -28,6 +28,13 @@ inline std::tm localtime_s(const std::time_t* src) if (!result) { throw std::runtime_error("localtime_r failed."); } return dst; } +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { throw std::runtime_error("gmtime_r failed."); } + return dst; +} #elif _MSC_VER inline std::tm localtime_s(const std::time_t* src) { @@ -36,13 +43,26 @@ inline std::tm localtime_s(const std::time_t* src) if (result) { throw std::runtime_error("localtime_s failed."); } return dst; } -#else +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { throw std::runtime_error("gmtime_s failed."); } + return dst; +} +#else // fallback. not threadsafe inline std::tm localtime_s(const std::time_t* src) { const auto result = std::localtime(src); if (!result) { throw std::runtime_error("localtime failed."); } return *result; } +inline std::tm gmtime_s(const std::time_t* src) +{ + const auto result = std::gmtime(src); + if (!result) { throw std::runtime_error("gmtime failed."); } + return *result; +} #endif } // detail From 62c01f9826a496cc9691c996c83897b96c274775 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Fri, 6 Dec 2019 20:57:51 +0900 Subject: [PATCH 04/11] fix: consider timezone correctly explicitly set tm.tm_isdst = 0 and use UTC offset --- toml/datetime.hpp | 63 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index ba241b1..4be9f57 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -470,37 +470,64 @@ struct offset_datetime : date(ld.date), time(ld.time), offset(get_local_offset()) {} explicit offset_datetime(const std::chrono::system_clock::time_point& tp) - : offset_datetime(local_datetime(tp)) - {} + : offset(0, 0) // use gmtime + { + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); + } explicit offset_datetime(const std::time_t& t) - : offset_datetime(local_datetime(t)) - {} + : offset(0, 0) // use gmtime + { + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); + } explicit offset_datetime(const std::tm& t) - : offset_datetime(local_datetime(t)) - {} + : offset(0, 0) // assume gmtime + { + this->date = local_date(t); + this->time = local_time(t); + } operator std::chrono::system_clock::time_point() const { // get date-time using internal_duration = typename std::chrono::system_clock::time_point::duration; - std::chrono::system_clock::time_point tp = - std::chrono::system_clock::time_point(this->date) + - std::chrono::duration_cast( - std::chrono::nanoseconds(this->time)); - // get date-time in UTC. let's say we are in +09:00 (JPN). - // writing 12:00:00 in +09:00 means 03:00:00Z. to represent - // 12:00:00Z, first we need to add +09:00. + // std::mktime returns date as **local** time zone. + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = 0; // do not consider DST; explicitly turn it off. + // all the offset info, including DST, should be in the offset part. + + std::chrono::system_clock::time_point tp = + std::chrono::system_clock::from_time_t(std::mktime(&t)) + + std::chrono::duration_cast( + std::chrono::nanoseconds(this->time)); + + // get date-time in UTC. + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. const auto ofs = get_local_offset(); tp += std::chrono::hours (ofs.hour); tp += std::chrono::minutes(ofs.minute); - // here, tp represents 12:00:00 in UTC but we have offset information. - // we need to subtract it. For example, let's say the input is - // 12:00:00-08:00. now we have tp = 12:00:00Z as a result of the above - // conversion. But the actual time we need to return is 20:00:00Z - // because of -08:00. + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. tp -= std::chrono::minutes(this->offset); return tp; } From 89714fb24b5f1ecc1756f555f8eed416dc7c2eb7 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Fri, 6 Dec 2019 21:15:31 +0900 Subject: [PATCH 05/11] doc: note about local timezone and datetime --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56c3f3f..f2b0d2d 100644 --- a/README.md +++ b/README.md @@ -699,6 +699,7 @@ date information, but it can be converted to `std::chrono::duration` that represents a duration from the beginning of the day, `00:00:00.000`. ```toml +# sample.toml date = 2018-12-23 time = 12:30:00 l_dt = 2018-12-23T12:30:00 @@ -715,8 +716,12 @@ const auto o_dt = toml::get(data.at("o_dt const auto time = toml::get(data.at("time")); // 12 * 60 + 30 min ``` -toml11 defines its own datetime classes. -You can see the definitions in [toml/datetime.hpp](toml/datetime.hpp). +`local_date` and `local_datetime` are assumed to be in the local timezone when +they are converted into `time_point`. On the other hand, `offset_datetime` only +uses the offset part of the data and it does not take local timezone into account. + +To contain datetime data, toml11 defines its own datetime types. +For more detail, you can see the definitions in [toml/datetime.hpp](toml/datetime.hpp). ## Getting with a fallback @@ -1447,8 +1452,6 @@ This feature is introduced to make it easy to write a custom serializer. Because `std::chrono::system_clock::time_point` is a __time point__, not capable of representing a Local Time independent from a specific day. -It is recommended to get `datetime`s as `std::chrono` classes through `toml::get`. - ## Unreleased TOML features There are some unreleased features in toml-lang/toml:master. From b246f5ac5c5928055f6b3dbc12421c6a26c59fd1 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 8 Dec 2019 22:38:49 +1100 Subject: [PATCH 06/11] fix: combine date and time to convert loc datetime Normally DST begins at A.M. 3 or 4. If we re-use conversion operator of local_date and local_time independently, the conversion fails if it is the day when DST begins or ends. Since local_date considers the time is 00:00 A.M. and local_time does not consider DST because it does not have any date information. We need to consider both date and time information at the same time to convert it correctly. --- toml/datetime.hpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index 4be9f57..6d609df 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -398,10 +398,31 @@ struct local_datetime { using internal_duration = typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + // std::mktime returns date as local time zone. no conversion needed - auto dt = std::chrono::system_clock::time_point(this->date); + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); dt += std::chrono::duration_cast( - std::chrono::nanoseconds(this->time)); + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); return dt; } From 331de4ea5d551f6979390158478825c110886f48 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Sun, 8 Dec 2019 22:44:12 +1100 Subject: [PATCH 07/11] fix: use datetime info while getting time offset to convert offset_datetime to system_clock::time_point. --- toml/datetime.hpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/toml/datetime.hpp b/toml/datetime.hpp index 6d609df..01b513c 100644 --- a/toml/datetime.hpp +++ b/toml/datetime.hpp @@ -488,7 +488,8 @@ struct offset_datetime : date(dt.date), time(dt.time), offset(o) {} explicit offset_datetime(const local_datetime& ld) - : date(ld.date), time(ld.time), offset(get_local_offset()) + : date(ld.date), time(ld.time), offset(get_local_offset(nullptr)) + // use the current local timezone offset {} explicit offset_datetime(const std::chrono::system_clock::time_point& tp) : offset(0, 0) // use gmtime @@ -518,30 +519,33 @@ struct offset_datetime using internal_duration = typename std::chrono::system_clock::time_point::duration; - // std::mktime returns date as **local** time zone. + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. std::tm t; - t.tm_sec = 0; - t.tm_min = 0; - t.tm_hour = 0; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); t.tm_mday = static_cast(this->date.day); t.tm_mon = static_cast(this->date.month); t.tm_year = static_cast(this->date.year) - 1900; t.tm_wday = 0; // the value will be ignored t.tm_yday = 0; // the value will be ignored - t.tm_isdst = 0; // do not consider DST; explicitly turn it off. - // all the offset info, including DST, should be in the offset part. + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); - std::chrono::system_clock::time_point tp = - std::chrono::system_clock::from_time_t(std::mktime(&t)) + - std::chrono::duration_cast( - std::chrono::nanoseconds(this->time)); + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); - // get date-time in UTC. // Since mktime uses local time zone, it should be corrected. // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need // to add `+09:00` to `03:00:00Z`. - const auto ofs = get_local_offset(); + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); tp += std::chrono::hours (ofs.hour); tp += std::chrono::minutes(ofs.minute); @@ -568,11 +572,10 @@ struct offset_datetime private: - static time_offset get_local_offset() + static time_offset get_local_offset(const std::time_t* tp) { - // get current timezone - const auto tmp1 = std::time(nullptr); - const auto t = detail::localtime_s(&tmp1); + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); std::array buf; const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 From 8fbeaabfd9cb2d64c66f8c1ec79b2837f6575c41 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 10 Dec 2019 00:00:05 +0900 Subject: [PATCH 08/11] feat: add operator[] to access table/array --- toml/value.hpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/toml/value.hpp b/toml/value.hpp index e2f5cf8..1b492a4 100644 --- a/toml/value.hpp +++ b/toml/value.hpp @@ -1579,6 +1579,14 @@ class basic_value { return this->as_table().at(k); } + value_type& operator[](const key& k) + { + if(this->is_uninitialized()) + { + *this = table_type{}; + } + return this->as_table()[k]; + } value_type& at(const std::size_t idx) { @@ -1589,6 +1597,15 @@ class basic_value return this->as_array().at(idx); } + value_type& operator[](const std::size_t idx) noexcept + { + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + return this->as_array(std::nothrow)[idx]; + } + source_location location() const { return source_location(this->region_info_.get()); From 0c084b3a5cfe352ffbbe14e6ca64bc34647a5ba6 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 10 Dec 2019 00:08:40 +0900 Subject: [PATCH 09/11] test: add test: accessing via bracket operator --- tests/test_value.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_value.cpp b/tests/test_value.cpp index 8c8c70c..c2f02db 100644 --- a/tests/test_value.cpp +++ b/tests/test_value.cpp @@ -934,3 +934,35 @@ BOOST_AUTO_TEST_CASE(test_value_at) BOOST_CHECK_THROW(v1.at(5), std::out_of_range); } } + +BOOST_AUTO_TEST_CASE(test_value_bracket) +{ + { + toml::value v1{{"foo", 42}, {"bar", 3.14}, {"baz", "qux"}}; + + BOOST_TEST(v1["foo"].as_integer() == 42); + BOOST_TEST(v1["bar"].as_floating() == 3.14); + BOOST_TEST(v1["baz"].as_string() == "qux"); + + v1["qux"] = 54; + BOOST_TEST(v1["qux"].as_integer() == 54); + } + { + toml::value v1; + v1["foo"] = 42; + + BOOST_TEST(v1.is_table()); + BOOST_TEST(v1["foo"].as_integer() == 42); + } + { + toml::value v1{1,2,3,4,5}; + + BOOST_TEST(v1[0].as_integer() == 1); + BOOST_TEST(v1[1].as_integer() == 2); + BOOST_TEST(v1[2].as_integer() == 3); + BOOST_TEST(v1[3].as_integer() == 4); + BOOST_TEST(v1[4].as_integer() == 5); + + BOOST_CHECK_THROW(v1["foo"], toml::type_error); + } +} From a41dc08025370c5a50b26d037574fa9d1b58b435 Mon Sep 17 00:00:00 2001 From: ToruNiina Date: Tue, 10 Dec 2019 20:06:01 +0900 Subject: [PATCH 10/11] doc: add document of operator[] --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index f2b0d2d..a198c92 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,40 @@ Note that, although `std::string` has `at()` member function, `toml::value::at` throws if the contained type is a string. Because `std::string` does not contain `toml::value`. +### `operator[]` + +You can also access to the element of a table and an array by +`toml::basic_value::operator[]`. + +```cpp +const toml::value v{1,2,3,4,5}; +std::cout << v[2].as_integer() << std::endl; // 3 + +const toml::value v{{"foo", 42}, {"bar", 3.14}}; +std::cout << v["foo"].as_integer() << std::endl; // 42 +``` + +When you access to a `toml::value` that is not initialized yet via +`operator[](const std::string&)`, the `toml::value` will be a table, +just like the `std::map`. + +```cpp +toml::value v; // not initialized as a table. +v["foo"] = 42; // OK. `v` will be a table. +``` + +Contrary, if you access to a `toml::value` that contains an array via `operator[]`, +it does not check anything. It converts `toml::value` without type check and then +access to the n-th element without boundary check, just like the `std::vector::operator[]`. + +```cpp +toml::value v; // not initialized as an array +v[2] = 42; // error! UB +``` + +Please make sure that the `toml::value` has an array inside when you access to +its element via `operator[]`. + ## Checking value type You can check the type of a value by `is_xxx` function. From 3190c1da9fdfca865701f3b78c367836189afeb9 Mon Sep 17 00:00:00 2001 From: OGAWA KenIchi Date: Wed, 11 Dec 2019 17:47:16 +0900 Subject: [PATCH 11/11] fix: suppress warning on Intel C++ Compiler --- toml/traits.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toml/traits.hpp b/toml/traits.hpp index c0c5ee9..ec05f08 100644 --- a/toml/traits.hpp +++ b/toml/traits.hpp @@ -105,7 +105,7 @@ struct has_into_toml_method : decltype(has_into_toml_method_impl::check(nullptr)){}; #ifdef __INTEL_COMPILER -#undef decltype(...) +#undef decltype #endif // ---------------------------------------------------------------------------