xref: /openbmc/bmcweb/features/redfish/include/utils/time_utils.hpp (revision 9ea15c35e10f4d9d3f61538b444e75efa15e426a)
1081ebf06SWludzik, Jozef #pragma once
2081ebf06SWludzik, Jozef 
34dbb8aeaSWludzik, Jozef #include "logging.hpp"
44dbb8aeaSWludzik, Jozef 
5*9ea15c35SEd Tanous #include <algorithm>
64dbb8aeaSWludzik, Jozef #include <charconv>
7081ebf06SWludzik, Jozef #include <chrono>
84dbb8aeaSWludzik, Jozef #include <cmath>
9*9ea15c35SEd Tanous #include <compare>
10*9ea15c35SEd Tanous #include <cstddef>
114dbb8aeaSWludzik, Jozef #include <optional>
12*9ea15c35SEd Tanous #include <ratio>
13081ebf06SWludzik, Jozef #include <string>
14*9ea15c35SEd Tanous #include <string_view>
154dbb8aeaSWludzik, Jozef #include <system_error>
16081ebf06SWludzik, Jozef 
17081ebf06SWludzik, Jozef namespace redfish
18081ebf06SWludzik, Jozef {
19081ebf06SWludzik, Jozef 
20081ebf06SWludzik, Jozef namespace time_utils
21081ebf06SWludzik, Jozef {
22081ebf06SWludzik, Jozef 
23081ebf06SWludzik, Jozef namespace details
24081ebf06SWludzik, Jozef {
25081ebf06SWludzik, Jozef 
266de264ccSEd Tanous constexpr intmax_t dayDuration = static_cast<intmax_t>(24 * 60 * 60);
276de264ccSEd Tanous using Days = std::chrono::duration<long long, std::ratio<dayDuration>>;
284dbb8aeaSWludzik, Jozef 
29081ebf06SWludzik, Jozef inline void leftZeroPadding(std::string& str, const std::size_t padding)
30081ebf06SWludzik, Jozef {
31081ebf06SWludzik, Jozef     if (str.size() < padding)
32081ebf06SWludzik, Jozef     {
33081ebf06SWludzik, Jozef         str.insert(0, padding - str.size(), '0');
34081ebf06SWludzik, Jozef     }
35081ebf06SWludzik, Jozef }
364dbb8aeaSWludzik, Jozef 
374dbb8aeaSWludzik, Jozef template <typename FromTime>
384dbb8aeaSWludzik, Jozef bool fromDurationItem(std::string_view& fmt, const char postfix,
394dbb8aeaSWludzik, Jozef                       std::chrono::milliseconds& out)
404dbb8aeaSWludzik, Jozef {
414dbb8aeaSWludzik, Jozef     const size_t pos = fmt.find(postfix);
424dbb8aeaSWludzik, Jozef     if (pos == std::string::npos)
434dbb8aeaSWludzik, Jozef     {
444dbb8aeaSWludzik, Jozef         return true;
454dbb8aeaSWludzik, Jozef     }
464dbb8aeaSWludzik, Jozef     if ((pos + 1U) > fmt.size())
474dbb8aeaSWludzik, Jozef     {
484dbb8aeaSWludzik, Jozef         return false;
494dbb8aeaSWludzik, Jozef     }
504dbb8aeaSWludzik, Jozef 
51543f4400SEd Tanous     const char* end = nullptr;
524dbb8aeaSWludzik, Jozef     std::chrono::milliseconds::rep ticks = 0;
534dbb8aeaSWludzik, Jozef     if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
544dbb8aeaSWludzik, Jozef     {
554dbb8aeaSWludzik, Jozef         end = fmt.data() + std::min<size_t>(pos, 3U);
564dbb8aeaSWludzik, Jozef     }
574dbb8aeaSWludzik, Jozef     else
584dbb8aeaSWludzik, Jozef     {
594dbb8aeaSWludzik, Jozef         end = fmt.data() + pos;
604dbb8aeaSWludzik, Jozef     }
614dbb8aeaSWludzik, Jozef 
624dbb8aeaSWludzik, Jozef     auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks);
634dbb8aeaSWludzik, Jozef     if (ptr != end || ec != std::errc())
644dbb8aeaSWludzik, Jozef     {
654dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: "
664dbb8aeaSWludzik, Jozef                          << static_cast<int>(ec) << "("
674dbb8aeaSWludzik, Jozef                          << std::make_error_code(ec).message() << "), ptr{"
684dbb8aeaSWludzik, Jozef                          << static_cast<const void*>(ptr) << "} != end{"
694dbb8aeaSWludzik, Jozef                          << static_cast<const void*>(end) << "})";
704dbb8aeaSWludzik, Jozef         return false;
714dbb8aeaSWludzik, Jozef     }
724dbb8aeaSWludzik, Jozef 
734dbb8aeaSWludzik, Jozef     if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
744dbb8aeaSWludzik, Jozef     {
754dbb8aeaSWludzik, Jozef         ticks *= static_cast<std::chrono::milliseconds::rep>(
764dbb8aeaSWludzik, Jozef             std::pow(10, 3 - std::min<size_t>(pos, 3U)));
774dbb8aeaSWludzik, Jozef     }
784dbb8aeaSWludzik, Jozef     if (ticks < 0)
794dbb8aeaSWludzik, Jozef     {
804dbb8aeaSWludzik, Jozef         return false;
814dbb8aeaSWludzik, Jozef     }
824dbb8aeaSWludzik, Jozef 
834dbb8aeaSWludzik, Jozef     out += FromTime(ticks);
844dbb8aeaSWludzik, Jozef     const auto maxConversionRange =
854dbb8aeaSWludzik, Jozef         std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max())
864dbb8aeaSWludzik, Jozef             .count();
874dbb8aeaSWludzik, Jozef     if (out < FromTime(ticks) || maxConversionRange < ticks)
884dbb8aeaSWludzik, Jozef     {
894dbb8aeaSWludzik, Jozef         return false;
904dbb8aeaSWludzik, Jozef     }
914dbb8aeaSWludzik, Jozef 
924dbb8aeaSWludzik, Jozef     fmt.remove_prefix(pos + 1U);
934dbb8aeaSWludzik, Jozef     return true;
944dbb8aeaSWludzik, Jozef }
95081ebf06SWludzik, Jozef } // namespace details
96081ebf06SWludzik, Jozef 
97081ebf06SWludzik, Jozef /**
984dbb8aeaSWludzik, Jozef  * @brief Convert string that represents value in Duration Format to its numeric
994dbb8aeaSWludzik, Jozef  *        equivalent.
1004dbb8aeaSWludzik, Jozef  */
1014f48d5f6SEd Tanous inline std::optional<std::chrono::milliseconds>
1024dbb8aeaSWludzik, Jozef     fromDurationString(const std::string& str)
1034dbb8aeaSWludzik, Jozef {
1044dbb8aeaSWludzik, Jozef     std::chrono::milliseconds out = std::chrono::milliseconds::zero();
1054dbb8aeaSWludzik, Jozef     std::string_view v = str;
1064dbb8aeaSWludzik, Jozef 
1074dbb8aeaSWludzik, Jozef     if (v.empty())
1084dbb8aeaSWludzik, Jozef     {
1094dbb8aeaSWludzik, Jozef         return out;
1104dbb8aeaSWludzik, Jozef     }
1114dbb8aeaSWludzik, Jozef     if (v.front() != 'P')
1124dbb8aeaSWludzik, Jozef     {
1134dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1144dbb8aeaSWludzik, Jozef         return std::nullopt;
1154dbb8aeaSWludzik, Jozef     }
1164dbb8aeaSWludzik, Jozef 
1174dbb8aeaSWludzik, Jozef     v.remove_prefix(1);
1184dbb8aeaSWludzik, Jozef     if (!details::fromDurationItem<details::Days>(v, 'D', out))
1194dbb8aeaSWludzik, Jozef     {
1204dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1214dbb8aeaSWludzik, Jozef         return std::nullopt;
1224dbb8aeaSWludzik, Jozef     }
1234dbb8aeaSWludzik, Jozef 
1244dbb8aeaSWludzik, Jozef     if (v.empty())
1254dbb8aeaSWludzik, Jozef     {
1264dbb8aeaSWludzik, Jozef         return out;
1274dbb8aeaSWludzik, Jozef     }
1284dbb8aeaSWludzik, Jozef     if (v.front() != 'T')
1294dbb8aeaSWludzik, Jozef     {
1304dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1314dbb8aeaSWludzik, Jozef         return std::nullopt;
1324dbb8aeaSWludzik, Jozef     }
1334dbb8aeaSWludzik, Jozef 
1344dbb8aeaSWludzik, Jozef     v.remove_prefix(1);
1354dbb8aeaSWludzik, Jozef     if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) ||
1364dbb8aeaSWludzik, Jozef         !details::fromDurationItem<std::chrono::minutes>(v, 'M', out))
1374dbb8aeaSWludzik, Jozef     {
1384dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1394dbb8aeaSWludzik, Jozef         return std::nullopt;
1404dbb8aeaSWludzik, Jozef     }
1414dbb8aeaSWludzik, Jozef 
1424dbb8aeaSWludzik, Jozef     if (v.find('.') != std::string::npos && v.find('S') != std::string::npos)
1434dbb8aeaSWludzik, Jozef     {
1444dbb8aeaSWludzik, Jozef         if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) ||
1454dbb8aeaSWludzik, Jozef             !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out))
1464dbb8aeaSWludzik, Jozef         {
1474dbb8aeaSWludzik, Jozef             BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1484dbb8aeaSWludzik, Jozef             return std::nullopt;
1494dbb8aeaSWludzik, Jozef         }
1504dbb8aeaSWludzik, Jozef     }
1514dbb8aeaSWludzik, Jozef     else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out))
1524dbb8aeaSWludzik, Jozef     {
1534dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1544dbb8aeaSWludzik, Jozef         return std::nullopt;
1554dbb8aeaSWludzik, Jozef     }
1564dbb8aeaSWludzik, Jozef 
1574dbb8aeaSWludzik, Jozef     if (!v.empty())
1584dbb8aeaSWludzik, Jozef     {
1594dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
1604dbb8aeaSWludzik, Jozef         return std::nullopt;
1614dbb8aeaSWludzik, Jozef     }
1624dbb8aeaSWludzik, Jozef     return out;
1634dbb8aeaSWludzik, Jozef }
1644dbb8aeaSWludzik, Jozef 
1654dbb8aeaSWludzik, Jozef /**
166081ebf06SWludzik, Jozef  * @brief Convert time value into duration format that is based on ISO 8601.
167081ebf06SWludzik, Jozef  *        Example output: "P12DT1M5.5S"
168081ebf06SWludzik, Jozef  *        Ref: Redfish Specification, Section 9.4.4. Duration values
169081ebf06SWludzik, Jozef  */
170b00dcc27SEd Tanous inline std::string toDurationString(std::chrono::milliseconds ms)
171081ebf06SWludzik, Jozef {
172081ebf06SWludzik, Jozef     if (ms < std::chrono::milliseconds::zero())
173081ebf06SWludzik, Jozef     {
174081ebf06SWludzik, Jozef         return "";
175081ebf06SWludzik, Jozef     }
176081ebf06SWludzik, Jozef 
177081ebf06SWludzik, Jozef     std::string fmt;
178081ebf06SWludzik, Jozef     fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS"));
179081ebf06SWludzik, Jozef 
1804dbb8aeaSWludzik, Jozef     details::Days days = std::chrono::floor<details::Days>(ms);
181081ebf06SWludzik, Jozef     ms -= days;
182081ebf06SWludzik, Jozef 
183081ebf06SWludzik, Jozef     std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
184081ebf06SWludzik, Jozef     ms -= hours;
185081ebf06SWludzik, Jozef 
186081ebf06SWludzik, Jozef     std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
187081ebf06SWludzik, Jozef     ms -= minutes;
188081ebf06SWludzik, Jozef 
189081ebf06SWludzik, Jozef     std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
190081ebf06SWludzik, Jozef     ms -= seconds;
191081ebf06SWludzik, Jozef 
192081ebf06SWludzik, Jozef     fmt = "P";
193081ebf06SWludzik, Jozef     if (days.count() > 0)
194081ebf06SWludzik, Jozef     {
195081ebf06SWludzik, Jozef         fmt += std::to_string(days.count()) + "D";
196081ebf06SWludzik, Jozef     }
197081ebf06SWludzik, Jozef     fmt += "T";
198081ebf06SWludzik, Jozef     if (hours.count() > 0)
199081ebf06SWludzik, Jozef     {
200081ebf06SWludzik, Jozef         fmt += std::to_string(hours.count()) + "H";
201081ebf06SWludzik, Jozef     }
202081ebf06SWludzik, Jozef     if (minutes.count() > 0)
203081ebf06SWludzik, Jozef     {
204081ebf06SWludzik, Jozef         fmt += std::to_string(minutes.count()) + "M";
205081ebf06SWludzik, Jozef     }
206081ebf06SWludzik, Jozef     if (seconds.count() != 0 || ms.count() != 0)
207081ebf06SWludzik, Jozef     {
208081ebf06SWludzik, Jozef         fmt += std::to_string(seconds.count()) + ".";
209081ebf06SWludzik, Jozef         std::string msStr = std::to_string(ms.count());
210081ebf06SWludzik, Jozef         details::leftZeroPadding(msStr, 3);
211081ebf06SWludzik, Jozef         fmt += msStr + "S";
212081ebf06SWludzik, Jozef     }
213081ebf06SWludzik, Jozef 
214081ebf06SWludzik, Jozef     return fmt;
215081ebf06SWludzik, Jozef }
216081ebf06SWludzik, Jozef 
2171b7e696bSLukasz Kazmierczak inline std::optional<std::string>
2181b7e696bSLukasz Kazmierczak     toDurationStringFromUint(const uint64_t timeMs)
2191b7e696bSLukasz Kazmierczak {
2201b7e696bSLukasz Kazmierczak     static const uint64_t maxTimeMs =
2211b7e696bSLukasz Kazmierczak         static_cast<uint64_t>(std::chrono::milliseconds::max().count());
2221b7e696bSLukasz Kazmierczak 
2231b7e696bSLukasz Kazmierczak     if (maxTimeMs < timeMs)
2241b7e696bSLukasz Kazmierczak     {
2251b7e696bSLukasz Kazmierczak         return std::nullopt;
2261b7e696bSLukasz Kazmierczak     }
2271b7e696bSLukasz Kazmierczak 
2281b7e696bSLukasz Kazmierczak     std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
2291b7e696bSLukasz Kazmierczak     if (duration.empty())
2301b7e696bSLukasz Kazmierczak     {
2311b7e696bSLukasz Kazmierczak         return std::nullopt;
2321b7e696bSLukasz Kazmierczak     }
2331b7e696bSLukasz Kazmierczak 
2341b7e696bSLukasz Kazmierczak     return std::make_optional(duration);
2351b7e696bSLukasz Kazmierczak }
2361b7e696bSLukasz Kazmierczak 
237081ebf06SWludzik, Jozef } // namespace time_utils
238081ebf06SWludzik, Jozef } // namespace redfish
239