/* ** Zabbix ** Copyright (C) 2001-2024 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "zbxtime.h" #include "zbxnum.h" /****************************************************************************** * * * Purpose: Gets the current time. * * * * Return value: Time in seconds * * * * Comments: Time in seconds since midnight (00:00:00), * * January 1, 1970, coordinated universal time (UTC). * * * ******************************************************************************/ double zbx_time(void) { zbx_timespec_t ts; zbx_timespec(&ts); return (double)ts.sec + 1.0e-9 * (double)ts.ns; } /****************************************************************************** * * * Purpose: Gets the current time. * * * * Comments: Time in seconds since midnight (00:00:00), * * January 1, 1970, coordinated universal time (UTC). * * * ******************************************************************************/ void zbx_timespec(zbx_timespec_t *ts) { static ZBX_THREAD_LOCAL zbx_timespec_t last_ts = {0, 0}; static ZBX_THREAD_LOCAL int corr = 0; #if defined(_WINDOWS) || defined(__MINGW32__) static ZBX_THREAD_LOCAL LARGE_INTEGER tickPerSecond = {0}; struct _timeb tb; int sec_diff; #else struct timeval tv; int rc = -1; # ifdef HAVE_TIME_CLOCK_GETTIME struct timespec tp; # endif #endif #if defined(_WINDOWS) || defined(__MINGW32__) if (0 == tickPerSecond.QuadPart) QueryPerformanceFrequency(&tickPerSecond); _ftime(&tb); ts->sec = (int)tb.time; ts->ns = tb.millitm * 1000000; if (0 != tickPerSecond.QuadPart) { LARGE_INTEGER tick; if (TRUE == QueryPerformanceCounter(&tick)) { static ZBX_THREAD_LOCAL LARGE_INTEGER last_tick = {0}; if (0 < last_tick.QuadPart) { LARGE_INTEGER qpc_tick = {0}, ntp_tick = {0}; /* _ftime () returns precision in milliseconds, but 'ns' could be increased up to 1ms */ if (last_ts.sec == ts->sec && last_ts.ns > ts->ns && 1000000 > (last_ts.ns - ts->ns)) { ts->ns = last_ts.ns; } else { ntp_tick.QuadPart = tickPerSecond.QuadPart * (ts->sec - last_ts.sec) + tickPerSecond.QuadPart * (ts->ns - last_ts.ns) / 1000000000; } /* host system time can shift backwards, then correction is not reasonable */ if (0 <= ntp_tick.QuadPart) qpc_tick.QuadPart = tick.QuadPart - last_tick.QuadPart - ntp_tick.QuadPart; if (0 < qpc_tick.QuadPart && qpc_tick.QuadPart < tickPerSecond.QuadPart) { int ns = (int)(1000000000 * qpc_tick.QuadPart / tickPerSecond.QuadPart); if (1000000 > ns) /* value less than 1 millisecond */ { ts->ns += ns; while (ts->ns >= 1000000000) { ts->sec++; ts->ns -= 1000000000; } } } } last_tick = tick; } } #else /* not _WINDOWS */ #ifdef HAVE_TIME_CLOCK_GETTIME if (0 == (rc = clock_gettime(CLOCK_REALTIME, &tp))) { ts->sec = (int)tp.tv_sec; ts->ns = (int)tp.tv_nsec; } #endif /* HAVE_TIME_CLOCK_GETTIME */ if (0 != rc && 0 == (rc = gettimeofday(&tv, NULL))) { ts->sec = (int)tv.tv_sec; ts->ns = (int)tv.tv_usec * 1000; } if (0 != rc) { ts->sec = (int)time(NULL); ts->ns = 0; } #endif /* not _WINDOWS */ #if defined(_WINDOWS) || defined(__MINGW32__) sec_diff = ts->sec - last_ts.sec; /* correction window is 1 sec before the corrected last _ftime clock reading */ if ((0 == sec_diff && ts->ns <= last_ts.ns) || (-1 == sec_diff && ts->ns > last_ts.ns)) #else if (last_ts.ns == ts->ns && last_ts.sec == ts->sec) #endif { ts->ns = last_ts.ns + (++corr); while (ts->ns >= 1000000000) { ts->sec++; ts->ns -= 1000000000; } } else { last_ts.sec = ts->sec; last_ts.ns = ts->ns; corr = 0; } } /****************************************************************************** * * * Purpose: Gets the current time including UTC offset * * * * Return value: Time in seconds * * * ******************************************************************************/ double zbx_current_time(void) { return zbx_time() + ZBX_JAN_1970_IN_SEC; } /****************************************************************************** * * * Return value: SUCCEED - year is a leap year * * FAIL - year is not a leap year * * * ******************************************************************************/ int zbx_is_leap_year(int year) { return 0 == year % 4 && (0 != year % 100 || 0 == year % 400) ? SUCCEED : FAIL; } /****************************************************************************** * * * Purpose: * * get current time and store it in memory locations provided by caller * * * * Parameters: * * tm - [OUT] broken-down representation of the current time * * milliseconds - [OUT] milliseconds since the previous second * * tz - [OUT] local time offset from UTC (optional) * * * * Comments: * * On Windows localtime() and gmtime() return pointers to static, * * thread-local storage locations. On Unix localtime() and gmtime() are * * not thread-safe and re-entrant as they return pointers to static * * storage locations which can be overwritten by localtime(), gmtime() * * or other time functions in other threads or signal handlers. To avoid * * this we use localtime_r() and gmtime_r(). * * * ******************************************************************************/ void zbx_get_time(struct tm *tm, long *milliseconds, zbx_timezone_t *tz) { #if defined(_WINDOWS) || defined(__MINGW32__) struct _timeb current_time; _ftime(¤t_time); *tm = *localtime(¤t_time.time); /* localtime() cannot return NULL if called with valid parameter */ *milliseconds = current_time.millitm; #else struct timeval current_time; gettimeofday(¤t_time, NULL); localtime_r(¤t_time.tv_sec, tm); *milliseconds = current_time.tv_usec / 1000; #endif if (NULL != tz) { long offset; #if defined(_WINDOWS) || defined(__MINGW32__) offset = zbx_get_timezone_offset(current_time.time, tm); #else offset = zbx_get_timezone_offset(current_time.tv_sec, tm); #endif tz->tz_sign = (0 <= offset ? '+' : '-'); tz->tz_hour = labs(offset) / SEC_PER_HOUR; tz->tz_min = (labs(offset) - tz->tz_hour * SEC_PER_HOUR) / SEC_PER_MIN; /* assuming no remaining seconds like in historic Asia/Riyadh87, Asia/Riyadh88 and Asia/Riyadh89 */ } } /****************************************************************************** * * * Purpose: get time offset from UTC * * * * Parameters: t - [IN] input time to calculate offset with * * tm - [OUT] broken-down representation of the current time * * * * Return value: Time offset from UTC in seconds * * * ******************************************************************************/ long zbx_get_timezone_offset(time_t t, struct tm *tm) { long offset; #ifndef HAVE_TM_TM_GMTOFF struct tm tm_utc; #endif *tm = *localtime(&t); #ifdef HAVE_TM_TM_GMTOFF offset = tm->tm_gmtoff; #else #if defined(_WINDOWS) || defined(__MINGW32__) tm_utc = *gmtime(&t); #else gmtime_r(&t, &tm_utc); #endif offset = (tm->tm_yday - tm_utc.tm_yday) * SEC_PER_DAY + (tm->tm_hour - tm_utc.tm_hour) * SEC_PER_HOUR + (tm->tm_min - tm_utc.tm_min) * SEC_PER_MIN; /* assuming seconds are equal */ while (tm->tm_year > tm_utc.tm_year) offset += (SUCCEED == zbx_is_leap_year(tm_utc.tm_year++) ? SEC_PER_YEAR + SEC_PER_DAY : SEC_PER_YEAR); while (tm->tm_year < tm_utc.tm_year) offset -= (SUCCEED == zbx_is_leap_year(--tm_utc.tm_year) ? SEC_PER_YEAR + SEC_PER_DAY : SEC_PER_YEAR); #endif return offset; } /****************************************************************************** * * * Purpose: get broken-down representation of the time in specified time zone * * * * Parameters: time - [IN] input time * * tz - [IN] time zone * * * * Return value: broken-down representation of the time in specified time zone* * * ******************************************************************************/ struct tm *zbx_localtime(const time_t *time, const char *tz) { #if defined(HAVE_GETENV) && defined(HAVE_PUTENV) && defined(HAVE_UNSETENV) && defined(HAVE_TZSET) && \ !defined(_WINDOWS) && !defined(__MINGW32__) char *old_tz; struct tm *tm; if (NULL == tz || 0 == strcmp(tz, "system")) return localtime(time); if (NULL != (old_tz = getenv("TZ"))) old_tz = zbx_strdup(NULL, old_tz); setenv("TZ", tz, 1); tzset(); tm = localtime(time); if (NULL != old_tz) { setenv("TZ", old_tz, 1); zbx_free(old_tz); } else unsetenv("TZ"); tzset(); return tm; #else ZBX_UNUSED(tz); return localtime(time); #endif } /****************************************************************************** * * * Purpose: get UTC time from broken down time elements * * * * Parameters: * * year - [IN] year (1970-...) * * month - [IN] month (1-12) * * mday - [IN] day of month (1-..., depending on month and year) * * hour - [IN] hours (0-23) * * min - [IN] minutes (0-59) * * sec - [IN] seconds (0-61, leap seconds are not strictly validated) * * t - [OUT] Epoch timestamp * * * * Return value: SUCCEED - date is valid and resulting timestamp is positive * * FAIL - otherwise * * * ******************************************************************************/ int zbx_utc_time(int year, int mon, int mday, int hour, int min, int sec, int *t) { /* number of leap years before but not including year */ #define ZBX_LEAP_YEARS(year) (((year) - 1) / 4 - ((year) - 1) / 100 + ((year) - 1) / 400) /* days since the beginning of non-leap year till the beginning of the month */ static const int month_day[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; static const int epoch_year = 1970; if (epoch_year <= year && 1 <= mon && mon <= 12 && 1 <= mday && mday <= zbx_day_in_month(year, mon) && 0 <= hour && hour <= 23 && 0 <= min && min <= 59 && 0 <= sec && sec <= 61 && 0 <= (*t = (year - epoch_year) * SEC_PER_YEAR + (ZBX_LEAP_YEARS(2 < mon ? year + 1 : year) - ZBX_LEAP_YEARS(epoch_year)) * SEC_PER_DAY + (month_day[mon - 1] + mday - 1) * SEC_PER_DAY + hour * SEC_PER_HOUR + min * SEC_PER_MIN + sec)) { return SUCCEED; } return FAIL; #undef ZBX_LEAP_YEARS } /****************************************************************************** * * * Purpose: returns number of days in a month * * * * Parameters: * * year - [IN] * * mon - [IN] month (1-12) * * * * Return value: 28-31 depending on number of days in the month, defaults to * * 30 if the month is outside of allowed range * * * ******************************************************************************/ int zbx_day_in_month(int year, int mon) { /* number of days in the month of a non-leap year */ static const unsigned char month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (1 <= mon && mon <= 12) /* add one day in February of a leap year */ return month[mon - 1] + (2 == mon && SUCCEED == zbx_is_leap_year(year) ? 1 : 0); return 30; } /****************************************************************************** * * * Purpose: get duration in milliseconds since time stamp till current time * * * * Parameters: * * ts - [IN] time from when duration should be counted * * * * Return value: duration in milliseconds since time stamp till current time * * * ******************************************************************************/ zbx_uint64_t zbx_get_duration_ms(const zbx_timespec_t *ts) { zbx_timespec_t now; zbx_timespec(&now); return (now.sec - ts->sec) * 1e3 + (now.ns - ts->ns) / 1e6; } static void tm_add(struct tm *tm, int multiplier, zbx_time_unit_t base); static void tm_sub(struct tm *tm, int multiplier, zbx_time_unit_t base); static int time_unit_seconds[ZBX_TIME_UNIT_COUNT] = {0, 1, SEC_PER_MIN, SEC_PER_HOUR, SEC_PER_DAY, SEC_PER_WEEK, 0, 0, 0}; zbx_time_unit_t zbx_tm_str_to_unit(const char *text) { switch (*text) { case 's': return ZBX_TIME_UNIT_SECOND; case 'm': return ZBX_TIME_UNIT_MINUTE; case 'h': return ZBX_TIME_UNIT_HOUR; case 'd': return ZBX_TIME_UNIT_DAY; case 'w': return ZBX_TIME_UNIT_WEEK; case 'M': return ZBX_TIME_UNIT_MONTH; case 'y': return ZBX_TIME_UNIT_YEAR; default: return ZBX_TIME_UNIT_UNKNOWN; } } /****************************************************************************** * * * Purpose: parse time period in format