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