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 // Creates a string from an integer in the most efficient way possible without 34 // using std::locale. Adds an exact zero pad based on the pad input parameter. 35 // Does not handle negative numbers. 36 inline std::string padZeros(int64_t value, size_t pad) 37 { 38 std::string result(pad, '0'); 39 for (int64_t val = value; pad > 0; pad--) 40 { 41 result[pad - 1] = static_cast<char>('0' + val % 10); 42 val /= 10; 43 } 44 return result; 45 } 46 47 template <typename FromTime> 48 bool fromDurationItem(std::string_view& fmt, const char postfix, 49 std::chrono::milliseconds& out) 50 { 51 const size_t pos = fmt.find(postfix); 52 if (pos == std::string::npos) 53 { 54 return true; 55 } 56 if ((pos + 1U) > fmt.size()) 57 { 58 return false; 59 } 60 61 const char* end = nullptr; 62 std::chrono::milliseconds::rep ticks = 0; 63 if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) 64 { 65 end = fmt.data() + std::min<size_t>(pos, 3U); 66 } 67 else 68 { 69 end = fmt.data() + pos; 70 } 71 72 auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks); 73 if (ptr != end || ec != std::errc()) 74 { 75 BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: " 76 << static_cast<int>(ec) << "(" 77 << std::make_error_code(ec).message() << "), ptr{" 78 << static_cast<const void*>(ptr) << "} != end{" 79 << static_cast<const void*>(end) << "})"; 80 return false; 81 } 82 83 if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>) 84 { 85 ticks *= static_cast<std::chrono::milliseconds::rep>( 86 std::pow(10, 3 - std::min<size_t>(pos, 3U))); 87 } 88 if (ticks < 0) 89 { 90 return false; 91 } 92 93 out += FromTime(ticks); 94 const auto maxConversionRange = 95 std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max()) 96 .count(); 97 if (out < FromTime(ticks) || maxConversionRange < ticks) 98 { 99 return false; 100 } 101 102 fmt.remove_prefix(pos + 1U); 103 return true; 104 } 105 } // namespace details 106 107 /** 108 * @brief Convert string that represents value in Duration Format to its numeric 109 * equivalent. 110 */ 111 inline std::optional<std::chrono::milliseconds> 112 fromDurationString(const std::string& str) 113 { 114 std::chrono::milliseconds out = std::chrono::milliseconds::zero(); 115 std::string_view v = str; 116 117 if (v.empty()) 118 { 119 return out; 120 } 121 if (v.front() != 'P') 122 { 123 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 124 return std::nullopt; 125 } 126 127 v.remove_prefix(1); 128 if (!details::fromDurationItem<details::Days>(v, 'D', out)) 129 { 130 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 131 return std::nullopt; 132 } 133 134 if (v.empty()) 135 { 136 return out; 137 } 138 if (v.front() != 'T') 139 { 140 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 141 return std::nullopt; 142 } 143 144 v.remove_prefix(1); 145 if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) || 146 !details::fromDurationItem<std::chrono::minutes>(v, 'M', out)) 147 { 148 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 149 return std::nullopt; 150 } 151 152 if (v.find('.') != std::string::npos && v.find('S') != std::string::npos) 153 { 154 if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) || 155 !details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out)) 156 { 157 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 158 return std::nullopt; 159 } 160 } 161 else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out)) 162 { 163 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 164 return std::nullopt; 165 } 166 167 if (!v.empty()) 168 { 169 BMCWEB_LOG_ERROR << "Invalid duration format: " << str; 170 return std::nullopt; 171 } 172 return out; 173 } 174 175 /** 176 * @brief Convert time value into duration format that is based on ISO 8601. 177 * Example output: "P12DT1M5.5S" 178 * Ref: Redfish Specification, Section 9.4.4. Duration values 179 */ 180 inline std::string toDurationString(std::chrono::milliseconds ms) 181 { 182 if (ms < std::chrono::milliseconds::zero()) 183 { 184 return ""; 185 } 186 187 std::string fmt; 188 fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS")); 189 190 details::Days days = std::chrono::floor<details::Days>(ms); 191 ms -= days; 192 193 std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); 194 ms -= hours; 195 196 std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms); 197 ms -= minutes; 198 199 std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms); 200 ms -= seconds; 201 202 fmt = "P"; 203 if (days.count() > 0) 204 { 205 fmt += std::to_string(days.count()) + "D"; 206 } 207 fmt += "T"; 208 if (hours.count() > 0) 209 { 210 fmt += std::to_string(hours.count()) + "H"; 211 } 212 if (minutes.count() > 0) 213 { 214 fmt += std::to_string(minutes.count()) + "M"; 215 } 216 if (seconds.count() != 0 || ms.count() != 0) 217 { 218 fmt += std::to_string(seconds.count()) + "."; 219 fmt += details::padZeros(ms.count(), 3); 220 fmt += "S"; 221 } 222 223 return fmt; 224 } 225 226 inline std::optional<std::string> 227 toDurationStringFromUint(const uint64_t timeMs) 228 { 229 static const uint64_t maxTimeMs = 230 static_cast<uint64_t>(std::chrono::milliseconds::max().count()); 231 232 if (maxTimeMs < timeMs) 233 { 234 return std::nullopt; 235 } 236 237 std::string duration = toDurationString(std::chrono::milliseconds(timeMs)); 238 if (duration.empty()) 239 { 240 return std::nullopt; 241 } 242 243 return std::make_optional(duration); 244 } 245 246 namespace details 247 { 248 // Returns year/month/day triple in civil calendar 249 // Preconditions: z is number of days since 1970-01-01 and is in the range: 250 // [numeric_limits<Int>::min(), 251 // numeric_limits<Int>::max()-719468]. 252 // Algorithm sourced from 253 // https://howardhinnant.github.io/date_algorithms.html#civil_from_days 254 // All constants are explained in the above 255 template <class IntType> 256 constexpr std::tuple<IntType, unsigned, unsigned> 257 civilFromDays(IntType z) noexcept 258 { 259 z += 719468; 260 IntType era = (z >= 0 ? z : z - 146096) / 146097; 261 unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096] 262 unsigned yoe = 263 (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] 264 IntType y = static_cast<IntType>(yoe) + era * 400; 265 unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] 266 unsigned mp = (5 * doy + 2) / 153; // [0, 11] 267 unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] 268 unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] 269 270 return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d); 271 } 272 273 template <typename IntType, typename Period> 274 std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t) 275 { 276 using seconds = std::chrono::duration<int>; 277 using minutes = std::chrono::duration<int, std::ratio<60>>; 278 using hours = std::chrono::duration<int, std::ratio<3600>>; 279 using days = std::chrono::duration< 280 IntType, std::ratio_multiply<hours::period, std::ratio<24>>>; 281 282 // d is days since 1970-01-01 283 days d = std::chrono::duration_cast<days>(t); 284 285 // t is now time duration since midnight of day d 286 t -= d; 287 288 // break d down into year/month/day 289 int year = 0; 290 int month = 0; 291 int day = 0; 292 std::tie(year, month, day) = details::civilFromDays(d.count()); 293 // Check against limits. Can't go above year 9999, and can't go below epoch 294 // (1970) 295 if (year >= 10000) 296 { 297 year = 9999; 298 month = 12; 299 day = 31; 300 t = days(1) - std::chrono::duration<IntType, Period>(1); 301 } 302 else if (year < 1970) 303 { 304 year = 1970; 305 month = 1; 306 day = 1; 307 t = std::chrono::duration<IntType, Period>::zero(); 308 } 309 310 std::string out; 311 out += details::padZeros(year, 4); 312 out += '-'; 313 out += details::padZeros(month, 2); 314 out += '-'; 315 out += details::padZeros(day, 2); 316 out += 'T'; 317 hours hr = duration_cast<hours>(t); 318 out += details::padZeros(hr.count(), 2); 319 t -= hr; 320 out += ':'; 321 322 minutes mt = duration_cast<minutes>(t); 323 out += details::padZeros(mt.count(), 2); 324 t -= mt; 325 out += ':'; 326 327 seconds se = duration_cast<seconds>(t); 328 out += details::padZeros(se.count(), 2); 329 t -= se; 330 331 if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>) 332 { 333 out += '.'; 334 using MilliDuration = std::chrono::duration<int, std::milli>; 335 MilliDuration subsec = duration_cast<MilliDuration>(t); 336 out += details::padZeros(subsec.count(), 3); 337 } 338 else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>) 339 { 340 out += '.'; 341 342 using MicroDuration = std::chrono::duration<int, std::micro>; 343 MicroDuration subsec = duration_cast<MicroDuration>(t); 344 out += details::padZeros(subsec.count(), 6); 345 } 346 347 out += "+00:00"; 348 return out; 349 } 350 } // namespace details 351 352 // Returns the formatted date time string. 353 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 354 // the given |secondsSinceEpoch| is too large, we return the maximum supported 355 // date. 356 inline std::string getDateTimeUint(uint64_t secondsSinceEpoch) 357 { 358 using DurationType = std::chrono::duration<uint64_t>; 359 DurationType sinceEpoch(secondsSinceEpoch); 360 return details::toISO8061ExtendedStr(sinceEpoch); 361 } 362 363 // Returns the formatted date time string with millisecond precision 364 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 365 // the given |secondsSinceEpoch| is too large, we return the maximum supported 366 // date. 367 inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch) 368 { 369 using DurationType = std::chrono::duration<uint64_t, std::milli>; 370 DurationType sinceEpoch(milliSecondsSinceEpoch); 371 return details::toISO8061ExtendedStr(sinceEpoch); 372 } 373 374 // Returns the formatted date time string with microsecond precision 375 inline std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch) 376 { 377 using DurationType = std::chrono::duration<uint64_t, std::micro>; 378 DurationType sinceEpoch(microSecondsSinceEpoch); 379 return details::toISO8061ExtendedStr(sinceEpoch); 380 } 381 382 inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch) 383 { 384 using DurationType = std::chrono::duration<std::time_t>; 385 DurationType sinceEpoch(secondsSinceEpoch); 386 return details::toISO8061ExtendedStr(sinceEpoch); 387 } 388 389 /** 390 * Returns the current Date, Time & the local Time Offset 391 * infromation in a pair 392 * 393 * @param[in] None 394 * 395 * @return std::pair<std::string, std::string>, which consist 396 * of current DateTime & the TimeOffset strings respectively. 397 */ 398 inline std::pair<std::string, std::string> getDateTimeOffsetNow() 399 { 400 std::time_t time = std::time(nullptr); 401 std::string dateTime = getDateTimeStdtime(time); 402 403 /* extract the local Time Offset value from the 404 * recevied dateTime string. 405 */ 406 std::string timeOffset("Z00:00"); 407 std::size_t lastPos = dateTime.size(); 408 std::size_t len = timeOffset.size(); 409 if (lastPos > len) 410 { 411 timeOffset = dateTime.substr(lastPos - len); 412 } 413 414 return std::make_pair(dateTime, timeOffset); 415 } 416 417 } // namespace time_utils 418 } // namespace redfish 419