X-Git-Url: http://developer.intra2net.com/git/?a=blobdiff_plain;f=src%2Ftimefunc.cpp;fp=src%2Ftimefunc.cpp;h=a4d38b863a48310e7c1def0091ff1f81740d8c53;hb=c443fce815c32e8aa716bcff6022e79b4f12a0e7;hp=e8b1917cc4c8fd8dd96a90a6434386896f14a666;hpb=4cefaf511d9ed79ad13ab59c46bd0ddc60e758f6;p=libi2ncommon diff --git a/src/timefunc.cpp b/src/timefunc.cpp index e8b1917..a4d38b8 100644 --- a/src/timefunc.cpp +++ b/src/timefunc.cpp @@ -24,6 +24,8 @@ on this file might be covered by the GNU General Public License. * */ +#include +#include #include #include #include @@ -771,3 +773,626 @@ bool realtime_clock_gettime(long int& seconds, long int& nano_seconds) } // eo realtime_clock_gettime(long int&,long int&) +/* + * There is a discrepancy of one input character + * due to the lack of sign handling in strptime(3): + * + * - strftime(3) needs the year specified as %5Y to account for the + * leading dash; + * - strptime(3) will not parse the leading dash with that format + * but apart from that it works well. + */ + +namespace iso8601 { + + /* + * max: -YYYYYYYYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 32 + * That is assuming the year in broken down time is an int we + * need to reserve ten decimal places. + */ +// static_assert (sizeof (((struct tm *)NULL)->tm_year) == 4); + static const size_t bufsize = 33; + + enum kind { + d = 0, + t = 1, + tz = 2, + dt = 3, + dtz = 4, + ISO8601_SIZE = 5, + }; + + /* + * Unfortunately the glibc strptime(3) on the Intranator trips over + * the length specifier in field descriptors so we can’t reuse the + * formatters here. This problem is fixed in newer glibc. For the time + * being we keep two tables of formatters and choose the appropriate + * at runtime. + */ + + static const char *const formatter [ISO8601_SIZE] = + { /* [iso8601::d ] = */ "%4Y-%m-%d", + /* [iso8601::t ] = */ "%T", + /* [iso8601::tz ] = */ "%TZ%z", + /* [iso8601::dt ] = */ "%4Y-%m-%dT%T", + /* [iso8601::dtz] = */ "%4Y-%m-%dT%TZ%z", + }; + + static const char *const scanner [ISO8601_SIZE] = + { /* [iso8601::d ] = */ "%Y-%m-%d", + /* [iso8601::t ] = */ "%T", + /* [iso8601::tz ] = */ "%TZ%z", + /* [iso8601::dt ] = */ "%Y-%m-%dT%T", + /* [iso8601::dtz] = */ "%Y-%m-%dT%TZ%z", + }; + + static inline const char * + pick_fmt (const bool date, const bool time, const bool tz, const bool scan=false) + { + const char *const *table = scan ? iso8601::scanner : iso8601::formatter; + enum iso8601::kind format = iso8601::dtz; + + if (date) { + if (time) { + if (tz) { + format = iso8601::dtz; + } else { + format = iso8601::dt; + } + } else { + format = iso8601::d; + } + } else if (time && tz) { + format = iso8601::tz; + } else { + format = iso8601::t; /* default to %T */ + } + + return table [format]; + } + +} /* [iso8601] */ + + +namespace { + + static inline int flip_tm_year (const int y) + { return (y + 1900) * -1 - 1900; } +} /* [namespace] */ + +/** + * @brief Format a time structure according to ISO-8601, e. g. + * “2018-01-09T10:40:00Z+0100”; see \c strftime(3) for + * the details. + * + * @param tm Time to format as broken-down \c struct tm. + * @param date Include the day part ([-]YYYY-MM-DD). + * @param time Include the time part (hh:mm:ss). + * @param tz Include the timezone ([±]ZZZZ); only needed if + * \c time is requested as well. + * + * @return The formatted timestamp. + */ +std::string format_iso8601 (const struct tm &tm, const bool date, + const bool time, const bool tz) +{ + struct tm tmp; + char buf [iso8601::bufsize] = { 0 }; + char *start = &buf [0]; + const char *format = iso8601::pick_fmt (date, time, tz); + + memcpy (&tmp, &tm, sizeof (tmp)); + + if (tmp.tm_year < -1900) { /* negative year */ + *start = '-'; + start++; + tmp.tm_year = flip_tm_year (tmp.tm_year); + } + + /* + * The sign is *always* handled above so the formatted string here + * is always one character shorter. + */ + if (strftime (start, iso8601::bufsize-1, format, &tmp) == 0) + { + return std::string (); + } + + buf [iso8601::bufsize-1] = '\0'; /* Just in case. */ + + return std::string (buf); +} + +typedef struct tm * (*time_breakdown_fn) (const time_t *, struct tm *); + +/** + * @brief Format a UNIX timestamp according to ISO-8601. Converts + * to broken down time first. + * + * @param t Time to format as broken-down \c struct tm. + * @param date Include the day part ([-]YYYY-MM-DD). + * @param time Include the time part (hh:mm:ss). + * @param tz Include the timezone ([±]ZZZZ); only heeded if + * \c time is requested as well. + * + * @return The formatted timestamp. + */ +std::string format_iso8601 (time_t t, const bool utc, const bool date, + const bool time, const bool tz) +{ + time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r; + struct tm tm; + + errno = 0; + if (breakdown (&t, &tm) == NULL) { + return std::string ("error analyzing timestamp: ") + strerror (errno); + } + + return format_iso8601 (tm, date, time, tz); +} + +/** + * @brief Read a ISO-8601 formatted date stamp into broken down time. + * + * @param s String containing the timestamp. + * + * @return \c boost::none if the input string was \c NULL or malformed, + * an optional \c struct tm with the extracted values otherwise. + */ +boost::optional +scan_iso8601 (const char *s, + const bool date, const bool time, const bool tz) NOEXCEPT +{ + struct tm tm; + const char *format = iso8601::pick_fmt (date, time, tz, true); + const char *start = s; + bool negyear = false; + + if (s == NULL) { + return boost::none; + } + + switch (s [0]) { + case '\0': { + return boost::none; + break; + } + /* + * Contrary to what the man page indicates, strptime(3) is *not* + * the inverse operation of strftime(3)! The later correctly formats + * negative year numbers with the %F modifier wheres the former trips + * over the sign character. + */ + case '-': { + negyear = true; + start++; + break; + } + default: { + break; + } + } + + memset (&tm, 0, sizeof (tm)); + + if (strptime (start, format, &tm) == NULL) { + return boost::none; + } + + if (negyear) { + tm.tm_year = flip_tm_year (tm.tm_year); + } + + return tm; +} + +/** + * @brief Format a \c struct timespec in the schema established by + * time(1): “3m14.159s”. + * + * @param ts The time spec to format. + * + * @return \c boost:none in case of error during formatting, an optional + * \c std::string otherwise. + */ +boost::optional +format_min_sec_msec (const struct timespec &ts) +{ + char ms [4] = { '\0', '\0', '\0', '\0' }; + + if (snprintf (ms, 4, "%.3ld", ts.tv_nsec / 1000000) < 0) { + return boost::none; + } + + const time_t min = ts.tv_sec / 60; + const time_t sec = ts.tv_sec - min * 60; + + return I2n::to_string (min) + "m" + + I2n::to_string (sec) + "." + + ms + "s" + ; +} + +namespace I2n { + +namespace clock { + + namespace { + + /** + * @brief For internal use only. Translates clock + * specification flags to kernel clock types. + * + * @param id Master clock id: \c mono, \c real, \c boot, or \c + * cpu. + * @param var Variant of clock if appropriate: \c raw, \c exact, \c + * process, or \c thread. Use \c dflt for the base + * variant. + * + * @return The clock id for using with kernel APIs. + */ + static inline clockid_t + clockid_of_flags (const enum type::id id, + const enum type::variant var) NOEXCEPT + { + clockid_t cid = CLOCK_MONOTONIC_COARSE; + + switch (id) { + + default: + case type::mono: { + switch (var) { + default: { + break; + } + case type::raw: { + cid = CLOCK_MONOTONIC_RAW; + break; + } + case type::exact: { + cid = CLOCK_MONOTONIC; + break; + } + } + break; + } + + case type::real: { + if (var == type::exact) { + cid = CLOCK_REALTIME; + } else { + cid = CLOCK_REALTIME_COARSE; + } + break; + } + + case type::boot: { + cid = CLOCK_BOOTTIME; + break; + } + + case type::cpu: { + if (var == type::thread) { + cid = CLOCK_THREAD_CPUTIME_ID; + } else { + cid = CLOCK_PROCESS_CPUTIME_ID; + } + break; + } + } /* [switch id] */ + + return cid; + } + + static const struct timespec zero_time = { 0, 0 }; + + } /* [namespace] */ + + Time::Time (const enum type::id id, + const enum type::variant var) NOEXCEPT + : value (zero_time) + , id (id) + , variant (var) + , err (0) + { } + + /* + * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will + * fail with years outside the range from epoch to 2038. + */ + Time::Time (const struct tm &tm, + const enum type::id id, + const enum type::variant var) + { + struct tm tmp_tm; /* dummy for mktime(3) */ + Time tmp_time; + + memcpy (&tmp_tm, &tm, sizeof (tmp_tm)); + + errno = 0; + const time_t t = mktime (&tmp_tm); + if (t == - 1) { /* Glibc does not set errno on out-of-range here! */ + const char *datestr = asctime (&tm); + throw conversion_error (errno, + std::string ("mktime: from struct tm {") + + std::string (datestr, 0, strlen(datestr)-1) + + "}"); + } + + tmp_time = Time (t, 0l, id, var); + + this->swap (tmp_time); + } + + int64_t + Time::as_nanosec (void) const NOEXCEPT + { + return int64_t (this->value.tv_sec) * TIME_CONST_FACTOR_NANO + + this->value.tv_nsec; + } + + long + Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */ + { return static_cast(this->as_nanosec ()); } + + Time & + Time::operator= (Time t2) NOEXCEPT + { + this->swap (t2); + + return *this; + } + + /* + * @note This operator is an up-assignment from a type containing less + * information than the structure assigned from. Since the + * operator can only be two valued we must normalize the remaining + * fields to the default clock. When assigning from non-default + * clocks, use the appropriate constructor and pass it the desired + * id and variant so as to assign the result. + */ + Time & + Time::operator= (struct timespec ts) NOEXCEPT + { + std::swap (this->value, ts); + this->id = clock::type::mono; + this->variant = clock::type::dflt; + this->err = 0; + + return *this; + } + + void + Time::unset (void) NOEXCEPT + { this->value = zero_time; } + + bool + Time::set (void) NOEXCEPT + { + struct timespec now; + + errno = 0; + if (clock_gettime (clockid_of_flags (this->id, this->variant), &now) + == -1) + { + this->err = errno; + this->unset (); + + return false; + } + this->err = 0; + this->value = now; + + return true; + } + + Time & + Time::add (const time_t sec, const long nsec) NOEXCEPT + { + this->value.tv_sec += sec; + this->value.tv_nsec += nsec; + + this->carry_nsec (); + + return *this; + } + + Time & + Time::subtract (const time_t sec, const long nsec) NOEXCEPT + { + this->value.tv_sec -= sec; + this->value.tv_nsec -= nsec; + + this->carry_nsec (); + + return *this; + } + + Time & + Time::scale (const int64_t factor) NOEXCEPT + { + this->value.tv_sec *= factor; + this->value.tv_nsec *= factor; + + this->carry_nsec (); + + return *this; + } + + /* + * Below division code purposely does not attempt to handle divide- + * by-zero just as any other C++ division function does. It is up to + * the caller to ensure that the divisor is not zero. + */ + Time & + Time::divide (const int64_t divisor) NOEXCEPT + { + const long sec = static_cast (this->value.tv_sec ); + int64_t nsec = static_cast (this->value.tv_nsec); + const ldiv_t div = ldiv (sec, divisor); + + if (div.rem != 0) { + nsec += div.rem * TIME_CONST_FACTOR_NANO; + } + + nsec /= divisor; + + this->value.tv_sec = static_cast (div.quot); + this->value.tv_nsec = static_cast (nsec); + + this->carry_nsec (); + + return *this; + } + + /** + * @brief Format timestamp according to the ISO standard rules. + * + * @param utc Whether to normalize the timestamp to UTC or local time. + * @param date Whether to include the date (%F). + * @param time Whether to include the time (%T). + * @param tz Whether to include the UTC offset (%z). + * + * @return \c none if the formatting operation failed, the + * formatted timestamp otherwise. + * + * @note The standard allows for extending the format using + * a fractional component. However, this is subject to + * local conventions so we don’t support it. For more + * than seconds granularity use a better suited format + * like LDAP Generalized time instead. + */ + boost::optional + Time::format_iso8601 (const bool utc, + const bool date, + const bool time, + const bool tz) const + { + time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r; + struct tm tm; + + if (breakdown (&this->value.tv_sec, &tm) == NULL) { + return boost::none; + } + + return ::format_iso8601 (tm, date, time, tz); + } + + std::string + Time::make_nice_time (void) const + { + /* XXX the cast below results in loss of precision with 64 bit time_t! */ + return ::make_nice_time (static_cast (this->value.tv_sec)); + } + + std::string + Time::format_full_time (void) const + { return ::format_full_time (this->value.tv_sec); } + + std::string + Time::format_date (void) const + { return ::format_date (this->value.tv_sec); } + + /** + * @brief Obtain the current time wrt. the given + * clock variants. + * + * @param id Clock id. + * @param var Clock variant. + * + * @return \c none if the underlying \c clock_gettime() operation + * failed, a fully initialized \c struct Time otherwise. + */ + boost::optional