/*
** Copyright (C) 2001-2026 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 <https://www.gnu.org/licenses/>.
**/

#include "zbxexpr.h"

#include "zbxnum.h"
#include "zbxtime.h"

#define ZBX_SCHEDULER_FILTER_DAY	1
#define ZBX_SCHEDULER_FILTER_HOUR	2
#define ZBX_SCHEDULER_FILTER_MINUTE	3
#define ZBX_SCHEDULER_FILTER_SECOND	4

typedef struct
{
	int	start_day;	/* day of week when period starts */
	int	end_day;	/* day of week when period ends, included */
	int	start_time;	/* number of seconds from the beginning of the day when period starts */
	int	end_time;	/* number of seconds from the beginning of the day when period ends, not included */
}
zbx_time_period_t;

typedef struct zbx_scheduler_filter
{
	int				start;
	int				end;
	int				step;

	struct zbx_scheduler_filter	*next;
}
zbx_scheduler_filter_t;

typedef struct zbx_scheduler_interval
{
	zbx_scheduler_filter_t		*mdays;
	zbx_scheduler_filter_t		*wdays;
	zbx_scheduler_filter_t		*hours;
	zbx_scheduler_filter_t		*minutes;
	zbx_scheduler_filter_t		*seconds;

	int				filter_level;

	struct zbx_scheduler_interval	*next;
}
zbx_scheduler_interval_t;

typedef struct zbx_flexible_interval
{
	zbx_time_period_t		period;
	int				delay;

	struct zbx_flexible_interval	*next;
}
zbx_flexible_interval_t;

struct zbx_custom_interval
{
	zbx_flexible_interval_t		*flexible;
	zbx_scheduler_interval_t	*scheduling;
};

/******************************************************************************
 *                                                                            *
 * Purpose: checks if current time is within given period                     *
 *                                                                            *
 * Parameters: period - [IN] preprocessed time period                         *
 *             tm     - [IN] broken-down time for comparison                  *
 *                                                                            *
 * Return value: FAIL - out of period, SUCCEED - within the period            *
 *                                                                            *
 ******************************************************************************/
