diff --git a/README.md b/README.md index 9486428..9bf0836 100644 --- a/README.md +++ b/README.md @@ -478,6 +478,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. @@ -700,6 +734,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 @@ -716,8 +751,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 @@ -1456,8 +1495,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. 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); + } +} 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()); } diff --git a/toml/datetime.hpp b/toml/datetime.hpp index dc52396..01b513c 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; @@ -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 @@ -378,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; } @@ -447,40 +488,71 @@ 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_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. - const auto ofs = get_local_offset(); + // 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 = 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; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + 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)); + + // 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`. + // 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); - // 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; } @@ -500,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 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 // --------------------------------------------------------------------------- diff --git a/toml/value.hpp b/toml/value.hpp index f0fc5ca..a9d2ba4 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());