chrono utils: Improve handling micro- and nanoseconds

DateTime and TimeSpan have an accuracy of 100 nanoseconds.

This commit ensures this is also handled in toString() and
fromString() methods and adds relevant convenience methods.
experiment/srcref_basic_cfg
Martchus 6 years ago
parent 9c8bb44843
commit 357ede4ee7

@ -127,8 +127,8 @@ set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities")
set(META_VERSION_MAJOR 4)
set(META_VERSION_MINOR 9)
set(META_VERSION_PATCH 2)
set(META_VERSION_MINOR 10)
set(META_VERSION_PATCH 0)
# find required 3rd party libraries
include(3rdParty)

@ -222,7 +222,19 @@ string DateTime::toIsoString(TimeSpan timeZoneDelta) const
stringstream s(stringstream::in | stringstream::out);
s << setfill('0');
s << setw(4) << year() << '-' << setw(2) << month() << '-' << setw(2) << day() << 'T' << setw(2) << hour() << ':' << setw(2) << minute() << ':'
<< setw(2) << second() << '.' << setw(3) << millisecond();
<< setw(2) << second();
const int milli(millisecond());
const int micro(microsecond());
const int nano(nanosecond());
if (milli || micro || nano) {
s << '.' << setw(3) << milli;
if (micro || nano) {
s << setw(3) << micro;
if (nano) {
s << nano / TimeSpan::nanosecondsPerTick;
}
}
}
if (!timeZoneDelta.isNull()) {
s << (timeZoneDelta.isNegative() ? '-' : '+');
s << setw(2) << timeZoneDelta.hours() << ':' << setw(2) << timeZoneDelta.minutes();
@ -302,13 +314,13 @@ uint64 DateTime::dateToTicks(int year, int month, int day)
if (!inRangeInclMax(month, 1, 12)) {
throw ConversionException("month is out of range");
}
const int *daysToMonth = isLeapYear(year) ? m_daysToMonth366 : m_daysToMonth365;
const auto *daysToMonth = reinterpret_cast<const unsigned int *>(isLeapYear(year) ? m_daysToMonth366 : m_daysToMonth365);
int passedMonth = month - 1;
if (!inRangeInclMax(day, 1, daysToMonth[month] - daysToMonth[passedMonth])) {
throw ConversionException("day is out of range");
}
int passedYears = year - 1;
int passedDays = day - 1;
const auto passedYears = static_cast<unsigned int>(year - 1);
const auto passedDays = static_cast<unsigned int>(day - 1);
return (passedYears * m_daysPerYear + passedYears / 4 - passedYears / 100 + passedYears / 400 + daysToMonth[passedMonth] + passedDays)
* TimeSpan::m_ticksPerDay;
}
@ -330,8 +342,8 @@ uint64 DateTime::timeToTicks(int hour, int minute, int second, double millisecon
if (!inRangeExclMax(millisecond, 0.0, 1000.0)) {
throw ConversionException("millisecond is out of range");
}
return (hour * TimeSpan::m_ticksPerHour) + (minute * TimeSpan::m_ticksPerMinute) + (second * TimeSpan::m_ticksPerSecond)
+ (uint64)(millisecond * (double)TimeSpan::m_ticksPerMillisecond);
return static_cast<uint64>(hour * TimeSpan::m_ticksPerHour) + static_cast<uint64>(minute * TimeSpan::m_ticksPerMinute)
+ static_cast<uint64>(second * TimeSpan::m_ticksPerSecond) + static_cast<uint64>(millisecond * TimeSpan::m_ticksPerMillisecond);
}
/*!

@ -73,6 +73,8 @@ public:
constexpr int minute() const;
constexpr int second() const;
constexpr int millisecond() const;
constexpr int microsecond() const;
constexpr int nanosecond() const;
constexpr bool isNull() const;
constexpr TimeSpan timeOfDay() const;
bool isLeapYear() const;
@ -278,6 +280,24 @@ constexpr inline int DateTime::millisecond() const
return m_ticks / TimeSpan::m_ticksPerMillisecond % 1000ul;
}
/*!
* \brief Gets the microsecond component of the date represented by this instance.
*/
constexpr int DateTime::microsecond() const
{
return m_ticks / TimeSpan::ticksPerMicrosecond % 1000ul;
}
/*!
* \brief Gets the nanosecond component of the date represented by this instance.
* \remarks The accuracy of the DateTime class is 100-nanoseconds. Hence the returned value
* will always have two zeros at the end (in decimal representation).
*/
constexpr int DateTime::nanosecond() const
{
return m_ticks % 10ul * TimeSpan::nanosecondsPerTick;
}
/*!
* \brief Returns ture if the date represented by the current DateTime class is null.
* \sa DateTime

@ -61,12 +61,12 @@ TimeSpan TimeSpan::fromString(const char *str, char separator)
* \brief Converts the value of the current TimeSpan object to its equivalent std::string representation
* according the given \a format.
*
* If \a noMilliseconds is true the time interval will be rounded to full seconds.
* If \a fullSeconds is true the time interval will be rounded to full seconds.
*/
string TimeSpan::toString(TimeSpanOutputFormat format, bool noMilliseconds) const
string TimeSpan::toString(TimeSpanOutputFormat format, bool fullSeconds) const
{
string result;
toString(result, format, noMilliseconds);
toString(result, format, fullSeconds);
return result;
}
@ -74,11 +74,11 @@ string TimeSpan::toString(TimeSpanOutputFormat format, bool noMilliseconds) cons
* \brief Converts the value of the current TimeSpan object to its equivalent std::string representation
* according the given \a format.
*
* If \a noMilliseconds is true the time interval will be rounded to full seconds.
* If \a fullSeconds is true the time interval will be rounded to full seconds.
*
* The string representation will be stored in \a result.
*/
void TimeSpan::toString(string &result, TimeSpanOutputFormat format, bool noMilliseconds) const
void TimeSpan::toString(string &result, TimeSpanOutputFormat format, bool fullSeconds) const
{
stringstream s(stringstream::in | stringstream::out);
TimeSpan positive(m_ticks);
@ -88,36 +88,75 @@ void TimeSpan::toString(string &result, TimeSpanOutputFormat format, bool noMill
}
switch (format) {
case TimeSpanOutputFormat::Normal:
s << setfill('0') << setw(2) << floor(positive.totalHours()) << ":" << setw(2) << positive.minutes() << ":" << setw(2) << positive.seconds()
<< " ";
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;
}
}
}
}
break;
case TimeSpanOutputFormat::WithMeasures:
if (isNull()) {
s << "0 s ";
result = "0 s";
return;
} else {
if (positive.totalMilliseconds() < 1.0) {
s << setprecision(2) << (m_ticks / 10.0) << " µs ";
if (!fullSeconds && positive.totalMilliseconds() < 1.0) {
s << setprecision(2) << positive.totalMicroseconds() << " µs";
} else {
bool needWhitespace = false;
if (const int days = positive.days()) {
s << days << " d ";
needWhitespace = true;
s << days << " d";
}
if (const int hours = positive.hours()) {
s << hours << " h ";
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << hours << " h";
}
if (const int minutes = positive.minutes()) {
s << minutes << " min ";
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << minutes << " min";
}
if (const int seconds = positive.seconds()) {
s << seconds << " s ";
if (needWhitespace)
s << ' ';
needWhitespace = true;
s << seconds << " s";
}
if (!noMilliseconds) {
if (!fullSeconds) {
if (const int milliseconds = positive.milliseconds()) {
s << milliseconds << " ms ";
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";
}
}
}
}
break;
}
result = s.str().substr(0, static_cast<string::size_type>(s.tellp()) - 1);
result = s.str();
}

