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 <string.h> |
33 |
#include <stdlib.h> |
34 |
#include "vary.h" |
35 |
|
36 |
struct trans { |
37 |
int val; |
38 |
const char *str; |
39 |
}; |
40 |
|
41 |
static struct trans trans_mon[] = { |
42 |
{ 1, "january" }, { 2, "february" }, { 3, "march" }, { 4, "april" }, |
43 |
{ 5, "may"}, { 6, "june" }, { 7, "july" }, { 8, "august" }, |
44 |
{ 9, "september" }, { 10, "october" }, { 11, "november" }, { 12, "december" }, |
45 |
{ -1, NULL } |
46 |
}; |
47 |
|
48 |
static struct trans trans_wday[] = { |
49 |
{ 0, "sunday" }, { 1, "monday" }, { 2, "tuesday" }, { 3, "wednesday" }, |
50 |
{ 4, "thursday" }, { 5, "friday" }, { 6, "saturday" }, |
51 |
{ -1, NULL } |
52 |
}; |
53 |
|
54 |
static char digits[] = "0123456789"; |
55 |
static int adjhour(struct tm *, char, int, int); |
56 |
|
57 |
static int |
58 |
domktime(struct tm *t, char type) |
59 |
{ |
60 |
time_t ret; |
61 |
|
62 |
while ((ret = mktime(t)) == -1 && t->tm_year > 68 && t->tm_year < 138) |
63 |
/* While mktime() fails, adjust by an hour */ |
64 |
adjhour(t, type == '-' ? type : '+', 1, 0); |
65 |
|
66 |
return ret; |
67 |
} |
68 |
|
69 |
static int |
70 |
trans(const struct trans t[], const char *arg) |
71 |
{ |
72 |
int f; |
73 |
|
74 |
for (f = 0; t[f].val != -1; f++) |
75 |
if (!strncasecmp(t[f].str, arg, 3) || |
76 |
!strncasecmp(t[f].str, arg, strlen(t[f].str))) |
77 |
return t[f].val; |
78 |
|
79 |
return -1; |
80 |
} |
81 |
|
82 |
struct vary * |
83 |
vary_append(struct vary *v, char *arg) |
84 |
{ |
85 |
struct vary *result, **nextp; |
86 |
|
87 |
if (v) { |
88 |
result = v; |
89 |
while (v->next) |
90 |
v = v->next; |
91 |
nextp = &v->next; |
92 |
} else |
93 |
nextp = &result; |
94 |
|
95 |
if ((*nextp = (struct vary *)malloc(sizeof(struct vary))) == NULL) |
96 |
err(1, "malloc"); |
97 |
(*nextp)->arg = arg; |
98 |
(*nextp)->next = NULL; |
99 |
return result; |
100 |
} |
101 |
|
102 |
static int mdays[12] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
103 |
|
104 |
static int |
105 |
daysinmonth(const struct tm *t) |
106 |
{ |
107 |
int year; |
108 |
|
109 |
year = t->tm_year + 1900; |
110 |
|
111 |
if (t->tm_mon == 1) |
112 |
if (!(year % 400)) |
113 |
return 29; |
114 |
else if (!(year % 100)) |
115 |
return 28; |
116 |
else if (!(year % 4)) |
117 |
return 29; |
118 |
else |
119 |
return 28; |
120 |
else if (t->tm_mon >= 0 && t->tm_mon < 12) |
121 |
return mdays[t->tm_mon]; |
122 |
|
123 |
return 0; |
124 |
} |
125 |
|
126 |
|
127 |
static int |
128 |
adjyear(struct tm *t, char type, int val, int mk) |
129 |
{ |
130 |
switch (type) { |
131 |
case '+': |
132 |
t->tm_year += val; |
133 |
break; |
134 |
case '-': |
135 |
t->tm_year -= val; |
136 |
break; |
137 |
default: |
138 |
t->tm_year = val; |
139 |
if (t->tm_year < 69) |
140 |
t->tm_year += 100; /* as per date.c */ |
141 |
else if (t->tm_year > 1900) |
142 |
t->tm_year -= 1900; /* struct tm holds years since 1900 */ |
143 |
break; |
144 |
} |
145 |
return !mk || domktime(t, type) != -1; |
146 |
} |
147 |
|
148 |
static int |
149 |
adjmon(struct tm *t, char type, int val, int istext, int mk) |
150 |
{ |
151 |
int lmdays; |
152 |
|
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, int val, int mk) |
210 |
{ |
211 |
int lmdays; |
212 |
|
213 |
switch (type) { |
214 |
case '+': |
215 |
while (val) { |
216 |
lmdays = daysinmonth(t); |
217 |
if (val > lmdays - t->tm_mday) { |
218 |
val -= lmdays - 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, int 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, int 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, int 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, int 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 |
const struct vary * |
416 |
vary_apply(const struct vary *v, struct tm *t) |
417 |
{ |
418 |
char type; |
419 |
char which; |
420 |
char *arg; |
421 |
size_t len; |
422 |
int val; |
423 |
|
424 |
for (; v; v = v->next) { |
425 |
type = *v->arg; |
426 |
arg = v->arg; |
427 |
if (type == '+' || type == '-') |
428 |
arg++; |
429 |
else |
430 |
type = '\0'; |
431 |
len = strlen(arg); |
432 |
if (len < 2) |
433 |
return v; |
434 |
|
435 |
if (type == '\0') |
436 |
t->tm_isdst = -1; |
437 |
|
438 |
if (strspn(arg, digits) != len-1) { |
439 |
val = trans(trans_wday, arg); |
440 |
if (val != -1) { |
441 |
if (!adjwday(t, type, val, 1, 1)) |
442 |
return v; |
443 |
} else { |
444 |
val = trans(trans_mon, arg); |
445 |
if (val != -1) { |
446 |
if (!adjmon(t, type, val, 1, 1)) |
447 |
return v; |
448 |
} else |
449 |
return v; |
450 |
} |
451 |
} else { |
452 |
val = atoi(arg); |
453 |
which = arg[len-1]; |
454 |
|
455 |
switch (which) { |
456 |
case 'S': |
457 |
if (!adjsec(t, type, val, 1)) |
458 |
return v; |
459 |
break; |
460 |
case 'M': |
461 |
if (!adjmin(t, type, val, 1)) |
462 |
return v; |
463 |
break; |
464 |
case 'H': |
465 |
if (!adjhour(t, type, val, 1)) |
466 |
return v; |
467 |
break; |
468 |
case 'd': |
469 |
t->tm_isdst = -1; |
470 |
if (!adjday(t, type, val, 1)) |
471 |
return v; |
472 |
break; |
473 |
case 'w': |
474 |
t->tm_isdst = -1; |
475 |
if (!adjwday(t, type, val, 0, 1)) |
476 |
return v; |
477 |
break; |
478 |
case 'm': |
479 |
t->tm_isdst = -1; |
480 |
if (!adjmon(t, type, val, 0, 1)) |
481 |
return v; |
482 |
break; |
483 |
case 'y': |
484 |
t->tm_isdst = -1; |
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 |
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 |
} |