1 /*-
2  * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * Lifted from FreeBSD: src/bin/date/vary.c,v 1.15
27  *
28  * $Id: timevary.c,v 1.6 2004/04/20 18:45:48 ejohnst Exp $
29  *
30  */
31 
32 #include <time.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #include <ctype.h>
37 #include "timevary.h"
38 #include "exif.h"
39 
40 struct trans {
41   int val;
42   const char *str;
43 };
44 
45 static struct trans trans_mon[] = {
46   { 1, "january" }, { 2, "february" }, { 3, "march" }, { 4, "april" },
47   { 5, "may"}, { 6, "june" }, { 7, "july" }, { 8, "august" },
48   { 9, "september" }, { 10, "october" }, { 11, "november" }, { 12, "december" },
49   { -1, NULL }
50 };
51 
52 static struct trans trans_wday[] = {
53   { 0, "sunday" }, { 1, "monday" }, { 2, "tuesday" }, { 3, "wednesday" },
54   { 4, "thursday" }, { 5, "friday" }, { 6, "saturday" },
55   { -1, NULL }
56 };
57 
58 static char digits[] = "0123456789";
59 static int adjhour(struct tm *, char, int, int);
60 
61 static int
domktime(struct tm * t,char type)62 domktime(struct tm *t, char type)
63 {
64   time_t ret;
65 
66   while ((ret = mktime(t)) == -1 && t->tm_year > 68 && t->tm_year < 138)
67     /* While mktime() fails, adjust by an hour */
68     adjhour(t, (char)(type == '-' ? type : '+'), 1, 0);
69 
70   return ret;
71 }
72 
73 static int
trans(const struct trans t[],const char * arg)74 trans(const struct trans t[], const char *arg)
75 {
76   int f;
77   char tmp[16];
78 
79   /* No strncasecmp() in Win32.  No month or day over 16 bytes long... */
80 
81   for (f = 0; f < sizeof(tmp) && arg[f]; f++)
82     tmp[f] = tolower(arg[f]);
83   tmp[15] = '\0';
84 
85   for (f = 0; t[f].val != -1; f++)
86     if (!strncmp(t[f].str, tmp, 3) ||
87         !strncmp(t[f].str, tmp, strlen(t[f].str)))
88       return t[f].val;
89 
90   return -1;
91 }
92 
93 struct vary *
vary_append(struct vary * v,char * arg)94 vary_append(struct vary *v, char *arg)
95 {
96   struct vary *result, **nextp;
97 
98   if (v) {
99     result = v;
100     while (v->next)
101       v = v->next;
102     nextp = &v->next;
103   } else
104     nextp = &result;
105 
106   if ((*nextp = (struct vary *)malloc(sizeof(struct vary))) == NULL)
107     exifdie((const char *)strerror(errno));
108   (*nextp)->arg = arg;
109   (*nextp)->next = NULL;
110   return result;
111 }
112 
113 static int mdays[12] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
114 
115 static int
daysinmonth(const struct tm * t)116 daysinmonth(const struct tm *t)
117 {
118   int year;
119 
120   year = t->tm_year + 1900;
121 
122   if (t->tm_mon == 1)
123     if (!(year % 400))
124       return 29;
125     else if (!(year % 100))
126       return 28;
127     else if (!(year % 4))
128       return 29;
129     else
130       return 28;
131   else if (t->tm_mon >= 0 && t->tm_mon < 12)
132     return mdays[t->tm_mon];
133 
134   return 0;
135 }
136 
137 
138 static int
adjyear(struct tm * t,char type,int val,int mk)139 adjyear(struct tm *t, char type, int val, int mk)
140 {
141   switch (type) {
142     case '+':
143       t->tm_year += val;
144       break;
145     case '-':
146       t->tm_year -= val;
147       break;
148     default:
149       t->tm_year = val;
150       if (t->tm_year < 69)
151       	t->tm_year += 100;		/* as per date.c */
152       else if (t->tm_year > 1900)
153         t->tm_year -= 1900;             /* struct tm holds years since 1900 */
154       break;
155   }
156   return !mk || domktime(t, type) != -1;
157 }
158 
159 static int
adjmon(struct tm * t,char type,int val,int istext,int mk)160 adjmon(struct tm *t, char type, int val, int istext, int mk)
161 {
162   if (val < 0)
163     return 0;
164 
165   switch (type) {
166     case '+':
167       if (istext) {
168         if (val <= t->tm_mon)
169           val += 11 - t->tm_mon;	/* early next year */
170         else
171           val -= t->tm_mon + 1;		/* later this year */
172       }
173       if (val) {
174         if (!adjyear(t, '+', (t->tm_mon + val) / 12, 0))
175           return 0;
176         val %= 12;
177         t->tm_mon += val;
178         if (t->tm_mon > 11)
179           t->tm_mon -= 12;
180       }
181       break;
182 
183     case '-':
184       if (istext) {
185         if (val-1 > t->tm_mon)
186           val = 13 - val + t->tm_mon;	/* later last year */
187         else
188           val = t->tm_mon - val + 1;	/* early this year */
189       }
190       if (val) {
191         if (!adjyear(t, '-', val / 12, 0))
192           return 0;
193         val %= 12;
194         if (val > t->tm_mon) {
195           if (!adjyear(t, '-', 1, 0))
196             return 0;
197           val -= 12;
198         }
199         t->tm_mon -= val;
200       }
201       break;
202 
203     default:
204       if (val > 12 || val < 1)
205         return 0;
206       t->tm_mon = --val;
207   }
208 
209   return !mk || domktime(t, type) != -1;
210 }
211 
212 static int
adjday(struct tm * t,char type,int val,int mk)213 adjday(struct tm *t, char type, int val, int mk)
214 {
215   int lmdays;
216 
217   switch (type) {
218     case '+':
219       while (val) {
220         lmdays = daysinmonth(t);
221         if (val > lmdays - t->tm_mday) {
222           val -= lmdays - t->tm_mday + 1;
223           t->tm_mday = 1;
224           if (!adjmon(t, '+', 1, 0, 0))
225             return 0;
226         } else {
227           t->tm_mday += val;
228           val = 0;
229         }
230       }
231       break;
232     case '-':
233       while (val)
234         if (val >= t->tm_mday) {
235           val -= t->tm_mday;
236           t->tm_mday = 1;
237           if (!adjmon(t, '-', 1, 0, 0))
238             return 0;
239           t->tm_mday = daysinmonth(t);
240         } else {
241           t->tm_mday -= val;
242           val = 0;
243         }
244       break;
245     default:
246       if (val > 0 && val <= daysinmonth(t))
247         t->tm_mday = val;
248       else
249         return 0;
250       break;
251   }
252 
253   return !mk || domktime(t, type) != -1;
254 }
255 
256 static int
adjwday(struct tm * t,char type,int val,int istext,int mk)257 adjwday(struct tm *t, char type, int val, int istext, int mk)
258 {
259   if (val < 0)
260     return 0;
261 
262   switch (type) {
263     case '+':
264       if (istext)
265         if (val < t->tm_wday)
266           val = 7 - t->tm_wday + val;  /* early next week */
267         else
268           val -= t->tm_wday;           /* later this week */
269       else
270         val *= 7;                      /* "-v+5w" == "5 weeks in the future" */
271       return !val || adjday(t, '+', val, mk);
272     case '-':
273       if (istext) {
274         if (val > t->tm_wday)
275           val = 7 - val + t->tm_wday;  /* later last week */
276         else
277           val = t->tm_wday - val;      /* early this week */
278       } else
279         val *= 7;                      /* "-v-5w" == "5 weeks ago" */
280       return !val || adjday(t, '-', val, mk);
281     default:
282       if (val < t->tm_wday)
283         return adjday(t, '-', t->tm_wday - val, mk);
284       else if (val > 6)
285         return 0;
286       else if (val > t->tm_wday)
287         return adjday(t, '+', val - t->tm_wday, mk);
288   }
289   return 1;
290 }
291 
292 static int
adjhour(struct tm * t,char type,int val,int mk)293 adjhour(struct tm *t, char type, int val, int mk)
294 {
295   if (val < 0)
296     return 0;
297 
298   switch (type) {
299     case '+':
300       if (val) {
301         int days;
302 
303         days = (t->tm_hour + val) / 24;
304         val %= 24;
305         t->tm_hour += val;
306         t->tm_hour %= 24;
307         if (!adjday(t, '+', days, 0))
308           return 0;
309       }
310       break;
311 
312     case '-':
313       if (val) {
314         int days;
315 
316         days = val / 24;
317         val %= 24;
318         if (val > t->tm_hour) {
319           days++;
320           val -= 24;
321         }
322         t->tm_hour -= val;
323         if (!adjday(t, '-', days, 0))
324           return 0;
325       }
326       break;
327 
328     default:
329       if (val > 23)
330         return 0;
331       t->tm_hour = val;
332   }
333 
334   return !mk || domktime(t, type) != -1;
335 }
336 
337 static int
adjmin(struct tm * t,char type,int val,int mk)338 adjmin(struct tm *t, char type, int val, int mk)
339 {
340   if (val < 0)
341     return 0;
342 
343   switch (type) {
344     case '+':
345       if (val) {
346         if (!adjhour(t, '+', (t->tm_min + val) / 60, 0))
347           return 0;
348         val %= 60;
349         t->tm_min += val;
350         if (t->tm_min > 59)
351           t->tm_min -= 60;
352       }
353       break;
354 
355     case '-':
356       if (val) {
357         if (!adjhour(t, '-', val / 60, 0))
358           return 0;
359         val %= 60;
360         if (val > t->tm_min) {
361           if (!adjhour(t, '-', 1, 0))
362             return 0;
363           val -= 60;
364         }
365         t->tm_min -= val;
366       }
367       break;
368 
369     default:
370       if (val > 59)
371         return 0;
372       t->tm_min = val;
373   }
374 
375   return !mk || domktime(t, type) != -1;
376 }
377 
378 static int
adjsec(struct tm * t,char type,int val,int mk)379 adjsec(struct tm *t, char type, int val, int mk)
380 {
381   if (val < 0)
382     return 0;
383 
384   switch (type) {
385     case '+':
386       if (val) {
387         if (!adjmin(t, '+', (t->tm_sec + val) / 60, 0))
388           return 0;
389         val %= 60;
390         t->tm_sec += val;
391         if (t->tm_sec > 59)
392           t->tm_sec -= 60;
393       }
394       break;
395 
396     case '-':
397       if (val) {
398         if (!adjmin(t, '-', val / 60, 0))
399           return 0;
400         val %= 60;
401         if (val > t->tm_sec) {
402           if (!adjmin(t, '-', 1, 0))
403             return 0;
404           val -= 60;
405         }
406         t->tm_sec -= val;
407       }
408       break;
409 
410     default:
411       if (val > 59)
412         return 0;
413       t->tm_sec = val;
414   }
415 
416   return !mk || domktime(t, type) != -1;
417 }
418 
419 const struct vary *
vary_apply(const struct vary * v,struct tm * t)420 vary_apply(const struct vary *v, struct tm *t)
421 {
422   char type;
423   char which;
424   char *arg;
425   size_t len;
426   int val;
427 
428   /* Ignore effects of DST. */
429   t->tm_isdst = -1;
430 
431   for (; v; v = v->next) {
432     type = *v->arg;
433     arg = v->arg;
434     if (type == '+' || type == '-')
435       arg++;
436     else
437       type = '\0';
438     len = strlen(arg);
439     if (len < 2)
440       return v;
441 
442     if (strspn(arg, digits) != len-1) {
443       val = trans(trans_wday, arg);
444       if (val != -1) {
445           if (!adjwday(t, type, val, 1, 1))
446             return v;
447       } else {
448         val = trans(trans_mon, arg);
449         if (val != -1) {
450           if (!adjmon(t, type, val, 1, 1))
451             return v;
452         } else
453           return v;
454       }
455     } else {
456       val = atoi(arg);
457       which = arg[len-1];
458 
459       switch (which) {
460         case 'S':
461           if (!adjsec(t, type, val, 1))
462             return v;
463           break;
464         case 'M':
465           if (!adjmin(t, type, val, 1))
466             return v;
467           break;
468         case 'H':
469           if (!adjhour(t, type, val, 1))
470             return v;
471           break;
472         case 'd':
473           if (!adjday(t, type, val, 1))
474             return v;
475           break;
476         case 'w':
477           if (!adjwday(t, type, val, 0, 1))
478             return v;
479           break;
480         case 'm':
481           if (!adjmon(t, type, val, 0, 1))
482             return v;
483           break;
484         case 'y':
485           if (!adjyear(t, type, val, 1))
486             return v;
487           break;
488         default:
489           return v;
490       }
491     }
492   }
493   return 0;
494 }
495 
496 void
vary_destroy(struct vary * v)497 vary_destroy(struct vary *v)
498 {
499   struct vary *n;
500 
501   while (v) {
502     n = v->next;
503     free(v);
504     v = n;
505   }
506 }
507