@ -42,12 +42,15 @@ public:
static constexpr TimeSpan infinity();
constexpr int64 totalTicks() const;
constexpr double totalMicroseconds() const;
constexpr double totalMilliseconds() const;
constexpr double totalSeconds() const;
constexpr double totalMinutes() const;
constexpr double totalHours() const;
constexpr double totalDays() const;
constexpr int nanoseconds() const;
constexpr int microseconds() const;
constexpr int milliseconds() const;
constexpr int seconds() const;
constexpr int minutes() const;
@ -65,14 +68,16 @@ public:
TimeSpan &operator+=(const TimeSpan &other);
TimeSpan &operator-=(const TimeSpan &other);
std::string toString(TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool noMilliseconds = false) const;
void toString(std::string &result, TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool noMilliseconds = false) const;
std::string toString(TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool fullSeconds = false) const;
void toString(std::string &result, TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool fullSeconds = false) const;
constexpr bool isNull() const;
constexpr bool isNegative() const;
constexpr bool isNegativeInfinity() const;
constexpr bool isInfinity() const;
// TODO: make those public constants signed in next major release and remove private ones then
static constexpr int64 nanosecondsPerTick = 100uL;
static constexpr int64 ticksPerMicrosecond = 10uL;
static constexpr uint64 ticksPerMillisecond = 10000uL;
static constexpr uint64 ticksPerSecond = 10000000uL;
static constexpr uint64 ticksPerMinute = 600000000uL;
@ -178,6 +183,14 @@ constexpr inline int64 TimeSpan::totalTicks() const
return m_ticks;
}
/*!
* \brief Gets the value of the current TimeSpan class expressed in whole and fractional microseconds.
*/
constexpr double TimeSpan::totalMicroseconds() const
{
return static_cast<double>(m_ticks) / static_cast<double>(ticksPerMicrosecond);
}
/*!
* \brief Gets the value of the current TimeSpan class expressed in whole and fractional milliseconds.
*/
@ -218,6 +231,24 @@ constexpr inline double TimeSpan::totalDays() const
return static_cast<double>(m_ticks) / static_cast<double>(m_ticksPerDay);
}
/*!
* \brief Gets the nanoseconds component of the time interval represented by the current TimeSpan class.
* \remarks The accuracy of the TimeSpan class is 100-nanoseconds. Hence the returned value
* will always have two zeros at the end (in decimal representation).
*/
constexpr int TimeSpan::nanoseconds() const
{
return m_ticks % 10l * TimeSpan::nanosecondsPerTick;
}
/*!
* \brief Gets the microseconds component of the time interval represented by the current TimeSpan class.
*/
constexpr int TimeSpan::microseconds() const
{
return (m_ticks / ticksPerMicrosecond) % 1000l;
}
/*!
* \brief Gets the miliseconds component of the time interval represented by the current TimeSpan class.
*/

