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