1 /*
2 * Copyright (c) 2009 NLNet Labs. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
19 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
21 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
23 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *
25 */
26
27 /**
28 *
29 * Durations.
30 */
31
32 #include "status.h"
33 #include "duration.h"
34 #include "log.h"
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <ctype.h>
41
42 static const char* duration_str = "duration";
43
44
45 /**
46 * Create a new 'instant' duration.
47 *
48 */
49 duration_type*
duration_create(void)50 duration_create(void)
51 {
52 duration_type* duration;
53
54 CHECKALLOC(duration = (duration_type*) malloc(sizeof(duration_type)));
55 duration->years = 0;
56 duration->months = 0;
57 duration->weeks = 0;
58 duration->days = 0;
59 duration->hours = 0;
60 duration->minutes = 0;
61 duration->seconds = 0;
62 return duration;
63 }
64
65
66 /**
67 * Compare durations.
68 *
69 */
70 int
duration_compare(duration_type * d1,duration_type * d2)71 duration_compare(duration_type* d1, duration_type* d2)
72 {
73 if (!d1 && !d2) {
74 return 0;
75 }
76 if (!d1 || !d2) {
77 return d1?-1:1;
78 }
79
80 if (d1->years != d2->years) {
81 return d1->years - d2->years;
82 }
83 if (d1->months != d2->months) {
84 return d1->months - d2->months;
85 }
86 if (d1->weeks != d2->weeks) {
87 return d1->weeks - d2->weeks;
88 }
89 if (d1->days != d2->days) {
90 return d1->days - d2->days;
91 }
92 if (d1->hours != d2->hours) {
93 return d1->hours - d2->hours;
94 }
95 if (d1->minutes != d2->minutes) {
96 return d1->minutes - d2->minutes;
97 }
98 if (d1->seconds != d2->seconds) {
99 return d1->seconds - d2->seconds;
100 }
101
102 return 0;
103 }
104
105
106 /**
107 * Create a duration from string.
108 *
109 */
110 duration_type*
duration_create_from_string(const char * str)111 duration_create_from_string(const char* str)
112 {
113 duration_type* duration = duration_create();
114 char* P, *X, *T, *W;
115 int not_weeks = 0;
116
117 if (!duration) {
118 ods_log_error("[%s] cannot create from string %s: create failed",
119 duration_str, str);
120 return NULL;
121 }
122 if (!str) {
123 return duration;
124 }
125
126 P = strchr(str, 'P');
127 if (!P) {
128 ods_log_error("[%s] cannot create from string %s: P not found",
129 duration_str, str);
130 duration_cleanup(duration);
131 return NULL;
132 }
133
134 T = strchr(str, 'T');
135 X = strchr(str, 'Y');
136 if (X) {
137 duration->years = atoi(str+1);
138 str = X;
139 not_weeks = 1;
140 }
141 X = strchr(str, 'M');
142 if (X && (!T || (size_t) (X-P) < (size_t) (T-P))) {
143 duration->months = atoi(str+1);
144 str = X;
145 not_weeks = 1;
146 }
147 X = strchr(str, 'D');
148 if (X) {
149 duration->days = atoi(str+1);
150 str = X;
151 not_weeks = 1;
152 }
153 if (T) {
154 str = T;
155 not_weeks = 1;
156 }
157 X = strchr(str, 'H');
158 if (X && T) {
159 duration->hours = atoi(str+1);
160 str = X;
161 not_weeks = 1;
162 }
163 X = strrchr(str, 'M');
164 if (X && T && (size_t) (X-P) > (size_t) (T-P)) {
165 duration->minutes = atoi(str+1);
166 str = X;
167 not_weeks = 1;
168 }
169 X = strchr(str, 'S');
170 if (X && T) {
171 duration->seconds = atoi(str+1);
172 str = X;
173 not_weeks = 1;
174 }
175
176 W = strchr(str, 'W');
177 if (W) {
178 if (not_weeks) {
179 ods_log_error("[%s] cannot create from string: parse error",
180 duration_str);
181 duration_cleanup(duration);
182 return NULL;
183 } else {
184 duration->weeks = atoi(str+1);
185 str = W;
186 }
187 }
188 return duration;
189 }
190
191
192 /**
193 * Get the number of digits in a number.
194 *
195 */
196 static size_t
digits_in_number(time_t duration)197 digits_in_number(time_t duration)
198 {
199 uint32_t period = (uint32_t) duration;
200 size_t count = 0;
201 if (!period) {
202 return 1;
203 }
204 while (period > 0) {
205 count++;
206 period /= 10;
207 }
208 return count;
209 }
210
211
212 /**
213 * Convert a duration to a string.
214 *
215 */
216 char*
duration2string(duration_type * duration)217 duration2string(duration_type* duration)
218 {
219 char* str = NULL, *num = NULL;
220 size_t count = 2;
221 int T = 0, D = 0;
222
223 if (!duration) {
224 return NULL;
225 }
226
227 if (duration->years > 0) {
228 count = count + 1 + digits_in_number(duration->years);
229 D = 1;
230 }
231 if (duration->months > 0) {
232 count = count + 1 + digits_in_number(duration->months);
233 D = 1;
234 }
235 if (duration->weeks > 0) {
236 count = count + 1 + digits_in_number(duration->weeks);
237 D = 1;
238 }
239 if (duration->days > 0) {
240 count = count + 1 + digits_in_number(duration->days);
241 D = 1;
242 }
243 if (duration->hours > 0) {
244 count = count + 1 + digits_in_number(duration->hours);
245 T = 1;
246 }
247 if (duration->minutes > 0) {
248 count = count + 1 + digits_in_number(duration->minutes);
249 T = 1;
250 }
251 if (duration->seconds > 0 ||
252 (!D && !duration->hours && !duration->minutes)) {
253 count = count + 1 + digits_in_number(duration->seconds);
254 T = 1;
255 }
256 if (T) {
257 count++;
258 }
259
260 str = (char*) calloc(count, sizeof(char));
261 str[0] = 'P';
262 str[1] = '\0';
263
264 if (duration->years > 0) {
265 count = digits_in_number(duration->years);
266 num = (char*) calloc(count+2, sizeof(char));
267 if (num) {
268 snprintf(num, count+2, "%uY", (uint32_t) duration->years);
269 str = strncat(str, num, count+2);
270 free((void*) num);
271 } else {
272 goto duration2string_num_calloc_failed;
273 }
274 }
275 if (duration->months > 0) {
276 count = digits_in_number(duration->months);
277 num = (char*) calloc(count+2, sizeof(char));
278 if (num) {
279 snprintf(num, count+2, "%uM", (uint32_t) duration->months);
280 str = strncat(str, num, count+2);
281 free((void*) num);
282 } else {
283 goto duration2string_num_calloc_failed;
284 }
285 }
286 if (duration->weeks > 0) {
287 count = digits_in_number(duration->weeks);
288 num = (char*) calloc(count+2, sizeof(char));
289 if (num) {
290 snprintf(num, count+2, "%uW", (uint32_t) duration->weeks);
291 str = strncat(str, num, count+2);
292 free((void*) num);
293 } else {
294 goto duration2string_num_calloc_failed;
295 }
296 }
297 if (duration->days > 0) {
298 count = digits_in_number(duration->days);
299 num = (char*) calloc(count+2, sizeof(char));
300 if (num) {
301 snprintf(num, count+2, "%uD", (uint32_t) duration->days);
302 str = strncat(str, num, count+2);
303 free((void*) num);
304 } else {
305 goto duration2string_num_calloc_failed;
306 }
307 }
308 if (T) {
309 str = strncat(str, "T", 1);
310 }
311 if (duration->hours > 0) {
312 count = digits_in_number(duration->hours);
313 num = (char*) calloc(count+2, sizeof(char));
314 if (num) {
315 snprintf(num, count+2, "%uH", (uint32_t) duration->hours);
316 str = strncat(str, num, count+2);
317 free((void*) num);
318 } else {
319 goto duration2string_num_calloc_failed;
320 }
321 }
322 if (duration->minutes > 0) {
323 count = digits_in_number(duration->minutes);
324 num = (char*) calloc(count+2, sizeof(char));
325 if (num) {
326 snprintf(num, count+2, "%uM", (uint32_t) duration->minutes);
327 str = strncat(str, num, count+2);
328 free((void*) num);
329 } else {
330 goto duration2string_num_calloc_failed;
331 }
332 }
333 if (duration->seconds > 0 ||
334 (!D && !duration->hours && !duration->minutes)) {
335 count = digits_in_number(duration->seconds);
336 num = (char*) calloc(count+2, sizeof(char));
337 if (num) {
338 snprintf(num, count+2, "%uS", (uint32_t) duration->seconds);
339 str = strncat(str, num, count+2);
340 free((void*) num);
341 } else {
342 goto duration2string_num_calloc_failed;
343 }
344 }
345 return str;
346
347 duration2string_num_calloc_failed:
348 ods_log_error("[%s] cannot create string: malloc error", duration_str);
349 free((void*) str);
350 return NULL;
351 }
352
353
354 /**
355 * Convert a duration to a time.
356 *
357 */
358 time_t
duration2time(duration_type * duration)359 duration2time(duration_type* duration)
360 {
361 time_t period = 0;
362 char* dstr = NULL;
363
364 if (duration) {
365 period += (duration->seconds);
366 period += (duration->minutes)*60;
367 period += (duration->hours)*3600;
368 period += (duration->days)*86400;
369 period += (duration->weeks)*86400*7;
370 period += (duration->months)*86400*31;
371 period += (duration->years)*86400*365;
372
373 if (duration->months || duration->years) {
374 /* [TODO] calculate correct number of days in this month/year */
375 dstr = duration2string(duration);
376 free((void*) dstr);
377 }
378 }
379 return period;
380 }
381
382 /**
383 * Set the duration based on a time_t.
384 */
duration_set_time(duration_type * duration,time_t time)385 int duration_set_time(duration_type* duration, time_t time) {
386 if (!duration) {
387 return 1;
388 }
389
390 duration->years = time / (86400*365);
391 time -= duration->years * 86400*365;
392 duration->months = time / (86400*31);
393 time -= duration->months * 86400*31;
394 duration->days = time / 86400;
395 time -= duration->days * 86400;
396 duration->hours = time / 3600;
397 time -= duration->hours * 3600;
398 duration->minutes = time / 60;
399 time -= duration->minutes * 60;
400 duration->seconds = time;
401
402 duration->weeks = 0;
403
404 return 0;
405 }
406
407 /**
408 * Return a random time.
409 *
410 */
411 time_t
ods_rand(time_t mod)412 ods_rand(time_t mod)
413 {
414 #ifdef HAVE_ARC4RANDOM_UNIFORM
415 return (time_t) (arc4random_uniform((uint32_t) mod+1));
416 #elif HAVE_ARC4RANDOM
417 return (time_t) (arc4random() % (unsigned) mod+1);
418 #else
419 return (time_t) (random() % (unsigned) mod+1);
420 #endif
421 }
422
423 static time_t time_now_set = 0;
424
425 /**
426 * Set the time_now to a new value.
427 * As long as this value is later than the real time now
428 * the overriden value is returned.
429 *
430 */
431 void
set_time_now(time_t now)432 set_time_now(time_t now)
433 {
434 time_now_set = now;
435 }
436
437 int
set_time_now_str(char * time_arg)438 set_time_now_str(char* time_arg)
439 {
440 char* endptr;
441 time_t epoch;
442 struct tm tm;
443 if (time_arg == NULL) {
444 epoch = 0;
445 } else if (strptime(time_arg, "%Y-%m-%d-%H:%M:%S", &tm)) {
446 tm.tm_isdst = -1; /* OS handles daylight savings */
447 epoch = mktime(&tm);
448 } else {
449 while (isspace(*time_arg))
450 ++time_arg;
451 epoch = strtol(time_arg, &endptr, 0);
452 if (endptr != time_arg) {
453 while (isspace(*endptr))
454 ++endptr;
455 if (*endptr != '\0')
456 return -1;
457 } else
458 return -2;
459 }
460 set_time_now(epoch);
461 return 0;
462 }
463
464 /**
465 * Return the time since Epoch, measured in seconds.
466 *
467 */
468 time_t
time_now(void)469 time_now(void)
470 {
471 return time_now_set ? time_now_set: time(NULL);
472 }
473
474 /**
475 * copycode: This code is based on the EXAMPLE in the strftime manual.
476 *
477 */
478 uint32_t
time_datestamp(time_t tt,const char * format,char ** str)479 time_datestamp(time_t tt, const char* format, char** str)
480 {
481 time_t t;
482 struct tm datetime;
483 struct tm *tmp;
484 uint32_t ut = 0;
485 char outstr[32];
486
487 if (tt) {
488 t = tt;
489 } else {
490 t = time_now();
491 }
492
493 tmp = localtime_r(&t,&datetime);
494 if (tmp == NULL) {
495 ods_log_error("[%s] time_datestamp: localtime_r() failed", duration_str);
496 return 0;
497 }
498
499 if (strftime(outstr, sizeof(outstr), format, tmp) == 0) {
500 ods_log_error("[%s] time_datestamp: strftime() failed", duration_str);
501 return 0;
502 }
503
504 ut = (uint32_t) strtoul(outstr, NULL, 10);
505 if (str) {
506 *str = strdup(outstr);
507 }
508 return ut;
509 }
510
511 /**
512 * Clean up duration.
513 *
514 */
515 void
duration_cleanup(duration_type * duration)516 duration_cleanup(duration_type* duration)
517 {
518 if (!duration) {
519 return;
520 }
521 free(duration);
522 }
523