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