// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved. // // Time utility class //#include //#include //#include #include #ifdef WIN32 #include #else #include #endif #include "eyelog.h" #include "eyetime.h" #include "eyeutils.h" using namespace sequencelogic; static char timezones[] = {'Y','W','X','V','U','T','S','R','Q','P','O','N','Z','A','B','C','D','E','F','G','I','K','L','M','\0'}; static unsigned int monthlengths[] = {31,28,31,30,31,30,31,31,30,31,30,31}; static int monthnums[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; static int monthnumsleap[] = {6, 2, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; static bool isLeapYear(unsigned int nYear) { return (((nYear % 4) == 0) && (((nYear % 100) != 0) || ((nYear % 400) == 0))); } // Constructor that parses a date/time string EyeTime::EyeTime (const char* timestr) { if (!SetTime (timestr)) { daylight = 0; utc = 0; year = 0; month = 0; day = 0; hour = 0; minute = 0; second = 0; millisec = 0; } } // Days since January 1st void EyeTime::SetYearDay() { yday = day - 1; // Day of this month, but yday starts from 0 for (unsigned int i = 1; i < month; ++i) yday += monthlengths[i-1]; // And, 1 for leap year, maybe... if (isLeapYear(year) && (month > 2)) ++yday; } // Days since Sunday void EyeTime::SetWeekDay() { unsigned int tmpDay = (year - 1900) / 4; tmpDay += (year - 1900); tmpDay += day; if (isLeapYear(year)) tmpDay += monthnumsleap[month-1]; else tmpDay += monthnums[month-1]; wday = tmpDay % 7; } bool EyeTime::SetTimeMs (long long tMs) { bool bRetVal = false; time_t timeSecs = (time_t)(tMs / 1000); bRetVal = SetTime(timeSecs); millisec = (unsigned int)(tMs % 1000); return bRetVal; } bool EyeTime::SetTime (time_t t) { struct tm tmpTime; #ifdef WIN32 // Store in GMT... ::gmtime_s(&tmpTime, &t); #else gmtime_r(&t, &tmpTime); #endif year = tmpTime.tm_year + 1900; // tm structure has year as years since 1900 month = tmpTime.tm_mon + 1; // tm structure has Jan as month '0' yday = tmpTime.tm_yday; wday = tmpTime.tm_wday; day = tmpTime.tm_mday; hour = tmpTime.tm_hour; minute = tmpTime.tm_min; second = tmpTime.tm_sec; millisec = 0; utc = 0; daylight = 0; return true; } time_t EyeTime::GetTime() { struct tm tmpTime; tmpTime.tm_year = year - 1900; // tm structure has year as years since 1900 tmpTime.tm_mon = month - 1; // tm structure has Jan as month '0' tmpTime.tm_yday = yday; tmpTime.tm_wday = wday; tmpTime.tm_mday = day; tmpTime.tm_hour = hour; tmpTime.tm_min = minute; tmpTime.tm_sec = second; time_t retVal = ::mktime(&tmpTime); return retVal; } #ifdef WIN32 bool EyeTime::SetTime (SYSTEMTIME sysTime) { year = sysTime.wYear; month = sysTime.wMonth; day = sysTime.wDay; wday = sysTime.wDayOfWeek; hour = sysTime.wHour; minute = sysTime.wMinute; second = sysTime.wSecond; millisec = sysTime.wMilliseconds; utc = 0; SetYearDay(); return true; } SYSTEMTIME EyeTime::GetSysTime() { SYSTEMTIME retVal = {0}; retVal.wYear = static_cast(year); retVal.wMonth = static_cast(month); retVal.wDayOfWeek = static_cast(wday); retVal.wDay = static_cast(day); retVal.wHour = static_cast(hour); retVal.wMinute = static_cast(minute); retVal.wSecond = static_cast(second); retVal.wMilliseconds = static_cast(millisec); return retVal; } #endif long long EyeTime::GetTimeMs() { long long nRetVal = 0; #ifdef WIN32 struct tm tmpTime = {}; #else struct tm tmpTime = {0,0,0,0,0,0,0,0,0,0,0}; #endif tmpTime.tm_year = year - 1900; // tm structure has year as years since 1900 tmpTime.tm_mon = month - 1; // tm structure has Jan as month '0' tmpTime.tm_wday = yday; tmpTime.tm_mday = day; tmpTime.tm_hour = hour; tmpTime.tm_min = minute; tmpTime.tm_sec = second; #ifdef WIN32 nRetVal = ((long long)::_mkgmtime(&tmpTime) * 1000) + millisec; #elif defined(ANDROID) char *pTZ = getenv("TZ"); setenv ("TZ", "", 1); tzset(); nRetVal = ((long long)mktime(&tmpTime) * 1000) + millisec; if (pTZ != NULL) setenv("TZ", pTZ, 1); else unsetenv("TZ"); tzset(); #else nRetVal = ((long long)::timegm(&tmpTime) * 1000) + millisec; #endif return nRetVal; } bool EyeTime::SetTime (const char* timestr) { if (sequencelogic::StrLen (timestr, 8) == 0) return false; daylight = 0; utc = 0; year = 0; month = 0; day = 0; hour = 0; minute = 0; second = 0; millisec = 0; char delim = '\0'; char tdelim = '\0'; const char* ptr = timestr; const char* token = ptr; bool gothour = false; bool gotmin = false; bool gotsec = false; bool gotmsec = false; bool gotzone = false; int val = 0; int len = 0; while (*ptr) { switch (*ptr) { case '-' : case '+' : case ':' : case '.' : case ' ' : delim = tdelim; tdelim = *ptr++; break; case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : val = *ptr++ - '0'; while (*ptr) { if (*ptr >= '0' && *ptr <='9') val = val * 10 + (*ptr++ - '0'); else { delim = tdelim; tdelim = *ptr; break; } } if (year == 0) { year = val; if (year > 3000 || year < 1970) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid year field", timestr); year = 2014; month = 1; day = 1; return false; } } else if (month == 0) { month = val; if (month > 12 || month < 1) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid month field", timestr); month = 1; day = 1; return false; } } else if (day == 0) { day = val; if (day < 1 || (day > monthlengths[month-1] && month != 2 && !isLeapYear(year) && day > 29)) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid day field", timestr); day = 1; return false; } } else if (!gothour) { hour = val; gothour = true; if (hour > 23) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid hour value %d", timestr, val); hour = 0; return false; } } else if (!gotmin) { minute = val; gotmin = true; if (minute > 59) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid minute value %d", timestr, val); minute = 0; return false; } } else if (!gotsec) { second = val; gotsec = true; if (second > 60) { // Allow for leap second IONUDEBUG ("EyeTime::SetTime(%s) - invalid second value %d", timestr, val); second = 0; return false; } } else if (delim == '.' && !gotmsec) { millisec = val; gotmsec = true; if (millisec > 999) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid millisecond value %d", timestr, val); millisec = 0; return false; } } // time value is an offset from UTC (for example, +01:00, -07:00) else if ((delim == '-' || delim == '+') && !gotzone) { if (val > 12) { IONUDEBUG ("EyeTime::SetTime(%s) - invalid timezone value %d", timestr, val); return false; } if (delim == '-') utc = -val; else utc = +val; while (*ptr) ptr++; // Skip to end gotzone = true; ConvertToGMTime(); break; } else { IONUDEBUG ("EyeTime::SetTime(%s) - invalid numeric field %s", timestr, ptr); return false; } default : while (*ptr) { token = ptr; if ((*ptr >= 'a' && *ptr <='z') || (*ptr >= 'A' && *ptr <='Z')) ptr++; else break; } len = (int)(ptr - token); if (len >= 3) { if (sequencelogic::StrnCmp (token, "Jan", 3) == 0) month = 1; else if (sequencelogic::StrnCmp (token, "Feb", 3) == 0) month = 2; else if (sequencelogic::StrnCmp (token, "Mar", 3) == 0) month = 3; else if (sequencelogic::StrnCmp (token, "Apr", 3) == 0) month = 4; else if (sequencelogic::StrnCmp (token, "May", 3) == 0) month = 5; else if (sequencelogic::StrnCmp (token, "Jun", 3) == 0) month = 6; else if (sequencelogic::StrnCmp (token, "Jul", 3) == 0) month = 7; else if (sequencelogic::StrnCmp (token, "Aug", 3) == 0) month = 8; else if (sequencelogic::StrnCmp (token, "Sep", 3) == 0) month = 9; else if (sequencelogic::StrnCmp (token, "Oct", 3) == 0) month = 10; else if (sequencelogic::StrnCmp (token, "Nov", 3) == 0) month = 11; else if (sequencelogic::StrnCmp (token, "Dec", 3) == 0) month = 12; } else if (len == 1) { for (int i = 0; i < 24; ++i) { if (timezones[i] == *token) { utc = i - 12; ConvertToGMTime(); break; } } } //else // Skip character and continue // ptr++; break; } } // Set the day of week and year SetWeekDay(); SetYearDay(); return true; } // Constructor that initializes with the current time EyeTime::EyeTime () { utc = 0; daylight = 0; #ifdef WIN32 SYSTEMTIME stUTC; GetSystemTime (&stUTC); SetTime (stUTC); #else timeval tv; gettimeofday(&tv, NULL); struct tm tm; gmtime_r(&(tv.tv_sec), &tm); year = tm.tm_year + 1900; month = tm.tm_mon + 1; // Posix starts at 0 day = tm.tm_mday; yday = tm.tm_yday; wday = tm.tm_wday; hour = tm.tm_hour; minute = tm.tm_min; second = tm.tm_sec; millisec = (unsigned int)(tv.tv_usec / 1000); // Set the day of week SetWeekDay(); #endif } EyeTime::~EyeTime () { } int EyeTime::Compare (const EyeTime* other) const { if (year > other->year) return 1; if (year < other->year) return -1; if (month > other->month) return 1; if (month < other->month) return -1; if (day > other->day) return 1; if (day < other->day) return -1; if (hour > other->hour) return 1; if (hour < other->hour) return -1; if (minute > other->minute) return 1; if (minute < other->minute) return -1; // Allow for rounding to the nearest second on one of the times if (millisec > 0 && other->millisec == 0) { unsigned int sec = second + (millisec > 499 ? 1 : 0); if (sec > other->second) return 1; if (sec < other->second) return -1; } else if (millisec == 0 && other->millisec > 0) { unsigned int sec = other->second + (other->millisec > 499 ? 1 : 0); if (second > sec) return 1; if (second < sec) return -1; } else { if (second > other->second) return 1; if (second < other->second) return -1; if (millisec > other->millisec) return 1; if (millisec < other->millisec) return -1; } return 0; } // Get the creation(Windows)/modified(Unix) time from a file bool EyeTime::GetFileTime (const std::string& path) { if (!sequencelogic::IsValidFilename (path)) return false; #ifdef WIN32 HANDLE hFile = CreateFile (path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return false; else { FILETIME ftCreate; SYSTEMTIME stUTC; // Retrieve the file creation time if (!::GetFileTime (hFile, (LPFILETIME) NULL, (LPFILETIME) NULL, &ftCreate)) { CloseHandle (hFile); return false; } if (FileTimeToSystemTime (&ftCreate, &stUTC)) { // Convert the create time to UTC year = stUTC.wYear + 1900; month = stUTC.wMonth; // Windows starts at 1 day = stUTC.wDay; wday = stUTC.wDayOfWeek; hour = stUTC.wHour; minute = stUTC.wMinute; second = stUTC.wSecond; millisec = stUTC.wMilliseconds; SetYearDay(); } else { IONUDEBUG("EyeTime::GetFileTime(%s) - FileTimeToSystemTime failed", path.c_str()); } CloseHandle (hFile); } #else struct tm tm; struct stat attrib; if (stat (path.c_str(), &attrib) != 0) return false; gmtime_r (reinterpret_cast(&(attrib.st_mtime)), &tm); utc = 0; year = tm.tm_year + 1900; month = tm.tm_mon + 1; // Posix starts at 0 day = tm.tm_mday; yday = tm.tm_yday; wday = tm.tm_wday; hour = tm.tm_hour; minute = tm.tm_min; second = tm.tm_sec; #if defined(ANDROID) millisec = (attrib.st_mtime_nsec / 1000000); #elif !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE) millisec = (unsigned int)(attrib.st_mtimespec.tv_nsec / 1000000); #else millisec = (unsigned int)(attrib.st_mtim.tv_nsec / 1000000); #endif SetWeekDay(); #endif return (true); } // Set the creation(Windows)/modified(Unix) time of a file bool EyeTime::SetFileTime (const std::string& path) { if (!sequencelogic::IsValidFilename (path)) return false; #ifdef WIN32 SYSTEMTIME systime = GetSysTime(); FILETIME filetime; SystemTimeToFileTime (&systime, &filetime); // Get a handle to eyefile with file_write_attributes access HANDLE hFile = CreateFile (path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { IONUDEBUG ("EyeTime::SetFileTime(%s) failed invalid file handle", path.c_str()); return false; } // Set the file time on the file if (!::SetFileTime (hFile, (LPFILETIME) NULL, (LPFILETIME) NULL, &filetime)) { CloseHandle (hFile); IONUDEBUG ("EyeTime::SetFileTime(%s) failed ::SetFileTime", path.c_str()); return false; } CloseHandle (hFile); #else struct timeval times[2]; times[0].tv_usec = millisec * 1000; // Access time times[1].tv_usec = millisec * 1000; // Modification time struct tm tm; tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_yday = yday; tm.tm_wday = wday; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; //time_t t = mktime (&tm) Can't use this as it's localtime() inverse, need gmtime() inverse // Compute seconds since the epoch using the POSIX formula time_t t = tm.tm_sec + tm.tm_min*60 + tm.tm_hour*3600 + tm.tm_yday*86400 + (tm.tm_year-70)*31536000 + ((tm.tm_year-69)/4)*86400 - ((tm.tm_year-1)/100)*86400 + ((tm.tm_year+299)/400)*86400; if (t > 0) { times[0].tv_sec = t; times[1].tv_sec = t; if (utimes (path.c_str(), times) != 0) { IONUDEBUG ("EyeTime::SetFileTime(%s) failed utimes()", path.c_str()); } } else { IONUDEBUG ("EyeTime::SetFileTime(%s) failed invalid time", path.c_str()); } #endif return true; } // If corrupted, cap to legal values void EyeTime::Cap() { if (year > 3000 || year < 1970) year = 2014; if (month < 1 || month > 12) month = 1; if (day < 1 || (day > monthlengths[month-1] && month != 2 && !isLeapYear(year) && day > 29)) day = 1; if (hour > 23) hour = 1; if (minute > 60) minute = 1; if (second > 61) second = 1; if (millisec > 999) millisec = 1; if (utc < -12 || utc > 11) utc = 0; } // Format time as ISO 8601 formatted string (20 characters) const char* EyeTime::GetISO8601Time () { Cap(); sprintf(_isodate, "%04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, minute, second); return _isodate; } // Format time as ISO 8601 formatted string, with millisecs (24 characters) const char* EyeTime::GetISO8601TimeMs () { Cap(); sprintf(_isodate, "%04u-%02u-%02uT%02u:%02u:%02u.%03uZ", year, month, day, hour, minute, second, millisec); return _isodate; } // Format time as ISO 8601 formatted string (20 characters) const char* EyeTime::GetLocalISO8601Time () { ConvertToLocalTime(); sprintf(_isodate, "%04u-%02u-%02uT%02u:%02u:%02u%c", year, month, day, hour, minute, second, timezones[utc+12]); ConvertToGMTime(); return _isodate; } void EyeTime::ConvertToLocalTime() { // Convert to Local as needed int offset = utc + daylight; if (offset != 0) { if ((int)hour + offset > 24) { hour = (int)hour + offset - 24; if (++day > monthlengths[month] && !(day == 29 && month == 2 && year % 4 == 0)) { if (month == 12) { month = 1; year++; } else month++; day = 1; } } else if ((int)hour + offset < 0) { hour = (int)hour + offset + 24; if (--day < 1) { if (month == 1) { month = 12; year--; } else month--; if (month == 2 && year % 4 == 0) day = 29; else day = monthlengths[month-1]; } } else hour += offset; } } void EyeTime::ConvertToGMTime() { // Convert to GMT as needed int offset = utc - daylight; if (offset != 0) { if ((int)hour - offset >= 24) { hour = (int)hour + offset - 24; if (++day > monthlengths[month] && !(day == 29 && month == 2 && year % 4 == 0)) { if (month == 12) { month = 1; year++; } else month++; day = 1; } } else if ((int)hour - offset < 0) { hour = (int)hour + offset + 24; if (--day < 1) { if (month == 1) { month = 12; year--; } else month--; if (month == 2 && year % 4 == 0) day = 29; else day = monthlengths[month-1]; } } else hour -= offset; } } // Format time as a Unix timestamp, long, usually 10 digits const char* EyeTime::GetUnixTime () { double epoch = static_cast(GetTimeMs()) / 1000.0; sprintf (_isodate, "%0.0f", epoch); return _isodate; } double EyeTime::SecondsFromNow () { int tm_year = year - 1900; #ifdef WIN32 SYSTEMTIME stUTC; GetSystemTime (&stUTC); int tm_yday = stUTC.wDay - 1; // Day of the month but yday starts at 0 int tm_nyear = stUTC.wYear - 1900; for (int i = 0; i < 12; ++i) { if (i + 1 < stUTC.wMonth) { tm_yday += monthlengths[i]; } } // Account for leap years if (stUTC.wMonth > 2 && isLeapYear (year)) tm_yday++; double snow = stUTC.wSecond + stUTC.wMinute*60 + stUTC.wHour*3600 + tm_yday*86400 + (tm_nyear-70)*31536000 + ((tm_nyear-69)/4)*86400 - ((tm_nyear-1)/100)*86400 + ((tm_nyear+299)/400)*86400; #else time_t now; time (&now); struct tm tm_now; gmtime_r(&now, &tm_now); double snow = tm_now.tm_sec + tm_now.tm_min*60 + tm_now.tm_hour*3600 + tm_now.tm_yday*86400 + (tm_now.tm_year-70)*31536000 + ((tm_now.tm_year-69)/4)*86400 - ((tm_now.tm_year-1)/100)*86400 + ((tm_now.tm_year+299)/400)*86400; #endif double sthen = second + minute*60 + hour*3600 + yday*86400 + (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 - ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400; return (snow - sthen); } void EyeTime::Dump() { std::cout << "year=" << year << " month=" << month << " day=" << day << " wday=" << wday << " yday=" << yday; std::cout << " hour=" << hour << " min=" << minute << " sec=" << second << " ms=" << millisec << " tz=" << utc; std::cout << std::endl; } namespace sequencelogic { std::ostream& operator<<(std::ostream &out, EyeTime &et) { out << et.GetISO8601Time(); return out; } }