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