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