blob: b4bc778c604678f94cc0ca5a673531e1fb724e91 [file] [log] [blame]
#pragma once
#include "logging.hpp"
#include <algorithm>
#include <charconv>
#include <chrono>
#include <cmath>
#include <compare>
#include <cstddef>
#include <optional>
#include <ratio>
#include <string>
#include <string_view>
#include <system_error>
namespace redfish
{
namespace time_utils
{
namespace details
{
constexpr intmax_t dayDuration = static_cast<intmax_t>(24 * 60 * 60);
using Days = std::chrono::duration<long long, std::ratio<dayDuration>>;
inline void leftZeroPadding(std::string& str, const std::size_t padding)
{
if (str.size() < padding)
{
str.insert(0, padding - str.size(), '0');
}
}
template <typename FromTime>
bool fromDurationItem(std::string_view& fmt, const char postfix,
std::chrono::milliseconds& out)
{
const size_t pos = fmt.find(postfix);
if (pos == std::string::npos)
{
return true;
}
if ((pos + 1U) > fmt.size())
{
return false;
}
const char* end = nullptr;
std::chrono::milliseconds::rep ticks = 0;
if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
{
end = fmt.data() + std::min<size_t>(pos, 3U);
}
else
{
end = fmt.data() + pos;
}
auto [ptr, ec] = std::from_chars(fmt.data(), end, ticks);
if (ptr != end || ec != std::errc())
{
BMCWEB_LOG_ERROR << "Failed to convert string to decimal with err: "
<< static_cast<int>(ec) << "("
<< std::make_error_code(ec).message() << "), ptr{"
<< static_cast<const void*>(ptr) << "} != end{"
<< static_cast<const void*>(end) << "})";
return false;
}
if constexpr (std::is_same_v<FromTime, std::chrono::milliseconds>)
{
ticks *= static_cast<std::chrono::milliseconds::rep>(
std::pow(10, 3 - std::min<size_t>(pos, 3U)));
}
if (ticks < 0)
{
return false;
}
out += FromTime(ticks);
const auto maxConversionRange =
std::chrono::duration_cast<FromTime>(std::chrono::milliseconds::max())
.count();
if (out < FromTime(ticks) || maxConversionRange < ticks)
{
return false;
}
fmt.remove_prefix(pos + 1U);
return true;
}
} // namespace details
/**
* @brief Convert string that represents value in Duration Format to its numeric
* equivalent.
*/
inline std::optional<std::chrono::milliseconds>
fromDurationString(const std::string& str)
{
std::chrono::milliseconds out = std::chrono::milliseconds::zero();
std::string_view v = str;
if (v.empty())
{
return out;
}
if (v.front() != 'P')
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
v.remove_prefix(1);
if (!details::fromDurationItem<details::Days>(v, 'D', out))
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
if (v.empty())
{
return out;
}
if (v.front() != 'T')
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
v.remove_prefix(1);
if (!details::fromDurationItem<std::chrono::hours>(v, 'H', out) ||
!details::fromDurationItem<std::chrono::minutes>(v, 'M', out))
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
if (v.find('.') != std::string::npos && v.find('S') != std::string::npos)
{
if (!details::fromDurationItem<std::chrono::seconds>(v, '.', out) ||
!details::fromDurationItem<std::chrono::milliseconds>(v, 'S', out))
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
}
else if (!details::fromDurationItem<std::chrono::seconds>(v, 'S', out))
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
if (!v.empty())
{
BMCWEB_LOG_ERROR << "Invalid duration format: " << str;
return std::nullopt;
}
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
*/
inline std::string toDurationString(std::chrono::milliseconds ms)
{
if (ms < std::chrono::milliseconds::zero())
{
return "";
}
std::string fmt;
fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS"));
details::Days days = std::chrono::floor<details::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;
fmt = "P";
if (days.count() > 0)
{
fmt += std::to_string(days.count()) + "D";
}
fmt += "T";
if (hours.count() > 0)
{
fmt += std::to_string(hours.count()) + "H";
}
if (minutes.count() > 0)
{
fmt += std::to_string(minutes.count()) + "M";
}
if (seconds.count() != 0 || ms.count() != 0)
{
fmt += std::to_string(seconds.count()) + ".";
std::string msStr = std::to_string(ms.count());
details::leftZeroPadding(msStr, 3);
fmt += msStr + "S";
}
return fmt;
}
inline std::optional<std::string>
toDurationStringFromUint(const uint64_t timeMs)
{
static const 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 time_utils
} // namespace redfish