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