2022-02-15 23:00:18 +01:00
|
|
|
#define CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
|
|
|
|
|
2015-09-06 20:19:09 +02:00
|
|
|
#include "./timespan.h"
|
2015-04-22 18:36:40 +02:00
|
|
|
|
2023-11-23 20:20:07 +01:00
|
|
|
#include "../conversion/stringbuilder.h"
|
2015-09-06 20:19:09 +02:00
|
|
|
#include "../conversion/stringconversion.h"
|
2015-04-22 18:36:40 +02:00
|
|
|
|
2023-12-07 10:44:27 +01:00
|
|
|
#include <array>
|
2023-11-23 20:20:07 +01:00
|
|
|
#include <charconv>
|
2015-04-22 18:36:40 +02:00
|
|
|
#include <cmath>
|
2023-12-05 11:42:26 +01:00
|
|
|
#include <cstdlib>
|
2015-04-22 18:36:40 +02:00
|
|
|
#include <iomanip>
|
2017-05-01 03:13:11 +02:00
|
|
|
#include <sstream>
|
2015-04-22 18:36:40 +02:00
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 21:56:46 +02:00
|
|
|
|
|
|
|
namespace CppUtilities {
|
2015-04-22 18:36:40 +02:00
|
|
|
|
2023-11-25 23:18:30 +01:00
|
|
|
/// \cond
|
2023-12-11 19:58:35 +01:00
|
|
|
|
|
|
|
#if defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 10
|
|
|
|
enum class chars_format { scientific = 1, fixed = 2, hex = 4, general = fixed | scientific };
|
|
|
|
#else
|
|
|
|
using char_format = std::chars_format;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
inline std::from_chars_result from_chars(const char *first, const char *last, double &value, chars_format fmt = chars_format::general) noexcept
|
2023-11-25 23:18:30 +01:00
|
|
|
{
|
2023-12-06 22:34:33 +01:00
|
|
|
#if defined(_LIBCPP_VERSION) || (defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 11)
|
|
|
|
// workaround std::from_chars() not being implemented for floating point numbers in libc++ and older libstdc++ versions
|
2023-11-25 23:18:30 +01:00
|
|
|
CPP_UTILITIES_UNUSED(fmt)
|
2023-12-05 11:42:26 +01:00
|
|
|
auto r = std::from_chars_result{ nullptr, std::errc() };
|
2023-11-25 23:18:30 +01:00
|
|
|
auto s = std::string(first, last);
|
|
|
|
auto l = s.data() + s.size();
|
|
|
|
auto d = std::strtod(s.data(), &l);
|
|
|
|
if (errno == ERANGE) {
|
|
|
|
r.ec = std::errc::result_out_of_range;
|
|
|
|
} else if (s.data() == l) {
|
|
|
|
r.ec = std::errc::invalid_argument;
|
|
|
|
} else {
|
|
|
|
value = d;
|
|
|
|
r.ptr = first + (l - s.data());
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
#else
|
|
|
|
return std::from_chars(first, last, value, fmt);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
/// \endcond
|
|
|
|
|
2015-04-22 18:36:40 +02:00
|
|
|
/*!
|
2019-06-10 21:56:46 +02:00
|
|
|
* \class TimeSpan
|
2015-04-22 18:36:40 +02:00
|
|
|
* \brief Represents a time interval.
|
2017-12-03 01:45:11 +01:00
|
|
|
*
|
|
|
|
* Note that the TimeSpan class is meant to express a time interval independently of the
|
|
|
|
* concrete starting DateTime and end DateTime and hence can not be expressed in years
|
|
|
|
* and month. For that use case, use the Period class instead.
|
|
|
|
*
|
2015-04-22 18:36:40 +02:00
|
|
|
* \remarks Time values are measured in 100-nanosecond units called ticks.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
2016-08-30 19:59:04 +02:00
|
|
|
* \brief Parses the given C-style string as TimeSpan.
|
2017-12-03 01:45:11 +01:00
|
|
|
* \throws Throws a ConversionException if the specified \a str does not match the expected format.
|
|
|
|
*
|
2023-11-23 20:20:07 +01:00
|
|
|
* The expected format is "days:hours:minutes:seconds", e.g. "5:31:4.521" for 5 hours, 31 minutes
|
2017-12-03 01:45:11 +01:00
|
|
|
* and 4.521 seconds. So parts at the front can be omitted and the parts can be fractions. The
|
2023-11-23 20:20:07 +01:00
|
|
|
* colon can be changed by specifying another \a separator. White-spaces before and after parts
|
|
|
|
* are ignored.
|
|
|
|
*
|
|
|
|
* It is also possible to specify one or more values with a unit, e.g. "2w 1d 5h 1m 0.5s".
|
|
|
|
* The units "w" (weeks), "d" (days), "h" (hours), "m" (minutes) and "s" (seconds) are supported.
|
2015-04-22 18:36:40 +02:00
|
|
|
*/
|
2016-08-30 19:59:04 +02:00
|
|
|
TimeSpan TimeSpan::fromString(const char *str, char separator)
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
2017-06-25 15:09:16 +02:00
|
|
|
if (!*str) {
|
|
|
|
return TimeSpan();
|
|
|
|
}
|
|
|
|
|
2023-11-23 18:48:48 +01:00
|
|
|
auto parts = std::array<double, 4>();
|
|
|
|
auto partsPresent = std::size_t();
|
2023-11-23 20:20:07 +01:00
|
|
|
auto specificationsWithUnits = TimeSpan();
|
|
|
|
|
2023-11-23 18:48:48 +01:00
|
|
|
for (const char *i = str;; ++i) {
|
2023-11-23 20:20:07 +01:00
|
|
|
// skip over white-spaces
|
|
|
|
if (*i == ' ' && i == str) {
|
|
|
|
str = i + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// consider non-separator and non-terminator characters as part to be interpreted as number
|
2023-11-23 18:48:48 +01:00
|
|
|
if (*i != separator && *i != '\0') {
|
|
|
|
continue;
|
|
|
|
}
|
2023-11-23 20:20:07 +01:00
|
|
|
|
|
|
|
// allow only up to 4 parts (days, hours, minutes and seconds)
|
|
|
|
if (partsPresent == 4) {
|
|
|
|
throw ConversionException("too many separators/parts");
|
2023-11-23 18:48:48 +01:00
|
|
|
}
|
2023-11-23 20:20:07 +01:00
|
|
|
|
|
|
|
// parse value of the part
|
2023-11-25 23:15:29 +01:00
|
|
|
auto valuePart = 0.0;
|
2023-11-23 20:20:07 +01:00
|
|
|
auto valueWithUnit = TimeSpan();
|
|
|
|
if (str != i) {
|
|
|
|
// parse value of the part as double
|
2023-11-25 23:15:29 +01:00
|
|
|
const auto res = from_chars(str, i, valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
if (res.ec != std::errc()) {
|
2023-11-25 23:15:29 +01:00
|
|
|
const auto part = std::string_view(str, static_cast<std::string_view::size_type>(i - str));
|
2023-11-23 20:20:07 +01:00
|
|
|
if (res.ec == std::errc::result_out_of_range) {
|
|
|
|
throw ConversionException(argsToString("part \"", part, "\" is too large"));
|
|
|
|
} else {
|
|
|
|
throw ConversionException(argsToString("part \"", part, "\" cannot be interpreted as floating point number"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// handle remaining characters; detect a possibly present unit suffix
|
|
|
|
for (const char *suffix = res.ptr; suffix != i; ++suffix) {
|
|
|
|
if (*suffix == ' ') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (valueWithUnit.isNull()) {
|
|
|
|
switch (*suffix) {
|
|
|
|
case 'w':
|
2023-11-25 23:15:29 +01:00
|
|
|
valueWithUnit = TimeSpan::fromDays(7.0 * valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
continue;
|
|
|
|
case 'd':
|
2023-11-25 23:15:29 +01:00
|
|
|
valueWithUnit = TimeSpan::fromDays(valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
continue;
|
|
|
|
case 'h':
|
2023-11-25 23:15:29 +01:00
|
|
|
valueWithUnit = TimeSpan::fromHours(valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
continue;
|
|
|
|
case 'm':
|
2023-11-25 23:15:29 +01:00
|
|
|
valueWithUnit = TimeSpan::fromMinutes(valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
continue;
|
|
|
|
case 's':
|
2023-11-25 23:15:29 +01:00
|
|
|
valueWithUnit = TimeSpan::fromSeconds(valuePart);
|
2023-11-23 20:20:07 +01:00
|
|
|
continue;
|
2023-12-05 11:42:26 +01:00
|
|
|
default:;
|
2023-11-23 20:20:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*suffix >= '0' && *suffix <= '9') {
|
|
|
|
str = i = suffix;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
throw ConversionException(argsToString("unexpected character \"", *suffix, '\"'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set part value; add value with unit
|
|
|
|
if (valueWithUnit.isNull()) {
|
2023-11-25 23:15:29 +01:00
|
|
|
parts[partsPresent++] = valuePart;
|
2023-11-23 20:20:07 +01:00
|
|
|
} else {
|
|
|
|
specificationsWithUnits += valueWithUnit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// expect next part starting after the separator or stop if terminator reached
|
2017-05-01 03:13:11 +02:00
|
|
|
if (*i == separator) {
|
2023-11-23 18:48:48 +01:00
|
|
|
str = i + 1;
|
2023-11-23 20:20:07 +01:00
|
|
|
} else if (*i == '\0') {
|
2015-04-22 18:36:40 +02:00
|
|
|
break;
|
2016-01-27 00:14:20 +01:00
|
|
|
}
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
2016-08-30 19:59:04 +02:00
|
|
|
|
2023-11-23 20:20:07 +01:00
|
|
|
// compute and return total value from specifications with units and parts
|
2023-11-23 18:48:48 +01:00
|
|
|
switch (partsPresent) {
|
2015-04-22 18:36:40 +02:00
|
|
|
case 1:
|
2023-11-23 20:20:07 +01:00
|
|
|
return specificationsWithUnits + TimeSpan::fromSeconds(parts.front());
|
2015-04-22 18:36:40 +02:00
|
|
|
case 2:
|
2023-11-23 20:20:07 +01:00
|
|
|
return specificationsWithUnits + TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
|
2015-04-22 18:36:40 +02:00
|
|
|
case 3:
|
2023-11-23 20:20:07 +01:00
|
|
|
return specificationsWithUnits + TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
|
2015-04-22 18:36:40 +02:00
|
|
|
default:
|
2023-12-05 11:42:26 +01:00
|
|
|
return specificationsWithUnits + TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2])
|
|
|
|
+ TimeSpan::fromSeconds(parts[3]);
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-01-18 23:41:30 +01:00
|
|
|
* \brief Converts the value of the current TimeSpan object to its equivalent std::string representation
|
|
|
|
* according the given \a format.
|
2015-04-22 18:36:40 +02:00
|
|
|
*
|
2017-08-23 23:04:22 +02:00
|
|
|
* If \a fullSeconds is true the time interval will be rounded to full seconds.
|
2015-04-22 18:36:40 +02:00
|
|
|
*
|
|
|
|
* The string representation will be stored in \a result.
|
|
|
|
*/
|
2017-08-23 23:04:22 +02:00
|
|
|
void TimeSpan::toString(string &result, TimeSpanOutputFormat format, bool fullSeconds) const
|
2015-04-22 18:36:40 +02:00
|
|
|
{
|
|
|
|
stringstream s(stringstream::in | stringstream::out);
|
2017-06-25 15:09:16 +02:00
|
|
|
TimeSpan positive(m_ticks);
|
|
|
|
if (positive.isNegative()) {
|
|
|
|
s << '-';
|
|
|
|
positive.m_ticks = -positive.m_ticks;
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
switch (format) {
|
2015-04-22 18:36:40 +02:00
|
|
|
case TimeSpanOutputFormat::Normal:
|
2017-08-23 23:04:22 +02:00
|
|
|
s << setfill('0') << setw(2) << floor(positive.totalHours()) << ":" << setw(2) << positive.minutes() << ":" << setw(2) << positive.seconds();
|
|
|
|
if (!fullSeconds) {
|
|
|
|
const int milli(positive.milliseconds());
|
|
|
|
const int micro(positive.microseconds());
|
|
|
|
const int nano(positive.nanoseconds());
|
|
|
|
if (milli || micro || nano) {
|
|
|
|
s << '.' << setw(3) << milli;
|
|
|
|
if (micro || nano) {
|
|
|
|
s << setw(3) << micro;
|
|
|
|
if (nano) {
|
|
|
|
s << nano / TimeSpan::nanosecondsPerTick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-04-22 18:36:40 +02:00
|
|
|
break;
|
|
|
|
case TimeSpanOutputFormat::WithMeasures:
|
2017-05-01 03:13:11 +02:00
|
|
|
if (isNull()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
result = "0 s";
|
|
|
|
return;
|
2015-04-22 18:36:40 +02:00
|
|
|
} else {
|
2017-08-23 23:04:22 +02:00
|
|
|
if (!fullSeconds && positive.totalMilliseconds() < 1.0) {
|
|
|
|
s << setprecision(2) << positive.totalMicroseconds() << " µs";
|
2017-06-25 15:09:16 +02:00
|
|
|
} else {
|
2017-08-23 23:04:22 +02:00
|
|
|
bool needWhitespace = false;
|
2017-06-25 15:09:16 +02:00
|
|
|
if (const int days = positive.days()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
needWhitespace = true;
|
|
|
|
s << days << " d";
|
2017-06-25 15:09:16 +02:00
|
|
|
}
|
|
|
|
if (const int hours = positive.hours()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
needWhitespace = true;
|
|
|
|
s << hours << " h";
|
2017-06-25 15:09:16 +02:00
|
|
|
}
|
|
|
|
if (const int minutes = positive.minutes()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
needWhitespace = true;
|
|
|
|
s << minutes << " min";
|
2017-06-25 15:09:16 +02:00
|
|
|
}
|
|
|
|
if (const int seconds = positive.seconds()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
needWhitespace = true;
|
|
|
|
s << seconds << " s";
|
2017-06-25 15:09:16 +02:00
|
|
|
}
|
2017-08-23 23:04:22 +02:00
|
|
|
if (!fullSeconds) {
|
2017-06-25 15:09:16 +02:00
|
|
|
if (const int milliseconds = positive.milliseconds()) {
|
2017-08-23 23:04:22 +02:00
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
needWhitespace = true;
|
|
|
|
s << milliseconds << " ms";
|
|
|
|
}
|
|
|
|
if (const int microseconds = positive.microseconds()) {
|
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
needWhitespace = true;
|
|
|
|
s << microseconds << " µs";
|
|
|
|
}
|
|
|
|
if (const int nanoseconds = positive.nanoseconds()) {
|
|
|
|
if (needWhitespace)
|
|
|
|
s << ' ';
|
|
|
|
s << nanoseconds << " ns";
|
2017-06-25 15:09:16 +02:00
|
|
|
}
|
|
|
|
}
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2017-11-27 10:24:26 +01:00
|
|
|
case TimeSpanOutputFormat::TotalSeconds:
|
|
|
|
if (fullSeconds) {
|
|
|
|
s << setprecision(0);
|
2017-11-29 23:11:26 +01:00
|
|
|
} else {
|
|
|
|
s << setprecision(10);
|
2017-11-27 10:24:26 +01:00
|
|
|
}
|
|
|
|
s << positive.totalSeconds();
|
|
|
|
break;
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
2017-08-23 23:04:22 +02:00
|
|
|
result = s.str();
|
2015-04-22 18:36:40 +02:00
|
|
|
}
|
2019-06-10 21:56:46 +02:00
|
|
|
|
|
|
|
} // namespace CppUtilities
|