static int	check_time_period(const zbx_time_period_t period, const struct tm *tm)
{
	int		day, time;

	day = 0 == tm->tm_wday ? 7 : tm->tm_wday;
	time = SEC_PER_HOUR * tm->tm_hour + SEC_PER_MIN * tm->tm_min + tm->tm_sec;

	return period.start_day <= day && day <= period.end_day && period.start_time <= time && time < period.end_time ?
			SUCCEED : FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: returns delay value that is currently applicable                  *
 *                                                                            *
 * Parameters: default_delay  - [IN] default delay value, can be overridden   *
 *             flex_intervals - [IN] preprocessed flexible intervals          *
 *             now            - [IN] current time                             *
 *                                                                            *
 * Return value: delay value - either default or minimum delay value          *
 *                             out of all applicable intervals                *
 *                                                                            *
 ******************************************************************************/
static int	get_current_delay(int default_delay, const zbx_flexible_interval_t *flex_intervals, time_t now)
{
	int		current_delay = -1;

	while (NULL != flex_intervals)
	{
		if ((-1 == current_delay || flex_intervals->delay < current_delay) &&
				SUCCEED == check_time_period(flex_intervals->period, zbx_localtime_now(&now)))
		{
			current_delay = flex_intervals->delay;
		}

		flex_intervals = flex_intervals->next;
	}

	return -1 == current_delay ? default_delay : current_delay;
}

/******************************************************************************
 *                                                                            *
 * Purpose: returns time when next delay settings take effect                 *
 *                                                                            *
 * Parameters: flex_intervals - [IN] preprocessed flexible intervals          *
 *             now            - [IN] current time                             *
 *             next_interval  - [OUT] start of next delay interval            *
 *                                                                            *
 * Return value: SUCCEED - there is next interval                             *
 *               FAIL    - otherwise (in this case, next_interval is          *
 *                         unaffected)                                        *
 *                                                                            *
 ******************************************************************************/
static int	get_next_delay_interval(const zbx_flexible_interval_t *flex_intervals, time_t now,
		time_t *next_interval)
{
	int		day, time, next = 0, candidate;
	const struct tm	*tm;

	if (NULL == flex_intervals)
		return FAIL;

	tm = zbx_localtime_now(&now);
	day = 0 == tm->tm_wday ? 7 : tm->tm_wday;
	time = SEC_PER_HOUR * tm->tm_hour + SEC_PER_MIN * tm->tm_min + tm->tm_sec;

	for (; NULL != flex_intervals; flex_intervals = flex_intervals->next)
	{
		const zbx_time_period_t	*p = &flex_intervals->period;

		if (p->start_day <= day && day <= p->end_day && time < p->end_time)	/* will be active today */
		{
			if (time < p->start_time)	/* hasn't been active today yet */
				candidate = p->start_time;
			else	/* currently active */
				candidate = p->end_time;
		}
		else if (day < p->end_day)	/* will be active this week */
		{
			if (day < p->start_day)	/* hasn't been active this week yet */
				candidate = SEC_PER_DAY * (p->start_day - day) + p->start_time;
			else	/* has been active this week and will be active at least once more by the end of it */
				candidate = SEC_PER_DAY + p->start_time;	/* therefore will be active tomorrow */
		}
		else	/* will be active next week */
			candidate = SEC_PER_DAY * (p->start_day + 7 - day) + p->start_time;

		if (0 == next || next > candidate)
			next = candidate;
	}

	if (0 == next)
		return FAIL;

	*next_interval = now - time + next;
	return SUCCEED;
}

/*******************************************************************************
 *                                                                             *
 * Purpose: parses time of day                                                 *
 *                                                                             *
 * Parameters: time       - [OUT] Number of seconds since the beginning of     *
 *                                the day corresponding to a given time of     *
 *                                the day.                                     *
 *             text       - [IN] text to parse                                 *
 *             len        - [IN] number of characters available for parsing    *
 *             parsed_len - [OUT] number of characters recognized as time      *
 *                                                                             *
 * Return value: SUCCEED - text was successfully parsed as time of day         *
 *               FAIL    - otherwise (time and parsed_len remain untouched)    *
 *                                                                             *
 * Comments: !!! Don't forget to sync code with PHP !!!                        *
 *           Supported formats are hh:mm, h:mm and 0h:mm; 0 <= hours <= 24;    *
 *           0 <= minutes <= 59; if hours == 24 then minutes must be 0.        *
 *                                                                             *
 *******************************************************************************/
static int	time_parse(int *time, const char *text, int len, int *parsed_len)
{
	const int	old_len = len;
	const char	*ptr;
	int		hours, minutes;

	for (ptr = text; 0 < len && 0 != isdigit(*ptr) && 2 >= ptr - text; len--, ptr++)
		;

	if (SUCCEED != zbx_is_uint_n_range(text, ptr - text, &hours, sizeof(hours), 0, 24))
		return FAIL;

	if (0 >= len-- || ':' != *ptr++)
		return FAIL;

	for (text = ptr; 0 < len && 0 != isdigit(*ptr) && 2 >= ptr - text; len--, ptr++)
		;

	if (2 != ptr - text)
		return FAIL;

	if (SUCCEED != zbx_is_uint_n_range(text, 2, &minutes, sizeof(minutes), 0, 59))
		return FAIL;

	if (24 == hours && 0 != minutes)
		return FAIL;

	*parsed_len = old_len - len;
	*time = SEC_PER_HOUR * hours + SEC_PER_MIN * minutes;
	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Parameters: period - [OUT] time period structure                           *
 *             text   - [IN] text to parse                                    *
 *             len    - [IN] number of characters available for parsing       *
 *                                                                            *
 * Return value: SUCCEED - text was successfully parsed as time period        *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: !!! Don't forget to sync code with PHP !!!                       *
 *           Supported format is d[-d],time-time where 1 <= d <= 7            *
 *                                                                            *
 ******************************************************************************/
static int	time_period_parse(zbx_time_period_t *period, const char *text, int len)
{
	int	parsed_len;

	if (0 >= len-- || '1' > *text || '7' < *text)
		return FAIL;

	period->start_day = *text++ - '0';

	if (0 >= len)
		return FAIL;

	if ('-' == *text)
	{
		text++;
		len--;

		if (0 >= len-- || '1' > *text || '7' < *text)
			return FAIL;

		period->end_day = *text++ - '0';

		if (period->start_day > period->end_day)
			return FAIL;
	}
	else
		period->end_day = period->start_day;

	if (0 >= len-- || ',' != *text++)
		return FAIL;

	if (SUCCEED != time_parse(&period->start_time, text, len, &parsed_len))
		return FAIL;

	text += parsed_len;
	len -= parsed_len;

	if (0 >= len-- || '-' != *text++)
		return FAIL;

	if (SUCCEED != time_parse(&period->end_time, text, len, &parsed_len))
		return FAIL;

	if (period->start_time >= period->end_time)
		return FAIL;

	if (0 != (len - parsed_len))
		return FAIL;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses text string into scheduler filter                          *
 *                                                                            *
 * Parameters: filter  - [IN/OUT] first filter                                *
 *             text    - [IN] text to parse                                   *
 *             len     - [IN/OUT] number of characters left to parse          *
 *             min     - [IN] minimal time unit value                         *
 *             max     - [IN] maximal time unit value                         *
 *             var_len - [IN] maximum number of characters for a filter       *
 *                            variable (<from>, <to>, <step>)                 *
 *                                                                            *
 * Return value: SUCCEED - filter was successfully parsed                     *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: This function recursively calls itself for each filter fragment. *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_parse_filter_r(zbx_scheduler_filter_t **filter, const char *text, int *len, int min, int max,
		int var_len)
{
	int			start = 0, end = 0, step = 1;
	const char		*pstart, *pend;
	zbx_scheduler_filter_t	*filter_new;

	pstart = pend = text;
	while (0 != isdigit(*pend) && 0 < *len)
	{
		pend++;
		(*len)--;
	}

	if (pend != pstart)
	{
		if (pend - pstart > var_len)
			return FAIL;

		if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &start, sizeof(start), min, max))
			return FAIL;

		if ('-' == *pend)
		{
			pstart = pend + 1;

			do
			{
				pend++;
				(*len)--;
			}
			while (0 != isdigit(*pend) && 0 < *len);

			/* empty or too long value, fail */
			if (pend == pstart || pend - pstart > var_len)
				return FAIL;

			if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &end, sizeof(end), min, max))
				return FAIL;

			if (end < start)
				return FAIL;
		}
		else
		{
			/* step is valid only for defined range */
			if ('/' == *pend)
				return FAIL;

			end = start;
		}
	}
	else
	{
		start = min;
		end = max;
	}

	if ('/' == *pend)
	{
		pstart = pend + 1;

		do
		{
			pend++;
			(*len)--;
		}
		while (0 != isdigit(*pend) && 0 < *len);

		/* empty or too long step, fail */
		if (pend == pstart || pend - pstart > var_len)
			return FAIL;

		if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &step, sizeof(step), 1, end - start))
			return FAIL;
	}
	else
	{
		if (pend == text)
			return FAIL;
	}

	if (',' == *pend)
	{
		/* no next filter after ',' */
		if (0 == --(*len))
			return FAIL;

		pend++;

		if (SUCCEED != scheduler_parse_filter_r(filter, pend, len, min, max, var_len))
			return FAIL;
	}

	filter_new = (zbx_scheduler_filter_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_filter_t));
	filter_new->start = start;
	filter_new->end = end;
	filter_new->step = step;
	filter_new->next = *filter;
	*filter = filter_new;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses text string into scheduler filter                          *
 *                                                                            *
 * Parameters: filter  - [IN/OUT] first filter                                *
 *             text    - [IN] text to parse                                   *
 *             len     - [IN/OUT] number of characters left to parse          *
 *             min     - [IN] minimal time unit value                         *
 *             max     - [IN] maximal time unit value                         *
 *             var_len - [IN] maximum number of characters for filter         *
 *                            variable (<from>, <to>, <step>)                 *
 *                                                                            *
 * Return value: SUCCEED - filter was successfully parsed                     *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: This function will fail if a filter already exists. This         *
 *           user from defining multiple filters of the same time unit in a   *
 *           single interval. For example: h0h12 is invalid filter and its    *
 *           parsing must fail.                                               *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_parse_filter(zbx_scheduler_filter_t **filter, const char *text, int *len, int min, int max,
		int var_len)
{
	if (NULL != *filter)
		return FAIL;

	return scheduler_parse_filter_r(filter, text, len, min, max, var_len);
}

