+/*
+The software in this package is distributed under the GNU General
+Public License version 2 (with a special exception described below).
+
+A copy of GNU General Public License (GPL) is included in this distribution,
+in the file COPYING.GPL.
+
+As a special exception, if other files instantiate templates or use macros
+or inline functions from this file, or you compile this file and link it
+with other works to produce a work based on this file, this file
+does not by itself cause the resulting work to be covered
+by the GNU General Public License.
+
+However the source code for this file must still be made available
+in accordance with section (3) of the GNU General Public License.
+
+This exception does not invalidate any other reasons why a work based
+on this file might be covered by the GNU General Public License.
+*/
/** @file
* @brief time related functions.
*
* @copyright Copyright © 2001-2008 by Intra2net AG
- * @license commercial
- * @contact info@intra2net.com
*
*/
-
+#include <cstdio>
+#include <errno.h>
#include <string>
#include <sstream>
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <sys/timeb.h>
-#include <sys/syscall.h>
#include <timefunc.hxx>
#include <i18n.h>
}
// converts ISO-DATE: 2003-06-13
-int date_to_seconds(const std::string &date)
+time_t date_to_seconds(const std::string &date)
{
- int rtn = -1, year = -1, month = -1, day = -1;
+ time_t rtn = 0;
+ int year = -1, month = -1, day = -1;
string::size_type pos = date.find("-");
if (pos == string::npos)
if (year < 0 || month == -1 || day == -1)
return rtn;
-
+
struct tm tm_struct;
- bzero (&tm_struct, sizeof(struct tm));
+ memset(&tm_struct, 0, sizeof(struct tm));
tm_struct.tm_year = year;
tm_struct.tm_mon = month;
tm_struct.tm_mday = day;
int days=seconds/86400;
seconds%=86400;
- int hours=seconds/3600;
- seconds%=3600;
-
- int minutes=seconds/60;
- seconds%=60;
-
- if (days==1)
- out << i18n("1 day") << ", ";
- else if (days>1)
- out << days << ' ' << i18n("days") << ", ";
+ int hours,minutes;
+ split_daysec(seconds,&hours,&minutes,&seconds);
+
+ if (days>0)
+ out << days << " " << i18n_plural("day", "days", days) << ", ";
out << setfill('0');
out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
return out.str();
}
-string format_full_time(int seconds)
+string format_full_time(time_t seconds)
{
char buf[50];
memset (buf, 0, 50);
- struct tm *ta = localtime ((time_t *)&seconds);
+ struct tm ta;
+ if (localtime_r((time_t *)&seconds, &ta) == NULL)
+ memset (&ta, 0, sizeof(struct tm));
- strftime (buf, 49, "%d.%m.%Y %H:%M", ta);
+ strftime (buf, 49, "%d.%m.%Y %H:%M", &ta);
+ return string(buf);
+}
+
+string format_date(time_t seconds)
+{
+ char buf[50];
+ memset (buf, 0, 50);
+ struct tm ta;
+ if (localtime_r((time_t *)&seconds, &ta) == NULL)
+ memset (&ta, 0, sizeof(struct tm));
+
+ strftime (buf, 49, "%d.%m.%Y", &ta);
return string(buf);
}
}
}
-std::string output_hour_minute(int hour, int minute, bool h_for_00)
+/**
+ * Split seconds into hours, minutes and seconds
+ * @param [in] daysec Seconds since start of day
+ * @param [out] outhours hours
+ * @param [out] outminutes minutes
+ * @param [out] outseconds seconds
+ */
+void split_daysec(int daysec, int *outhours, int *outminutes, int *outseconds)
+{
+ int hours=daysec/3600;
+ daysec%=3600;
+
+ int minutes=daysec/60;
+ daysec%=60;
+
+ if (outhours)
+ *outhours=hours;
+
+ if (outminutes)
+ *outminutes=minutes;
+
+ if (outseconds)
+ *outseconds=daysec;
+}
+
+std::string output_hour_minute(int hour, int minute, bool h_for_00, int seconds)
{
ostringstream out;
out << '0';
out << hour;
- if (!h_for_00 || minute != 0)
+ if (!h_for_00 || minute != 0 || seconds > 0)
{
out << ':';
if (minute >= 0 && minute < 10)
else
out << 'h';
- return out.str();
-}
-
-void WEEK::set(const std::string& daystring)
-{
- int len=daystring.length();
- for (int p=0; p < len; p++)
+ if (seconds > 0)
{
- char nr[2];
- nr[0]=daystring[p];
- nr[1]=0;
- istringstream c(nr);
- int wnr=-1;
- if (!(c >> wnr) || wnr<0 || wnr >6)
- throw range_error("illegal weekday >"+string(nr)+"< in "+daystring);
-
- days.set(wnr);
+ out << ':';
+ if (seconds > 0 && seconds < 10)
+ out << '0';
+ out << seconds;
}
-}
-
-std::string WEEK::get_daystring() const
-{
- ostringstream out;
- for (int i = 0; i < 7; i++)
- if (days[i])
- out << i;
return out.str();
}
-std::string WEEK::get_displaystring() const
-{
- string weekdays_str;
-
- // From Monday to Saturday
- int j;
- for (int i = 1; i < 7; i++)
- {
- if (days[i])
- {
- if (!weekdays_str.empty())
- weekdays_str += ", ";
-
- weekdays_str += get_day_display(static_cast<WEEKDAY>(i));
-
- // check if we can group two or more days
- j = i;
- while (days[j] && j < 7)
- j++;
- j--;
-
- // Sunday end of week? j -> 7
- if (j-i > 0 && j == 6 && days[0])
- j++;
-
- if (j-i > 1)
- {
- if (j == 7)
- weekdays_str += "-" + get_day_display(SU);
- else
- weekdays_str += "-" + get_day_display(static_cast<WEEKDAY>(j));
-
- i = j;
- }
- }
- }
-
- // special: sunday
- if (days[0] && j != 7)
- {
- if (!weekdays_str.empty())
- weekdays_str += ", ";
-
- weekdays_str += get_day_display(SU);
- }
-
- return weekdays_str;
-}
-
-std::string WEEK::get_netfilterstring() const
-{
- string out;
- for (int i = 0; i < 7; i++)
- if (days[i])
- {
- if (!out.empty())
- out+=",";
- out+=get_english_display(static_cast<WEEKDAY>(i));;
- }
-
- return out;
-}
-
-std::string WEEK::get_day_display(WEEKDAY day)
-{
- string weekday_str;
-
- switch (day) {
- case MO:
- weekday_str = i18n("Mon");
- break;
- case TU:
- weekday_str = i18n("Tue");
- break;
- case WE:
- weekday_str = i18n("Wed");
- break;
- case TH:
- weekday_str = i18n("Thu");
- break;
- case FR:
- weekday_str = i18n("Fri");
- break;
- case SA:
- weekday_str = i18n("Sat");
- break;
- case SU:
- weekday_str = i18n("Sun");
- break;
- default:
- break;
- }
-
- return weekday_str;
-}
-
-std::string WEEK::get_english_display(WEEKDAY day)
-{
- string weekday_str;
-
- switch (day) {
- case MO:
- weekday_str = "Mon";
- break;
- case TU:
- weekday_str = "Tue";
- break;
- case WE:
- weekday_str = "Wed";
- break;
- case TH:
- weekday_str = "Thu";
- break;
- case FR:
- weekday_str = "Fri";
- break;
- case SA:
- weekday_str = "Sat";
- break;
- case SU:
- weekday_str = "Sun";
- break;
- default:
- break;
- }
-
- return weekday_str;
-}
-
string get_month_name(unsigned char month)
{
string rtn;
bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
{
struct timespec tp[1];
- int res= ::syscall(__NR_clock_gettime, CLOCK_MONOTONIC, tp);
+ int res= clock_gettime (CLOCK_MONOTONIC, tp);
if (0 == res)
{
seconds= tp->tv_sec;
bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
{
struct timespec tp[1];
- int res= ::syscall(__NR_clock_gettime, CLOCK_REALTIME, tp);
+ int res= clock_gettime(CLOCK_REALTIME, tp);
if (0 == res)
{
seconds= tp->tv_sec;
} // 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<struct tm>
+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<std::string>
+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 {
+
+ 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: {
+ if (var & type::exact) {
+ 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<long>(this->as_nanosec ()); }
+
+ Time &
+ Time::operator= (Time t2) NOEXCEPT
+ {
+ this->swap (t2);
+
+ return *this;
+ }
+
+ 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<long> (this->value.tv_sec );
+ int64_t nsec = static_cast<int64_t> (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<time_t> (div.quot);
+ this->value.tv_nsec = static_cast<long> (nsec);
+
+ this->carry_nsec ();
+
+ return *this;
+ }
+
+ boost::optional<std::string>
+ 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<int> (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); }
+
+ boost::optional<Time>
+ now (const enum type::id id, const enum type::variant var) NOEXCEPT
+ {
+ Time ret (id, var);
+
+ if (!ret.set ()) {
+ return boost::none;
+ }
+
+ return ret;
+ }
+
+ Time
+ zero (const enum type::id id, const enum type::variant var) NOEXCEPT
+ { return Time (id, var); }
+
+ int
+ compare (const Time &t1, const Time &t2) NOEXCEPT
+ {
+ if (t1.value.tv_sec < t2.value.tv_sec) {
+ return -1;
+ }
+
+ if (t1.value.tv_sec > t2.value.tv_sec) {
+ return 1;
+ }
+
+ if (t1.value.tv_nsec < t2.value.tv_nsec) {
+ return -1;
+ }
+
+ if (t1.value.tv_nsec > t2.value.tv_nsec) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ boost::optional<Time>
+ time_of_iso8601 (const std::string &s,
+ const bool date,
+ const bool time,
+ const bool tz,
+ const enum type::id id,
+ const enum type::variant var) NOEXCEPT
+ {
+ boost::optional<struct tm> tm = scan_iso8601 (s, date, time, tz);
+
+ if (!tm) {
+ return boost::none;
+ }
+
+ try {
+ return Time (*tm, id, var);
+ }
+ catch (conversion_error &_unused) { }
+
+ return boost::none;
+ }
+
+} /* [namespace clock] */
+
+} /* [namespace I2n] */