/*
** 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 Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#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_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 broken-down representation of the time and cache result *
* *
* Parameters: time - [IN] input time *
* *
* Return value: broken-down representation of the time *
* *
******************************************************************************/
const struct tm *zbx_localtime_now(const time_t *time)
{
static ZBX_THREAD_LOCAL struct tm tm_last;
static ZBX_THREAD_LOCAL time_t time_last;
if (time_last != *time)
{
time_last = *time;
localtime_r(time, &tm_last);
}
return &tm_last;
}
/******************************************************************************
* *
* 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 *
* *
* Comments: *
* Timestamp value 'ts' must be before or equal to current time. *
* *
******************************************************************************/
zbx_uint64_t zbx_get_duration_ms(const zbx_timespec_t *ts)
{
zbx_timespec_t now;
zbx_timespec(&now);
return (zbx_uint64_t)((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