/******************************************************************************
 *                                                                            *
 * Parameters: interval - [IN/OUT] first interval                             *
 *             text     - [IN] text to parse                                  *
 *             len      - [IN] text length                                    *
 *                                                                            *
 * Return value: SUCCEED - interval was successfully parsed                   *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_interval_parse(zbx_scheduler_interval_t *interval, const char *text, int len)
{
	int	ret = SUCCEED;

	if (0 == len)
		return FAIL;

	while (SUCCEED == ret && 0 != len)
	{
		int	old_len = len--;

		switch (*text)
		{
			case '\0':
				return FAIL;
			case 'h':
				if (ZBX_SCHEDULER_FILTER_HOUR < interval->filter_level)
					return FAIL;

				ret = scheduler_parse_filter(&interval->hours, text + 1, &len, 0, 23, 2);
				interval->filter_level = ZBX_SCHEDULER_FILTER_HOUR;

				break;
			case 's':
				if (ZBX_SCHEDULER_FILTER_SECOND < interval->filter_level)
					return FAIL;

				ret = scheduler_parse_filter(&interval->seconds, text + 1, &len, 0, 59, 2);
				interval->filter_level = ZBX_SCHEDULER_FILTER_SECOND;

				break;
			case 'w':
				if ('d' != text[1])
					return FAIL;

				if (ZBX_SCHEDULER_FILTER_DAY < interval->filter_level)
					return FAIL;

				len--;
				ret = scheduler_parse_filter(&interval->wdays, text + 2, &len, 1, 7, 1);
				interval->filter_level = ZBX_SCHEDULER_FILTER_DAY;

				break;
			case 'm':
				if ('d' == text[1])
				{
					if (ZBX_SCHEDULER_FILTER_DAY < interval->filter_level ||
							NULL != interval->wdays)
					{
						return FAIL;
					}

					len--;
					ret = scheduler_parse_filter(&interval->mdays, text + 2, &len, 1, 31, 2);
					interval->filter_level = ZBX_SCHEDULER_FILTER_DAY;
				}
				else
				{
					if (ZBX_SCHEDULER_FILTER_MINUTE < interval->filter_level)
						return FAIL;

					ret = scheduler_parse_filter(&interval->minutes, text + 1, &len, 0, 59, 2);
					interval->filter_level = ZBX_SCHEDULER_FILTER_MINUTE;
				}

				break;
			default:
				return FAIL;
		}

		text += old_len - len;
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees scheduler interval filter                                   *
 *                                                                            *
 * Parameters: filter - [IN] scheduler interval filter                        *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_filter_free(zbx_scheduler_filter_t *filter)
{
	zbx_scheduler_filter_t	*filter_next;

	for (; NULL != filter; filter = filter_next)
	{
		filter_next = filter->next;
		zbx_free(filter);
	}
}

/******************************************************************************
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_interval_free(zbx_scheduler_interval_t *interval)
{
	zbx_scheduler_interval_t	*interval_next;

	for (; NULL != interval; interval = interval_next)
	{
		interval_next = interval->next;

		scheduler_filter_free(interval->mdays);
		scheduler_filter_free(interval->wdays);
		scheduler_filter_free(interval->hours);
		scheduler_filter_free(interval->minutes);
		scheduler_filter_free(interval->seconds);

		zbx_free(interval);
	}
}

/******************************************************************************
 *                                                                            *
 * Parameters: interval - [IN/OUT] first interval                             *
 *             text     - [IN] text to parse                                  *
 *             len      - [IN] text length                                    *
 *                                                                            *
 * Return value: SUCCEED - interval was successfully parsed                   *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: !!! Don't forget to sync code with PHP !!!                       *
 *           Supported format is delay/period                                 *
 *                                                                            *
 ******************************************************************************/
static int	flexible_interval_parse(zbx_flexible_interval_t *interval, const char *text, int len)
{
	const char	*ptr;

	for (ptr = text; 0 < len && '\0' != *ptr && '/' != *ptr; len--, ptr++)
		;

	if (SUCCEED != zbx_is_time_suffix(text, &interval->delay, (int)(ptr - text)))
		return FAIL;

	if (0 >= len-- || '/' != *ptr++)
		return FAIL;

	return time_period_parse(&interval->period, ptr, len);
}

/******************************************************************************
 *                                                                            *
 * Parameters: interval - [IN] flexible interval                              *
 *                                                                            *
 ******************************************************************************/
