1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 
22 static void	tm_add(struct tm *tm, int multiplier, zbx_time_unit_t base);
23 static void	tm_sub(struct tm *tm, int multiplier, zbx_time_unit_t base);
24 
25 static int	time_unit_seconds[ZBX_TIME_UNIT_COUNT] = {0, 1, SEC_PER_MIN, SEC_PER_HOUR, SEC_PER_DAY, SEC_PER_WEEK, 0,
26 		0};
27 
zbx_tm_str_to_unit(const char * text)28 zbx_time_unit_t	zbx_tm_str_to_unit(const char *text)
29 {
30 	switch (*text)
31 	{
32 		case 's':
33 			return ZBX_TIME_UNIT_SECOND;
34 		case 'm':
35 			return ZBX_TIME_UNIT_MINUTE;
36 		case 'h':
37 			return ZBX_TIME_UNIT_HOUR;
38 		case 'd':
39 			return ZBX_TIME_UNIT_DAY;
40 		case 'w':
41 			return ZBX_TIME_UNIT_WEEK;
42 		case 'M':
43 			return ZBX_TIME_UNIT_MONTH;
44 		case 'y':
45 			return ZBX_TIME_UNIT_YEAR;
46 		default:
47 			return ZBX_TIME_UNIT_UNKNOWN;
48 	}
49 }
50 
51 /******************************************************************************
52  *                                                                            *
53  * Function: zbx_tm_parse_period                                              *
54  *                                                                            *
55  * Purpose: parse time period in format <multiplier><time unit>               *
56  *                                                                            *
57  * Parameters: period     - [IN] the time period                              *
58  *             len        - [OUT] the length of parsed time period            *
59  *             multiplier - [OUT] the parsed multiplier                       *
60  *             base       - [OUT] the parsed time unit                        *
61  *             error      - [OUT] the error message if parsing failed         *
62  *                                                                            *
63  * Return value: SUCCEED - period was parsed successfully                     *
64  *               FAIL    - invalid time period was specified                  *
65  *                                                                            *
66  ******************************************************************************/
zbx_tm_parse_period(const char * period,size_t * len,int * multiplier,zbx_time_unit_t * base,char ** error)67 int	zbx_tm_parse_period(const char *period, size_t *len, int *multiplier, zbx_time_unit_t *base, char **error)
68 {
69 	const char	*ptr;
70 
71 	for (ptr = period; 0 != isdigit(*ptr); ptr++)
72 		;
73 
74 	if (FAIL == is_uint_n_range(period, ptr - period, multiplier, sizeof(*multiplier), 1, UINT32_MAX))
75 	{
76 		*error = zbx_strdup(*error, "invalid period multiplier");
77 		return FAIL;
78 	}
79 
80 	if (ZBX_TIME_UNIT_UNKNOWN == (*base = zbx_tm_str_to_unit(ptr)))
81 	{
82 		*error = zbx_strdup(*error, "invalid period time unit");
83 		return FAIL;
84 	}
85 
86 	*len = ptr - period + 1;
87 
88 	return SUCCEED;
89 }
90 
91 /******************************************************************************
92  *                                                                            *
93  * Function: tm_add_seconds                                                   *
94  *                                                                            *
95  * Purpose: add seconds to the time and adjust result by dst                  *
96  *                                                                            *
97  * Parameter: tm      - [IN/OUT] the time structure                           *
98  *            seconds - [IN] the seconds to add (can be negative)             *
99  *                                                                            *
100  ******************************************************************************/
tm_add_seconds(struct tm * tm,int seconds)101 static void	tm_add_seconds(struct tm *tm, int seconds)
102 {
103 	time_t		time_new;
104 	struct tm	tm_new = *tm;
105 
106 	if (-1 == (time_new = mktime(&tm_new)))
107 	{
108 		THIS_SHOULD_NEVER_HAPPEN;
109 		return;
110 	}
111 
112 	time_new += seconds;
113 	localtime_r(&time_new, &tm_new);
114 
115 	if (tm->tm_isdst != tm_new.tm_isdst && -1 != tm->tm_isdst && -1 != tm_new.tm_isdst)
116 	{
117 		if (0 == tm_new.tm_isdst)
118 			tm_add(&tm_new, 1, ZBX_TIME_UNIT_HOUR);
119 		else
120 			tm_sub(&tm_new, 1, ZBX_TIME_UNIT_HOUR);
121 	}
122 
123 	*tm = tm_new;
124 }
125 
126 /******************************************************************************
127  *                                                                            *
128  * Function: tm_add                                                           *
129  *                                                                            *
130  * Purpose: add time duration without adjusting DST clocks                    *
131  *                                                                            *
132  * Parameter: tm         - [IN/OUT] the time structure                        *
133  *            multiplier - [IN] the unit multiplier                           *
134  *            base       - [IN] the time unit to add                          *
135  *                                                                            *
136  ******************************************************************************/
tm_add(struct tm * tm,int multiplier,zbx_time_unit_t base)137 static void	tm_add(struct tm *tm, int multiplier, zbx_time_unit_t base)
138 {
139 	int	shift;
140 
141 	switch (base)
142 	{
143 		case ZBX_TIME_UNIT_HOUR:
144 			tm->tm_hour += multiplier;
145 			if (24 <= tm->tm_hour)
146 			{
147 				shift = tm->tm_hour / 24;
148 				tm->tm_hour %= 24;
149 				tm_add(tm, shift, ZBX_TIME_UNIT_DAY);
150 			}
151 			break;
152 		case ZBX_TIME_UNIT_DAY:
153 			tm->tm_mday += multiplier;
154 			while (tm->tm_mday > (shift = zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1)))
155 			{
156 				tm->tm_mday -= shift;
157 				tm_add(tm, 1, ZBX_TIME_UNIT_MONTH);
158 			}
159 			tm->tm_wday += multiplier;
160 			tm->tm_wday %= 7;
161 			break;
162 		case ZBX_TIME_UNIT_WEEK:
163 			tm_add(tm, multiplier * 7, ZBX_TIME_UNIT_DAY);
164 			break;
165 		case ZBX_TIME_UNIT_MONTH:
166 			tm->tm_mon += multiplier;
167 			if (12 <= tm->tm_mon)
168 			{
169 				shift = tm->tm_mon / 12;
170 				tm->tm_mon %= 12;
171 				tm_add(tm, shift, ZBX_TIME_UNIT_YEAR);
172 			}
173 			break;
174 		case ZBX_TIME_UNIT_YEAR:
175 			tm->tm_year += multiplier;
176 			break;
177 		default:
178 			break;
179 	}
180 }
181 
182 /******************************************************************************
183  *                                                                            *
184  * Function: zbx_tm_add                                                       *
185  *                                                                            *
186  * Purpose: add time duration                                                 *
187  *                                                                            *
188  * Parameter: tm         - [IN/OUT] the time structure                        *
189  *            multiplier - [IN] the unit multiplier                           *
190  *            base       - [IN] the time unit to add                          *
191  *                                                                            *
192  ******************************************************************************/
zbx_tm_add(struct tm * tm,int multiplier,zbx_time_unit_t base)193 void	zbx_tm_add(struct tm *tm, int multiplier, zbx_time_unit_t base)
194 {
195 	if (ZBX_TIME_UNIT_MONTH == base || ZBX_TIME_UNIT_YEAR == base)
196 		tm_add(tm, multiplier, base);
197 
198 	tm_add_seconds(tm, multiplier * time_unit_seconds[base]);
199 
200 	return;
201 }
202 
203 /******************************************************************************
204  *                                                                            *
205  * Function: neg_to_pos_wrap                                                  *
206  *                                                                            *
207  * Purpose: convert negative number to postive by wrapping around the base    *
208  *                                                                            *
209  * Parameter: value - [IN/OUT] the value to convert                           *
210  *            base  - [IN] the wrap base                                      *
211  *                                                                            *
212  ******************************************************************************/
neg_to_pos_wrap(int * value,int base)213 static void	neg_to_pos_wrap(int *value, int base)
214 {
215 	int	reminder = *value % base;
216 
217 	*value = (0 == reminder ? 0 : base + reminder);
218 }
219 
220 /******************************************************************************
221  *                                                                            *
222  * Function: tm_sub                                                           *
223  *                                                                            *
224  * Purpose: subtracts time duration without adjusting DST clocks              *
225  *                                                                            *
226  * Parameter: tm         - [IN/OUT] the time structure                        *
227  *            multiplier - [IN] the unit multiplier                           *
228  *            base       - [IN] the time unit to add                          *
229  *                                                                            *
230  ******************************************************************************/
tm_sub(struct tm * tm,int multiplier,zbx_time_unit_t base)231 static void	tm_sub(struct tm *tm, int multiplier, zbx_time_unit_t base)
232 {
233 	int	shift;
234 
235 	switch (base)
236 	{
237 		case ZBX_TIME_UNIT_HOUR:
238 			tm->tm_hour -= multiplier;
239 			if (0 > tm->tm_hour)
240 			{
241 				shift = -tm->tm_hour / 24;
242 				neg_to_pos_wrap(&tm->tm_hour, 24);
243 				if (0 != tm->tm_hour)
244 					shift++;
245 				tm_sub(tm, shift, ZBX_TIME_UNIT_DAY);
246 			}
247 			return;
248 		case ZBX_TIME_UNIT_DAY:
249 			tm->tm_mday -= multiplier;
250 			while (0 >= tm->tm_mday)
251 			{
252 				int	prev_mon;
253 
254 				if (0 > (prev_mon = tm->tm_mon - 1))
255 					prev_mon = 11;
256 				prev_mon++;
257 
258 				tm->tm_mday += zbx_day_in_month(tm->tm_year + 1900, prev_mon);
259 				tm_sub(tm, 1, ZBX_TIME_UNIT_MONTH);
260 			}
261 			tm->tm_wday -= multiplier;
262 			if (0 > tm->tm_wday)
263 				neg_to_pos_wrap(&tm->tm_wday, 7);
264 			return;
265 		case ZBX_TIME_UNIT_WEEK:
266 			tm_sub(tm, multiplier * 7, ZBX_TIME_UNIT_DAY);
267 			return;
268 		case ZBX_TIME_UNIT_MONTH:
269 			tm->tm_mon -= multiplier;
270 			if (0 > tm->tm_mon)
271 			{
272 				shift = -tm->tm_mon / 12;
273 				neg_to_pos_wrap(&tm->tm_mon, 12);
274 				if (0 != tm->tm_mon)
275 					shift++;
276 				tm_sub(tm, shift, ZBX_TIME_UNIT_YEAR);
277 			}
278 			return;
279 		case ZBX_TIME_UNIT_YEAR:
280 			tm->tm_year -= multiplier;
281 			return;
282 		default:
283 			return;
284 	}
285 }
286 
287 /******************************************************************************
288  *                                                                            *
289  * Function: zbx_tm_sub                                                       *
290  *                                                                            *
291  * Purpose: subtracts time duration                                           *
292  *                                                                            *
293  * Parameter: tm         - [IN/OUT] the time structure                        *
294  *            multiplier - [IN] the unit multiplier                           *
295  *            base       - [IN] the time unit to add                          *
296  *                                                                            *
297  ******************************************************************************/
zbx_tm_sub(struct tm * tm,int multiplier,zbx_time_unit_t base)298 void	zbx_tm_sub(struct tm *tm, int multiplier, zbx_time_unit_t base)
299 {
300 	if (ZBX_TIME_UNIT_MONTH == base || ZBX_TIME_UNIT_YEAR == base)
301 		tm_sub(tm, multiplier, base);
302 
303 	tm_add_seconds(tm, -multiplier * time_unit_seconds[base]);
304 
305 	return;
306 }
307 
308 /******************************************************************************
309  *                                                                            *
310  * Function: zbx_tm_round_up                                                  *
311  *                                                                            *
312  * Purpose: rounds time by the specified unit upwards                         *
313  *                                                                            *
314  * Parameter: tm         - [IN/OUT] the time structure                        *
315  *            base       - [IN] the time unit                                 *
316  *                                                                            *
317  ******************************************************************************/
zbx_tm_round_up(struct tm * tm,zbx_time_unit_t base)318 void	zbx_tm_round_up(struct tm *tm, zbx_time_unit_t base)
319 {
320 	if (0 != tm->tm_sec)
321 	{
322 		tm->tm_sec = 0;
323 		zbx_tm_add(tm, 1, ZBX_TIME_UNIT_MINUTE);
324 	}
325 
326 	if (ZBX_TIME_UNIT_MINUTE == base)
327 		return;
328 
329 	if (0 != tm->tm_min)
330 	{
331 		tm->tm_min = 0;
332 		zbx_tm_add(tm, 1, ZBX_TIME_UNIT_HOUR);
333 	}
334 
335 	if (ZBX_TIME_UNIT_HOUR == base)
336 		return;
337 
338 	if (0 != tm->tm_hour)
339 	{
340 		tm->tm_hour = 0;
341 		zbx_tm_add(tm, 1, ZBX_TIME_UNIT_DAY);
342 	}
343 
344 	if (ZBX_TIME_UNIT_DAY == base)
345 		return;
346 
347 	if (ZBX_TIME_UNIT_WEEK == base)
348 	{
349 		if (1 != tm->tm_wday)
350 		{
351 			zbx_tm_add(tm, (0 == tm->tm_wday ? 1 : 8 - tm->tm_wday), ZBX_TIME_UNIT_DAY);
352 			tm->tm_wday = 1;
353 		}
354 		return;
355 	}
356 
357 	if (1 != tm->tm_mday)
358 	{
359 		tm->tm_mday = 1;
360 		zbx_tm_add(tm, 1, ZBX_TIME_UNIT_MONTH);
361 	}
362 
363 	if (ZBX_TIME_UNIT_MONTH == base)
364 		return;
365 
366 	if (0 != tm->tm_mon)
367 	{
368 		tm->tm_mon = 0;
369 		zbx_tm_add(tm, 1, ZBX_TIME_UNIT_YEAR);
370 	}
371 
372 	return;
373 }
374 
375 /******************************************************************************
376  *                                                                            *
377  * Function: zbx_tm_round_down                                                *
378  *                                                                            *
379  * Purpose: rounds time by the specified unit downwards                       *
380  *                                                                            *
381  * Parameter: tm         - [IN/OUT] the time structure                        *
382  *            base       - [IN] the time unit                                 *
383  *                                                                            *
384  ******************************************************************************/
zbx_tm_round_down(struct tm * tm,zbx_time_unit_t base)385 void	zbx_tm_round_down(struct tm *tm, zbx_time_unit_t base)
386 {
387 	switch (base)
388 	{
389 		case ZBX_TIME_UNIT_WEEK:
390 			if (1 != tm->tm_wday)
391 			{
392 				zbx_tm_sub(tm, (0 == tm->tm_wday ? 6 : tm->tm_wday - 1), ZBX_TIME_UNIT_DAY);
393 				tm->tm_wday = 1;
394 			}
395 
396 			tm->tm_hour = 0;
397 			tm->tm_min = 0;
398 			tm->tm_sec = 0;
399 			break;
400 		case ZBX_TIME_UNIT_YEAR:
401 			tm->tm_mon = 0;
402 			ZBX_FALLTHROUGH;
403 		case ZBX_TIME_UNIT_MONTH:
404 			tm->tm_mday = 1;
405 			ZBX_FALLTHROUGH;
406 		case ZBX_TIME_UNIT_DAY:
407 			tm->tm_hour = 0;
408 			ZBX_FALLTHROUGH;
409 		case ZBX_TIME_UNIT_HOUR:
410 			tm->tm_min = 0;
411 			ZBX_FALLTHROUGH;
412 		case ZBX_TIME_UNIT_MINUTE:
413 			tm->tm_sec = 0;
414 			break;
415 		default:
416 			break;
417 	}
418 
419 	tm_add_seconds(tm, 0);
420 
421 	return;
422 }
423 
zbx_timespec_str(const zbx_timespec_t * ts)424 const char	*zbx_timespec_str(const zbx_timespec_t *ts)
425 {
426 	static ZBX_THREAD_LOCAL char	str[32];
427 
428 	time_t		ts_time = ts->sec;
429 	struct tm	tm;
430 
431 	localtime_r(&ts_time, &tm);
432 	zbx_snprintf(str, sizeof(str), "%04d.%02d.%02d %02d:%02d:%02d.%09d", tm.tm_year + 1900, tm.tm_mon + 1,
433 			tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ts->ns);
434 
435 	return str;
436 }
437