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