xref: /openbmc/bmcweb/features/redfish/include/utils/time_utils.hpp (revision 4dbb8aea7651dc61a4dc384625567b34393742a2)
1081ebf06SWludzik, Jozef #pragma once
2081ebf06SWludzik, Jozef 
3*4dbb8aeaSWludzik, Jozef #include "logging.hpp"
4*4dbb8aeaSWludzik, Jozef 
5*4dbb8aeaSWludzik, Jozef #include <charconv>
6081ebf06SWludzik, Jozef #include <chrono>
7*4dbb8aeaSWludzik, Jozef #include <cmath>
8*4dbb8aeaSWludzik, Jozef #include <optional>
9081ebf06SWludzik, Jozef #include <string>
10*4dbb8aeaSWludzik, Jozef #include <system_error>
11081ebf06SWludzik, Jozef 
12081ebf06SWludzik, Jozef namespace redfish
13081ebf06SWludzik, Jozef {
14081ebf06SWludzik, Jozef 
15081ebf06SWludzik, Jozef namespace time_utils
16081ebf06SWludzik, Jozef {
17081ebf06SWludzik, Jozef 
18081ebf06SWludzik, Jozef namespace details
19081ebf06SWludzik, Jozef {
20081ebf06SWludzik, Jozef 
21*4dbb8aeaSWludzik, Jozef using Days = std::chrono::duration<long long, std::ratio<24 * 60 * 60>>;
22*4dbb8aeaSWludzik, Jozef 
23081ebf06SWludzik, Jozef inline void leftZeroPadding(std::string& str, const std::size_t padding)
24081ebf06SWludzik, Jozef {
25081ebf06SWludzik, Jozef     if (str.size() < padding)
26081ebf06SWludzik, Jozef     {
27081ebf06SWludzik, Jozef         str.insert(0, padding - str.size(), '0');
28081ebf06SWludzik, Jozef     }
29081ebf06SWludzik, Jozef }
30*4dbb8aeaSWludzik, Jozef 
31*4dbb8aeaSWludzik, Jozef template <typename FromTime>
32*4dbb8aeaSWludzik, Jozef bool fromDurationItem(std::string_view& fmt, const char postfix,
33*4dbb8aeaSWludzik, Jozef                       std::chrono::milliseconds& out)
34*4dbb8aeaSWludzik, Jozef {
35*4dbb8aeaSWludzik, Jozef     const size_t pos = fmt.find(postfix);
36*4dbb8aeaSWludzik, Jozef     if (pos == std::string::npos)
37*4dbb8aeaSWludzik, Jozef     {
38*4dbb8aeaSWludzik, Jozef         return true;
39*4dbb8aeaSWludzik, Jozef     }
40*4dbb8aeaSWludzik, Jozef     if ((pos + 1U) > fmt.size())
41*4dbb8aeaSWludzik, Jozef     {
42*4dbb8aeaSWludzik, Jozef         return false;
43*4dbb8aeaSWludzik, Jozef     }
44*4dbb8aeaSWludzik, Jozef 
45*4dbb8aeaSWludzik, Jozef     const char* end;
46*4dbb8aeaSWludzik, Jozef     std::chrono::milliseconds::rep ticks = 0;
47*4dbb8aeaSWludzik, Jozef     if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
48*4dbb8aeaSWludzik, Jozef     {
49*4dbb8aeaSWludzik, Jozef         end = fmt.data() + std::min<size_t>(pos, 3U);
50*4dbb8aeaSWludzik, Jozef     }
51*4dbb8aeaSWludzik, Jozef     else
52*4dbb8aeaSWludzik, Jozef     {
53*4dbb8aeaSWludzik, Jozef         end = fmt.data() + pos;
54*4dbb8aeaSWludzik, Jozef     }
55*4dbb8aeaSWludzik, Jozef 
56*4dbb8aeaSWludzik, Jozef     auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks);
57*4dbb8aeaSWludzik, Jozef     if (ptr != end || ec != std::errc())
58*4dbb8aeaSWludzik, Jozef     {
59*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: "
60*4dbb8aeaSWludzik, Jozef                          << static_cast<int>(ec) << "("
61*4dbb8aeaSWludzik, Jozef                          << std::make_error_code(ec).message() << "), ptr{"
62*4dbb8aeaSWludzik, Jozef                          << static_cast<const void*>(ptr) << "} != end{"
63*4dbb8aeaSWludzik, Jozef                          << static_cast<const void*>(end) << "})";
64*4dbb8aeaSWludzik, Jozef         return false;
65*4dbb8aeaSWludzik, Jozef     }
66*4dbb8aeaSWludzik, Jozef 
67*4dbb8aeaSWludzik, Jozef     if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
68*4dbb8aeaSWludzik, Jozef     {
69*4dbb8aeaSWludzik, Jozef         ticks *= static_cast<std::chrono::milliseconds::rep>(
70*4dbb8aeaSWludzik, Jozef             std::pow(10, 3 - std::min<size_t>(pos, 3U)));
71*4dbb8aeaSWludzik, Jozef     }
72*4dbb8aeaSWludzik, Jozef     if (ticks < 0)
73*4dbb8aeaSWludzik, Jozef     {
74*4dbb8aeaSWludzik, Jozef         return false;
75*4dbb8aeaSWludzik, Jozef     }
76*4dbb8aeaSWludzik, Jozef 
77*4dbb8aeaSWludzik, Jozef     out += FromTime(ticks);
78*4dbb8aeaSWludzik, Jozef     const auto maxConversionRange =
79*4dbb8aeaSWludzik, Jozef         std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max())
80*4dbb8aeaSWludzik, Jozef             .count();
81*4dbb8aeaSWludzik, Jozef     if (out < FromTime(ticks) || maxConversionRange < ticks)
82*4dbb8aeaSWludzik, Jozef     {
83*4dbb8aeaSWludzik, Jozef         return false;
84*4dbb8aeaSWludzik, Jozef     }
85*4dbb8aeaSWludzik, Jozef 
86*4dbb8aeaSWludzik, Jozef     fmt.remove_prefix(pos + 1U);
87*4dbb8aeaSWludzik, Jozef     return true;
88*4dbb8aeaSWludzik, Jozef }
89081ebf06SWludzik, Jozef } // namespace details
90081ebf06SWludzik, Jozef 
91081ebf06SWludzik, Jozef /**
92*4dbb8aeaSWludzik, Jozef  * @brief Convert string that represents value in Duration Format to its numeric
93*4dbb8aeaSWludzik, Jozef  *        equivalent.
94*4dbb8aeaSWludzik, Jozef  */
95*4dbb8aeaSWludzik, Jozef std::optional<std::chrono::milliseconds>
96*4dbb8aeaSWludzik, Jozef     fromDurationString(const std::string& str)
97*4dbb8aeaSWludzik, Jozef {
98*4dbb8aeaSWludzik, Jozef     std::chrono::milliseconds out = std::chrono::milliseconds::zero();
99*4dbb8aeaSWludzik, Jozef     std::string_view v = str;
100*4dbb8aeaSWludzik, Jozef 
101*4dbb8aeaSWludzik, Jozef     if (v.empty())
102*4dbb8aeaSWludzik, Jozef     {
103*4dbb8aeaSWludzik, Jozef         return out;
104*4dbb8aeaSWludzik, Jozef     }
105*4dbb8aeaSWludzik, Jozef     if (v.front() != 'P')
106*4dbb8aeaSWludzik, Jozef     {
107*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
108*4dbb8aeaSWludzik, Jozef         return std::nullopt;
109*4dbb8aeaSWludzik, Jozef     }
110*4dbb8aeaSWludzik, Jozef 
111*4dbb8aeaSWludzik, Jozef     v.remove_prefix(1);
112*4dbb8aeaSWludzik, Jozef     if (!details::fromDurationItem<details::Days>(v, 'D', out))
113*4dbb8aeaSWludzik, Jozef     {
114*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
115*4dbb8aeaSWludzik, Jozef         return std::nullopt;
116*4dbb8aeaSWludzik, Jozef     }
117*4dbb8aeaSWludzik, Jozef 
118*4dbb8aeaSWludzik, Jozef     if (v.empty())
119*4dbb8aeaSWludzik, Jozef     {
120*4dbb8aeaSWludzik, Jozef         return out;
121*4dbb8aeaSWludzik, Jozef     }
122*4dbb8aeaSWludzik, Jozef     if (v.front() != 'T')
123*4dbb8aeaSWludzik, Jozef     {
124*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
125*4dbb8aeaSWludzik, Jozef         return std::nullopt;
126*4dbb8aeaSWludzik, Jozef     }
127*4dbb8aeaSWludzik, Jozef 
128*4dbb8aeaSWludzik, Jozef     v.remove_prefix(1);
129*4dbb8aeaSWludzik, Jozef     if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) ||
130*4dbb8aeaSWludzik, Jozef         !details::fromDurationItem<std::chrono::minutes>(v, 'M', out))
131*4dbb8aeaSWludzik, Jozef     {
132*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
133*4dbb8aeaSWludzik, Jozef         return std::nullopt;
134*4dbb8aeaSWludzik, Jozef     }
135*4dbb8aeaSWludzik, Jozef 
136*4dbb8aeaSWludzik, Jozef     if (v.find('.') != std::string::npos && v.find('S') != std::string::npos)
137*4dbb8aeaSWludzik, Jozef     {
138*4dbb8aeaSWludzik, Jozef         if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) ||
139*4dbb8aeaSWludzik, Jozef             !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out))
140*4dbb8aeaSWludzik, Jozef         {
141*4dbb8aeaSWludzik, Jozef             BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
142*4dbb8aeaSWludzik, Jozef             return std::nullopt;
143*4dbb8aeaSWludzik, Jozef         }
144*4dbb8aeaSWludzik, Jozef     }
145*4dbb8aeaSWludzik, Jozef     else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out))
146*4dbb8aeaSWludzik, Jozef     {
147*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
148*4dbb8aeaSWludzik, Jozef         return std::nullopt;
149*4dbb8aeaSWludzik, Jozef     }
150*4dbb8aeaSWludzik, Jozef 
151*4dbb8aeaSWludzik, Jozef     if (!v.empty())
152*4dbb8aeaSWludzik, Jozef     {
153*4dbb8aeaSWludzik, Jozef         BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
154*4dbb8aeaSWludzik, Jozef         return std::nullopt;
155*4dbb8aeaSWludzik, Jozef     }
156*4dbb8aeaSWludzik, Jozef     return out;
157*4dbb8aeaSWludzik, Jozef }
158*4dbb8aeaSWludzik, Jozef 
159*4dbb8aeaSWludzik, Jozef /**
160081ebf06SWludzik, Jozef  * @brief Convert time value into duration format that is based on ISO 8601.
161081ebf06SWludzik, Jozef  *        Example output: "P12DT1M5.5S"
162081ebf06SWludzik, Jozef  *        Ref: Redfish Specification, Section 9.4.4. Duration values
163081ebf06SWludzik, Jozef  */
164b00dcc27SEd Tanous inline std::string toDurationString(std::chrono::milliseconds ms)
165081ebf06SWludzik, Jozef {
166081ebf06SWludzik, Jozef     if (ms < std::chrono::milliseconds::zero())
167081ebf06SWludzik, Jozef     {
168081ebf06SWludzik, Jozef         return "";
169081ebf06SWludzik, Jozef     }
170081ebf06SWludzik, Jozef 
171081ebf06SWludzik, Jozef     std::string fmt;
172081ebf06SWludzik, Jozef     fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS"));
173081ebf06SWludzik, Jozef 
174*4dbb8aeaSWludzik, Jozef     details::Days days = std::chrono::floor<details::Days>(ms);
175081ebf06SWludzik, Jozef     ms -= days;
176081ebf06SWludzik, Jozef 
177081ebf06SWludzik, Jozef     std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
178081ebf06SWludzik, Jozef     ms -= hours;
179081ebf06SWludzik, Jozef 
180081ebf06SWludzik, Jozef     std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
181081ebf06SWludzik, Jozef     ms -= minutes;
182081ebf06SWludzik, Jozef 
183081ebf06SWludzik, Jozef     std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
184081ebf06SWludzik, Jozef     ms -= seconds;
185081ebf06SWludzik, Jozef 
186081ebf06SWludzik, Jozef     fmt = "P";
187081ebf06SWludzik, Jozef     if (days.count() > 0)
188081ebf06SWludzik, Jozef     {
189081ebf06SWludzik, Jozef         fmt += std::to_string(days.count()) + "D";
190081ebf06SWludzik, Jozef     }
191081ebf06SWludzik, Jozef     fmt += "T";
192081ebf06SWludzik, Jozef     if (hours.count() > 0)
193081ebf06SWludzik, Jozef     {
194081ebf06SWludzik, Jozef         fmt += std::to_string(hours.count()) + "H";
195081ebf06SWludzik, Jozef     }
196081ebf06SWludzik, Jozef     if (minutes.count() > 0)
197081ebf06SWludzik, Jozef     {
198081ebf06SWludzik, Jozef         fmt += std::to_string(minutes.count()) + "M";
199081ebf06SWludzik, Jozef     }
200081ebf06SWludzik, Jozef     if (seconds.count() != 0 || ms.count() != 0)
201081ebf06SWludzik, Jozef     {
202081ebf06SWludzik, Jozef         fmt += std::to_string(seconds.count()) + ".";
203081ebf06SWludzik, Jozef         std::string msStr = std::to_string(ms.count());
204081ebf06SWludzik, Jozef         details::leftZeroPadding(msStr, 3);
205081ebf06SWludzik, Jozef         fmt += msStr + "S";
206081ebf06SWludzik, Jozef     }
207081ebf06SWludzik, Jozef 
208081ebf06SWludzik, Jozef     return fmt;
209081ebf06SWludzik, Jozef }
210081ebf06SWludzik, Jozef 
211081ebf06SWludzik, Jozef } // namespace time_utils
212081ebf06SWludzik, Jozef } // namespace redfish
213