1081ebf06SWludzik, Jozef #pragma once
2081ebf06SWludzik, Jozef
34dbb8aeaSWludzik, Jozef #include "logging.hpp"
44dbb8aeaSWludzik, Jozef
59ea15c35SEd Tanous #include <algorithm>
64dbb8aeaSWludzik, Jozef #include <charconv>
7081ebf06SWludzik, Jozef #include <chrono>
89ea15c35SEd Tanous #include <cstddef>
9d5c80ad9SNan Zhou #include <cstdint>
10*f653730dSEd Tanous #include <format>
114dbb8aeaSWludzik, Jozef #include <optional>
129ea15c35SEd Tanous #include <ratio>
13081ebf06SWludzik, Jozef #include <string>
149ea15c35SEd Tanous #include <string_view>
15081ebf06SWludzik, Jozef
16d5c80ad9SNan Zhou // IWYU pragma: no_include <stddef.h>
17d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h>
18d5c80ad9SNan Zhou
19081ebf06SWludzik, Jozef namespace redfish
20081ebf06SWludzik, Jozef {
21081ebf06SWludzik, Jozef
22081ebf06SWludzik, Jozef namespace time_utils
23081ebf06SWludzik, Jozef {
24081ebf06SWludzik, Jozef
25081ebf06SWludzik, Jozef /**
264dbb8aeaSWludzik, Jozef * @brief Convert string that represents value in Duration Format to its numeric
274dbb8aeaSWludzik, Jozef * equivalent.
284dbb8aeaSWludzik, Jozef */
294f48d5f6SEd Tanous inline std::optional<std::chrono::milliseconds>
fromDurationString(std::string_view v)301249e9cbSEd Tanous fromDurationString(std::string_view v)
314dbb8aeaSWludzik, Jozef {
324dbb8aeaSWludzik, Jozef std::chrono::milliseconds out = std::chrono::milliseconds::zero();
331249e9cbSEd Tanous enum class ProcessingStage
344dbb8aeaSWludzik, Jozef {
351249e9cbSEd Tanous // P1DT1H1M1.100S
361249e9cbSEd Tanous P,
371249e9cbSEd Tanous Days,
381249e9cbSEd Tanous Hours,
391249e9cbSEd Tanous Minutes,
401249e9cbSEd Tanous Seconds,
411249e9cbSEd Tanous Milliseconds,
421249e9cbSEd Tanous Done,
431249e9cbSEd Tanous };
441249e9cbSEd Tanous ProcessingStage stage = ProcessingStage::P;
451249e9cbSEd Tanous
461249e9cbSEd Tanous while (!v.empty())
471249e9cbSEd Tanous {
481249e9cbSEd Tanous if (stage == ProcessingStage::P)
491249e9cbSEd Tanous {
504dbb8aeaSWludzik, Jozef if (v.front() != 'P')
514dbb8aeaSWludzik, Jozef {
524dbb8aeaSWludzik, Jozef return std::nullopt;
534dbb8aeaSWludzik, Jozef }
544dbb8aeaSWludzik, Jozef v.remove_prefix(1);
551249e9cbSEd Tanous stage = ProcessingStage::Days;
561249e9cbSEd Tanous continue;
571249e9cbSEd Tanous }
58c98dbc64SEd Tanous if (stage == ProcessingStage::Days)
594dbb8aeaSWludzik, Jozef {
601249e9cbSEd Tanous if (v.front() == 'T')
611249e9cbSEd Tanous {
624dbb8aeaSWludzik, Jozef v.remove_prefix(1);
631249e9cbSEd Tanous stage = ProcessingStage::Hours;
641249e9cbSEd Tanous continue;
651249e9cbSEd Tanous }
661249e9cbSEd Tanous }
671249e9cbSEd Tanous uint64_t ticks = 0;
681249e9cbSEd Tanous auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks);
691249e9cbSEd Tanous if (ec != std::errc())
704dbb8aeaSWludzik, Jozef {
7162598e31SEd Tanous BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v);
721249e9cbSEd Tanous return std::nullopt;
731249e9cbSEd Tanous }
741249e9cbSEd Tanous size_t charactersRead = static_cast<size_t>(ptr - v.data());
751249e9cbSEd Tanous if (ptr >= v.end())
761249e9cbSEd Tanous {
7762598e31SEd Tanous BMCWEB_LOG_ERROR("Missing postfix");
781249e9cbSEd Tanous return std::nullopt;
791249e9cbSEd Tanous }
801249e9cbSEd Tanous if (*ptr == 'D')
811249e9cbSEd Tanous {
821249e9cbSEd Tanous if (stage > ProcessingStage::Days)
831249e9cbSEd Tanous {
841249e9cbSEd Tanous return std::nullopt;
851249e9cbSEd Tanous }
861249e9cbSEd Tanous out += std::chrono::days(ticks);
871249e9cbSEd Tanous }
881249e9cbSEd Tanous else if (*ptr == 'H')
891249e9cbSEd Tanous {
901249e9cbSEd Tanous if (stage > ProcessingStage::Hours)
911249e9cbSEd Tanous {
921249e9cbSEd Tanous return std::nullopt;
931249e9cbSEd Tanous }
941249e9cbSEd Tanous out += std::chrono::hours(ticks);
951249e9cbSEd Tanous }
961249e9cbSEd Tanous else if (*ptr == 'M')
971249e9cbSEd Tanous {
981249e9cbSEd Tanous if (stage > ProcessingStage::Minutes)
991249e9cbSEd Tanous {
1001249e9cbSEd Tanous return std::nullopt;
1011249e9cbSEd Tanous }
1021249e9cbSEd Tanous out += std::chrono::minutes(ticks);
1031249e9cbSEd Tanous }
1041249e9cbSEd Tanous else if (*ptr == '.')
1051249e9cbSEd Tanous {
1061249e9cbSEd Tanous if (stage > ProcessingStage::Seconds)
1071249e9cbSEd Tanous {
1081249e9cbSEd Tanous return std::nullopt;
1091249e9cbSEd Tanous }
1101249e9cbSEd Tanous out += std::chrono::seconds(ticks);
1111249e9cbSEd Tanous stage = ProcessingStage::Milliseconds;
1121249e9cbSEd Tanous }
1131249e9cbSEd Tanous else if (*ptr == 'S')
1141249e9cbSEd Tanous {
1151249e9cbSEd Tanous // We could be seeing seconds for the first time, (as is the case in
1161249e9cbSEd Tanous // 1S) or for the second time (in the case of 1.1S).
1171249e9cbSEd Tanous if (stage <= ProcessingStage::Seconds)
1181249e9cbSEd Tanous {
1191249e9cbSEd Tanous out += std::chrono::seconds(ticks);
1201249e9cbSEd Tanous stage = ProcessingStage::Milliseconds;
1211249e9cbSEd Tanous }
1221249e9cbSEd Tanous else if (stage > ProcessingStage::Milliseconds)
1231249e9cbSEd Tanous {
12462598e31SEd Tanous BMCWEB_LOG_ERROR("Got unexpected information at end of parse");
1251249e9cbSEd Tanous return std::nullopt;
1261249e9cbSEd Tanous }
1271249e9cbSEd Tanous else
1281249e9cbSEd Tanous {
1291249e9cbSEd Tanous // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S);
1301249e9cbSEd Tanous // Handle them all milliseconds are after the decimal point,
1311249e9cbSEd Tanous // so they need right padded.
1321249e9cbSEd Tanous if (charactersRead == 1)
1331249e9cbSEd Tanous {
1341249e9cbSEd Tanous ticks *= 100;
1351249e9cbSEd Tanous }
1361249e9cbSEd Tanous else if (charactersRead == 2)
1371249e9cbSEd Tanous {
1381249e9cbSEd Tanous ticks *= 10;
1391249e9cbSEd Tanous }
1401249e9cbSEd Tanous out += std::chrono::milliseconds(ticks);
1411249e9cbSEd Tanous stage = ProcessingStage::Milliseconds;
1421249e9cbSEd Tanous }
1431249e9cbSEd Tanous }
1441249e9cbSEd Tanous else
1451249e9cbSEd Tanous {
14662598e31SEd Tanous BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr);
1474dbb8aeaSWludzik, Jozef return std::nullopt;
1484dbb8aeaSWludzik, Jozef }
1494dbb8aeaSWludzik, Jozef
1501249e9cbSEd Tanous v.remove_prefix(charactersRead + 1U);
1514dbb8aeaSWludzik, Jozef }
1524dbb8aeaSWludzik, Jozef return out;
1534dbb8aeaSWludzik, Jozef }
1544dbb8aeaSWludzik, Jozef
1554dbb8aeaSWludzik, Jozef /**
156081ebf06SWludzik, Jozef * @brief Convert time value into duration format that is based on ISO 8601.
157081ebf06SWludzik, Jozef * Example output: "P12DT1M5.5S"
158081ebf06SWludzik, Jozef * Ref: Redfish Specification, Section 9.4.4. Duration values
159081ebf06SWludzik, Jozef */
toDurationString(std::chrono::milliseconds ms)160b00dcc27SEd Tanous inline std::string toDurationString(std::chrono::milliseconds ms)
161081ebf06SWludzik, Jozef {
162081ebf06SWludzik, Jozef if (ms < std::chrono::milliseconds::zero())
163081ebf06SWludzik, Jozef {
164081ebf06SWludzik, Jozef return "";
165081ebf06SWludzik, Jozef }
166081ebf06SWludzik, Jozef
1671249e9cbSEd Tanous std::chrono::days days = std::chrono::floor<std::chrono::days>(ms);
168081ebf06SWludzik, Jozef ms -= days;
169081ebf06SWludzik, Jozef
170081ebf06SWludzik, Jozef std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
171081ebf06SWludzik, Jozef ms -= hours;
172081ebf06SWludzik, Jozef
173081ebf06SWludzik, Jozef std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
174081ebf06SWludzik, Jozef ms -= minutes;
175081ebf06SWludzik, Jozef
176081ebf06SWludzik, Jozef std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
177081ebf06SWludzik, Jozef ms -= seconds;
178*f653730dSEd Tanous std::string daysStr;
179081ebf06SWludzik, Jozef if (days.count() > 0)
180081ebf06SWludzik, Jozef {
181*f653730dSEd Tanous daysStr = std::format("{}D", days.count());
182081ebf06SWludzik, Jozef }
183*f653730dSEd Tanous std::string hoursStr;
184081ebf06SWludzik, Jozef if (hours.count() > 0)
185081ebf06SWludzik, Jozef {
186*f653730dSEd Tanous hoursStr = std::format("{}H", hours.count());
187081ebf06SWludzik, Jozef }
188*f653730dSEd Tanous std::string minStr;
189081ebf06SWludzik, Jozef if (minutes.count() > 0)
190081ebf06SWludzik, Jozef {
191*f653730dSEd Tanous minStr = std::format("{}M", minutes.count());
192081ebf06SWludzik, Jozef }
193*f653730dSEd Tanous std::string secStr;
194081ebf06SWludzik, Jozef if (seconds.count() != 0 || ms.count() != 0)
195081ebf06SWludzik, Jozef {
196*f653730dSEd Tanous secStr = std::format("{}.{:03}S", seconds.count(), ms.count());
197081ebf06SWludzik, Jozef }
198081ebf06SWludzik, Jozef
199*f653730dSEd Tanous return std::format("P{}T{}{}{}", daysStr, hoursStr, minStr, secStr);
200081ebf06SWludzik, Jozef }
201081ebf06SWludzik, Jozef
2021b7e696bSLukasz Kazmierczak inline std::optional<std::string>
toDurationStringFromUint(const uint64_t timeMs)2031b7e696bSLukasz Kazmierczak toDurationStringFromUint(const uint64_t timeMs)
2041b7e696bSLukasz Kazmierczak {
205*f653730dSEd Tanous constexpr uint64_t maxTimeMs =
2061b7e696bSLukasz Kazmierczak static_cast<uint64_t>(std::chrono::milliseconds::max().count());
2071b7e696bSLukasz Kazmierczak
2081b7e696bSLukasz Kazmierczak if (maxTimeMs < timeMs)
2091b7e696bSLukasz Kazmierczak {
2101b7e696bSLukasz Kazmierczak return std::nullopt;
2111b7e696bSLukasz Kazmierczak }
2121b7e696bSLukasz Kazmierczak
2131b7e696bSLukasz Kazmierczak std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
2141b7e696bSLukasz Kazmierczak if (duration.empty())
2151b7e696bSLukasz Kazmierczak {
2161b7e696bSLukasz Kazmierczak return std::nullopt;
2171b7e696bSLukasz Kazmierczak }
2181b7e696bSLukasz Kazmierczak
2191b7e696bSLukasz Kazmierczak return std::make_optional(duration);
2201b7e696bSLukasz Kazmierczak }
2211b7e696bSLukasz Kazmierczak
2222b82937eSEd Tanous namespace details
2232b82937eSEd Tanous {
2242b82937eSEd Tanous // Returns year/month/day triple in civil calendar
2252b82937eSEd Tanous // Preconditions: z is number of days since 1970-01-01 and is in the range:
2262b82937eSEd Tanous // [numeric_limits<Int>::min(),
2272b82937eSEd Tanous // numeric_limits<Int>::max()-719468].
2282b82937eSEd Tanous // Algorithm sourced from
2292b82937eSEd Tanous // https://howardhinnant.github.io/date_algorithms.html#civil_from_days
2302b82937eSEd Tanous // All constants are explained in the above
2312b82937eSEd Tanous template <class IntType>
2322b82937eSEd Tanous constexpr std::tuple<IntType, unsigned, unsigned>
civilFromDays(IntType z)2332b82937eSEd Tanous civilFromDays(IntType z) noexcept
2342b82937eSEd Tanous {
2352b82937eSEd Tanous z += 719468;
2362b82937eSEd Tanous IntType era = (z >= 0 ? z : z - 146096) / 146097;
2372b82937eSEd Tanous unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
23889492a15SPatrick Williams unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) /
23989492a15SPatrick Williams 365; // [0, 399]
2402b82937eSEd Tanous IntType y = static_cast<IntType>(yoe) + era * 400;
2412b82937eSEd Tanous unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
2422b82937eSEd Tanous unsigned mp = (5 * doy + 2) / 153; // [0, 11]
2432b82937eSEd Tanous unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
2442b82937eSEd Tanous unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12]
2452b82937eSEd Tanous
2462b82937eSEd Tanous return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d);
2472b82937eSEd Tanous }
2482b82937eSEd Tanous
2492b82937eSEd Tanous template <typename IntType, typename Period>
toISO8061ExtendedStr(std::chrono::duration<IntType,Period> t)2502b82937eSEd Tanous std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t)
2512b82937eSEd Tanous {
2522b82937eSEd Tanous using seconds = std::chrono::duration<int>;
2532b82937eSEd Tanous using minutes = std::chrono::duration<int, std::ratio<60>>;
2542b82937eSEd Tanous using hours = std::chrono::duration<int, std::ratio<3600>>;
2552b82937eSEd Tanous using days = std::chrono::duration<
2562b82937eSEd Tanous IntType, std::ratio_multiply<hours::period, std::ratio<24>>>;
2572b82937eSEd Tanous
2582b82937eSEd Tanous // d is days since 1970-01-01
2592b82937eSEd Tanous days d = std::chrono::duration_cast<days>(t);
2602b82937eSEd Tanous
2612b82937eSEd Tanous // t is now time duration since midnight of day d
2622b82937eSEd Tanous t -= d;
2632b82937eSEd Tanous
2642b82937eSEd Tanous // break d down into year/month/day
2652b82937eSEd Tanous int year = 0;
2662b82937eSEd Tanous int month = 0;
2672b82937eSEd Tanous int day = 0;
2682b82937eSEd Tanous std::tie(year, month, day) = details::civilFromDays(d.count());
2692b82937eSEd Tanous // Check against limits. Can't go above year 9999, and can't go below epoch
2702b82937eSEd Tanous // (1970)
2712b82937eSEd Tanous if (year >= 10000)
2722b82937eSEd Tanous {
2732b82937eSEd Tanous year = 9999;
2742b82937eSEd Tanous month = 12;
2752b82937eSEd Tanous day = 31;
2762b82937eSEd Tanous t = days(1) - std::chrono::duration<IntType, Period>(1);
2772b82937eSEd Tanous }
2782b82937eSEd Tanous else if (year < 1970)
2792b82937eSEd Tanous {
2802b82937eSEd Tanous year = 1970;
2812b82937eSEd Tanous month = 1;
2822b82937eSEd Tanous day = 1;
2832b82937eSEd Tanous t = std::chrono::duration<IntType, Period>::zero();
2842b82937eSEd Tanous }
2852b82937eSEd Tanous
2862b82937eSEd Tanous hours hr = duration_cast<hours>(t);
2872b82937eSEd Tanous t -= hr;
2882b82937eSEd Tanous
2892b82937eSEd Tanous minutes mt = duration_cast<minutes>(t);
2902b82937eSEd Tanous t -= mt;
2912b82937eSEd Tanous
2922b82937eSEd Tanous seconds se = duration_cast<seconds>(t);
293*f653730dSEd Tanous
2942b82937eSEd Tanous t -= se;
2952b82937eSEd Tanous
296*f653730dSEd Tanous std::string subseconds;
2972b82937eSEd Tanous if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>)
2982b82937eSEd Tanous {
2992b82937eSEd Tanous using MilliDuration = std::chrono::duration<int, std::milli>;
3002b82937eSEd Tanous MilliDuration subsec = duration_cast<MilliDuration>(t);
301*f653730dSEd Tanous subseconds = std::format(".{:03}", subsec.count());
3022b82937eSEd Tanous }
3032b82937eSEd Tanous else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>)
3042b82937eSEd Tanous {
3052b82937eSEd Tanous using MicroDuration = std::chrono::duration<int, std::micro>;
3062b82937eSEd Tanous MicroDuration subsec = duration_cast<MicroDuration>(t);
307*f653730dSEd Tanous subseconds = std::format(".{:06}", subsec.count());
3082b82937eSEd Tanous }
3092b82937eSEd Tanous
310*f653730dSEd Tanous return std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}+00:00", year,
311*f653730dSEd Tanous month, day, hr.count(), mt.count(), se.count(),
312*f653730dSEd Tanous subseconds);
3132b82937eSEd Tanous }
3142b82937eSEd Tanous } // namespace details
3152b82937eSEd Tanous
3162b82937eSEd Tanous // Returns the formatted date time string.
3172b82937eSEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
3182b82937eSEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported
3192b82937eSEd Tanous // date.
getDateTimeUint(uint64_t secondsSinceEpoch)3202b82937eSEd Tanous inline std::string getDateTimeUint(uint64_t secondsSinceEpoch)
3212b82937eSEd Tanous {
3222b82937eSEd Tanous using DurationType = std::chrono::duration<uint64_t>;
3232b82937eSEd Tanous DurationType sinceEpoch(secondsSinceEpoch);
3242b82937eSEd Tanous return details::toISO8061ExtendedStr(sinceEpoch);
3252b82937eSEd Tanous }
3262b82937eSEd Tanous
3272b82937eSEd Tanous // Returns the formatted date time string with millisecond precision
3282b82937eSEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
3292b82937eSEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported
3302b82937eSEd Tanous // date.
getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)3312b82937eSEd Tanous inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)
3322b82937eSEd Tanous {
3332b82937eSEd Tanous using DurationType = std::chrono::duration<uint64_t, std::milli>;
3342b82937eSEd Tanous DurationType sinceEpoch(milliSecondsSinceEpoch);
3352b82937eSEd Tanous return details::toISO8061ExtendedStr(sinceEpoch);
3362b82937eSEd Tanous }
3372b82937eSEd Tanous
3382b82937eSEd Tanous // Returns the formatted date time string with microsecond precision
getDateTimeUintUs(uint64_t microSecondsSinceEpoch)3392b82937eSEd Tanous inline std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch)
3402b82937eSEd Tanous {
3412b82937eSEd Tanous using DurationType = std::chrono::duration<uint64_t, std::micro>;
3422b82937eSEd Tanous DurationType sinceEpoch(microSecondsSinceEpoch);
3432b82937eSEd Tanous return details::toISO8061ExtendedStr(sinceEpoch);
3442b82937eSEd Tanous }
3452b82937eSEd Tanous
getDateTimeStdtime(std::time_t secondsSinceEpoch)3462b82937eSEd Tanous inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch)
3472b82937eSEd Tanous {
3482b82937eSEd Tanous using DurationType = std::chrono::duration<std::time_t>;
3492b82937eSEd Tanous DurationType sinceEpoch(secondsSinceEpoch);
3502b82937eSEd Tanous return details::toISO8061ExtendedStr(sinceEpoch);
3512b82937eSEd Tanous }
3522b82937eSEd Tanous
3532b82937eSEd Tanous /**
3542b82937eSEd Tanous * Returns the current Date, Time & the local Time Offset
3558ece0e45SEd Tanous * information in a pair
3562b82937eSEd Tanous *
3572b82937eSEd Tanous * @param[in] None
3582b82937eSEd Tanous *
3592b82937eSEd Tanous * @return std::pair<std::string, std::string>, which consist
3602b82937eSEd Tanous * of current DateTime & the TimeOffset strings respectively.
3612b82937eSEd Tanous */
getDateTimeOffsetNow()3622b82937eSEd Tanous inline std::pair<std::string, std::string> getDateTimeOffsetNow()
3632b82937eSEd Tanous {
3642b82937eSEd Tanous std::time_t time = std::time(nullptr);
3652b82937eSEd Tanous std::string dateTime = getDateTimeStdtime(time);
3662b82937eSEd Tanous
3672b82937eSEd Tanous /* extract the local Time Offset value from the
3688ece0e45SEd Tanous * received dateTime string.
3692b82937eSEd Tanous */
3702b82937eSEd Tanous std::string timeOffset("Z00:00");
3712b82937eSEd Tanous std::size_t lastPos = dateTime.size();
3722b82937eSEd Tanous std::size_t len = timeOffset.size();
3732b82937eSEd Tanous if (lastPos > len)
3742b82937eSEd Tanous {
3752b82937eSEd Tanous timeOffset = dateTime.substr(lastPos - len);
3762b82937eSEd Tanous }
3772b82937eSEd Tanous
3782b82937eSEd Tanous return std::make_pair(dateTime, timeOffset);
3792b82937eSEd Tanous }
3802b82937eSEd Tanous
381c51afd54SEd Tanous using usSinceEpoch = std::chrono::duration<int64_t, std::micro>;
3821b8b02a4SEd Tanous std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime);
383081ebf06SWludzik, Jozef } // namespace time_utils
384081ebf06SWludzik, Jozef } // namespace redfish
385