diff --git a/CMakeLists.txt b/CMakeLists.txt index ef5bc36..cb3b43a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/chrono/datetime.cpp b/chrono/datetime.cpp index 88040cf..7a713f3 100644 --- a/chrono/datetime.cpp +++ b/chrono/datetime.cpp @@ -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(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(year - 1); + const auto passedDays = static_cast(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(hour * TimeSpan::m_ticksPerHour) + static_cast(minute * TimeSpan::m_ticksPerMinute) + + static_cast(second * TimeSpan::m_ticksPerSecond) + static_cast(millisecond * TimeSpan::m_ticksPerMillisecond); } /*! diff --git a/chrono/datetime.h b/chrono/datetime.h index b423bfa..66125df 100644 --- a/chrono/datetime.h +++ b/chrono/datetime.h @@ -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 diff --git a/chrono/timespan.cpp b/chrono/timespan.cpp index 0b00a96..ddb40ef 100644 --- a/chrono/timespan.cpp +++ b/chrono/timespan.cpp @@ -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(s.tellp()) - 1); + result = s.str(); } diff --git a/chrono/timespan.h b/chrono/timespan.h index 4994766..c666ff0 100644 --- a/chrono/timespan.h +++ b/chrono/timespan.h @@ -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(m_ticks) / static_cast(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(m_ticks) / static_cast(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. */ diff --git a/tests/chronotests.cpp b/tests/chronotests.cpp index 9f9b58d..65e8f3a 100644 --- a/tests/chronotests.cpp +++ b/tests/chronotests.cpp @@ -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);