static void	flexible_interval_free(zbx_flexible_interval_t *interval)
{
	zbx_flexible_interval_t	*interval_next;

	for (; NULL != interval; interval = interval_next)
	{
		interval_next = interval->next;
		zbx_free(interval);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses item and low-level discovery rule update intervals         *
 *                                                                            *
 * Parameters: interval_str     - [IN] update interval string to parse        *
 *             simple_interval  - [OUT] simple update interval                *
 *             custom_intervals - [OUT] flexible and scheduling intervals     *
 *             error            - [OUT] error message                         *
 *                                                                            *
 * Return value: SUCCEED - intervals are valid                                *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: !!! Don't forget to sync code with PHP !!!                       *
 *           Supported format:                                                *
 *             SimpleInterval, {";", FlexibleInterval | SchedulingInterval};  *
 *                                                                            *
 ******************************************************************************/
int	zbx_interval_preproc(const char *interval_str, int *simple_interval, zbx_custom_interval_t **custom_intervals,
		char **error)
{
	zbx_flexible_interval_t		*flexible = NULL;
	zbx_scheduler_interval_t	*scheduling = NULL;
	const char			*delim, *interval_type;

	if (SUCCEED != zbx_is_time_suffix(interval_str, simple_interval,
			(int)(NULL == (delim = strchr(interval_str, ';')) ? ZBX_LENGTH_UNLIMITED :
			delim - interval_str)))
	{
		interval_type = "update";
		goto fail;
	}

	if (NULL == custom_intervals)	/* caller wasn't interested in custom intervals, don't parse them */
		return SUCCEED;

	while (NULL != delim)
	{
		interval_str = delim + 1;
		delim = strchr(interval_str, ';');

		if (0 != isdigit(*interval_str))
		{
			zbx_flexible_interval_t	*new_interval;

			new_interval = (zbx_flexible_interval_t *)zbx_malloc(NULL, sizeof(zbx_flexible_interval_t));

			if (SUCCEED != flexible_interval_parse(new_interval, interval_str,
					(NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str))) ||
					(0 == *simple_interval && 0 == new_interval->delay))
			{
				zbx_free(new_interval);
				interval_type = "flexible";
				goto fail;
			}

			new_interval->next = flexible;
			flexible = new_interval;
		}
		else
		{
			zbx_scheduler_interval_t	*new_interval;

			new_interval = (zbx_scheduler_interval_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_interval_t));
			memset(new_interval, 0, sizeof(zbx_scheduler_interval_t));

			if (SUCCEED != scheduler_interval_parse(new_interval, interval_str,
					(NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str))))
			{
				scheduler_interval_free(new_interval);
				interval_type = "scheduling";
				goto fail;
			}

			new_interval->next = scheduling;
			scheduling = new_interval;
		}
	}

	if ((NULL == flexible && NULL == scheduling && 0 == *simple_interval) || SEC_PER_DAY < *simple_interval)
	{
		interval_type = "update";
		goto fail;
	}

	*custom_intervals = (zbx_custom_interval_t *)zbx_malloc(NULL, sizeof(zbx_custom_interval_t));
	(*custom_intervals)->flexible = flexible;
	(*custom_intervals)->scheduling = scheduling;

	return SUCCEED;
fail:
	if (NULL != error)
	{
		*error = zbx_dsprintf(*error, "Invalid %s interval \"%.*s\".", interval_type,
				(NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str)),
				interval_str);
	}

	flexible_interval_free(flexible);
	scheduler_interval_free(scheduling);

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses user macro and finds it's length                           *
 *                                                                            *
 * Parameters: str   - [IN] string to check                                   *
 *             len   - [OUT] length simple interval string until separator    *
 *             sep   - [IN] separator to calculate length                     *
 *             value - [OUT] interval value                                   *
 *                                                                            *
 * Return Value:                                                              *
 *     SUCCEED - macro was parsed successfully                                *
 *     FAIL    - Macro parsing failed, the content of output variables is not *
 *               defined.                                                     *
 *                                                                            *
 ******************************************************************************/
