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