140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 31b8b02a4SEd Tanous #include "utils/time_utils.hpp" 41b8b02a4SEd Tanous 5*a93e9c77SEd Tanous #include "error_messages.hpp" 6*a93e9c77SEd Tanous #include "http_response.hpp" 7*a93e9c77SEd Tanous #include "logging.hpp" 8*a93e9c77SEd Tanous 967b2e53bSEd Tanous #include <version> 1067b2e53bSEd Tanous 11a8770740SEd Tanous #if __cpp_lib_chrono < 201907L 121b8b02a4SEd Tanous #include "utils/extern/date.h" 13a8770740SEd Tanous #endif 141b8b02a4SEd Tanous #include <array> 15*a93e9c77SEd Tanous #include <charconv> 161b8b02a4SEd Tanous #include <chrono> 17*a93e9c77SEd Tanous #include <cstddef> 18*a93e9c77SEd Tanous #include <cstdint> 19*a93e9c77SEd Tanous #include <ctime> 20*a93e9c77SEd Tanous #include <format> 211b8b02a4SEd Tanous #include <optional> 22*a93e9c77SEd Tanous #include <ratio> 231b8b02a4SEd Tanous #include <sstream> 241b8b02a4SEd Tanous #include <string> 251b8b02a4SEd Tanous #include <string_view> 26*a93e9c77SEd Tanous #include <system_error> 27*a93e9c77SEd Tanous #include <utility> 281b8b02a4SEd Tanous 291b8b02a4SEd Tanous namespace redfish::time_utils 301b8b02a4SEd Tanous { 31c51afd54SEd Tanous 32*a93e9c77SEd Tanous /** 33*a93e9c77SEd Tanous * @brief Convert string that represents value in Duration Format to its numeric 34*a93e9c77SEd Tanous * equivalent. 35*a93e9c77SEd Tanous */ 36*a93e9c77SEd Tanous std::optional<std::chrono::milliseconds> fromDurationString(std::string_view v) 37*a93e9c77SEd Tanous { 38*a93e9c77SEd Tanous std::chrono::milliseconds out = std::chrono::milliseconds::zero(); 39*a93e9c77SEd Tanous enum class ProcessingStage 40*a93e9c77SEd Tanous { 41*a93e9c77SEd Tanous // P1DT1H1M1.100S 42*a93e9c77SEd Tanous P, 43*a93e9c77SEd Tanous Days, 44*a93e9c77SEd Tanous Hours, 45*a93e9c77SEd Tanous Minutes, 46*a93e9c77SEd Tanous Seconds, 47*a93e9c77SEd Tanous Milliseconds, 48*a93e9c77SEd Tanous Done, 49*a93e9c77SEd Tanous }; 50*a93e9c77SEd Tanous ProcessingStage stage = ProcessingStage::P; 51*a93e9c77SEd Tanous 52*a93e9c77SEd Tanous while (!v.empty()) 53*a93e9c77SEd Tanous { 54*a93e9c77SEd Tanous if (stage == ProcessingStage::P) 55*a93e9c77SEd Tanous { 56*a93e9c77SEd Tanous if (v.front() != 'P') 57*a93e9c77SEd Tanous { 58*a93e9c77SEd Tanous return std::nullopt; 59*a93e9c77SEd Tanous } 60*a93e9c77SEd Tanous v.remove_prefix(1); 61*a93e9c77SEd Tanous stage = ProcessingStage::Days; 62*a93e9c77SEd Tanous continue; 63*a93e9c77SEd Tanous } 64*a93e9c77SEd Tanous if (stage == ProcessingStage::Days) 65*a93e9c77SEd Tanous { 66*a93e9c77SEd Tanous if (v.front() == 'T') 67*a93e9c77SEd Tanous { 68*a93e9c77SEd Tanous v.remove_prefix(1); 69*a93e9c77SEd Tanous stage = ProcessingStage::Hours; 70*a93e9c77SEd Tanous continue; 71*a93e9c77SEd Tanous } 72*a93e9c77SEd Tanous } 73*a93e9c77SEd Tanous uint64_t ticks = 0; 74*a93e9c77SEd Tanous auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks); 75*a93e9c77SEd Tanous if (ec != std::errc()) 76*a93e9c77SEd Tanous { 77*a93e9c77SEd Tanous BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v); 78*a93e9c77SEd Tanous return std::nullopt; 79*a93e9c77SEd Tanous } 80*a93e9c77SEd Tanous size_t charactersRead = static_cast<size_t>(ptr - v.data()); 81*a93e9c77SEd Tanous if (ptr >= v.end()) 82*a93e9c77SEd Tanous { 83*a93e9c77SEd Tanous BMCWEB_LOG_ERROR("Missing postfix"); 84*a93e9c77SEd Tanous return std::nullopt; 85*a93e9c77SEd Tanous } 86*a93e9c77SEd Tanous if (*ptr == 'D') 87*a93e9c77SEd Tanous { 88*a93e9c77SEd Tanous if (stage > ProcessingStage::Days) 89*a93e9c77SEd Tanous { 90*a93e9c77SEd Tanous return std::nullopt; 91*a93e9c77SEd Tanous } 92*a93e9c77SEd Tanous out += std::chrono::days(ticks); 93*a93e9c77SEd Tanous } 94*a93e9c77SEd Tanous else if (*ptr == 'H') 95*a93e9c77SEd Tanous { 96*a93e9c77SEd Tanous if (stage > ProcessingStage::Hours) 97*a93e9c77SEd Tanous { 98*a93e9c77SEd Tanous return std::nullopt; 99*a93e9c77SEd Tanous } 100*a93e9c77SEd Tanous out += std::chrono::hours(ticks); 101*a93e9c77SEd Tanous } 102*a93e9c77SEd Tanous else if (*ptr == 'M') 103*a93e9c77SEd Tanous { 104*a93e9c77SEd Tanous if (stage > ProcessingStage::Minutes) 105*a93e9c77SEd Tanous { 106*a93e9c77SEd Tanous return std::nullopt; 107*a93e9c77SEd Tanous } 108*a93e9c77SEd Tanous out += std::chrono::minutes(ticks); 109*a93e9c77SEd Tanous } 110*a93e9c77SEd Tanous else if (*ptr == '.') 111*a93e9c77SEd Tanous { 112*a93e9c77SEd Tanous if (stage > ProcessingStage::Seconds) 113*a93e9c77SEd Tanous { 114*a93e9c77SEd Tanous return std::nullopt; 115*a93e9c77SEd Tanous } 116*a93e9c77SEd Tanous out += std::chrono::seconds(ticks); 117*a93e9c77SEd Tanous stage = ProcessingStage::Milliseconds; 118*a93e9c77SEd Tanous } 119*a93e9c77SEd Tanous else if (*ptr == 'S') 120*a93e9c77SEd Tanous { 121*a93e9c77SEd Tanous // We could be seeing seconds for the first time, (as is the case in 122*a93e9c77SEd Tanous // 1S) or for the second time (in the case of 1.1S). 123*a93e9c77SEd Tanous if (stage <= ProcessingStage::Seconds) 124*a93e9c77SEd Tanous { 125*a93e9c77SEd Tanous out += std::chrono::seconds(ticks); 126*a93e9c77SEd Tanous stage = ProcessingStage::Milliseconds; 127*a93e9c77SEd Tanous } 128*a93e9c77SEd Tanous else if (stage > ProcessingStage::Milliseconds) 129*a93e9c77SEd Tanous { 130*a93e9c77SEd Tanous BMCWEB_LOG_ERROR("Got unexpected information at end of parse"); 131*a93e9c77SEd Tanous return std::nullopt; 132*a93e9c77SEd Tanous } 133*a93e9c77SEd Tanous else 134*a93e9c77SEd Tanous { 135*a93e9c77SEd Tanous // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S); 136*a93e9c77SEd Tanous // Handle them all milliseconds are after the decimal point, 137*a93e9c77SEd Tanous // so they need right padded. 138*a93e9c77SEd Tanous if (charactersRead == 1) 139*a93e9c77SEd Tanous { 140*a93e9c77SEd Tanous ticks *= 100; 141*a93e9c77SEd Tanous } 142*a93e9c77SEd Tanous else if (charactersRead == 2) 143*a93e9c77SEd Tanous { 144*a93e9c77SEd Tanous ticks *= 10; 145*a93e9c77SEd Tanous } 146*a93e9c77SEd Tanous out += std::chrono::milliseconds(ticks); 147*a93e9c77SEd Tanous stage = ProcessingStage::Milliseconds; 148*a93e9c77SEd Tanous } 149*a93e9c77SEd Tanous } 150*a93e9c77SEd Tanous else 151*a93e9c77SEd Tanous { 152*a93e9c77SEd Tanous BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr); 153*a93e9c77SEd Tanous return std::nullopt; 154*a93e9c77SEd Tanous } 155*a93e9c77SEd Tanous 156*a93e9c77SEd Tanous v.remove_prefix(charactersRead + 1U); 157*a93e9c77SEd Tanous } 158*a93e9c77SEd Tanous return out; 159*a93e9c77SEd Tanous } 160*a93e9c77SEd Tanous 161*a93e9c77SEd Tanous /** 162*a93e9c77SEd Tanous * @brief Convert time value into duration format that is based on ISO 8601. 163*a93e9c77SEd Tanous * Example output: "P12DT1M5.5S" 164*a93e9c77SEd Tanous * Ref: Redfish Specification, Section 9.4.4. Duration values 165*a93e9c77SEd Tanous */ 166*a93e9c77SEd Tanous std::string toDurationString(std::chrono::milliseconds ms) 167*a93e9c77SEd Tanous { 168*a93e9c77SEd Tanous if (ms < std::chrono::milliseconds::zero()) 169*a93e9c77SEd Tanous { 170*a93e9c77SEd Tanous return ""; 171*a93e9c77SEd Tanous } 172*a93e9c77SEd Tanous 173*a93e9c77SEd Tanous std::chrono::days days = std::chrono::floor<std::chrono::days>(ms); 174*a93e9c77SEd Tanous ms -= days; 175*a93e9c77SEd Tanous 176*a93e9c77SEd Tanous std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms); 177*a93e9c77SEd Tanous ms -= hours; 178*a93e9c77SEd Tanous 179*a93e9c77SEd Tanous std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms); 180*a93e9c77SEd Tanous ms -= minutes; 181*a93e9c77SEd Tanous 182*a93e9c77SEd Tanous std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms); 183*a93e9c77SEd Tanous ms -= seconds; 184*a93e9c77SEd Tanous std::string daysStr; 185*a93e9c77SEd Tanous if (days.count() > 0) 186*a93e9c77SEd Tanous { 187*a93e9c77SEd Tanous daysStr = std::format("{}D", days.count()); 188*a93e9c77SEd Tanous } 189*a93e9c77SEd Tanous std::string hoursStr; 190*a93e9c77SEd Tanous if (hours.count() > 0) 191*a93e9c77SEd Tanous { 192*a93e9c77SEd Tanous hoursStr = std::format("{}H", hours.count()); 193*a93e9c77SEd Tanous } 194*a93e9c77SEd Tanous std::string minStr; 195*a93e9c77SEd Tanous if (minutes.count() > 0) 196*a93e9c77SEd Tanous { 197*a93e9c77SEd Tanous minStr = std::format("{}M", minutes.count()); 198*a93e9c77SEd Tanous } 199*a93e9c77SEd Tanous std::string secStr; 200*a93e9c77SEd Tanous if (seconds.count() != 0 || ms.count() != 0) 201*a93e9c77SEd Tanous { 202*a93e9c77SEd Tanous secStr = std::format("{}.{:03}S", seconds.count(), ms.count()); 203*a93e9c77SEd Tanous } 204*a93e9c77SEd Tanous 205*a93e9c77SEd Tanous return std::format("P{}T{}{}{}", daysStr, hoursStr, minStr, secStr); 206*a93e9c77SEd Tanous } 207*a93e9c77SEd Tanous 208*a93e9c77SEd Tanous std::optional<std::string> toDurationStringFromUint(uint64_t timeMs) 209*a93e9c77SEd Tanous { 210*a93e9c77SEd Tanous constexpr uint64_t maxTimeMs = 211*a93e9c77SEd Tanous static_cast<uint64_t>(std::chrono::milliseconds::max().count()); 212*a93e9c77SEd Tanous 213*a93e9c77SEd Tanous if (maxTimeMs < timeMs) 214*a93e9c77SEd Tanous { 215*a93e9c77SEd Tanous return std::nullopt; 216*a93e9c77SEd Tanous } 217*a93e9c77SEd Tanous 218*a93e9c77SEd Tanous std::string duration = toDurationString(std::chrono::milliseconds(timeMs)); 219*a93e9c77SEd Tanous if (duration.empty()) 220*a93e9c77SEd Tanous { 221*a93e9c77SEd Tanous return std::nullopt; 222*a93e9c77SEd Tanous } 223*a93e9c77SEd Tanous 224*a93e9c77SEd Tanous return std::make_optional(duration); 225*a93e9c77SEd Tanous } 226*a93e9c77SEd Tanous 227*a93e9c77SEd Tanous namespace details 228*a93e9c77SEd Tanous { 229*a93e9c77SEd Tanous // This code is left for support of gcc < 13 which didn't have support for 230*a93e9c77SEd Tanous // timezones. It should be removed at some point in the future. 231*a93e9c77SEd Tanous #if __cpp_lib_chrono < 201907L 232*a93e9c77SEd Tanous 233*a93e9c77SEd Tanous // Returns year/month/day triple in civil calendar 234*a93e9c77SEd Tanous // Preconditions: z is number of days since 1970-01-01 and is in the range: 235*a93e9c77SEd Tanous // [numeric_limits<Int>::min(), 236*a93e9c77SEd Tanous // numeric_limits<Int>::max()-719468]. 237*a93e9c77SEd Tanous // Algorithm sourced from 238*a93e9c77SEd Tanous // https://howardhinnant.github.io/date_algorithms.html#civil_from_days 239*a93e9c77SEd Tanous // All constants are explained in the above 240*a93e9c77SEd Tanous template <class IntType> 241*a93e9c77SEd Tanous constexpr std::tuple<IntType, unsigned, unsigned> 242*a93e9c77SEd Tanous civilFromDays(IntType z) noexcept 243*a93e9c77SEd Tanous { 244*a93e9c77SEd Tanous z += 719468; 245*a93e9c77SEd Tanous IntType era = (z >= 0 ? z : z - 146096) / 146097; 246*a93e9c77SEd Tanous unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096] 247*a93e9c77SEd Tanous unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 248*a93e9c77SEd Tanous 365; // [0, 399] 249*a93e9c77SEd Tanous IntType y = static_cast<IntType>(yoe) + era * 400; 250*a93e9c77SEd Tanous unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] 251*a93e9c77SEd Tanous unsigned mp = (5 * doy + 2) / 153; // [0, 11] 252*a93e9c77SEd Tanous unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] 253*a93e9c77SEd Tanous unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] 254*a93e9c77SEd Tanous 255*a93e9c77SEd Tanous return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d); 256*a93e9c77SEd Tanous } 257*a93e9c77SEd Tanous 258*a93e9c77SEd Tanous template <typename IntType, typename Period> 259*a93e9c77SEd Tanous std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t) 260*a93e9c77SEd Tanous { 261*a93e9c77SEd Tanous using seconds = std::chrono::duration<int>; 262*a93e9c77SEd Tanous using minutes = std::chrono::duration<int, std::ratio<60>>; 263*a93e9c77SEd Tanous using hours = std::chrono::duration<int, std::ratio<3600>>; 264*a93e9c77SEd Tanous using days = std::chrono::duration< 265*a93e9c77SEd Tanous IntType, std::ratio_multiply<hours::period, std::ratio<24>>>; 266*a93e9c77SEd Tanous 267*a93e9c77SEd Tanous // d is days since 1970-01-01 268*a93e9c77SEd Tanous days d = std::chrono::duration_cast<days>(t); 269*a93e9c77SEd Tanous 270*a93e9c77SEd Tanous // t is now time duration since midnight of day d 271*a93e9c77SEd Tanous t -= d; 272*a93e9c77SEd Tanous 273*a93e9c77SEd Tanous // break d down into year/month/day 274*a93e9c77SEd Tanous int year = 0; 275*a93e9c77SEd Tanous int month = 0; 276*a93e9c77SEd Tanous int day = 0; 277*a93e9c77SEd Tanous std::tie(year, month, day) = details::civilFromDays(d.count()); 278*a93e9c77SEd Tanous // Check against limits. Can't go above year 9999, and can't go below epoch 279*a93e9c77SEd Tanous // (1970) 280*a93e9c77SEd Tanous if (year >= 10000) 281*a93e9c77SEd Tanous { 282*a93e9c77SEd Tanous year = 9999; 283*a93e9c77SEd Tanous month = 12; 284*a93e9c77SEd Tanous day = 31; 285*a93e9c77SEd Tanous t = days(1) - std::chrono::duration<IntType, Period>(1); 286*a93e9c77SEd Tanous } 287*a93e9c77SEd Tanous else if (year < 1970) 288*a93e9c77SEd Tanous { 289*a93e9c77SEd Tanous year = 1970; 290*a93e9c77SEd Tanous month = 1; 291*a93e9c77SEd Tanous day = 1; 292*a93e9c77SEd Tanous t = std::chrono::duration<IntType, Period>::zero(); 293*a93e9c77SEd Tanous } 294*a93e9c77SEd Tanous 295*a93e9c77SEd Tanous hours hr = std::chrono::duration_cast<hours>(t); 296*a93e9c77SEd Tanous t -= hr; 297*a93e9c77SEd Tanous 298*a93e9c77SEd Tanous minutes mt = std::chrono::duration_cast<minutes>(t); 299*a93e9c77SEd Tanous t -= mt; 300*a93e9c77SEd Tanous 301*a93e9c77SEd Tanous seconds se = std::chrono::duration_cast<seconds>(t); 302*a93e9c77SEd Tanous 303*a93e9c77SEd Tanous t -= se; 304*a93e9c77SEd Tanous 305*a93e9c77SEd Tanous std::string subseconds; 306*a93e9c77SEd Tanous if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>) 307*a93e9c77SEd Tanous { 308*a93e9c77SEd Tanous using MilliDuration = std::chrono::duration<int, std::milli>; 309*a93e9c77SEd Tanous MilliDuration subsec = std::chrono::duration_cast<MilliDuration>(t); 310*a93e9c77SEd Tanous subseconds = std::format(".{:03}", subsec.count()); 311*a93e9c77SEd Tanous } 312*a93e9c77SEd Tanous else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>) 313*a93e9c77SEd Tanous { 314*a93e9c77SEd Tanous using MicroDuration = std::chrono::duration<int, std::micro>; 315*a93e9c77SEd Tanous MicroDuration subsec = std::chrono::duration_cast<MicroDuration>(t); 316*a93e9c77SEd Tanous subseconds = std::format(".{:06}", subsec.count()); 317*a93e9c77SEd Tanous } 318*a93e9c77SEd Tanous 319*a93e9c77SEd Tanous return std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}+00:00", year, 320*a93e9c77SEd Tanous month, day, hr.count(), mt.count(), se.count(), 321*a93e9c77SEd Tanous subseconds); 322*a93e9c77SEd Tanous } 323*a93e9c77SEd Tanous 324*a93e9c77SEd Tanous #else 325*a93e9c77SEd Tanous 326*a93e9c77SEd Tanous template <typename IntType, typename Period> 327*a93e9c77SEd Tanous 328*a93e9c77SEd Tanous std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> dur) 329*a93e9c77SEd Tanous { 330*a93e9c77SEd Tanous using namespace std::literals::chrono_literals; 331*a93e9c77SEd Tanous 332*a93e9c77SEd Tanous using SubType = std::chrono::duration<IntType, Period>; 333*a93e9c77SEd Tanous 334*a93e9c77SEd Tanous // d is days since 1970-01-01 335*a93e9c77SEd Tanous std::chrono::days days = std::chrono::floor<std::chrono::days>(dur); 336*a93e9c77SEd Tanous std::chrono::sys_days sysDays(days); 337*a93e9c77SEd Tanous std::chrono::year_month_day ymd(sysDays); 338*a93e9c77SEd Tanous 339*a93e9c77SEd Tanous // Enforce 3 constraints 340*a93e9c77SEd Tanous // the result cant under or overflow the calculation 341*a93e9c77SEd Tanous // the resulting string needs to be representable as 4 digits 342*a93e9c77SEd Tanous // The resulting string can't be before epoch 343*a93e9c77SEd Tanous if (dur.count() <= 0) 344*a93e9c77SEd Tanous { 345*a93e9c77SEd Tanous BMCWEB_LOG_WARNING("Underflow from value {}", dur.count()); 346*a93e9c77SEd Tanous ymd = 1970y / std::chrono::January / 1d; 347*a93e9c77SEd Tanous dur = std::chrono::duration<IntType, Period>::zero(); 348*a93e9c77SEd Tanous } 349*a93e9c77SEd Tanous else if (dur > SubType::max() - std::chrono::days(1)) 350*a93e9c77SEd Tanous { 351*a93e9c77SEd Tanous BMCWEB_LOG_WARNING("Overflow from value {}", dur.count()); 352*a93e9c77SEd Tanous ymd = 9999y / std::chrono::December / 31d; 353*a93e9c77SEd Tanous dur = std::chrono::days(1) - SubType(1); 354*a93e9c77SEd Tanous } 355*a93e9c77SEd Tanous else if (ymd.year() >= 10000y) 356*a93e9c77SEd Tanous { 357*a93e9c77SEd Tanous BMCWEB_LOG_WARNING("Year {} not representable", ymd.year()); 358*a93e9c77SEd Tanous ymd = 9999y / std::chrono::December / 31d; 359*a93e9c77SEd Tanous dur = std::chrono::days(1) - SubType(1); 360*a93e9c77SEd Tanous } 361*a93e9c77SEd Tanous else if (ymd.year() < 1970y) 362*a93e9c77SEd Tanous { 363*a93e9c77SEd Tanous BMCWEB_LOG_WARNING("Year {} not representable", ymd.year()); 364*a93e9c77SEd Tanous ymd = 1970y / std::chrono::January / 1d; 365*a93e9c77SEd Tanous dur = SubType::zero(); 366*a93e9c77SEd Tanous } 367*a93e9c77SEd Tanous else 368*a93e9c77SEd Tanous { 369*a93e9c77SEd Tanous // t is now time duration since midnight of day d 370*a93e9c77SEd Tanous dur -= days; 371*a93e9c77SEd Tanous } 372*a93e9c77SEd Tanous std::chrono::hh_mm_ss<SubType> hms(dur); 373*a93e9c77SEd Tanous 374*a93e9c77SEd Tanous return std::format("{}T{}+00:00", ymd, hms); 375*a93e9c77SEd Tanous } 376*a93e9c77SEd Tanous 377*a93e9c77SEd Tanous #endif 378*a93e9c77SEd Tanous } // namespace details 379*a93e9c77SEd Tanous 380*a93e9c77SEd Tanous // Returns the formatted date time string. 381*a93e9c77SEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 382*a93e9c77SEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported 383*a93e9c77SEd Tanous // date. 384*a93e9c77SEd Tanous std::string getDateTimeUint(uint64_t secondsSinceEpoch) 385*a93e9c77SEd Tanous { 386*a93e9c77SEd Tanous using DurationType = std::chrono::duration<uint64_t>; 387*a93e9c77SEd Tanous DurationType sinceEpoch(secondsSinceEpoch); 388*a93e9c77SEd Tanous return details::toISO8061ExtendedStr(sinceEpoch); 389*a93e9c77SEd Tanous } 390*a93e9c77SEd Tanous 391*a93e9c77SEd Tanous // Returns the formatted date time string with millisecond precision 392*a93e9c77SEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 393*a93e9c77SEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported 394*a93e9c77SEd Tanous // date. 395*a93e9c77SEd Tanous std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch) 396*a93e9c77SEd Tanous { 397*a93e9c77SEd Tanous using DurationType = std::chrono::duration<uint64_t, std::milli>; 398*a93e9c77SEd Tanous DurationType sinceEpoch(milliSecondsSinceEpoch); 399*a93e9c77SEd Tanous return details::toISO8061ExtendedStr(sinceEpoch); 400*a93e9c77SEd Tanous } 401*a93e9c77SEd Tanous 402*a93e9c77SEd Tanous // Returns the formatted date time string with microsecond precision 403*a93e9c77SEd Tanous std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch) 404*a93e9c77SEd Tanous { 405*a93e9c77SEd Tanous using DurationType = std::chrono::duration<uint64_t, std::micro>; 406*a93e9c77SEd Tanous DurationType sinceEpoch(microSecondsSinceEpoch); 407*a93e9c77SEd Tanous return details::toISO8061ExtendedStr(sinceEpoch); 408*a93e9c77SEd Tanous } 409*a93e9c77SEd Tanous 410*a93e9c77SEd Tanous std::string getDateTimeStdtime(std::time_t secondsSinceEpoch) 411*a93e9c77SEd Tanous { 412*a93e9c77SEd Tanous using DurationType = std::chrono::duration<std::time_t>; 413*a93e9c77SEd Tanous DurationType sinceEpoch(secondsSinceEpoch); 414*a93e9c77SEd Tanous return details::toISO8061ExtendedStr(sinceEpoch); 415*a93e9c77SEd Tanous } 416*a93e9c77SEd Tanous 417*a93e9c77SEd Tanous /** 418*a93e9c77SEd Tanous * Returns the current Date, Time & the local Time Offset 419*a93e9c77SEd Tanous * information in a pair 420*a93e9c77SEd Tanous * 421*a93e9c77SEd Tanous * @param[in] None 422*a93e9c77SEd Tanous * 423*a93e9c77SEd Tanous * @return std::pair<std::string, std::string>, which consist 424*a93e9c77SEd Tanous * of current DateTime & the TimeOffset strings respectively. 425*a93e9c77SEd Tanous */ 426*a93e9c77SEd Tanous std::pair<std::string, std::string> getDateTimeOffsetNow() 427*a93e9c77SEd Tanous { 428*a93e9c77SEd Tanous std::time_t time = std::time(nullptr); 429*a93e9c77SEd Tanous std::string dateTime = getDateTimeStdtime(time); 430*a93e9c77SEd Tanous 431*a93e9c77SEd Tanous /* extract the local Time Offset value from the 432*a93e9c77SEd Tanous * received dateTime string. 433*a93e9c77SEd Tanous */ 434*a93e9c77SEd Tanous std::string timeOffset("Z00:00"); 435*a93e9c77SEd Tanous std::size_t lastPos = dateTime.size(); 436*a93e9c77SEd Tanous std::size_t len = timeOffset.size(); 437*a93e9c77SEd Tanous if (lastPos > len) 438*a93e9c77SEd Tanous { 439*a93e9c77SEd Tanous timeOffset = dateTime.substr(lastPos - len); 440*a93e9c77SEd Tanous } 441*a93e9c77SEd Tanous 442*a93e9c77SEd Tanous return std::make_pair(dateTime, timeOffset); 443*a93e9c77SEd Tanous } 444*a93e9c77SEd Tanous 445*a93e9c77SEd Tanous using usSinceEpoch = std::chrono::duration<int64_t, std::micro>; 446*a93e9c77SEd Tanous 447*a93e9c77SEd Tanous /** 448*a93e9c77SEd Tanous * @brief Returns the datetime in ISO 8601 format 449*a93e9c77SEd Tanous * 450*a93e9c77SEd Tanous * @param[in] std::string_view the date of item manufacture in ISO 8601 format, 451*a93e9c77SEd Tanous * either as YYYYMMDD or YYYYMMDDThhmmssZ 452*a93e9c77SEd Tanous * Ref: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/ 453*a93e9c77SEd Tanous * xyz/openbmc_project/Inventory/Decorator/Asset.interface.yaml#L16 454*a93e9c77SEd Tanous * 455*a93e9c77SEd Tanous * @return std::string which consist the datetime 456*a93e9c77SEd Tanous */ 457*a93e9c77SEd Tanous std::optional<std::string> getDateTimeIso8601(std::string_view datetime) 458*a93e9c77SEd Tanous { 459*a93e9c77SEd Tanous std::optional<usSinceEpoch> us = dateStringToEpoch(datetime); 460*a93e9c77SEd Tanous if (!us) 461*a93e9c77SEd Tanous { 462*a93e9c77SEd Tanous return std::nullopt; 463*a93e9c77SEd Tanous } 464*a93e9c77SEd Tanous auto secondsDuration = 465*a93e9c77SEd Tanous std::chrono::duration_cast<std::chrono::seconds>(*us); 466*a93e9c77SEd Tanous 467*a93e9c77SEd Tanous return std::make_optional( 468*a93e9c77SEd Tanous getDateTimeUint(static_cast<uint64_t>(secondsDuration.count()))); 469*a93e9c77SEd Tanous } 470*a93e9c77SEd Tanous 471*a93e9c77SEd Tanous /** 472*a93e9c77SEd Tanous * @brief ProductionDate report 473*a93e9c77SEd Tanous */ 474*a93e9c77SEd Tanous void productionDateReport(crow::Response& res, const std::string& buildDate) 475*a93e9c77SEd Tanous { 476*a93e9c77SEd Tanous std::optional<std::string> valueStr = getDateTimeIso8601(buildDate); 477*a93e9c77SEd Tanous if (!valueStr) 478*a93e9c77SEd Tanous { 479*a93e9c77SEd Tanous messages::internalError(); 480*a93e9c77SEd Tanous return; 481*a93e9c77SEd Tanous } 482*a93e9c77SEd Tanous res.jsonValue["ProductionDate"] = *valueStr; 483*a93e9c77SEd Tanous } 484*a93e9c77SEd Tanous 4851b8b02a4SEd Tanous std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime) 4861b8b02a4SEd Tanous { 487352e3b78SHieu Huynh for (const char* format : std::to_array( 488352e3b78SHieu Huynh {"%FT%T%Ez", "%FT%TZ", "%FT%T", "%Y%m%d", "%Y%m%dT%H%M%SZ"})) 4891b8b02a4SEd Tanous { 4901b8b02a4SEd Tanous // Parse using signed so we can detect negative dates 491c51afd54SEd Tanous std::chrono::sys_time<usSinceEpoch> date; 4921b8b02a4SEd Tanous std::istringstream iss(std::string{datetime}); 4931b8b02a4SEd Tanous #if __cpp_lib_chrono >= 201907L 4941b8b02a4SEd Tanous namespace chrono_from_stream = std::chrono; 4951b8b02a4SEd Tanous #else 4961b8b02a4SEd Tanous namespace chrono_from_stream = date; 4971b8b02a4SEd Tanous #endif 4981b8b02a4SEd Tanous if (chrono_from_stream::from_stream(iss, format, date)) 4991b8b02a4SEd Tanous { 5001b8b02a4SEd Tanous if (date.time_since_epoch().count() < 0) 5011b8b02a4SEd Tanous { 5021b8b02a4SEd Tanous return std::nullopt; 5031b8b02a4SEd Tanous } 5041b8b02a4SEd Tanous if (iss.rdbuf()->in_avail() != 0) 5051b8b02a4SEd Tanous { 5061b8b02a4SEd Tanous // More information left at end of string. 5071b8b02a4SEd Tanous continue; 5081b8b02a4SEd Tanous } 509c51afd54SEd Tanous return date.time_since_epoch(); 5101b8b02a4SEd Tanous } 5111b8b02a4SEd Tanous } 5121b8b02a4SEd Tanous return std::nullopt; 5131b8b02a4SEd Tanous } 5141b8b02a4SEd Tanous } // namespace redfish::time_utils 515