@ -90,6 +90,13 @@ void ChronoTests::testDateTime()
const auto test3 = DateTime::fromIsoString("2016-08-29T21:32:31.125+02:00");
CPPUNIT_ASSERT_EQUAL("2016-08-29T21:32:31.125+02:00"s, test3.first.toIsoString(test3.second));
CPPUNIT_ASSERT_THROW(DateTime::fromString("#"), ConversionException);
// test accuracy (of 100 nanoseconds)
const auto test4 = DateTime::fromIsoString("2017-08-23T19:40:15.985077682+02:00");
CPPUNIT_ASSERT_EQUAL(15, test4.first.second());
CPPUNIT_ASSERT_EQUAL(985, test4.first.millisecond());
CPPUNIT_ASSERT_EQUAL(77, test4.first.microsecond());
CPPUNIT_ASSERT_EQUAL(600, test4.first.nanosecond());
CPPUNIT_ASSERT_EQUAL("2017-08-23T19:40:15.9850776+02:00"s, test4.first.toIsoString(test4.second));
// test now() and exactNow() (or at least whether both behave the same)
#if defined(PLATFORM_UNIX)
@ -124,6 +131,15 @@ void ChronoTests::testTimeSpan()
CPPUNIT_ASSERT_EQUAL("-5 s"s, TimeSpan::fromSeconds(-5.0).toString(TimeSpanOutputFormat::WithMeasures, false));
CPPUNIT_ASSERT_EQUAL("0 s"s, TimeSpan().toString(TimeSpanOutputFormat::WithMeasures, false));
CPPUNIT_ASSERT_EQUAL("5e+02 µs"s, TimeSpan::fromMilliseconds(0.5).toString(TimeSpanOutputFormat::WithMeasures, false));
// test accuracy (of 100 nanoseconds)
const auto test2 = TimeSpan::fromString("15.985077682");
CPPUNIT_ASSERT_EQUAL(15.9850776, test2.totalSeconds());
CPPUNIT_ASSERT_EQUAL(15, test2.seconds());
CPPUNIT_ASSERT_EQUAL(985, test2.milliseconds());
CPPUNIT_ASSERT_EQUAL(77, test2.microseconds());
CPPUNIT_ASSERT_EQUAL(600, test2.nanoseconds());
CPPUNIT_ASSERT_EQUAL("00:00:15.9850776"s, test2.toString());
CPPUNIT_ASSERT_EQUAL("15 s 985 ms 77 µs 600 ns"s, test2.toString(TimeSpanOutputFormat::WithMeasures));
// test whether ConversionException() is thrown when invalid values are specified
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("2:34a:53:32.5"), ConversionException);

Loading…
Cancel
Save