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