1 #pragma once 2 3 #include "logging.hpp" 4 5 #include <charconv> 6 #include <chrono> 7 #include <cmath> 8 #include <optional> 9 #include <string> 10 #include <system_error> 11 12 namespace redfish 13 { 14 15 namespace time_utils 16 { 17 18 namespace details 19 { 20 21 constexpr intmax_t dayDuration = static_cast<intmax_t>(24 * 60 * 60); 22 using Days = std::chrono::duration<long long, std::ratio<dayDuration>>; 23 24 inline void leftZeroPadding(std::string& str, const std::size_t padding) 25 { 26 if (str.size() < padding) 27 { 28 str.insert(0, padding - str.size(), '0'); 29 } 30 } 31 32 template <typename FromTime> 33 bool fromDurationItem(std::string_view& fmt, const char postfix, 34 std::chrono::milliseconds& out) 35 { 36 const size_t pos = fmt.find(postfix); 37 if (pos == std::string::npos) 38 { 39 return true; 40 } 41 if ((pos + 1U) > fmt.size()) 42 { 43 return false; 44 } 45 46 const char* end; 47 std::chrono::milliseconds::rep ticks = 0; 48 if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) 49 { 50 end = fmt.data() + std::min<size_t>(pos, 3U); 51 } 52 else 53 { 54 end = fmt.data() + pos; 55 } 56 57 auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks); 58 if (ptr != end || ec != std::errc()) 59 { 60 BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " 61 << static_cast<int>(ec) << "(" 62 << std::make_error_code(ec).message() << "), ptr{" 63 << static_cast<const void*>(ptr) << "} != end{" 64 << static_cast<const void*>(end) << "})"; 65 return false; 66 } 67 68 if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) 69 { 70 ticks *= static_cast<std::chrono::milliseconds::rep>( 71 std::pow(10, 3 - std::min<size_t>(pos, 3U))); 72 } 73 if (ticks < 0) 74 { 75 return false; 76 } 77 78 out += FromTime(ticks); 79 const auto maxConversionRange = 80 std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max()) 81 .count(); 82 if (out < FromTime(ticks) || maxConversionRange < ticks) 83 { 84 return false; 85 } 86 87 fmt.remove_prefix(pos + 1U); 88 return true; 89 } 90 } // namespace details 91 92 /** 93 * @brief Convert string that represents value in Duration Format to its numeric 94 * equivalent. 95 */ 96 inline std::optional<std::chrono::milliseconds> 97 fromDurationString(const std::string& str) 98 { 99 std::chrono::milliseconds out = std::chrono::milliseconds::zero(); 100 std::string_view v = str; 101 102 if (v.empty()) 103 { 104 return out; 105 } 106 if (v.front() != 'P') 107 { 108 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 109 return std::nullopt; 110 } 111 112 v.remove_prefix(1); 113 if (!details::fromDurationItem<details::Days>(v, 'D', out)) 114 { 115 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 116 return std::nullopt; 117 } 118 119 if (v.empty()) 120 { 121 return out; 122 } 123 if (v.front() != 'T') 124 { 125 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 126 return std::nullopt; 127 } 128 129 v.remove_prefix(1); 130 if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) || 131 !details::fromDurationItem<std::chrono::minutes>(v, 'M', out)) 132 { 133 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 134 return std::nullopt; 135 } 136 137 if (v.find('.') != std::string::npos && v.find('S') != std::string::npos) 138 { 139 if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) || 140 !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out)) 141 { 142 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 143 return std::nullopt; 144 } 145 } 146 else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out)) 147 { 148 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 149 return std::nullopt; 150 } 151 152 if (!v.empty()) 153 { 154 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 155 return std::nullopt; 156 } 157 return out; 158 } 159 160 /** 161 * @brief Convert time value into duration format that is based on ISO 8601. 162 * Example output: "P12DT1M5.5S" 163 * Ref: Redfish Specification, Section 9.4.4. Duration values 164 */ 165 inline std::string toDurationString(std::chrono::milliseconds ms) 166 { 167 if (ms < std::chrono::milliseconds::zero()) 168 { 169 return ""; 170 } 171 172 std::string fmt; 173 fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); 174 175 details::Days days = std::chrono::floor<details::Days>(ms); 176 ms -= days; 177 178 std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); 179 ms -= hours; 180 181 std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms); 182 ms -= minutes; 183 184 std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms); 185 ms -= seconds; 186 187 fmt = "P"; 188 if (days.count() > 0) 189 { 190 fmt += std::to_string(days.count()) + "D"; 191 } 192 fmt += "T"; 193 if (hours.count() > 0) 194 { 195 fmt += std::to_string(hours.count()) + "H"; 196 } 197 if (minutes.count() > 0) 198 { 199 fmt += std::to_string(minutes.count()) + "M"; 200 } 201 if (seconds.count() != 0 || ms.count() != 0) 202 { 203 fmt += std::to_string(seconds.count()) + "."; 204 std::string msStr = std::to_string(ms.count()); 205 details::leftZeroPadding(msStr, 3); 206 fmt += msStr + "S"; 207 } 208 209 return fmt; 210 } 211 212 inline std::optional<std::string> 213 toDurationStringFromUint(const uint64_t timeMs) 214 { 215 static const uint64_t maxTimeMs = 216 static_cast<uint64_t>(std::chrono::milliseconds::max().count()); 217 218 if (maxTimeMs < timeMs) 219 { 220 return std::nullopt; 221 } 222 223 std::string duration = toDurationString(std::chrono::milliseconds(timeMs)); 224 if (duration.empty()) 225 { 226 return std::nullopt; 227 } 228 229 return std::make_optional(duration); 230 } 231 232 } // namespace time_utils 233 } // namespace redfish 234