static int	parse_simple_interval(const char *str, int *len, char sep, int *value)
{
	const char	*delim;

	if (SUCCEED != zbx_is_time_suffix(str, value,
			(int)(NULL == (delim = strchr(str, sep)) ? ZBX_LENGTH_UNLIMITED : delim - str)))
	{
		return FAIL;
	}

	*len = NULL == delim ? (int)strlen(str) : delim - str;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses user macro and finds it's length                           *
 *                                                                            *
 * Parameters: str  - [IN] string to check                                    *
 *             len  - [OUT] length of macro                                   *
 *                                                                            *
 * Return Value:                                                              *
 *     SUCCEED - macro was parsed successfully                                *
 *     FAIL    - Macro parsing failed, the content of output variables        *
 *               is not defined.                                              *
 *                                                                            *
 ******************************************************************************/
static int	parse_user_macro(const char *str, int *len)
{
	int	macro_r, context_l, context_r;

	if ('{' != *str || '$' != *(str + 1) || SUCCEED != zbx_user_macro_parse(str, &macro_r, &context_l, &context_r,
			NULL))
	{
		return FAIL;
	}

	*len = macro_r + 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: validates update interval, flexible and scheduling intervals      *
 *                                                                            *
 * Parameters: str   - [IN]  string to check                                  *
 *             error - [OUT] validation error                                 *
 *                                                                            *
 * Return Value:                                                              *
 *     SUCCEED - parsed successfully                                          *
 *     FAIL    - parsing failed                                               *
 *                                                                            *
 ******************************************************************************/
int	zbx_validate_interval(const char *str, char **error)
{
	int		simple_interval, interval, len, custom = 0, macro;
	const char	*delim;

	if (SUCCEED == parse_user_macro(str, &len) && ('\0' == *(delim = str + len) || ';' == *delim))
	{
		if ('\0' == *delim)
			delim = NULL;

		simple_interval = 1;
	}
	else if (SUCCEED == parse_simple_interval(str, &len, ';', &simple_interval))
	{
		if ('\0' == *(delim = str + len))
			delim = NULL;
	}
	else
	{
		*error = zbx_dsprintf(*error, "Invalid update interval \"%.*s\".",
				NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str), str);
		return FAIL;
	}

	while (NULL != delim)
	{
		str = delim + 1;

		if ((SUCCEED == (macro = parse_user_macro(str, &len)) ||
				SUCCEED == parse_simple_interval(str, &len, '/', &interval)) &&
				'/' == *(delim = str + len))
		{
			zbx_time_period_t period;

			custom = 1;

			if (SUCCEED == macro)
				interval = 1;

			if (0 == interval && 0 == simple_interval)
			{
				*error = zbx_dsprintf(*error, "Invalid flexible interval \"%.*s\".", (int)(delim - str),
						str);
				return FAIL;
			}

			str = delim + 1;

			if (SUCCEED == parse_user_macro(str, &len) && ('\0' == *(delim = str + len) || ';' == *delim))
			{
				if ('\0' == *delim)
					delim = NULL;

				continue;
			}

			if (SUCCEED == time_period_parse(&period, str,
					NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str)))
			{
				continue;
			}

			*error = zbx_dsprintf(*error, "Invalid flexible period \"%.*s\".",
					NULL == delim ? (int)strlen(str) : (int)(delim - str), str);
			return FAIL;
		}
		else
		{
			zbx_scheduler_interval_t	*new_interval;

			custom = 1;

			if (SUCCEED == macro && ('\0' == *(delim = str + len) || ';' == *delim))
			{
				if ('\0' == *delim)
					delim = NULL;

				continue;
			}

			new_interval = (zbx_scheduler_interval_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_interval_t));
			memset(new_interval, 0, sizeof(zbx_scheduler_interval_t));

			if (SUCCEED == scheduler_interval_parse(new_interval, str,
					NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str)))
			{
				scheduler_interval_free(new_interval);
				continue;
			}
			scheduler_interval_free(new_interval);

			*error = zbx_dsprintf(*error, "Invalid custom interval \"%.*s\".",
					NULL == delim ? (int)strlen(str) : (int)(delim - str), str);

			return FAIL;
		}
	}

	if ((0 == custom && 0 == simple_interval) || SEC_PER_DAY < simple_interval)
	{
		*error = zbx_dsprintf(*error, "Invalid update interval \"%d\"", simple_interval);
		return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: checks if custom interval contains scheduling interval            *
 *                                                                            *
 * Parameters: custom_intervals - [IN]                                        *
 *                                                                            *
 * Return value: SUCCEED - if custom interval contains scheduling interval    *
 *               FAIL - otherwise                                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_custom_interval_is_scheduling(const zbx_custom_interval_t *custom_intervals)
{
	return NULL == custom_intervals->scheduling ? FAIL : SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees custom update intervals                                     *
 *                                                                            *
 * Parameters: custom_intervals - [IN]                                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_custom_interval_free(zbx_custom_interval_t *custom_intervals)
{
	flexible_interval_free(custom_intervals->flexible);
	scheduler_interval_free(custom_intervals->scheduling);
	zbx_free(custom_intervals);
}

/******************************************************************************
 *                                                                            *
 * Purpose: increments struct tm value by one second                          *
 *                                                                            *
 * Parameters: tm - [IN/OUT] tm structure to increment                        *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_tm_inc(struct tm *tm)
{
	if (60 > ++tm->tm_sec)
		return;

	tm->tm_sec = 0;
	if (60 > ++tm->tm_min)
		return;

	tm->tm_min = 0;
	if (24 > ++tm->tm_hour)
		return;

	tm->tm_hour = 0;
	if (zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1) >= ++tm->tm_mday)
		return;

	tm->tm_mday = 1;
	if (12 > ++tm->tm_mon)
		return;

	tm->tm_mon = 0;
	tm->tm_year++;
	return;
}

/******************************************************************************
 *                                                                            *
 * Purpose: finds daylight saving change time inside specified time period    *
 *                                                                            *
 * Parameters: time_start - [IN] time period start                            *
 *             time_end   - [IN] time period end                              *
 *                                                                            *
 * Return Value: Time when the daylight saving changes should occur.          *
 *                                                                            *
 * Comments: The calculated time is cached and reused if it first the         *
 *           specified period.                                                *
 *                                                                            *
 ******************************************************************************/
static time_t	scheduler_find_dst_change(time_t time_start, time_t time_end)
{
	static time_t	time_dst = 0;
	struct tm	*tm;
	time_t		time_mid;
	int		start, end, mid, dst_start;

	if (time_dst < time_start || time_dst > time_end)
	{
		/* assume that daylight saving will change only on 0 seconds */
		start = time_start / 60;
		end = time_end / 60;

		tm = zbx_localtime(&time_start, NULL);
		dst_start = tm->tm_isdst;

		while (end > start + 1)
		{
			mid = (start + end) / 2;
			time_mid = mid * 60;

			tm = zbx_localtime(&time_mid, NULL);

			if (tm->tm_isdst == dst_start)
				start = mid;
			else
				end = mid;
		}

		time_dst = end * 60;
	}

	return time_dst;
}

/******************************************************************************
 *                                                                            *
 * Parameters: year - [IN] year (>1752)                                       *
 *             mon  - [IN] month (1-12)                                       *
 *             mday - [IN] month day (1-31)                                   *
 *                                                                            *
 * Return value: The day of week: 1 - Monday, 2 - Tuesday, ...                *
 *                                                                            *
 ******************************************************************************/
static int	calculate_dayofweek(int year, int mon, int mday)
{
	static int	mon_table[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};

	if (mon < 3)
		year--;

	return (year + year / 4 - year / 100 + year / 400 + mon_table[mon - 1] + mday - 1) % 7 + 1;
}

/******************************************************************************
 *                                                                            *
 * Purpose: checks if specified date satisfies week day filter                *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN] date & time to validate                        *
 *                                                                            *
 * Return value: SUCCEED - input date satisfies week day filter               *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_validate_wday_filter(const zbx_scheduler_interval_t *interval, struct tm *tm)
{
	const zbx_scheduler_filter_t	*filter;
	int				value;

	if (NULL == interval->wdays)
		return SUCCEED;

	value = calculate_dayofweek(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);

	/* check if the value match week day filter */
	for (filter = interval->wdays; NULL != filter; filter = filter->next)
	{
		if (filter->start <= value && value <= filter->end)
		{
			int	next = value, offset;

			/* apply step */
			offset = (next - filter->start) % filter->step;
			if (0 != offset)
				next += filter->step - offset;

			/* succeed if the calculated value is still in filter range */
			if (next <= filter->end)
				return SUCCEED;
		}
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets next nearest value that satisfies filter chain               *
 *                                                                            *
 * Parameters: filter - [IN] filter chain                                     *
 *             value  - [IN] current value                                    *
 *                      [OUT] next nearest value (>= than input value)        *
 *                                                                            *
 * Return value: SUCCEED - next nearest value was successfully found          *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_get_nearest_filter_value(const zbx_scheduler_filter_t *filter, int *value)
{
	const zbx_scheduler_filter_t	*filter_next = NULL;

	for (; NULL != filter; filter = filter->next)
	{
		/* find matching filter */
		if (filter->start <= *value && *value <= filter->end)
		{
			int	next = *value, offset;

			/* apply step */
			offset = (next - filter->start) % filter->step;
			if (0 != offset)
				next += filter->step - offset;

			/* succeed if the calculated value is still in filter range */
			if (next <= filter->end)
			{
				*value = next;
				return SUCCEED;
			}
		}

		/* find the next nearest filter */
		if (filter->start > *value && (NULL == filter_next || filter_next->start > filter->start))
			filter_next = filter;
	}

	/* The value is not in a range of any filters, but we have next nearest filter. */
	if (NULL != filter_next)
	{
		*value = filter_next->start;
		return SUCCEED;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates next day that satisfies week day filter                *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 * Return value: SUCCEED - next day was found                                 *
 *               FAIL    - next day satisfying week day filter was not        *
 *                         found in current month                             *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_get_wday_nextcheck(const zbx_scheduler_interval_t *interval, struct tm *tm)
{
	int	value_now, value_next;

	if (NULL == interval->wdays)
		return SUCCEED;

	value_now = value_next = calculate_dayofweek(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);

	/* get the nearest week day from the current week day*/
	if (SUCCEED != scheduler_get_nearest_filter_value(interval->wdays, &value_next))
	{
		/* in the case of failure move month day to the next week, reset week day and try again */
		tm->tm_mday += 7 - value_now + 1;
		value_now = value_next = 1;

		if (SUCCEED != scheduler_get_nearest_filter_value(interval->wdays, &value_next))
		{
			/* a valid week day filter must always match some day of a new week */
			THIS_SHOULD_NEVER_HAPPEN;
			return FAIL;
		}
	}

	/* adjust the month day by the week day offset */
	tm->tm_mday += value_next - value_now;

	/* check if the resulting month day is valid */
	return (tm->tm_mday <= zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1) ? SUCCEED : FAIL);
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates next day that satisfies month and week day filters     *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 * Return value: SUCCEED - next day was found                                 *
 *               FAIL    - next day satisfying day filters was not            *
 *                         found in the current month                         *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_get_day_nextcheck(const zbx_scheduler_interval_t *interval, struct tm *tm)
{
	int	tmp;

	/* first check if the provided tm structure has valid date format */
	if (FAIL == zbx_utc_time(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
			&tmp))
	{
		return FAIL;
	}

	if (NULL == interval->mdays)
		return scheduler_get_wday_nextcheck(interval, tm);

	/* iterate through month days until week day filter matches or we have run out of month days */
	while (SUCCEED == scheduler_get_nearest_filter_value(interval->mdays, &tm->tm_mday))
	{
		/* check if the date is still valid - we haven't run out of month days */
		if (tm->tm_mday > zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1))
			break;

		if (SUCCEED == scheduler_validate_wday_filter(interval, tm))
			return SUCCEED;

		tm->tm_mday++;

		/* check if the date is still valid - we haven't run out of month days */
		if (tm->tm_mday > zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1))
			break;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates time/day that satisfies specified filter               *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             level    - [IN] filter level, see ZBX_SCHEDULER_FILTER_*       *
 *                             defines                                        *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 * Return value: SUCCEED - next time/day was found                            *
 *               FAIL    - next time/day was not found on current filter      *
 *                         level                                              *
 *                                                                            *
 ******************************************************************************/
static int	scheduler_get_filter_nextcheck(const zbx_scheduler_interval_t *interval, int level, struct tm *tm)
{
	const zbx_scheduler_filter_t	*filter;
	int				max, *value;

	/* initialize data depending on filter level */
	switch (level)
	{
		case ZBX_SCHEDULER_FILTER_DAY:
			return scheduler_get_day_nextcheck(interval, tm);
		case ZBX_SCHEDULER_FILTER_HOUR:
			max = 23;
			filter = interval->hours;
			value = &tm->tm_hour;
			break;
		case ZBX_SCHEDULER_FILTER_MINUTE:
			max = 59;
			filter = interval->minutes;
			value = &tm->tm_min;
			break;
		case ZBX_SCHEDULER_FILTER_SECOND:
			max = 59;
			filter = interval->seconds;
			value = &tm->tm_sec;
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			return FAIL;
	}

	if (max < *value)
		return FAIL;

	/* handle unspecified (default) filter */
	if (NULL == filter)
	{
		/* Empty filter matches all valid values if the filter level is less than        */
		/* interval filter level. For example if interval filter level is minutes - m30, */
		/* then hour filter matches all hours.                                           */
		if (interval->filter_level > level)
			return SUCCEED;

		/* If the filter level is greater than interval filter level, then filter       */
		/* matches only 0 value. For example if interval filter level is minutes - m30, */
		/* then seconds filter matches the 0th second.                                  */
		return 0 == *value ? SUCCEED : FAIL;
	}

	return scheduler_get_nearest_filter_value(filter, value);
}

/******************************************************************************
 *                                                                            *
 * Purpose: Applies the day filter to the specified time/day calculating the  *
 *          next scheduled check.                                             *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_apply_day_filter(zbx_scheduler_interval_t *interval, struct tm *tm)
{
	int	day = tm->tm_mday, mon = tm->tm_mon, year = tm->tm_year;

	while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_DAY, tm))
	{
		if (11 < ++tm->tm_mon)
		{
			tm->tm_mon = 0;
			tm->tm_year++;
		}

		tm->tm_mday = 1;
	}

	/* reset hours, minutes and seconds if the day has been changed */
	if (tm->tm_mday != day || tm->tm_mon != mon || tm->tm_year != year)
	{
		tm->tm_hour = 0;
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: Applies the hour filter to the specified time/day calculating the *
 *          next scheduled check                                              *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_apply_hour_filter(zbx_scheduler_interval_t *interval, struct tm *tm)
{
	int	hour = tm->tm_hour;

	while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_HOUR, tm))
	{
		tm->tm_mday++;
		tm->tm_hour = 0;

		/* day has been changed, we have to reapply day filter */
		scheduler_apply_day_filter(interval, tm);
	}

	/* reset minutes and seconds if hours has been changed */
	if (tm->tm_hour != hour)
	{
		tm->tm_min = 0;
		tm->tm_sec = 0;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: Applies the minute filter to the specified time/day calculating   *
 *          the next scheduled check.                                         *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_apply_minute_filter(zbx_scheduler_interval_t *interval, struct tm *tm)
{
	int	min = tm->tm_min;

	while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_MINUTE, tm))
	{
		tm->tm_hour++;
		tm->tm_min = 0;

		/* hours have been changed, we have to reapply hour filter */
		scheduler_apply_hour_filter(interval, tm);
	}

	/* reset seconds if minutes has been changed */
	if (tm->tm_min != min)
		tm->tm_sec = 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: Applies the second filter to the specified time/day calculating   *
 *          the next scheduled check                                          *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             tm       - [IN/OUT] input/output date & time                   *
 *                                                                            *
 ******************************************************************************/
static void	scheduler_apply_second_filter(zbx_scheduler_interval_t *interval, struct tm *tm)
{
	while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_SECOND, tm))
	{
		tm->tm_min++;
		tm->tm_sec = 0;

		/* minutes have been changed, we have to reapply minute filter */
		scheduler_apply_minute_filter(interval, tm);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: finds next timestamp satisfying one of intervals                  *
 *                                                                            *
 * Parameters: interval - [IN] scheduler interval                             *
 *             now      - [IN] current timestamp                              *
 *                                                                            *
 * Return Value: Timestamp when the next check must be scheduled.             *
 *                                                                            *
 ******************************************************************************/
static time_t	scheduler_get_nextcheck(zbx_scheduler_interval_t *interval, time_t now)
{
	struct tm	tm_start, tm, tm_dst;
	time_t		nextcheck = 0, current_nextcheck;

	tm_start = *zbx_localtime_now(&now);

	for (; NULL != interval; interval = interval->next)
	{
		tm = tm_start;

		do
		{
			scheduler_tm_inc(&tm);
			scheduler_apply_day_filter(interval, &tm);
			scheduler_apply_hour_filter(interval, &tm);
			scheduler_apply_minute_filter(interval, &tm);
			scheduler_apply_second_filter(interval, &tm);

			tm.tm_isdst = tm_start.tm_isdst;
		}
		while (-1 == (current_nextcheck = mktime(&tm)));

		tm_dst = *(zbx_localtime(&current_nextcheck, NULL));
		if (tm_dst.tm_isdst != tm_start.tm_isdst)
		{
			int	dst = tm_dst.tm_isdst;
			time_t	time_dst;

			time_dst = scheduler_find_dst_change(now, current_nextcheck);
			tm_dst = *zbx_localtime(&time_dst, NULL);

			scheduler_apply_day_filter(interval, &tm_dst);
			scheduler_apply_hour_filter(interval, &tm_dst);
			scheduler_apply_minute_filter(interval, &tm_dst);
			scheduler_apply_second_filter(interval, &tm_dst);

			tm_dst.tm_isdst = dst;
			current_nextcheck = mktime(&tm_dst);
		}

		if (0 == nextcheck || current_nextcheck < nextcheck)
			nextcheck = current_nextcheck;
	}

	return nextcheck;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates nextcheck timestamp for item                           *
 *                                                                            *
 * Parameters: seed             - [IN] seed value applied to delay to spread  *
 *                                     item checks over delay period          *
 *             item_type        - [IN]                                        *
 *             simple_interval  - [IN] default delay value, can be overridden *
 *             custom_intervals - [IN] preprocessed custom intervals          *
 *             now              - [IN] current timestamp                      *
 *                                                                            *
 * Return value: nextcheck value                                              *
 *                                                                            *
 * Comments: If item check is forbidden with delay=0 (default and flexible),  *
 *           a timestamp very far in the future is returned.                  *
 *                                                                            *
 *           Old algorithm: now+delay                                         *
 *           New one: preserve period, if delay==5, nextcheck = 0,5,10,15,... *
 *                                                                            *
 ******************************************************************************/
int	zbx_calculate_item_nextcheck(zbx_uint64_t seed, int item_type, int simple_interval,
		const zbx_custom_interval_t *custom_intervals, time_t now)
{
	int	nextcheck = 0;

	/* special processing of active items to see better view in queue */
	if (ITEM_TYPE_ZABBIX_ACTIVE == item_type)
	{
		if (0 != simple_interval)
			nextcheck = (int)now + simple_interval;
		else
			nextcheck = ZBX_JAN_2038;
	}
	else
	{
		int	current_delay, attempt = 0;
		time_t	next_interval, t, tmax, scheduled_check = 0;

		/* first try to parse out and calculate scheduled intervals */
		if (NULL != custom_intervals)
			scheduled_check = scheduler_get_nextcheck(custom_intervals->scheduling, now);

		/* Try to find the nearest 'nextcheck' value with condition */
		/* 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR. If it is not */
		/* possible to check the item within a year, fail. */

		t = now;
		tmax = now + SEC_PER_YEAR;

		while (t < tmax)
		{
			/* calculate 'nextcheck' value for the current interval */
			if (NULL != custom_intervals)
				current_delay = get_current_delay(simple_interval, custom_intervals->flexible, t);
			else
				current_delay = simple_interval;

			if (0 != current_delay)
			{
				nextcheck = current_delay * (int)(t / (time_t)current_delay) +
						(int)(seed % (zbx_uint64_t)current_delay);

				if (0 == attempt)
				{
					while (nextcheck <= t)
						nextcheck += current_delay;
				}
				else
				{
					while (nextcheck < t)
						nextcheck += current_delay;
				}
			}
			else
				nextcheck = ZBX_JAN_2038;

			if (NULL == custom_intervals)
				break;

			/* 'nextcheck' < end of the current interval ? */
			/* the end of the current interval is the beginning of the next interval - 1 */
			if (FAIL != get_next_delay_interval(custom_intervals->flexible, t, &next_interval) &&
					nextcheck >= next_interval)
			{
				/* 'nextcheck' is beyond the current interval */
				t = next_interval;
				attempt++;
			}
			else
				break;	/* nextcheck is within the current interval */
		}

		if (0 != scheduled_check && scheduled_check < nextcheck)
			nextcheck = (int)scheduled_check;
	}

	return nextcheck;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates nextcheck timestamp for item on unreachable host       *
 *                                                                            *
 * Parameters: simple_interval  - [IN] default delay value, can be overridden *
 *             custom_intervals - [IN] preprocessed custom intervals          *
 *             disable_until    - [IN] timestamp for next check               *
 *                                                                            *
 * Return value: nextcheck value                                              *
 *                                                                            *
 ******************************************************************************/
int	zbx_calculate_item_nextcheck_unreachable(int simple_interval, const zbx_custom_interval_t *custom_intervals,
		time_t disable_until)
{
	int	nextcheck = 0;
	time_t	next_interval, tmax, scheduled_check = 0;

	/* first try to parse out and calculate scheduled intervals */
	if (NULL != custom_intervals)
		scheduled_check = scheduler_get_nextcheck(custom_intervals->scheduling, disable_until);

	/* Try to find the nearest 'nextcheck' value with condition */
	/* 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR. If it is not */
	/* possible to check the item within a year, fail. */

	nextcheck = disable_until;
	tmax = disable_until + SEC_PER_YEAR;

	if (NULL != custom_intervals)
	{
		while (nextcheck < tmax)
		{
			if (0 != get_current_delay(simple_interval, custom_intervals->flexible, nextcheck))
				break;

			/* find the flexible interval change */
			if (FAIL == get_next_delay_interval(custom_intervals->flexible, nextcheck, &next_interval))
			{
				nextcheck = ZBX_JAN_2038;
				break;
			}
			nextcheck = next_interval;
		}
	}

	if (0 != scheduled_check && scheduled_check < nextcheck)
		nextcheck = (int)scheduled_check;

	return nextcheck;
}

/******************************************************************************
 *                                                                            *
 * Purpose: validates time period and check if specified time is within it    *
 *                                                                            *
 * Parameters: period - [IN]  semicolon-separated list of time periods in one *
 *                            of the following formats:                       *
 *                              d1-d2,h1:m1-h2:m2                             *
 *                              or d1,h1:m1-h2:m2                             *
 *             time   - [IN]  time to check                                   *
 *             tz     - [IN]                                                  *
 *             res    - [OUT] check result:                                   *
 *                              SUCCEED - if time is within period            *
 *                              FAIL    - otherwise                           *
 *                                                                            *
 * Return value: validation result (SUCCEED - valid, FAIL - invalid)          *
 *                                                                            *
 * Comments:   !!! Don't forget to sync code with PHP !!!                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_check_time_period(const char *period, time_t time, const char *tz, int *res)
{
	int			res_total = FAIL;
	const char		*next;
	struct tm		*tm;
	zbx_time_period_t	tp;

	tm = zbx_localtime(&time, tz);

	next = strchr(period, ';');
	while  (SUCCEED == time_period_parse(&tp, period, (NULL == next ? (int)strlen(period) : (int)(next - period))))
	{
		if (SUCCEED == check_time_period(tp, tm))
			res_total = SUCCEED;	/* no short-circuits, validate all periods before return */

		if (NULL == next)
		{
			*res = res_total;
			return SUCCEED;
		}

		period = next + 1;
		next = strchr(period, ';');
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates item nextcheck for Zabbix agent type items             *
 *                                                                            *
 ******************************************************************************/
int	zbx_get_agent_item_nextcheck(zbx_uint64_t itemid, const char *delay, int now,
		int *nextcheck, int *scheduling, char **error)
{
	int			simple_interval;
	zbx_custom_interval_t	*custom_intervals;

	if (SUCCEED != zbx_interval_preproc(delay, &simple_interval, &custom_intervals, error))
	{
		*nextcheck = ZBX_JAN_2038;
		return FAIL;
	}

	if (NULL != custom_intervals->scheduling)
		*scheduling = SUCCEED;
	else
		*scheduling = FAIL;

	*nextcheck = zbx_calculate_item_nextcheck(itemid, ITEM_TYPE_ZABBIX, simple_interval, custom_intervals, now);
	zbx_custom_interval_free(custom_intervals);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates report nextcheck                                       *
 *                                                                            *
 * Parameters: now        - [IN] current timestamp                            *
 *             cycle      - [IN] report cycle                                 *
 *             weekdays   - [IN] week days report should be prepared,         *
 *                               bitmask (0x01 - Monday, 0x02 - Tuesday...)   *
 *             start_time - [IN] report start time in seconds after           *
 *                               midnight                                     *
 *                                                                            *
 * Return value: The timestamp when the report must be prepared or -1 if an   *
 *               error occurred.                                              *
 *                                                                            *
 ******************************************************************************/
int	zbx_get_report_nextcheck(int now, unsigned char cycle, unsigned char weekdays, int start_time)
{
	struct tm	tm;
	time_t		yesterday = now - SEC_PER_DAY;
	int		nextcheck, tm_hour, tm_min, tm_sec;

	tm = *zbx_localtime(&yesterday, NULL);	/* zbx_tm_(add/sub) should call zbx_localtime again */

	tm_sec = start_time % 60;
	start_time /= 60;
	tm_min = start_time % 60;
	start_time /= 60;
	tm_hour = start_time;

	do
	{
		/* handle midnight startup times */
		if (0 == tm.tm_sec && 0 == tm.tm_min && 0 == tm.tm_hour)
			zbx_tm_add(&tm, 1, ZBX_TIME_UNIT_DAY);

		switch (cycle)
		{
			case ZBX_REPORT_CYCLE_YEARLY:
				zbx_tm_round_up(&tm, ZBX_TIME_UNIT_YEAR);
				break;
			case ZBX_REPORT_CYCLE_MONTHLY:
				zbx_tm_round_up(&tm, ZBX_TIME_UNIT_MONTH);
				break;
			case ZBX_REPORT_CYCLE_WEEKLY:
				if (0 == weekdays)
					return -1;
				zbx_tm_round_up(&tm, ZBX_TIME_UNIT_DAY);

				while (0 == (weekdays & (1 << (tm.tm_wday + 6) % 7)))
					zbx_tm_add(&tm, 1, ZBX_TIME_UNIT_DAY);

				break;
			case ZBX_REPORT_CYCLE_DAILY:
				zbx_tm_round_up(&tm, ZBX_TIME_UNIT_DAY);
				break;
		}

		tm.tm_sec = tm_sec;
		tm.tm_min = tm_min;
		tm.tm_hour = tm_hour;
		tm.tm_isdst = -1;	/* we don't know DST state after the time change */

		nextcheck = (int)mktime(&tm);
	}
	while (-1 != nextcheck && nextcheck <= now);

	return nextcheck;
}
