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