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 details 242 { 243 // Returns year/month/day triple in civil calendar 244 // Preconditions: z is number of days since 1970-01-01 and is in the range: 245 // [numeric_limits<Int>::min(), 246 // numeric_limits<Int>::max()-719468]. 247 // Algorithm sourced from 248 // https://howardhinnant.github.io/date_algorithms.html#civil_from_days 249 // All constants are explained in the above 250 template <class IntType> 251 constexpr std::tuple<IntType, unsigned, unsigned> 252 civilFromDays(IntType z) noexcept 253 { 254 z += 719468; 255 IntType era = (z >= 0 ? z : z - 146096) / 146097; 256 unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096] 257 unsigned yoe = 258 (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] 259 IntType y = static_cast<IntType>(yoe) + era * 400; 260 unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] 261 unsigned mp = (5 * doy + 2) / 153; // [0, 11] 262 unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] 263 unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] 264 265 return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d); 266 } 267 268 // Creates a string from an integer in the most efficient way possible without 269 // using std::locale. Adds an exact zero pad based on the pad input parameter. 270 // Does nt handle negative numbers. 271 inline std::string padZeros(int value, size_t pad) 272 { 273 std::string result(pad, '0'); 274 for (int val = value; pad > 0; pad--) 275 { 276 result[pad - 1] = static_cast<char>('0' + val % 10); 277 val /= 10; 278 } 279 return result; 280 } 281 282 template <typename IntType, typename Period> 283 std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t) 284 { 285 using seconds = std::chrono::duration<int>; 286 using minutes = std::chrono::duration<int, std::ratio<60>>; 287 using hours = std::chrono::duration<int, std::ratio<3600>>; 288 using days = std::chrono::duration< 289 IntType, std::ratio_multiply<hours::period, std::ratio<24>>>; 290 291 // d is days since 1970-01-01 292 days d = std::chrono::duration_cast<days>(t); 293 294 // t is now time duration since midnight of day d 295 t -= d; 296 297 // break d down into year/month/day 298 int year = 0; 299 int month = 0; 300 int day = 0; 301 std::tie(year, month, day) = details::civilFromDays(d.count()); 302 // Check against limits. Can't go above year 9999, and can't go below epoch 303 // (1970) 304 if (year >= 10000) 305 { 306 year = 9999; 307 month = 12; 308 day = 31; 309 t = days(1) - std::chrono::duration<IntType, Period>(1); 310 } 311 else if (year < 1970) 312 { 313 year = 1970; 314 month = 1; 315 day = 1; 316 t = std::chrono::duration<IntType, Period>::zero(); 317 } 318 319 std::string out; 320 out += details::padZeros(year, 4); 321 out += '-'; 322 out += details::padZeros(month, 2); 323 out += '-'; 324 out += details::padZeros(day, 2); 325 out += 'T'; 326 hours hr = duration_cast<hours>(t); 327 out += details::padZeros(hr.count(), 2); 328 t -= hr; 329 out += ':'; 330 331 minutes mt = duration_cast<minutes>(t); 332 out += details::padZeros(mt.count(), 2); 333 t -= mt; 334 out += ':'; 335 336 seconds se = duration_cast<seconds>(t); 337 out += details::padZeros(se.count(), 2); 338 t -= se; 339 340 if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>) 341 { 342 out += '.'; 343 using MilliDuration = std::chrono::duration<int, std::milli>; 344 MilliDuration subsec = duration_cast<MilliDuration>(t); 345 out += details::padZeros(subsec.count(), 3); 346 } 347 else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>) 348 { 349 out += '.'; 350 351 using MicroDuration = std::chrono::duration<int, std::micro>; 352 MicroDuration subsec = duration_cast<MicroDuration>(t); 353 out += details::padZeros(subsec.count(), 6); 354 } 355 356 out += "+00:00"; 357 return out; 358 } 359 } // namespace details 360 361 // Returns the formatted date time string. 362 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 363 // the given |secondsSinceEpoch| is too large, we return the maximum supported 364 // date. 365 inline std::string getDateTimeUint(uint64_t secondsSinceEpoch) 366 { 367 using DurationType = std::chrono::duration<uint64_t>; 368 DurationType sinceEpoch(secondsSinceEpoch); 369 return details::toISO8061ExtendedStr(sinceEpoch); 370 } 371 372 // Returns the formatted date time string with millisecond precision 373 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 374 // the given |secondsSinceEpoch| is too large, we return the maximum supported 375 // date. 376 inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch) 377 { 378 using DurationType = std::chrono::duration<uint64_t, std::milli>; 379 DurationType sinceEpoch(milliSecondsSinceEpoch); 380 return details::toISO8061ExtendedStr(sinceEpoch); 381 } 382 383 // Returns the formatted date time string with microsecond precision 384 inline std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch) 385 { 386 using DurationType = std::chrono::duration<uint64_t, std::micro>; 387 DurationType sinceEpoch(microSecondsSinceEpoch); 388 return details::toISO8061ExtendedStr(sinceEpoch); 389 } 390 391 inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch) 392 { 393 using DurationType = std::chrono::duration<std::time_t>; 394 DurationType sinceEpoch(secondsSinceEpoch); 395 return details::toISO8061ExtendedStr(sinceEpoch); 396 } 397 398 /** 399 * Returns the current Date, Time & the local Time Offset 400 * infromation in a pair 401 * 402 * @param[in] None 403 * 404 * @return std::pair<std::string, std::string>, which consist 405 * of current DateTime & the TimeOffset strings respectively. 406 */ 407 inline std::pair<std::string, std::string> getDateTimeOffsetNow() 408 { 409 std::time_t time = std::time(nullptr); 410 std::string dateTime = getDateTimeStdtime(time); 411 412 /* extract the local Time Offset value from the 413 * recevied dateTime string. 414 */ 415 std::string timeOffset("Z00:00"); 416 std::size_t lastPos = dateTime.size(); 417 std::size_t len = timeOffset.size(); 418 if (lastPos > len) 419 { 420 timeOffset = dateTime.substr(lastPos - len); 421 } 422 423 return std::make_pair(dateTime, timeOffset); 424 } 425 426 } // namespace time_utils 427 } // namespace redfish 428