Support units in `TimeSpan::fromString()`
This commit is contained in:
parent
a425363eac
commit
11de58141b
|
@ -2,8 +2,10 @@
|
|||
|
||||
#include "./timespan.h"
|
||||
|
||||
#include "../conversion/stringbuilder.h"
|
||||
#include "../conversion/stringconversion.h"
|
||||
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
@ -31,9 +33,13 @@ namespace CppUtilities {
|
|||
* \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", eg. "5:31:4.521" for 5 hours, 31 minutes
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
TimeSpan TimeSpan::fromString(const char *str, char separator)
|
||||
{
|
||||
|
@ -43,32 +49,98 @@ TimeSpan TimeSpan::fromString(const char *str, char separator)
|
|||
|
||||
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;
|
||||
}
|
||||
if (partsPresent >= 4) {
|
||||
throw ConversionException("the time span specifications contains too many separators");
|
||||
|
||||
// allow only up to 4 parts (days, hours, minutes and seconds)
|
||||
if (partsPresent == 4) {
|
||||
throw ConversionException("too many separators/parts");
|
||||
}
|
||||
const auto part = std::string_view(str, i - str);
|
||||
parts[partsPresent++] = part.empty() ? 0.0 : stringToNumber<double>(part);
|
||||
|
||||
// parse value of the part
|
||||
auto part = 0.0;
|
||||
auto valueWithUnit = TimeSpan();
|
||||
if (str != i) {
|
||||
// parse value of the part as double
|
||||
const auto res = std::from_chars(str, i, part);
|
||||
if (res.ec != std::errc()) {
|
||||
const auto part = std::string_view(str, 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 * part);
|
||||
continue;
|
||||
case 'd':
|
||||
valueWithUnit = TimeSpan::fromDays(part);
|
||||
continue;
|
||||
case 'h':
|
||||
valueWithUnit = TimeSpan::fromHours(part);
|
||||
continue;
|
||||
case 'm':
|
||||
valueWithUnit = TimeSpan::fromMinutes(part);
|
||||
continue;
|
||||
case 's':
|
||||
valueWithUnit = TimeSpan::fromSeconds(part);
|
||||
continue;
|
||||
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++] = part;
|
||||
} else {
|
||||
specificationsWithUnits += valueWithUnit;
|
||||
}
|
||||
|
||||
// expect next part starting after the separator or stop if terminator reached
|
||||
if (*i == separator) {
|
||||
str = i + 1;
|
||||
}
|
||||
if (*i == '\0') {
|
||||
} else if (*i == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// compute and return total value from specifications with units and parts
|
||||
switch (partsPresent) {
|
||||
case 1:
|
||||
return TimeSpan::fromSeconds(parts.front());
|
||||
return specificationsWithUnits + TimeSpan::fromSeconds(parts.front());
|
||||
case 2:
|
||||
return TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
|
||||
return specificationsWithUnits + TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
|
||||
case 3:
|
||||
return TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
|
||||
return specificationsWithUnits + TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
|
||||
default:
|
||||
return TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2]) + TimeSpan::fromSeconds(parts[3]);
|
||||
return specificationsWithUnits + TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2]) + TimeSpan::fromSeconds(parts[3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -323,6 +323,12 @@ void ChronoTests::testTimeSpan()
|
|||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(5.5), TimeSpan::fromString("5:30"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromHours(7.0) + TimeSpan::fromMinutes(5.5), TimeSpan::fromString("7:5:30"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0), TimeSpan::fromString("14:::"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0), TimeSpan::fromString("14d"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromHours(5.0), TimeSpan::fromString("14d 5h"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromMinutes(5.0), TimeSpan::fromString(" 14d 5m"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromMinutes(5.0) + TimeSpan::fromSeconds(24.5), TimeSpan::fromString("2 w 24.5s 5m "));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromSeconds(24.5), TimeSpan::fromString("2 w 24.5"));
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromString("1:2:3:4"), TimeSpan::fromString("2 w 1:2:3:4"));
|
||||
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("2:34a:53:32.5"), ConversionException);
|
||||
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("1:2:3:4:5"), ConversionException);
|
||||
|
||||
|
|
Loading…
Reference in New Issue