nrelease - fix/improve livecd
[dragonfly.git] / bin / date / vary.c
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  * $FreeBSD: src/bin/date/vary.c,v 1.16 2004/08/09 13:43:39 yar Exp $
27  * $DragonFly: src/bin/date/vary.c,v 1.4 2005/07/20 06:10:51 cpressey Exp $
28  */
29
30 #include <err.h>
31 #include <time.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include <stdio.h>
36 #include "vary.h"
37
38 struct trans {
39   long val;
40   const char *str;
41 };
42
43 static struct trans trans_mon[] = {
44   { 1, "january" }, { 2, "february" }, { 3, "march" }, { 4, "april" },
45   { 5, "may"}, { 6, "june" }, { 7, "july" }, { 8, "august" },
46   { 9, "september" }, { 10, "october" }, { 11, "november" }, { 12, "december" },
47   { -1, NULL }
48 };
49
50 static struct trans trans_wday[] = {
51   { 0, "sunday" }, { 1, "monday" }, { 2, "tuesday" }, { 3, "wednesday" },
52   { 4, "thursday" }, { 5, "friday" }, { 6, "saturday" },
53   { -1, NULL }
54 };
55
56 static int adjhour(struct tm *, char, long, 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, long 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, long val, int istext, int mk)
151 {
152   int lmdays;
153   if (val < 0)
154     return 0;
155
156   switch (type) {
157     case '+':
158       if (istext) {
159         if (val <= t->tm_mon)
160           val += 11 - t->tm_mon;        /* early next year */
161         else
162           val -= t->tm_mon + 1;         /* later this year */
163       }
164       if (val) {
165         if (!adjyear(t, '+', (t->tm_mon + val) / 12, 0))
166           return 0;
167         val %= 12;
168         t->tm_mon += val;
169         if (t->tm_mon > 11)
170           t->tm_mon -= 12;
171       }
172       break;
173
174     case '-':
175       if (istext) {
176         if (val-1 > t->tm_mon)
177           val = 13 - val + t->tm_mon;   /* later last year */
178         else
179           val = t->tm_mon - val + 1;    /* early this year */
180       }
181       if (val) {
182         if (!adjyear(t, '-', val / 12, 0))
183           return 0;
184         val %= 12;
185         if (val > t->tm_mon) {
186           if (!adjyear(t, '-', 1, 0))
187             return 0;
188           val -= 12;
189         }
190         t->tm_mon -= val;
191       }
192       break;
193
194     default:
195       if (val > 12 || val < 1)
196         return 0;
197       t->tm_mon = --val;
198   }
199
200   /* e.g., -v-1m on March, 31 is the last day of February in common sense */
201   lmdays = daysinmonth(t);
202   if (t->tm_mday > lmdays)
203     t->tm_mday = lmdays;
204
205   return !mk || domktime(t, type) != -1;
206 }
207
208 static int
209 adjday(struct tm *t, char type, long val, int mk)
210 {
211   int daycount;
212
213   switch (type) {
214     case '+':
215       while (val) {
216         daycount = daysinmonth(t);
217         if (val > daycount - t->tm_mday) {
218           val -= daycount - t->tm_mday + 1;
219           t->tm_mday = 1;
220           if (!adjmon(t, '+', 1, 0, 0))
221             return 0;
222         } else {
223           t->tm_mday += val;
224           val = 0;
225         }
226       }
227       break;
228     case '-':
229       while (val)
230         if (val >= t->tm_mday) {
231           val -= t->tm_mday;
232           t->tm_mday = 1;
233           if (!adjmon(t, '-', 1, 0, 0))
234             return 0;
235           t->tm_mday = daysinmonth(t);
236         } else {
237           t->tm_mday -= val;
238           val = 0;
239         }
240       break;
241     default:
242       if (val > 0 && val <= daysinmonth(t))
243         t->tm_mday = val;
244       else
245         return 0;
246       break;
247   }
248
249   return !mk || domktime(t, type) != -1;
250 }
251
252 static int
253 adjwday(struct tm *t, char type, long val, int istext, int mk)
254 {
255   if (val < 0)
256     return 0;
257
258   switch (type) {
259     case '+':
260       if (istext)
261         if (val < t->tm_wday)
262           val = 7 - t->tm_wday + val;  /* early next week */
263         else
264           val -= t->tm_wday;           /* later this week */
265       else
266         val *= 7;                      /* "-v+5w" == "5 weeks in the future" */
267       return !val || adjday(t, '+', val, mk);
268     case '-':
269       if (istext) {
270         if (val > t->tm_wday)
271           val = 7 - val + t->tm_wday;  /* later last week */
272         else
273           val = t->tm_wday - val;      /* early this week */
274       } else
275         val *= 7;                      /* "-v-5w" == "5 weeks ago" */
276       return !val || adjday(t, '-', val, mk);
277     default:
278       if (val < t->tm_wday)
279         return adjday(t, '-', t->tm_wday - val, mk);
280       else if (val > 6)
281         return 0;
282       else if (val > t->tm_wday)
283         return adjday(t, '+', val - t->tm_wday, mk);
284   }
285   return 1;
286 }
287
288 static int
289 adjhour(struct tm *t, char type, long val, int mk)
290 {
291   if (val < 0)
292     return 0;
293
294   switch (type) {
295     case '+':
296       if (val) {
297         int days;
298
299         days = (t->tm_hour + val) / 24;
300         val %= 24;
301         t->tm_hour += val;
302         t->tm_hour %= 24;
303         if (!adjday(t, '+', days, 0))
304           return 0;
305       }
306       break;
307
308     case '-':
309       if (val) {
310         int days;
311
312         days = val / 24;
313         val %= 24;
314         if (val > t->tm_hour) {
315           days++;
316           val -= 24;
317         }
318         t->tm_hour -= val;
319         if (!adjday(t, '-', days, 0))
320           return 0;
321       }
322       break;
323
324     default:
325       if (val > 23)
326         return 0;
327       t->tm_hour = val;
328   }
329
330   return !mk || domktime(t, type) != -1;
331 }
332
333 static int
334 adjmin(struct tm *t, char type, long val, int mk)
335 {
336   if (val < 0)
337     return 0;
338
339   switch (type) {
340     case '+':
341       if (val) {
342         if (!adjhour(t, '+', (t->tm_min + val) / 60, 0))
343           return 0;
344         val %= 60;
345         t->tm_min += val;
346         if (t->tm_min > 59)
347           t->tm_min -= 60;
348       }
349       break;
350
351     case '-':
352       if (val) {
353         if (!adjhour(t, '-', val / 60, 0))
354           return 0;
355         val %= 60;
356         if (val > t->tm_min) {
357           if (!adjhour(t, '-', 1, 0))
358             return 0;
359           val -= 60;
360         }
361         t->tm_min -= val;
362       }
363       break;
364
365     default:
366       if (val > 59)
367         return 0;
368       t->tm_min = val;
369   }
370
371   return !mk || domktime(t, type) != -1;
372 }
373
374 static int
375 adjsec(struct tm *t, char type, long val, int mk)
376 {
377   if (val < 0)
378     return 0;
379
380   switch (type) {
381     case '+':
382       if (val) {
383         if (!adjmin(t, '+', (t->tm_sec + val) / 60, 0))
384           return 0;
385         val %= 60;
386         t->tm_sec += val;
387         if (t->tm_sec > 59)
388           t->tm_sec -= 60;
389       }
390       break;
391
392     case '-':
393       if (val) {
394         if (!adjmin(t, '-', val / 60, 0))
395           return 0;
396         val %= 60;
397         if (val > t->tm_sec) {
398           if (!adjmin(t, '-', 1, 0))
399             return 0;
400           val -= 60;
401         }
402         t->tm_sec -= val;
403       }
404       break;
405
406     default:
407       if (val > 59)
408         return 0;
409       t->tm_sec = val;
410   }
411
412   return !mk || domktime(t, type) != -1;
413 }
414
415 /*
416  * WARNING!  Severely deficient relative to gnu date's -d option
417  *
418  * - timezone handling is all wrong (see also TZ= test in date.c)
419  * - doesn't check for 'Z' suffix
420  * - doesn't handle a multitude of formats that gnu date handles
421  * - is generally a mess
422  */
423
424 const struct vary *
425 vary_apply(const struct vary *vb, time_t tval, struct tm *t)
426 {
427   const struct vary *v;
428   char type;
429   char *arg;
430   char *tmp;
431   size_t len;
432   long val;
433
434   *t = *localtime(&tval);
435
436   for (v = vb; v; v = v->next) {
437     //int domonth = 1;
438     //int doday = 1;
439     int dohms = 1;
440     int doyear = 1;
441     int dotzone = 1;
442
443     arg = v->arg;
444
445     type = *arg;
446
447     /*
448      * Unix time stamp
449      */
450     if (type == '@') {
451       time_t tt;
452       tt = strtoul(arg + 1, NULL, 10);
453       *t = *gmtime(&tt);
454       continue;
455     }
456
457     /*
458      * Delta verses absolute
459      */
460     if (type == '+' || type == '-') {
461       arg++;
462     } else if (strncmp(arg, "next", 4) == 0) {
463       type = '+';
464       arg += 4;
465     } else if (strncmp(arg, "last", 4) == 0) {
466       type = '-';
467       arg += 4;
468     } else {
469       type = '\0';
470     }
471
472     /*
473      * At least 2 chars
474      */
475     while (isspace(arg[0]))
476         ++arg;
477     len = strlen(arg);
478     if (len < 2)
479       return v;
480
481     /*
482      * Reset dst calculation for absolute specifications so
483      * it gets recalculated.
484      */
485     if (type == '\0')
486       t->tm_isdst = -1;
487
488     /*
489      * N{S,M,H,m,d,y,w}
490      */
491     val = strtoul(arg, &tmp, 10);
492     if (tmp != arg && isalpha(tmp[0])) {
493       if (strcmp(tmp, "S") == 0 || strncmp(tmp, "sec", 3) == 0) {
494           if (!adjsec(t, type, val, 1))
495             return v;
496       } else if (strcmp(tmp, "M") == 0 || strncmp(tmp, "min", 3) == 0) {
497           if (!adjmin(t, type, val, 1))
498             return v;
499       } else if (strcmp(tmp, "H") == 0 || strncmp(tmp, "hour", 4) == 0) {
500           if (!adjhour(t, type, val, 1))
501             return v;
502       } else if (strcmp(tmp, "d") == 0 || strncmp(tmp, "day", 3) == 0) {
503           t->tm_isdst = -1;
504           if (!adjday(t, type, val, 1))
505             return v;
506       } else if (strcmp(tmp, "w") == 0 || strncmp(tmp, "week", 4) == 0) {
507           t->tm_isdst = -1;
508           if (!adjwday(t, type, val, 0, 1))
509             return v;
510       } else if (strcmp(tmp, "m") == 0 || strncmp(tmp, "mon", 3) == 0) {
511           t->tm_isdst = -1;
512           if (!adjmon(t, type, val, 0, 1))
513             return v;
514       } else if (strcmp(tmp, "y") == 0 || strncmp(tmp, "year", 4) == 0) {
515           t->tm_isdst = -1;
516           if (!adjyear(t, type, val, 1))
517             return v;
518       } else {
519           return v;
520       }
521       continue;
522     }
523
524     /*
525      * wdayname
526      * monthname
527      * [wdayname[,]] monthname day h:m:s tzone yyyy
528      * [wdayname[,]] monthname day h:m:s YYYY tzone
529      * year-month-day h:m:s[Z]
530      */
531
532     /*
533      * Weekday_string
534      */
535     if ((val = trans(trans_wday, arg)) != -1) {
536         if (!adjwday(t, type, val, 1, 1))
537           return v;
538         while (isalpha(*arg))
539           ++arg;
540         while (isspace(arg[0]))
541             ++arg;
542     }
543
544     /*
545      * Month_string [day ]
546      */
547     if ((val = trans(trans_mon, arg)) != -1) {
548         if (!adjmon(t, type, val, 1, 1))
549           return v;
550         //domonth = 0;
551         while (isalpha(*arg))
552           ++arg;
553         while (isspace(*arg))
554           ++arg;
555
556         val = strtoul(arg, &tmp, 10);
557         if (tmp != arg && (isspace(*tmp) || *tmp == 0)) {
558           t->tm_isdst = -1;
559           if (!adjday(t, 0, val, 1))
560             return v;
561           arg = tmp;
562           //doday = 0;
563           while (isspace(arg[0]))
564             ++arg;
565         }
566     }
567
568     /*
569      * h:m:s or year
570      * year or h:m:s
571      */
572     if (doyear) {
573       val = strtol(arg, &tmp, 10);
574       if (tmp != arg && (*tmp == 0 || isspace(*tmp))) {
575         if (!adjyear(t, type, val, 1))
576           return v;
577         arg = tmp;
578         doyear = 0;
579         while (isspace(arg[0]))
580             ++arg;
581       }
582     }
583
584     val = strtol(arg, &tmp, 10);
585     if (dohms && tmp != arg && *tmp == ':') {
586       int hr = -1;
587       int mi = -1;
588       int se = -1;
589       char zflag = 0;
590
591       sscanf(arg, "%d:%d:%d%c", &hr, &mi, &se, &zflag);
592       if (hr >= 0) {
593         if (!adjhour(t, type, hr, 1))
594           return v;
595       }
596       if (mi >= 0) {
597         if (!adjmin(t, type, mi, 1))
598           return v;
599       }
600       if (se >= 0) {
601         if (!adjsec(t, type, se, 1))
602           return v;
603       }
604       arg = tmp;
605       dohms = 0;
606       if (zflag) {
607         /* XXX */
608       }
609       while (arg[0] && !isspace(arg[0]))
610           ++arg;
611       while (isspace(arg[0]))
612           ++arg;
613     }
614
615     if (doyear) {
616       val = strtol(arg, &tmp, 10);
617       if (tmp != arg && *tmp == 0) {
618         if (!adjyear(t, type, val, 1))
619           return v;
620         arg = tmp;
621         doyear = 0;
622         while (isspace(arg[0]))
623             ++arg;
624       }
625     }
626
627     if (dotzone) {
628         /* XXX */
629     }
630
631
632 #if 0
633     /*
634      * Date adjustment
635      */
636     val = strtol(arg, NULL, 10);
637     which = arg[len-1];
638 #endif
639
640   }
641   return 0;
642 }
643
644 void
645 vary_destroy(struct vary *v)
646 {
647   struct vary *n;
648
649   while (v) {
650     n = v->next;
651     free(v);
652     v = n;
653   }
654 }