131921Sbrian/*-
231921Sbrian * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
331921Sbrian * All rights reserved.
431921Sbrian *
531921Sbrian * Redistribution and use in source and binary forms, with or without
631921Sbrian * modification, are permitted provided that the following conditions
731921Sbrian * are met:
831921Sbrian * 1. Redistributions of source code must retain the above copyright
931921Sbrian *    notice, this list of conditions and the following disclaimer.
1031921Sbrian * 2. Redistributions in binary form must reproduce the above copyright
1131921Sbrian *    notice, this list of conditions and the following disclaimer in the
1231921Sbrian *    documentation and/or other materials provided with the distribution.
1331921Sbrian *
1431921Sbrian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1531921Sbrian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1631921Sbrian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1731921Sbrian * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1831921Sbrian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1931921Sbrian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2031921Sbrian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2131921Sbrian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2231921Sbrian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2331921Sbrian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2431921Sbrian * SUCH DAMAGE.
2531921Sbrian */
2631921Sbrian
2799109Sobrien#include <sys/cdefs.h>
2899109Sobrien__FBSDID("$FreeBSD$");
2935773Scharnier
3069457Sbrian#include <err.h>
3127874Sbrian#include <time.h>
3227874Sbrian#include <string.h>
3327874Sbrian#include <stdlib.h>
3427874Sbrian#include "vary.h"
3527874Sbrian
3627874Sbrianstruct trans {
3727874Sbrian  int val;
3891079Smarkm  const char *str;
3927874Sbrian};
4027874Sbrian
4127874Sbrianstatic struct trans trans_mon[] = {
4227874Sbrian  { 1, "january" }, { 2, "february" }, { 3, "march" }, { 4, "april" },
4357326Salfred  { 5, "may"}, { 6, "june" }, { 7, "july" }, { 8, "august" },
4457326Salfred  { 9, "september" }, { 10, "october" }, { 11, "november" }, { 12, "december" },
4527874Sbrian  { -1, NULL }
4627874Sbrian};
4727874Sbrian
4827874Sbrianstatic struct trans trans_wday[] = {
4927874Sbrian  { 0, "sunday" }, { 1, "monday" }, { 2, "tuesday" }, { 3, "wednesday" },
5027874Sbrian  { 4, "thursday" }, { 5, "friday" }, { 6, "saturday" },
5127874Sbrian  { -1, NULL }
5227874Sbrian};
5327874Sbrian
5427874Sbrianstatic char digits[] = "0123456789";
5559175Sbrianstatic int adjhour(struct tm *, char, int, int);
5627874Sbrian
5727874Sbrianstatic int
5859022Sbriandomktime(struct tm *t, char type)
5959022Sbrian{
6059175Sbrian  time_t ret;
6159022Sbrian
6259175Sbrian  while ((ret = mktime(t)) == -1 && t->tm_year > 68 && t->tm_year < 138)
6359175Sbrian    /* While mktime() fails, adjust by an hour */
6459175Sbrian    adjhour(t, type == '-' ? type : '+', 1, 0);
6559022Sbrian
6659022Sbrian  return ret;
6759022Sbrian}
6859022Sbrian
6959022Sbrianstatic int
7027874Sbriantrans(const struct trans t[], const char *arg)
7127874Sbrian{
7227874Sbrian  int f;
7327874Sbrian
7427874Sbrian  for (f = 0; t[f].val != -1; f++)
7528025Sbrian    if (!strncasecmp(t[f].str, arg, 3) ||
7628025Sbrian        !strncasecmp(t[f].str, arg, strlen(t[f].str)))
7727874Sbrian      return t[f].val;
7827874Sbrian
7927874Sbrian  return -1;
8027874Sbrian}
8127874Sbrian
8227874Sbrianstruct vary *
8328025Sbrianvary_append(struct vary *v, char *arg)
8427874Sbrian{
8527874Sbrian  struct vary *result, **nextp;
8627874Sbrian
8727874Sbrian  if (v) {
8827874Sbrian    result = v;
8927874Sbrian    while (v->next)
9027874Sbrian      v = v->next;
9127874Sbrian    nextp = &v->next;
9227874Sbrian  } else
9327874Sbrian    nextp = &result;
9427874Sbrian
9569457Sbrian  if ((*nextp = (struct vary *)malloc(sizeof(struct vary))) == NULL)
9669457Sbrian    err(1, "malloc");
9727874Sbrian  (*nextp)->arg = arg;
9827874Sbrian  (*nextp)->next = NULL;
9927874Sbrian  return result;
10027874Sbrian}
10127874Sbrian
10227874Sbrianstatic int mdays[12] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
10327874Sbrian
10427874Sbrianstatic int
10527874Sbriandaysinmonth(const struct tm *t)
10627874Sbrian{
10727874Sbrian  int year;
10827874Sbrian
10927874Sbrian  year = t->tm_year + 1900;
11027874Sbrian
11127874Sbrian  if (t->tm_mon == 1)
11227874Sbrian    if (!(year % 400))
11327874Sbrian      return 29;
11427874Sbrian    else if (!(year % 100))
11527874Sbrian      return 28;
11627874Sbrian    else if (!(year % 4))
11727874Sbrian      return 29;
11827874Sbrian    else
11927874Sbrian      return 28;
12027874Sbrian  else if (t->tm_mon >= 0 && t->tm_mon < 12)
12127874Sbrian    return mdays[t->tm_mon];
12227874Sbrian
12327874Sbrian  return 0;
12427874Sbrian}
12527874Sbrian
12627874Sbrian
12727874Sbrianstatic int
12859175Sbrianadjyear(struct tm *t, char type, int val, int mk)
12927874Sbrian{
13027874Sbrian  switch (type) {
13127874Sbrian    case '+':
13227874Sbrian      t->tm_year += val;
13327874Sbrian      break;
13427874Sbrian    case '-':
13527874Sbrian      t->tm_year -= val;
13627874Sbrian      break;
13727874Sbrian    default:
13827874Sbrian      t->tm_year = val;
13927874Sbrian      if (t->tm_year < 69)
14027874Sbrian      	t->tm_year += 100;		/* as per date.c */
14127874Sbrian      else if (t->tm_year > 1900)
14227874Sbrian        t->tm_year -= 1900;             /* struct tm holds years since 1900 */
14327874Sbrian      break;
14427874Sbrian  }
14559175Sbrian  return !mk || domktime(t, type) != -1;
14627874Sbrian}
14727874Sbrian
14827874Sbrianstatic int
14959175Sbrianadjmon(struct tm *t, char type, int val, int istext, int mk)
15027874Sbrian{
151133381Syar  int lmdays;
152133381Syar
15327874Sbrian  if (val < 0)
15427874Sbrian    return 0;
15527874Sbrian
15627874Sbrian  switch (type) {
15727874Sbrian    case '+':
15846073Simp      if (istext) {
15927874Sbrian        if (val <= t->tm_mon)
16027874Sbrian          val += 11 - t->tm_mon;	/* early next year */
16127874Sbrian        else
16227874Sbrian          val -= t->tm_mon + 1;		/* later this year */
16346073Simp      }
16459022Sbrian      if (val) {
16559175Sbrian        if (!adjyear(t, '+', (t->tm_mon + val) / 12, 0))
16659022Sbrian          return 0;
16759022Sbrian        val %= 12;
16859022Sbrian        t->tm_mon += val;
16959022Sbrian        if (t->tm_mon > 11)
17059022Sbrian          t->tm_mon -= 12;
17159022Sbrian      }
17227874Sbrian      break;
17327874Sbrian
17427874Sbrian    case '-':
17546073Simp      if (istext) {
17627874Sbrian        if (val-1 > t->tm_mon)
17727874Sbrian          val = 13 - val + t->tm_mon;	/* later last year */
17827874Sbrian        else
17927874Sbrian          val = t->tm_mon - val + 1;	/* early this year */
18046073Simp      }
18159022Sbrian      if (val) {
18259175Sbrian        if (!adjyear(t, '-', val / 12, 0))
18327874Sbrian          return 0;
18459022Sbrian        val %= 12;
18559022Sbrian        if (val > t->tm_mon) {
18659175Sbrian          if (!adjyear(t, '-', 1, 0))
18759022Sbrian            return 0;
18859022Sbrian          val -= 12;
18959022Sbrian        }
19059022Sbrian        t->tm_mon -= val;
19127874Sbrian      }
19227874Sbrian      break;
19327874Sbrian
19427874Sbrian    default:
19527874Sbrian      if (val > 12 || val < 1)
19627874Sbrian        return 0;
19727874Sbrian      t->tm_mon = --val;
19827874Sbrian  }
19927874Sbrian
200133381Syar  /* e.g., -v-1m on March, 31 is the last day of February in common sense */
201133381Syar  lmdays = daysinmonth(t);
202133381Syar  if (t->tm_mday > lmdays)
203133381Syar    t->tm_mday = lmdays;
204133381Syar
20559175Sbrian  return !mk || domktime(t, type) != -1;
20627874Sbrian}
20727874Sbrian
20827874Sbrianstatic int
20959175Sbrianadjday(struct tm *t, char type, int val, int mk)
21027874Sbrian{
21191079Smarkm  int lmdays;
21259022Sbrian
21327874Sbrian  switch (type) {
21427874Sbrian    case '+':
21527874Sbrian      while (val) {
21691079Smarkm        lmdays = daysinmonth(t);
21791079Smarkm        if (val > lmdays - t->tm_mday) {
21891079Smarkm          val -= lmdays - t->tm_mday + 1;
21927874Sbrian          t->tm_mday = 1;
22059175Sbrian          if (!adjmon(t, '+', 1, 0, 0))
22127874Sbrian            return 0;
22227874Sbrian        } else {
22327874Sbrian          t->tm_mday += val;
22427874Sbrian          val = 0;
22527874Sbrian        }
22627874Sbrian      }
22727874Sbrian      break;
22827874Sbrian    case '-':
22927874Sbrian      while (val)
23027874Sbrian        if (val >= t->tm_mday) {
23127874Sbrian          val -= t->tm_mday;
23227874Sbrian          t->tm_mday = 1;
23359175Sbrian          if (!adjmon(t, '-', 1, 0, 0))
23427874Sbrian            return 0;
23527874Sbrian          t->tm_mday = daysinmonth(t);
23627874Sbrian        } else {
23727874Sbrian          t->tm_mday -= val;
23827874Sbrian          val = 0;
23927874Sbrian        }
24027874Sbrian      break;
24127874Sbrian    default:
24227874Sbrian      if (val > 0 && val <= daysinmonth(t))
24327874Sbrian        t->tm_mday = val;
24427874Sbrian      else
24527874Sbrian        return 0;
24627874Sbrian      break;
24727874Sbrian  }
24827874Sbrian
24959175Sbrian  return !mk || domktime(t, type) != -1;
25027874Sbrian}
25127874Sbrian
25227874Sbrianstatic int
25359175Sbrianadjwday(struct tm *t, char type, int val, int istext, int mk)
25427874Sbrian{
25527874Sbrian  if (val < 0)
25627874Sbrian    return 0;
25727874Sbrian
25828025Sbrian  switch (type) {
25927874Sbrian    case '+':
26027874Sbrian      if (istext)
26127874Sbrian        if (val < t->tm_wday)
26227874Sbrian          val = 7 - t->tm_wday + val;  /* early next week */
26327874Sbrian        else
26427874Sbrian          val -= t->tm_wday;           /* later this week */
26527874Sbrian      else
26659022Sbrian        val *= 7;                      /* "-v+5w" == "5 weeks in the future" */
26760836Sbrian      return !val || adjday(t, '+', val, mk);
26827874Sbrian    case '-':
26959022Sbrian      if (istext) {
27027874Sbrian        if (val > t->tm_wday)
27127874Sbrian          val = 7 - val + t->tm_wday;  /* later last week */
27227874Sbrian        else
27327874Sbrian          val = t->tm_wday - val;      /* early this week */
27459022Sbrian      } else
27559022Sbrian        val *= 7;                      /* "-v-5w" == "5 weeks ago" */
27660836Sbrian      return !val || adjday(t, '-', val, mk);
27727874Sbrian    default:
27827874Sbrian      if (val < t->tm_wday)
27960836Sbrian        return adjday(t, '-', t->tm_wday - val, mk);
28027874Sbrian      else if (val > 6)
28127874Sbrian        return 0;
28227874Sbrian      else if (val > t->tm_wday)
28360836Sbrian        return adjday(t, '+', val - t->tm_wday, mk);
28427874Sbrian  }
28527874Sbrian  return 1;
28627874Sbrian}
28727874Sbrian
28827874Sbrianstatic int
28959175Sbrianadjhour(struct tm *t, char type, int val, int mk)
29027874Sbrian{
29128025Sbrian  if (val < 0)
29228025Sbrian    return 0;
29328025Sbrian
29428025Sbrian  switch (type) {
29527874Sbrian    case '+':
29659022Sbrian      if (val) {
29759022Sbrian        int days;
29859022Sbrian
29959022Sbrian        days = (t->tm_hour + val) / 24;
30059022Sbrian        val %= 24;
30159022Sbrian        t->tm_hour += val;
30259022Sbrian        t->tm_hour %= 24;
30359175Sbrian        if (!adjday(t, '+', days, 0))
30459022Sbrian          return 0;
30559022Sbrian      }
30628025Sbrian      break;
30728025Sbrian
30827874Sbrian    case '-':
30959022Sbrian      if (val) {
31059022Sbrian        int days;
31159022Sbrian
31259022Sbrian        days = val / 24;
31359022Sbrian        val %= 24;
31459022Sbrian        if (val > t->tm_hour) {
31559022Sbrian          days++;
31659022Sbrian          val -= 24;
31759022Sbrian        }
31859022Sbrian        t->tm_hour -= val;
31959175Sbrian        if (!adjday(t, '-', days, 0))
32028025Sbrian          return 0;
32128025Sbrian      }
32228025Sbrian      break;
32328025Sbrian
32427874Sbrian    default:
32528025Sbrian      if (val > 23)
32628025Sbrian        return 0;
32728025Sbrian      t->tm_hour = val;
32827874Sbrian  }
32928025Sbrian
33059175Sbrian  return !mk || domktime(t, type) != -1;
33127874Sbrian}
33227874Sbrian
33328025Sbrianstatic int
33459175Sbrianadjmin(struct tm *t, char type, int val, int mk)
33528025Sbrian{
33628025Sbrian  if (val < 0)
33728025Sbrian    return 0;
33828025Sbrian
33928025Sbrian  switch (type) {
34028025Sbrian    case '+':
34159022Sbrian      if (val) {
34259175Sbrian        if (!adjhour(t, '+', (t->tm_min + val) / 60, 0))
34359022Sbrian          return 0;
34459022Sbrian        val %= 60;
34559022Sbrian        t->tm_min += val;
34659022Sbrian        if (t->tm_min > 59)
34759022Sbrian          t->tm_min -= 60;
34859022Sbrian      }
34928025Sbrian      break;
35028025Sbrian
35128025Sbrian    case '-':
35259022Sbrian      if (val) {
35359175Sbrian        if (!adjhour(t, '-', val / 60, 0))
35428025Sbrian          return 0;
35559022Sbrian        val %= 60;
35659022Sbrian        if (val > t->tm_min) {
35759175Sbrian          if (!adjhour(t, '-', 1, 0))
35859022Sbrian            return 0;
35959022Sbrian          val -= 60;
36059022Sbrian        }
36159022Sbrian        t->tm_min -= val;
36228025Sbrian      }
36328025Sbrian      break;
36428025Sbrian
36528025Sbrian    default:
36628025Sbrian      if (val > 59)
36728025Sbrian        return 0;
36828025Sbrian      t->tm_min = val;
36928025Sbrian  }
37028025Sbrian
37159175Sbrian  return !mk || domktime(t, type) != -1;
37228025Sbrian}
37328025Sbrian
37444598Sbrianstatic int
37559175Sbrianadjsec(struct tm *t, char type, int val, int mk)
37644598Sbrian{
37744598Sbrian  if (val < 0)
37844598Sbrian    return 0;
37944598Sbrian
38044598Sbrian  switch (type) {
38144598Sbrian    case '+':
38259022Sbrian      if (val) {
38359175Sbrian        if (!adjmin(t, '+', (t->tm_sec + val) / 60, 0))
38459022Sbrian          return 0;
38559022Sbrian        val %= 60;
38659022Sbrian        t->tm_sec += val;
38759022Sbrian        if (t->tm_sec > 59)
38859022Sbrian          t->tm_sec -= 60;
38959022Sbrian      }
39044598Sbrian      break;
39144598Sbrian
39244598Sbrian    case '-':
39359022Sbrian      if (val) {
39459175Sbrian        if (!adjmin(t, '-', val / 60, 0))
39544598Sbrian          return 0;
39659022Sbrian        val %= 60;
39759022Sbrian        if (val > t->tm_sec) {
39859175Sbrian          if (!adjmin(t, '-', 1, 0))
39959022Sbrian            return 0;
40059022Sbrian          val -= 60;
40159022Sbrian        }
40259022Sbrian        t->tm_sec -= val;
40344598Sbrian      }
40444598Sbrian      break;
40544598Sbrian
40644598Sbrian    default:
40744598Sbrian      if (val > 59)
40844598Sbrian        return 0;
40944598Sbrian      t->tm_sec = val;
41044598Sbrian  }
41144598Sbrian
41259175Sbrian  return !mk || domktime(t, type) != -1;
41344598Sbrian}
41444598Sbrian
41527874Sbrianconst struct vary *
41627874Sbrianvary_apply(const struct vary *v, struct tm *t)
41727874Sbrian{
41828025Sbrian  char type;
41928025Sbrian  char which;
42028025Sbrian  char *arg;
42191079Smarkm  size_t len;
42228025Sbrian  int val;
42328025Sbrian
42427874Sbrian  for (; v; v = v->next) {
42528025Sbrian    type = *v->arg;
42628025Sbrian    arg = v->arg;
42728025Sbrian    if (type == '+' || type == '-')
42828025Sbrian      arg++;
42928025Sbrian    else
43028025Sbrian      type = '\0';
43128025Sbrian    len = strlen(arg);
43228025Sbrian    if (len < 2)
43328025Sbrian      return v;
43428025Sbrian
43559175Sbrian    if (type == '\0')
43659175Sbrian      t->tm_isdst = -1;
43759175Sbrian
43828025Sbrian    if (strspn(arg, digits) != len-1) {
43928025Sbrian      val = trans(trans_wday, arg);
44028025Sbrian      if (val != -1) {
44159175Sbrian          if (!adjwday(t, type, val, 1, 1))
44228025Sbrian            return v;
44328025Sbrian      } else {
44428025Sbrian        val = trans(trans_mon, arg);
44528025Sbrian        if (val != -1) {
44659175Sbrian          if (!adjmon(t, type, val, 1, 1))
44728025Sbrian            return v;
44828025Sbrian        } else
44927874Sbrian          return v;
45028025Sbrian      }
45128025Sbrian    } else {
45228025Sbrian      val = atoi(arg);
45328025Sbrian      which = arg[len-1];
45428025Sbrian
45528025Sbrian      switch (which) {
45644598Sbrian        case 'S':
45759175Sbrian          if (!adjsec(t, type, val, 1))
45844598Sbrian            return v;
45944598Sbrian          break;
46028025Sbrian        case 'M':
46159175Sbrian          if (!adjmin(t, type, val, 1))
46228025Sbrian            return v;
46328025Sbrian          break;
46428025Sbrian        case 'H':
46559175Sbrian          if (!adjhour(t, type, val, 1))
46628025Sbrian            return v;
46728025Sbrian          break;
46828025Sbrian        case 'd':
46959175Sbrian          t->tm_isdst = -1;
47059175Sbrian          if (!adjday(t, type, val, 1))
47128025Sbrian            return v;
47228025Sbrian          break;
47328025Sbrian        case 'w':
47459175Sbrian          t->tm_isdst = -1;
47559175Sbrian          if (!adjwday(t, type, val, 0, 1))
47628025Sbrian            return v;
47728025Sbrian          break;
47828025Sbrian        case 'm':
47959175Sbrian          t->tm_isdst = -1;
48059175Sbrian          if (!adjmon(t, type, val, 0, 1))
48128025Sbrian            return v;
48228025Sbrian          break;
48328025Sbrian        case 'y':
48459175Sbrian          t->tm_isdst = -1;
48559175Sbrian          if (!adjyear(t, type, val, 1))
48628025Sbrian            return v;
48728025Sbrian          break;
48828025Sbrian        default:
48927874Sbrian          return v;
49028025Sbrian      }
49127874Sbrian    }
49227874Sbrian  }
49327874Sbrian  return 0;
49427874Sbrian}
49527874Sbrian
49627874Sbrianvoid
49727874Sbrianvary_destroy(struct vary *v)
49827874Sbrian{
49927874Sbrian  struct vary *n;
50027874Sbrian
50127874Sbrian  while (v) {
50227874Sbrian    n = v->next;
50327874Sbrian    free(v);
50427874Sbrian    v = n;
50527874Sbrian  }
50627874Sbrian}
507