diff --git a/include/cybozu/time.hpp b/include/cybozu/time.hpp new file mode 100644 index 0000000..fbf7355 --- /dev/null +++ b/include/cybozu/time.hpp @@ -0,0 +1,281 @@ +#pragma once +/** + @file + @brief tiny time class + + @author MITSUNARI Shigeo(@herumi) +*/ +#include +#include +#include +#include +#ifdef _WIN32 + #include +#else + #include +#endif + +namespace cybozu { + +/** + time struct with time_t and msec + @note time MUST be latesr than 1970/1/1 +*/ +class Time { + static const uint64_t epochBias = 116444736000000000ull; + std::time_t time_; + int msec_; +public: + explicit Time(std::time_t time = 0, int msec = 0) + : time_(time) + , msec_(msec) + { + } + explicit Time(bool doSet) + { + if (doSet) setCurrentTime(); + } + Time& setTime(std::time_t time, int msec = 0) + { + time_ = time; + msec_ = msec; + return *this; + } + /* + Windows FILETIME is defined as + struct FILETILME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; + }; + the value represents the number of 100-nanosecond intervals since January 1, 1601 (UTC). + */ + void setByFILETIME(uint32_t low, uint32_t high) + { + const uint64_t fileTime = (((uint64_t(high) << 32) | low) - epochBias) / 10000; + time_ = fileTime / 1000; + msec_ = fileTime % 1000; + } + /* + DWORD is defined as unsigned long in windows + */ + template + void getFILETIME(dword& low, dword& high) const + { + const uint64_t fileTime = (time_ * 1000 + msec_) * 10000 + epochBias; + low = dword(fileTime); + high = dword(fileTime >> 32); + } + explicit Time(const std::string& in) + { + fromString(in); + } + explicit Time(const char *in) + { + fromString(in, in + strlen(in)); + } + const std::time_t& getTime() const { return time_; } + int getMsec() const { return msec_; } + double getTimeSec() const { return time_ + msec_ * 1e-3; } + void addSec(int sec) { time_ += sec; } + bool operator<(const Time& rhs) const { return (time_ < rhs.time_) || (time_ == rhs.time_ && msec_ < rhs.msec_); } +// bool operator<=(const Time& rhs) const { return (*this < rhs) || (*this == rhs); } +// bool operator>(const Time& rhs) const { return rhs < *this; } + bool operator==(const Time& rhs) const { return (time_ == rhs.time_) && (msec_ == rhs.msec_); } + bool operator!=(const Time& rhs) const { return !(*this == rhs); } + /** + set time from string such as + 2009-Jan-23T02:53:44Z + 2009-Jan-23T02:53:44.078Z + 2009-01-23T02:53:44Z + 2009-01-23T02:53:44.078Z + @note 'T' may be ' '. '-' may be '/'. last char 'Z' is omissible + */ + void fromString(bool *pb, const std::string& in) { fromString(pb, &in[0], &in[0] + in.size()); } + void fromString(const std::string& in) { fromString(0, in); } + + void fromString(bool *pb, const char *begin, const char *end) + { + const size_t len = end - begin; + if (len >= 19) { + const char *p = begin; + struct tm tm; + int num; + bool b; + tm.tm_year = getNum(&b, p, 4, 1970, 3000) - 1900; + if (!b) goto ERR; + p += 4; + char sep = *p++; + if (sep != '-' && sep != '/') goto ERR; + + p = getMonth(&num, p); + if (p == 0) goto ERR; + tm.tm_mon = num; + if (*p++ != sep) goto ERR; + + tm.tm_mday = getNum(&b, p, 2, 1, 31); + if (!b) goto ERR; + p += 2; + if (*p != ' ' && *p != 'T') goto ERR; + p++; + + tm.tm_hour = getNum(&b, p, 2, 0, 23); + if (!b) goto ERR; + p += 2; + if (*p++ != ':') goto ERR; + + tm.tm_min = getNum(&b, p, 2, 0, 59); + if (!b) goto ERR; + p += 2; + if (*p++ != ':') goto ERR; + + tm.tm_sec = getNum(&b, p, 2, 0, 59); + if (!b) goto ERR; + p += 2; + + if (p == end) { + msec_ = 0; + } else if (p + 1 == end && *p == 'Z') { + msec_ = 0; + p++; + } else if (*p == '.' && (p + 4 == end || (p + 5 == end && *(p + 4) == 'Z'))) { + msec_ = getNum(&b, p + 1, 3, 0, 999); + if (!b) goto ERR; +// p += 4; + } else { + goto ERR; + } +#ifdef _WIN32 + time_ = _mkgmtime64(&tm); + if (time_ == -1) goto ERR; +#else + time_ = timegm(&tm); +#endif + if (pb) { + *pb = true; + } + return; + } + ERR: + if (pb) { + *pb = false; + return; + } + throw cybozu::Exception("time::fromString") << std::string(begin, 24); + } + void fromString(const char *begin, const char *end) { fromString(0, begin, end); } + + /** + get current time with format + @param out [out] output string + @param format [in] foramt for strftime and append three digits for msec + @param appendMsec [in] appemd + @param doClear (append to out if false) + @note ex. "%Y-%b-%d %H:%M:%S." to get 2009-Jan-23 02:53:44.078 + */ + void toString(std::string& out, const char *format, bool appendMsec = true, bool doClear = true) const + { + if (doClear) out.clear(); + char buf[128]; + struct tm tm; +#ifdef _WIN32 + bool isOK = _gmtime64_s(&tm, &time_) == 0; +#else + bool isOK = gmtime_r(&time_, &tm) != 0; +#endif + if (!isOK) throw cybozu::Exception("time::toString") << time_; +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + if (std::strftime(buf, sizeof(buf), format, &tm) == 0) { + throw cybozu::Exception("time::toString::too long") << format << time_; + } +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif + out += buf; + if (appendMsec) { + out += cybozu::itoaWithZero(msec_, 3); + } + } + + /** + get current time such as 2009-01-23 02:53:44.078 + @param out [out] sink string + */ + void toString(std::string& out, bool appendMsec = true, bool doClear = true) const + { + const char *format = appendMsec ? "%Y-%m-%d %H:%M:%S." : "%Y-%m-%d %H:%M:%S"; + toString(out, format, appendMsec, doClear); + } + std::string toString(bool appendMsec = true) const { std::string out; toString(out, appendMsec); return out; } + /** + get current time + */ + Time& setCurrentTime() + { +#ifdef _WIN32 + struct _timeb timeb; + _ftime_s(&timeb); + time_ = timeb.time; + msec_ = timeb.millitm; +#else + struct timeval tv; + gettimeofday(&tv, 0); + time_ = tv.tv_sec; + msec_ = tv.tv_usec / 1000; +#endif + return *this; + } +private: + + int getNum(bool *b, const char *in, size_t len, int min, int max) const + { + int ret = cybozu::atoi(b, in, len); + if (min <= ret && ret <= max) { + return ret; + } else { + *b = false; + return 0; + } + } + + /* + convert month-str to [0, 11] + @param ret [out] return idx + @param p [in] month-str + @retval next pointer or null + */ + const char *getMonth(int *ret, const char *p) const + { + static const char monthTbl[12][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + for (size_t i = 0; i < CYBOZU_NUM_OF_ARRAY(monthTbl); i++) { + if (memcmp(p, monthTbl[i], 3) == 0) { + *ret = (int)i; + return p + 3; + } + } + bool b; + *ret = getNum(&b, p, 2, 1, 12) - 1; + if (b) { + return p + 2; + } else { + return 0; + } + } +}; + +inline std::ostream& operator<<(std::ostream& os, const cybozu::Time& time) +{ + return os << time.toString(); +} + +inline double GetCurrentTimeSec() +{ + return cybozu::Time(true).getTimeSec(); +} + +} // cybozu