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