cpp-utilities/chrono/timespan.cpp

277 lines
9.9 KiB
C++
Raw Permalink Normal View History

#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
#include "../conversion/stringbuilder.h"
2015-09-06 20:19:09 +02:00
#include "../conversion/stringconversion.h"
2015-04-22 18:36:40 +02:00
#include <array>
#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;
namespace CppUtilities {
2015-04-22 18:36:40 +02:00
/// \cond
#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
{
#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
CPP_UTILITIES_UNUSED(fmt)
2023-12-05 11:42:26 +01:00
auto r = std::from_chars_result{ nullptr, std::errc() };
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
/*!
* \class TimeSpan
2015-04-22 18:36:40 +02:00
* \brief Represents a time interval.
*
* 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.
*/
/*!
* \brief Parses the given C-style string as TimeSpan.
* \throws Throws a ConversionException if the specified \a str does not match the expected format.
*
* The expected format is "days:hours:minutes:seconds", e.g. "5:31:4.521" for 5 hours, 31 minutes
* and 4.521 seconds. So parts at the front can be omitted and the parts can be fractions. The
* 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
*/
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();
}
auto parts = std::array<double, 4>();
auto partsPresent = std::size_t();
auto specificationsWithUnits = TimeSpan();
for (const char *i = str;; ++i) {
// 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
if (*i != separator && *i != '\0') {
continue;
}
// allow only up to 4 parts (days, hours, minutes and seconds)
if (partsPresent == 4) {
throw ConversionException("too many separators/parts");
}
// parse value of the part
auto valuePart = 0.0;
auto valueWithUnit = TimeSpan();
if (str != i) {
// parse value of the part as double
const auto res = from_chars(str, i, valuePart);
if (res.ec != std::errc()) {
const auto part = std::string_view(str, static_cast<std::string_view::size_type>(i - str));
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':
valueWithUnit = TimeSpan::fromDays(7.0 * valuePart);
continue;
case 'd':
valueWithUnit = TimeSpan::fromDays(valuePart);
continue;
case 'h':
valueWithUnit = TimeSpan::fromHours(valuePart);
continue;
case 'm':
valueWithUnit = TimeSpan::fromMinutes(valuePart);
continue;
case 's':
valueWithUnit = TimeSpan::fromSeconds(valuePart);
continue;
2023-12-05 11:42:26 +01:00
default:;
}
}
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()) {
parts[partsPresent++] = valuePart;
} 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) {
str = i + 1;
} 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
}
// compute and return total value from specifications with units and parts
switch (partsPresent) {
2015-04-22 18:36:40 +02:00
case 1:
return specificationsWithUnits + TimeSpan::fromSeconds(parts.front());
2015-04-22 18:36:40 +02:00
case 2:
return specificationsWithUnits + TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
2015-04-22 18:36:40 +02:00
case 3:
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
*
* 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.
*/
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:
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()) {
result = "0 s";
return;
2015-04-22 18:36:40 +02:00
} else {
if (!fullSeconds && positive.totalMilliseconds() < 1.0) {
s << setprecision(2) << positive.totalMicroseconds() << " µs";
2017-06-25 15:09:16 +02:00
} else {
bool needWhitespace = false;
2017-06-25 15:09:16 +02:00
if (const int days = positive.days()) {
needWhitespace = true;
s << days << " d";
2017-06-25 15:09:16 +02:00
}
if (const int hours = positive.hours()) {
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << hours << " h";
2017-06-25 15:09:16 +02:00
}
if (const int minutes = positive.minutes()) {
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << minutes << " min";
2017-06-25 15:09:16 +02:00
}
if (const int seconds = positive.seconds()) {
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << seconds << " s";
2017-06-25 15:09:16 +02:00
}
if (!fullSeconds) {
2017-06-25 15:09:16 +02:00
if (const int milliseconds = positive.milliseconds()) {
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;
case TimeSpanOutputFormat::TotalSeconds:
if (fullSeconds) {
s << setprecision(0);
} else {
s << setprecision(10);
}
s << positive.totalSeconds();
break;
2015-04-22 18:36:40 +02:00
}
result = s.str();
2015-04-22 18:36:40 +02:00
}
} // namespace CppUtilities