Move time_utils to compile unit
There's no reason for these functions to be in a header, and pulling
them into a compile unit can reduce compile times overall.
Tested: Unit tests pass (Good coverage)
Change-Id: Ia6dc50d16bf2967e647a3c7437ba13bd7ab7ca3c
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp
index e2c42be..ab80d31 100644
--- a/redfish-core/include/utils/time_utils.hpp
+++ b/redfish-core/include/utils/time_utils.hpp
@@ -2,17 +2,12 @@
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
#pragma once
-#include "app.hpp"
+#include "http_response.hpp"
#include "logging.hpp"
-#include <algorithm>
-#include <charconv>
#include <chrono>
#include <cstddef>
-#include <cstdint>
-#include <format>
#include <optional>
-#include <ratio>
#include <string>
#include <string_view>
@@ -26,386 +21,33 @@
* @brief Convert string that represents value in Duration Format to its numeric
* equivalent.
*/
-inline std::optional<std::chrono::milliseconds>
- fromDurationString(std::string_view v)
-{
- std::chrono::milliseconds out = std::chrono::milliseconds::zero();
- enum class ProcessingStage
- {
- // P1DT1H1M1.100S
- P,
- Days,
- Hours,
- Minutes,
- Seconds,
- Milliseconds,
- Done,
- };
- ProcessingStage stage = ProcessingStage::P;
-
- while (!v.empty())
- {
- if (stage == ProcessingStage::P)
- {
- if (v.front() != 'P')
- {
- return std::nullopt;
- }
- v.remove_prefix(1);
- stage = ProcessingStage::Days;
- continue;
- }
- if (stage == ProcessingStage::Days)
- {
- if (v.front() == 'T')
- {
- v.remove_prefix(1);
- stage = ProcessingStage::Hours;
- continue;
- }
- }
- uint64_t ticks = 0;
- auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks);
- if (ec != std::errc())
- {
- BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v);
- return std::nullopt;
- }
- size_t charactersRead = static_cast<size_t>(ptr - v.data());
- if (ptr >= v.end())
- {
- BMCWEB_LOG_ERROR("Missing postfix");
- return std::nullopt;
- }
- if (*ptr == 'D')
- {
- if (stage > ProcessingStage::Days)
- {
- return std::nullopt;
- }
- out += std::chrono::days(ticks);
- }
- else if (*ptr == 'H')
- {
- if (stage > ProcessingStage::Hours)
- {
- return std::nullopt;
- }
- out += std::chrono::hours(ticks);
- }
- else if (*ptr == 'M')
- {
- if (stage > ProcessingStage::Minutes)
- {
- return std::nullopt;
- }
- out += std::chrono::minutes(ticks);
- }
- else if (*ptr == '.')
- {
- if (stage > ProcessingStage::Seconds)
- {
- return std::nullopt;
- }
- out += std::chrono::seconds(ticks);
- stage = ProcessingStage::Milliseconds;
- }
- else if (*ptr == 'S')
- {
- // We could be seeing seconds for the first time, (as is the case in
- // 1S) or for the second time (in the case of 1.1S).
- if (stage <= ProcessingStage::Seconds)
- {
- out += std::chrono::seconds(ticks);
- stage = ProcessingStage::Milliseconds;
- }
- else if (stage > ProcessingStage::Milliseconds)
- {
- BMCWEB_LOG_ERROR("Got unexpected information at end of parse");
- return std::nullopt;
- }
- else
- {
- // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S);
- // Handle them all milliseconds are after the decimal point,
- // so they need right padded.
- if (charactersRead == 1)
- {
- ticks *= 100;
- }
- else if (charactersRead == 2)
- {
- ticks *= 10;
- }
- out += std::chrono::milliseconds(ticks);
- stage = ProcessingStage::Milliseconds;
- }
- }
- else
- {
- BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr);
- return std::nullopt;
- }
-
- v.remove_prefix(charactersRead + 1U);
- }
- return out;
-}
+std::optional<std::chrono::milliseconds> fromDurationString(std::string_view v);
/**
* @brief Convert time value into duration format that is based on ISO 8601.
* Example output: "P12DT1M5.5S"
* Ref: Redfish Specification, Section 9.4.4. Duration values
*/
-inline std::string toDurationString(std::chrono::milliseconds ms)
-{
- if (ms < std::chrono::milliseconds::zero())
- {
- return "";
- }
+std::string toDurationString(std::chrono::milliseconds ms);
- std::chrono::days days = std::chrono::floor<std::chrono::days>(ms);
- ms -= days;
-
- std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
- ms -= hours;
-
- std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
- ms -= minutes;
-
- std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
- ms -= seconds;
- std::string daysStr;
- if (days.count() > 0)
- {
- daysStr = std::format("{}D", days.count());
- }
- std::string hoursStr;
- if (hours.count() > 0)
- {
- hoursStr = std::format("{}H", hours.count());
- }
- std::string minStr;
- if (minutes.count() > 0)
- {
- minStr = std::format("{}M", minutes.count());
- }
- std::string secStr;
- if (seconds.count() != 0 || ms.count() != 0)
- {
- secStr = std::format("{}.{:03}S", seconds.count(), ms.count());
- }
-
- return std::format("P{}T{}{}{}", daysStr, hoursStr, minStr, secStr);
-}
-
-inline std::optional<std::string>
- toDurationStringFromUint(const uint64_t timeMs)
-{
- constexpr uint64_t maxTimeMs =
- static_cast<uint64_t>(std::chrono::milliseconds::max().count());
-
- if (maxTimeMs < timeMs)
- {
- return std::nullopt;
- }
-
- std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
- if (duration.empty())
- {
- return std::nullopt;
- }
-
- return std::make_optional(duration);
-}
-
-namespace details
-{
-// This code is left for support of gcc < 13 which didn't have support for
-// timezones. It should be removed at some point in the future.
-#if __cpp_lib_chrono < 201907L
-
-// Returns year/month/day triple in civil calendar
-// Preconditions: z is number of days since 1970-01-01 and is in the range:
-// [numeric_limits<Int>::min(),
-// numeric_limits<Int>::max()-719468].
-// Algorithm sourced from
-// https://howardhinnant.github.io/date_algorithms.html#civil_from_days
-// All constants are explained in the above
-template <class IntType>
-constexpr std::tuple<IntType, unsigned, unsigned>
- civilFromDays(IntType z) noexcept
-{
- z += 719468;
- IntType era = (z >= 0 ? z : z - 146096) / 146097;
- unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
- unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) /
- 365; // [0, 399]
- IntType y = static_cast<IntType>(yoe) + era * 400;
- unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
- unsigned mp = (5 * doy + 2) / 153; // [0, 11]
- unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
- unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12]
-
- return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d);
-}
-
-template <typename IntType, typename Period>
-std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t)
-{
- using seconds = std::chrono::duration<int>;
- using minutes = std::chrono::duration<int, std::ratio<60>>;
- using hours = std::chrono::duration<int, std::ratio<3600>>;
- using days = std::chrono::duration<
- IntType, std::ratio_multiply<hours::period, std::ratio<24>>>;
-
- // d is days since 1970-01-01
- days d = std::chrono::duration_cast<days>(t);
-
- // t is now time duration since midnight of day d
- t -= d;
-
- // break d down into year/month/day
- int year = 0;
- int month = 0;
- int day = 0;
- std::tie(year, month, day) = details::civilFromDays(d.count());
- // Check against limits. Can't go above year 9999, and can't go below epoch
- // (1970)
- if (year >= 10000)
- {
- year = 9999;
- month = 12;
- day = 31;
- t = days(1) - std::chrono::duration<IntType, Period>(1);
- }
- else if (year < 1970)
- {
- year = 1970;
- month = 1;
- day = 1;
- t = std::chrono::duration<IntType, Period>::zero();
- }
-
- hours hr = duration_cast<hours>(t);
- t -= hr;
-
- minutes mt = duration_cast<minutes>(t);
- t -= mt;
-
- seconds se = duration_cast<seconds>(t);
-
- t -= se;
-
- std::string subseconds;
- if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>)
- {
- using MilliDuration = std::chrono::duration<int, std::milli>;
- MilliDuration subsec = duration_cast<MilliDuration>(t);
- subseconds = std::format(".{:03}", subsec.count());
- }
- else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>)
- {
- using MicroDuration = std::chrono::duration<int, std::micro>;
- MicroDuration subsec = duration_cast<MicroDuration>(t);
- subseconds = std::format(".{:06}", subsec.count());
- }
-
- return std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}+00:00", year,
- month, day, hr.count(), mt.count(), se.count(),
- subseconds);
-}
-
-#else
-
-template <typename IntType, typename Period>
-std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> dur)
-{
- using namespace std::literals::chrono_literals;
-
- using SubType = std::chrono::duration<IntType, Period>;
-
- // d is days since 1970-01-01
- std::chrono::days days = std::chrono::floor<std::chrono::days>(dur);
- std::chrono::sys_days sysDays(days);
- std::chrono::year_month_day ymd(sysDays);
-
- // Enforce 3 constraints
- // the result cant under or overflow the calculation
- // the resulting string needs to be representable as 4 digits
- // The resulting string can't be before epoch
- if (dur.count() <= 0)
- {
- BMCWEB_LOG_WARNING("Underflow from value {}", dur.count());
- ymd = 1970y / std::chrono::January / 1d;
- dur = std::chrono::duration<IntType, Period>::zero();
- }
- else if (dur > SubType::max() - std::chrono::days(1))
- {
- BMCWEB_LOG_WARNING("Overflow from value {}", dur.count());
- ymd = 9999y / std::chrono::December / 31d;
- dur = std::chrono::days(1) - SubType(1);
- }
- else if (ymd.year() >= 10000y)
- {
- BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
- ymd = 9999y / std::chrono::December / 31d;
- dur = std::chrono::days(1) - SubType(1);
- }
- else if (ymd.year() < 1970y)
- {
- BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
- ymd = 1970y / std::chrono::January / 1d;
- dur = SubType::zero();
- }
- else
- {
- // t is now time duration since midnight of day d
- dur -= days;
- }
- std::chrono::hh_mm_ss<SubType> hms(dur);
-
- return std::format("{}T{}+00:00", ymd, hms);
-}
-#endif
-} // namespace details
+std::optional<std::string> toDurationStringFromUint(uint64_t timeMs);
// Returns the formatted date time string.
// Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
// the given |secondsSinceEpoch| is too large, we return the maximum supported
// date.
-inline std::string getDateTimeUint(uint64_t secondsSinceEpoch)
-{
- using DurationType = std::chrono::duration<uint64_t>;
- DurationType sinceEpoch(secondsSinceEpoch);
- return details::toISO8061ExtendedStr(sinceEpoch);
-}
+std::string getDateTimeUint(uint64_t secondsSinceEpoch);
// Returns the formatted date time string with millisecond precision
// Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
// the given |secondsSinceEpoch| is too large, we return the maximum supported
// date.
-inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)
-{
- using DurationType = std::chrono::duration<uint64_t, std::milli>;
- DurationType sinceEpoch(milliSecondsSinceEpoch);
- return details::toISO8061ExtendedStr(sinceEpoch);
-}
+std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch);
// Returns the formatted date time string with microsecond precision
-inline std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch)
-{
- using DurationType = std::chrono::duration<uint64_t, std::micro>;
- DurationType sinceEpoch(microSecondsSinceEpoch);
- return details::toISO8061ExtendedStr(sinceEpoch);
-}
+std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch);
-inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch)
-{
- using DurationType = std::chrono::duration<std::time_t>;
- DurationType sinceEpoch(secondsSinceEpoch);
- return details::toISO8061ExtendedStr(sinceEpoch);
-}
+std::string getDateTimeStdtime(std::time_t secondsSinceEpoch);
/**
* Returns the current Date, Time & the local Time Offset
@@ -416,24 +58,7 @@
* @return std::pair<std::string, std::string>, which consist
* of current DateTime & the TimeOffset strings respectively.
*/
-inline std::pair<std::string, std::string> getDateTimeOffsetNow()
-{
- std::time_t time = std::time(nullptr);
- std::string dateTime = getDateTimeStdtime(time);
-
- /* extract the local Time Offset value from the
- * received dateTime string.
- */
- std::string timeOffset("Z00:00");
- std::size_t lastPos = dateTime.size();
- std::size_t len = timeOffset.size();
- if (lastPos > len)
- {
- timeOffset = dateTime.substr(lastPos - len);
- }
-
- return std::make_pair(dateTime, timeOffset);
-}
+std::pair<std::string, std::string> getDateTimeOffsetNow();
using usSinceEpoch = std::chrono::duration<int64_t, std::micro>;
std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime);
@@ -448,36 +73,12 @@
*
* @return std::string which consist the datetime
*/
-inline std::optional<std::string> getDateTimeIso8601(std::string_view datetime)
-{
- std::optional<redfish::time_utils::usSinceEpoch> us =
- redfish::time_utils::dateStringToEpoch(datetime);
- if (!us)
- {
- return std::nullopt;
- }
- auto secondsDuration =
- std::chrono::duration_cast<std::chrono::seconds>(*us);
-
- return std::make_optional(redfish::time_utils::getDateTimeUint(
- static_cast<uint64_t>(secondsDuration.count())));
-}
+std::optional<std::string> getDateTimeIso8601(std::string_view datetime);
/**
* @brief ProductionDate report
*/
-inline void productionDateReport(crow::Response& res,
- const std::string& buildDate)
-{
- std::optional<std::string> valueStr =
- redfish::time_utils::getDateTimeIso8601(buildDate);
- if (!valueStr)
- {
- messages::internalError();
- return;
- }
- res.jsonValue["ProductionDate"] = *valueStr;
-}
+void productionDateReport(crow::Response& res, const std::string& buildDate);
} // namespace time_utils
} // namespace redfish
diff --git a/redfish-core/src/utils/time_utils.cpp b/redfish-core/src/utils/time_utils.cpp
index c01f0d9..1543758 100644
--- a/redfish-core/src/utils/time_utils.cpp
+++ b/redfish-core/src/utils/time_utils.cpp
@@ -2,22 +2,486 @@
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
#include "utils/time_utils.hpp"
+#include "error_messages.hpp"
+#include "http_response.hpp"
+#include "logging.hpp"
+
#include <version>
#if __cpp_lib_chrono < 201907L
#include "utils/extern/date.h"
#endif
-
#include <array>
+#include <charconv>
#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <ctime>
+#include <format>
#include <optional>
+#include <ratio>
#include <sstream>
#include <string>
#include <string_view>
+#include <system_error>
+#include <utility>
namespace redfish::time_utils
{
+/**
+ * @brief Convert string that represents value in Duration Format to its numeric
+ * equivalent.
+ */
+std::optional<std::chrono::milliseconds> fromDurationString(std::string_view v)
+{
+ std::chrono::milliseconds out = std::chrono::milliseconds::zero();
+ enum class ProcessingStage
+ {
+ // P1DT1H1M1.100S
+ P,
+ Days,
+ Hours,
+ Minutes,
+ Seconds,
+ Milliseconds,
+ Done,
+ };
+ ProcessingStage stage = ProcessingStage::P;
+
+ while (!v.empty())
+ {
+ if (stage == ProcessingStage::P)
+ {
+ if (v.front() != 'P')
+ {
+ return std::nullopt;
+ }
+ v.remove_prefix(1);
+ stage = ProcessingStage::Days;
+ continue;
+ }
+ if (stage == ProcessingStage::Days)
+ {
+ if (v.front() == 'T')
+ {
+ v.remove_prefix(1);
+ stage = ProcessingStage::Hours;
+ continue;
+ }
+ }
+ uint64_t ticks = 0;
+ auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks);
+ if (ec != std::errc())
+ {
+ BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v);
+ return std::nullopt;
+ }
+ size_t charactersRead = static_cast<size_t>(ptr - v.data());
+ if (ptr >= v.end())
+ {
+ BMCWEB_LOG_ERROR("Missing postfix");
+ return std::nullopt;
+ }
+ if (*ptr == 'D')
+ {
+ if (stage > ProcessingStage::Days)
+ {
+ return std::nullopt;
+ }
+ out += std::chrono::days(ticks);
+ }
+ else if (*ptr == 'H')
+ {
+ if (stage > ProcessingStage::Hours)
+ {
+ return std::nullopt;
+ }
+ out += std::chrono::hours(ticks);
+ }
+ else if (*ptr == 'M')
+ {
+ if (stage > ProcessingStage::Minutes)
+ {
+ return std::nullopt;
+ }
+ out += std::chrono::minutes(ticks);
+ }
+ else if (*ptr == '.')
+ {
+ if (stage > ProcessingStage::Seconds)
+ {
+ return std::nullopt;
+ }
+ out += std::chrono::seconds(ticks);
+ stage = ProcessingStage::Milliseconds;
+ }
+ else if (*ptr == 'S')
+ {
+ // We could be seeing seconds for the first time, (as is the case in
+ // 1S) or for the second time (in the case of 1.1S).
+ if (stage <= ProcessingStage::Seconds)
+ {
+ out += std::chrono::seconds(ticks);
+ stage = ProcessingStage::Milliseconds;
+ }
+ else if (stage > ProcessingStage::Milliseconds)
+ {
+ BMCWEB_LOG_ERROR("Got unexpected information at end of parse");
+ return std::nullopt;
+ }
+ else
+ {
+ // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S);
+ // Handle them all milliseconds are after the decimal point,
+ // so they need right padded.
+ if (charactersRead == 1)
+ {
+ ticks *= 100;
+ }
+ else if (charactersRead == 2)
+ {
+ ticks *= 10;
+ }
+ out += std::chrono::milliseconds(ticks);
+ stage = ProcessingStage::Milliseconds;
+ }
+ }
+ else
+ {
+ BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr);
+ return std::nullopt;
+ }
+
+ v.remove_prefix(charactersRead + 1U);
+ }
+ return out;
+}
+
+/**
+ * @brief Convert time value into duration format that is based on ISO 8601.
+ * Example output: "P12DT1M5.5S"
+ * Ref: Redfish Specification, Section 9.4.4. Duration values
+ */
+std::string toDurationString(std::chrono::milliseconds ms)
+{
+ if (ms < std::chrono::milliseconds::zero())
+ {
+ return "";
+ }
+
+ std::chrono::days days = std::chrono::floor<std::chrono::days>(ms);
+ ms -= days;
+
+ std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
+ ms -= hours;
+
+ std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
+ ms -= minutes;
+
+ std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
+ ms -= seconds;
+ std::string daysStr;
+ if (days.count() > 0)
+ {
+ daysStr = std::format("{}D", days.count());
+ }
+ std::string hoursStr;
+ if (hours.count() > 0)
+ {
+ hoursStr = std::format("{}H", hours.count());
+ }
+ std::string minStr;
+ if (minutes.count() > 0)
+ {
+ minStr = std::format("{}M", minutes.count());
+ }
+ std::string secStr;
+ if (seconds.count() != 0 || ms.count() != 0)
+ {
+ secStr = std::format("{}.{:03}S", seconds.count(), ms.count());
+ }
+
+ return std::format("P{}T{}{}{}", daysStr, hoursStr, minStr, secStr);
+}
+
+std::optional<std::string> toDurationStringFromUint(uint64_t timeMs)
+{
+ constexpr uint64_t maxTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count());
+
+ if (maxTimeMs < timeMs)
+ {
+ return std::nullopt;
+ }
+
+ std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
+ if (duration.empty())
+ {
+ return std::nullopt;
+ }
+
+ return std::make_optional(duration);
+}
+
+namespace details
+{
+// This code is left for support of gcc < 13 which didn't have support for
+// timezones. It should be removed at some point in the future.
+#if __cpp_lib_chrono < 201907L
+
+// Returns year/month/day triple in civil calendar
+// Preconditions: z is number of days since 1970-01-01 and is in the range:
+// [numeric_limits<Int>::min(),
+// numeric_limits<Int>::max()-719468].
+// Algorithm sourced from
+// https://howardhinnant.github.io/date_algorithms.html#civil_from_days
+// All constants are explained in the above
+template <class IntType>
+constexpr std::tuple<IntType, unsigned, unsigned>
+ civilFromDays(IntType z) noexcept
+{
+ z += 719468;
+ IntType era = (z >= 0 ? z : z - 146096) / 146097;
+ unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
+ unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) /
+ 365; // [0, 399]
+ IntType y = static_cast<IntType>(yoe) + era * 400;
+ unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
+ unsigned mp = (5 * doy + 2) / 153; // [0, 11]
+ unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
+ unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12]
+
+ return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d);
+}
+
+template <typename IntType, typename Period>
+std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t)
+{
+ using seconds = std::chrono::duration<int>;
+ using minutes = std::chrono::duration<int, std::ratio<60>>;
+ using hours = std::chrono::duration<int, std::ratio<3600>>;
+ using days = std::chrono::duration<
+ IntType, std::ratio_multiply<hours::period, std::ratio<24>>>;
+
+ // d is days since 1970-01-01
+ days d = std::chrono::duration_cast<days>(t);
+
+ // t is now time duration since midnight of day d
+ t -= d;
+
+ // break d down into year/month/day
+ int year = 0;
+ int month = 0;
+ int day = 0;
+ std::tie(year, month, day) = details::civilFromDays(d.count());
+ // Check against limits. Can't go above year 9999, and can't go below epoch
+ // (1970)
+ if (year >= 10000)
+ {
+ year = 9999;
+ month = 12;
+ day = 31;
+ t = days(1) - std::chrono::duration<IntType, Period>(1);
+ }
+ else if (year < 1970)
+ {
+ year = 1970;
+ month = 1;
+ day = 1;
+ t = std::chrono::duration<IntType, Period>::zero();
+ }
+
+ hours hr = std::chrono::duration_cast<hours>(t);
+ t -= hr;
+
+ minutes mt = std::chrono::duration_cast<minutes>(t);
+ t -= mt;
+
+ seconds se = std::chrono::duration_cast<seconds>(t);
+
+ t -= se;
+
+ std::string subseconds;
+ if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>)
+ {
+ using MilliDuration = std::chrono::duration<int, std::milli>;
+ MilliDuration subsec = std::chrono::duration_cast<MilliDuration>(t);
+ subseconds = std::format(".{:03}", subsec.count());
+ }
+ else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>)
+ {
+ using MicroDuration = std::chrono::duration<int, std::micro>;
+ MicroDuration subsec = std::chrono::duration_cast<MicroDuration>(t);
+ subseconds = std::format(".{:06}", subsec.count());
+ }
+
+ return std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}+00:00", year,
+ month, day, hr.count(), mt.count(), se.count(),
+ subseconds);
+}
+
+#else
+
+template <typename IntType, typename Period>
+
+std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> dur)
+{
+ using namespace std::literals::chrono_literals;
+
+ using SubType = std::chrono::duration<IntType, Period>;
+
+ // d is days since 1970-01-01
+ std::chrono::days days = std::chrono::floor<std::chrono::days>(dur);
+ std::chrono::sys_days sysDays(days);
+ std::chrono::year_month_day ymd(sysDays);
+
+ // Enforce 3 constraints
+ // the result cant under or overflow the calculation
+ // the resulting string needs to be representable as 4 digits
+ // The resulting string can't be before epoch
+ if (dur.count() <= 0)
+ {
+ BMCWEB_LOG_WARNING("Underflow from value {}", dur.count());
+ ymd = 1970y / std::chrono::January / 1d;
+ dur = std::chrono::duration<IntType, Period>::zero();
+ }
+ else if (dur > SubType::max() - std::chrono::days(1))
+ {
+ BMCWEB_LOG_WARNING("Overflow from value {}", dur.count());
+ ymd = 9999y / std::chrono::December / 31d;
+ dur = std::chrono::days(1) - SubType(1);
+ }
+ else if (ymd.year() >= 10000y)
+ {
+ BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
+ ymd = 9999y / std::chrono::December / 31d;
+ dur = std::chrono::days(1) - SubType(1);
+ }
+ else if (ymd.year() < 1970y)
+ {
+ BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
+ ymd = 1970y / std::chrono::January / 1d;
+ dur = SubType::zero();
+ }
+ else
+ {
+ // t is now time duration since midnight of day d
+ dur -= days;
+ }
+ std::chrono::hh_mm_ss<SubType> hms(dur);
+
+ return std::format("{}T{}+00:00", ymd, hms);
+}
+
+#endif
+} // namespace details
+
+// Returns the formatted date time string.
+// Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
+// the given |secondsSinceEpoch| is too large, we return the maximum supported
+// date.
+std::string getDateTimeUint(uint64_t secondsSinceEpoch)
+{
+ using DurationType = std::chrono::duration<uint64_t>;
+ DurationType sinceEpoch(secondsSinceEpoch);
+ return details::toISO8061ExtendedStr(sinceEpoch);
+}
+
+// Returns the formatted date time string with millisecond precision
+// Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
+// the given |secondsSinceEpoch| is too large, we return the maximum supported
+// date.
+std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)
+{
+ using DurationType = std::chrono::duration<uint64_t, std::milli>;
+ DurationType sinceEpoch(milliSecondsSinceEpoch);
+ return details::toISO8061ExtendedStr(sinceEpoch);
+}
+
+// Returns the formatted date time string with microsecond precision
+std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch)
+{
+ using DurationType = std::chrono::duration<uint64_t, std::micro>;
+ DurationType sinceEpoch(microSecondsSinceEpoch);
+ return details::toISO8061ExtendedStr(sinceEpoch);
+}
+
+std::string getDateTimeStdtime(std::time_t secondsSinceEpoch)
+{
+ using DurationType = std::chrono::duration<std::time_t>;
+ DurationType sinceEpoch(secondsSinceEpoch);
+ return details::toISO8061ExtendedStr(sinceEpoch);
+}
+
+/**
+ * Returns the current Date, Time & the local Time Offset
+ * information in a pair
+ *
+ * @param[in] None
+ *
+ * @return std::pair<std::string, std::string>, which consist
+ * of current DateTime & the TimeOffset strings respectively.
+ */
+std::pair<std::string, std::string> getDateTimeOffsetNow()
+{
+ std::time_t time = std::time(nullptr);
+ std::string dateTime = getDateTimeStdtime(time);
+
+ /* extract the local Time Offset value from the
+ * received dateTime string.
+ */
+ std::string timeOffset("Z00:00");
+ std::size_t lastPos = dateTime.size();
+ std::size_t len = timeOffset.size();
+ if (lastPos > len)
+ {
+ timeOffset = dateTime.substr(lastPos - len);
+ }
+
+ return std::make_pair(dateTime, timeOffset);
+}
+
+using usSinceEpoch = std::chrono::duration<int64_t, std::micro>;
+
+/**
+ * @brief Returns the datetime in ISO 8601 format
+ *
+ * @param[in] std::string_view the date of item manufacture in ISO 8601 format,
+ * either as YYYYMMDD or YYYYMMDDThhmmssZ
+ * Ref: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/
+ * xyz/openbmc_project/Inventory/Decorator/Asset.interface.yaml#L16
+ *
+ * @return std::string which consist the datetime
+ */
+std::optional<std::string> getDateTimeIso8601(std::string_view datetime)
+{
+ std::optional<usSinceEpoch> us = dateStringToEpoch(datetime);
+ if (!us)
+ {
+ return std::nullopt;
+ }
+ auto secondsDuration =
+ std::chrono::duration_cast<std::chrono::seconds>(*us);
+
+ return std::make_optional(
+ getDateTimeUint(static_cast<uint64_t>(secondsDuration.count())));
+}
+
+/**
+ * @brief ProductionDate report
+ */
+void productionDateReport(crow::Response& res, const std::string& buildDate)
+{
+ std::optional<std::string> valueStr = getDateTimeIso8601(buildDate);
+ if (!valueStr)
+ {
+ messages::internalError();
+ return;
+ }
+ res.jsonValue["ProductionDate"] = *valueStr;
+}
+
std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime)
{
for (const char* format : std::to_array(