xref: /freebsd/bin/date/vary.c (revision 66fd12cf4896eb08ad8e7a2627537f84ead84dd3)
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 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <err.h>
31 #include <time.h>
32 #include <stdint.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include "vary.h"
36 
37 struct trans {
38   int64_t val;
39   const char *str;
40 };
41 
42 static struct trans trans_mon[] = {
43   { 1, "january" }, { 2, "february" }, { 3, "march" }, { 4, "april" },
44   { 5, "may"}, { 6, "june" }, { 7, "july" }, { 8, "august" },
45   { 9, "september" }, { 10, "october" }, { 11, "november" }, { 12, "december" },
46   { -1, NULL }
47 };
48 
49 static struct trans trans_wday[] = {
50   { 0, "sunday" }, { 1, "monday" }, { 2, "tuesday" }, { 3, "wednesday" },
51   { 4, "thursday" }, { 5, "friday" }, { 6, "saturday" },
52   { -1, NULL }
53 };
54 
55 static char digits[] = "0123456789";
56 static int adjhour(struct tm *, char, int64_t, int);
57 
58 static int
59 domktime(struct tm *t, char type)
60 {
61   time_t ret;
62 
63   while ((ret = mktime(t)) == -1 && t->tm_year > 68 && t->tm_year < 138)
64     /* While mktime() fails, adjust by an hour */
65     adjhour(t, type == '-' ? type : '+', 1, 0);
66 
67   return ret;
68 }
69 
70 static int
71 trans(const struct trans t[], const char *arg)
72 {
73   int f;
74 
75   for (f = 0; t[f].val != -1; f++)
76     if (!strncasecmp(t[f].str, arg, 3) ||
77         !strncasecmp(t[f].str, arg, strlen(t[f].str)))
78       return t[f].val;
79 
80   return -1;
81 }
82 
83 struct vary *
84 vary_append(struct vary *v, char *arg)
85 {
86   struct vary *result, **nextp;
87 
88   if (v) {
89     result = v;
90     while (v->next)
91       v = v->next;
92     nextp = &v->next;
93   } else
94     nextp = &result;
95 
96   if ((*nextp = (struct vary *)malloc(sizeof(struct vary))) == NULL)
97     err(1, "malloc");
98   (*nextp)->arg = arg;
99   (*nextp)->next = NULL;
100   return result;
101 }
102 
103 static int mdays[12] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
104 
105 static int
106 daysinmonth(const struct tm *t)
107 {
108   int year;
109 
110   year = t->tm_year + 1900;
111 
112   if (t->tm_mon == 1)
113     if (!(year % 400))
114       return 29;
115     else if (!(year % 100))
116       return 28;
117     else if (!(year % 4))
118       return 29;
119     else
120       return 28;
121   else if (t->tm_mon >= 0 && t->tm_mon < 12)
122     return mdays[t->tm_mon];
123 
124   return 0;
125 }
126 
127 
128 static int
129 adjyear(struct tm *t, char type, int64_t val, int mk)
130 {
131   switch (type) {
132     case '+':
133       t->tm_year += val;
134       break;
135     case '-':
136       t->tm_year -= val;
137       break;
138     default:
139       t->tm_year = val;
140       if (t->tm_year < 69)
141       	t->tm_year += 100;		/* as per date.c */
142       else if (t->tm_year > 1900)
143         t->tm_year -= 1900;             /* struct tm holds years since 1900 */
144       break;
145   }
146   return !mk || domktime(t, type) != -1;
147 }
148 
149 static int
150 adjmon(struct tm *t, char type, int64_t val, int istext, int mk)
151 {
152   int lmdays;
153 
154   if (val < 0)
155     return 0;
156 
157   switch (type) {
158     case '+':
159       if (istext) {
160         if (val <= t->tm_mon)
161           val += 11 - t->tm_mon;	/* early next year */
162         else
163           val -= t->tm_mon + 1;		/* later this year */
164       }
165       if (val) {
166         if (!adjyear(t, '+', (t->tm_mon + val) / 12, 0))
167           return 0;
168         val %= 12;
169         t->tm_mon += val;
170         if (t->tm_mon > 11)
171           t->tm_mon -= 12;
172       }
173       break;
174 
175     case '-':
176       if (istext) {
177         if (val-1 > t->tm_mon)
178           val = 13 - val + t->tm_mon;	/* later last year */
179         else
180           val = t->tm_mon - val + 1;	/* early this year */
181       }
182       if (val) {
183         if (!adjyear(t, '-', val / 12, 0))
184           return 0;
185         val %= 12;
186         if (val > t->tm_mon) {
187           if (!adjyear(t, '-', 1, 0))
188             return 0;
189           val -= 12;
190         }
191         t->tm_mon -= val;
192       }
193       break;
194 
195     default:
196       if (val > 12 || val < 1)
197         return 0;
198       t->tm_mon = --val;
199   }
200 
201   /* e.g., -v-1m on March, 31 is the last day of February in common sense */
202   lmdays = daysinmonth(t);
203   if (t->tm_mday > lmdays)
204     t->tm_mday = lmdays;
205 
206   return !mk || domktime(t, type) != -1;
207 }
208 
209 static int
210 adjday(struct tm *t, char type, int64_t val, int mk)
211 {
212   int lmdays;
213 
214   switch (type) {
215     case '+':
216       while (val) {
217         lmdays = daysinmonth(t);
218         if (val > lmdays - t->tm_mday) {
219           val -= lmdays - t->tm_mday + 1;
220           t->tm_mday = 1;
221           if (!adjmon(t, '+', 1, 0, 0))
222             return 0;
223         } else {
224           t->tm_mday += val;
225           val = 0;
226         }
227       }
228       break;
229     case '-':
230       while (val)
231         if (val >= t->tm_mday) {
232           val -= t->tm_mday;
233           t->tm_mday = 1;
234           if (!adjmon(t, '-', 1, 0, 0))
235             return 0;
236           t->tm_mday = daysinmonth(t);
237         } else {
238           t->tm_mday -= val;
239           val = 0;
240         }
241       break;
242     default:
243       if (val > 0 && val <= daysinmonth(t))
244         t->tm_mday = val;
245       else
246         return 0;
247       break;
248   }
249 
250   return !mk || domktime(t, type) != -1;
251 }
252 
253 static int
254 adjwday(struct tm *t, char type, int64_t val, int istext, int mk)
255 {
256   if (val < 0)
257     return 0;
258 
259   switch (type) {
260     case '+':
261       if (istext)
262         if (val < t->tm_wday)
263           val = 7 - t->tm_wday + val;  /* early next week */
264         else
265           val -= t->tm_wday;           /* later this week */
266       else
267         val *= 7;                      /* "-v+5w" == "5 weeks in the future" */
268       return !val || adjday(t, '+', val, mk);
269     case '-':
270       if (istext) {
271         if (val > t->tm_wday)
272           val = 7 - val + t->tm_wday;  /* later last week */
273         else
274           val = t->tm_wday - val;      /* early this week */
275       } else
276         val *= 7;                      /* "-v-5w" == "5 weeks ago" */
277       return !val || adjday(t, '-', val, mk);
278     default:
279       if (val < t->tm_wday)
280         return adjday(t, '-', t->tm_wday - val, mk);
281       else if (val > 6)
282         return 0;
283       else if (val > t->tm_wday)
284         return adjday(t, '+', val - t->tm_wday, mk);
285   }
286   return 1;
287 }
288 
289 static int
290 adjhour(struct tm *t, char type, int64_t val, int mk)
291 {
292   if (val < 0)
293     return 0;
294 
295   switch (type) {
296     case '+':
297       if (val) {
298         int days;
299 
300         days = (t->tm_hour + val) / 24;
301         val %= 24;
302         t->tm_hour += val;
303         t->tm_hour %= 24;
304         if (!adjday(t, '+', days, 0))
305           return 0;
306       }
307       break;
308 
309     case '-':
310       if (val) {
311         int days;
312 
313         days = val / 24;
314         val %= 24;
315         if (val > t->tm_hour) {
316           days++;
317           val -= 24;
318         }
319         t->tm_hour -= val;
320         if (!adjday(t, '-', days, 0))
321           return 0;
322       }
323       break;
324 
325     default:
326       if (val > 23)
327         return 0;
328       t->tm_hour = val;
329   }
330 
331   return !mk || domktime(t, type) != -1;
332 }
333 
334 static int
335 adjmin(struct tm *t, char type, int64_t val, int mk)
336 {
337   if (val < 0)
338     return 0;
339 
340   switch (type) {
341     case '+':
342       if (val) {
343         if (!adjhour(t, '+', (t->tm_min + val) / 60, 0))
344           return 0;
345         val %= 60;
346         t->tm_min += val;
347         if (t->tm_min > 59)
348           t->tm_min -= 60;
349       }
350       break;
351 
352     case '-':
353       if (val) {
354         if (!adjhour(t, '-', val / 60, 0))
355           return 0;
356         val %= 60;
357         if (val > t->tm_min) {
358           if (!adjhour(t, '-', 1, 0))
359             return 0;
360           val -= 60;
361         }
362         t->tm_min -= val;
363       }
364       break;
365 
366     default:
367       if (val > 59)
368         return 0;
369       t->tm_min = val;
370   }
371 
372   return !mk || domktime(t, type) != -1;
373 }
374 
375 static int
376 adjsec(struct tm *t, char type, int64_t val, int mk)
377 {
378   if (val < 0)
379     return 0;
380 
381   switch (type) {
382     case '+':
383       if (val) {
384         if (!adjmin(t, '+', (t->tm_sec + val) / 60, 0))
385           return 0;
386         val %= 60;
387         t->tm_sec += val;
388         if (t->tm_sec > 59)
389           t->tm_sec -= 60;
390       }
391       break;
392 
393     case '-':
394       if (val) {
395         if (!adjmin(t, '-', val / 60, 0))
396           return 0;
397         val %= 60;
398         if (val > t->tm_sec) {
399           if (!adjmin(t, '-', 1, 0))
400             return 0;
401           val -= 60;
402         }
403         t->tm_sec -= val;
404       }
405       break;
406 
407     default:
408       if (val > 59)
409         return 0;
410       t->tm_sec = val;
411   }
412 
413   return !mk || domktime(t, type) != -1;
414 }
415 
416 const struct vary *
417 vary_apply(const struct vary *v, struct tm *t)
418 {
419   char type;
420   char which;
421   char *arg;
422   size_t len;
423   int64_t val;
424 
425   for (; v; v = v->next) {
426     type = *v->arg;
427     arg = v->arg;
428     if (type == '+' || type == '-')
429       arg++;
430     else
431       type = '\0';
432     len = strlen(arg);
433     if (len < 2)
434       return v;
435 
436     if (type == '\0')
437       t->tm_isdst = -1;
438 
439     if (strspn(arg, digits) != len-1) {
440       val = trans(trans_wday, arg);
441       if (val != -1) {
442           if (!adjwday(t, type, val, 1, 1))
443             return v;
444       } else {
445         val = trans(trans_mon, arg);
446         if (val != -1) {
447           if (!adjmon(t, type, val, 1, 1))
448             return v;
449         } else
450           return v;
451       }
452     } else {
453       val = atoi(arg);
454       which = arg[len-1];
455 
456       switch (which) {
457         case 'S':
458           if (!adjsec(t, type, val, 1))
459             return v;
460           break;
461         case 'M':
462           if (!adjmin(t, type, val, 1))
463             return v;
464           break;
465         case 'H':
466           if (!adjhour(t, type, val, 1))
467             return v;
468           break;
469         case 'd':
470           t->tm_isdst = -1;
471           if (!adjday(t, type, val, 1))
472             return v;
473           break;
474         case 'w':
475           t->tm_isdst = -1;
476           if (!adjwday(t, type, val, 0, 1))
477             return v;
478           break;
479         case 'm':
480           t->tm_isdst = -1;
481           if (!adjmon(t, type, val, 0, 1))
482             return v;
483           break;
484         case 'y':
485           t->tm_isdst = -1;
486           if (!adjyear(t, type, val, 1))
487             return v;
488           break;
489         default:
490           return v;
491       }
492     }
493   }
494   return 0;
495 }
496 
497 void
498 vary_destroy(struct vary *v)
499 {
500   struct vary *n;
501 
502   while (v) {
503     n = v->next;
504     free(v);
505     v = n;
506   }
507 }
508