1 /*
2 * deals with the holiday file. A yacc parser is used to parse the file.
3 * All the holidays of the specified year are calculated at once and stored
4 * in two arrays that have one entry for each day of the year. The day
5 * drawing routines just use the julian date to index into these arrays.
6 * There are two arrays because holidays can be printed either on a full
7 * line under the day number, or as a small line to the right of the day
8 * number. It's convenient to have both.
9 *
10 * reset_all_hdays() reset holidays, for "reset" keyword
11 * parse_holidays(year, force) read the holiday file and evaluate
12 * all the holiday definitions for
13 * <year>. Sets holiday and sm_holiday
14 * arrays. If force is set, re-eval even
15 * if year is the same as last time.
16 * dump_holiday(year) print holiday list for a year (webplan)
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <time.h>
23 #if !defined NEWSOS4 && !defined STDLIBMALLOC && !defined MACOSX
24 #include <malloc.h>
25 #endif
26 #ifdef CPP_PATH
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #endif
30 #include <Xm/Xm.h>
31 #include "cal.h"
32
33
34 /*
35 * Before you mail and complain that the following macro is incorrect,
36 * please consider that this is one of the main battlegrounds of the
37 * Annual Usenet Flame Wars. 2000 is a leap year. Just trust me on this :-)
38 */
39
40 #define LEAPYEAR(y) !((y)&3)
41 #define JULIAN(m,d) (monthbegin[m] + (d)-1+((m)>1 && LEAPYEAR(parse_year)))
42 #define LAST 999
43 #define ANY 0
44 #define BEFORE -1
45 #define AFTER -2
46
47 static void setliteraldate(int, int, int, int *);
48 static int calc_easter(int);
49 static int calc_pascha(int);
50 extern int yyparse(void);
51
52
53 #if defined(bsdi)||defined(linux)||defined(__NetBSD__)||defined(HPGCC)||defined(__EMX__)||defined(__OpenBSD__)||defined(MACOSX)
54 int yylineno;
55 #else
56 extern int yylineno; /* current line # being parsed */
57 #endif
58 extern char *yytext; /* current token being parsed */
59 extern FILE *yyin; /* the file the parser reads from */
60 extern BOOL yacc_small; /* small string or on its own line? */
61 extern int yacc_stringcolor; /* color of holiday name text, 1..8 */
62 extern char *yacc_string; /* holiday name text */
63 extern int yacc_daycolor; /* color of day number, 1..8 */
64 extern char *progname; /* argv[0] */
65 int parse_year = -1; /* year being parsed, 0=1970..99=2069*/
66 static char *filename; /* holiday filename */
67 static char errormsg[200]; /* error message if any, or "" */
68 static int easter_julian; /* julian date of Easter Sunday */
69 static int pascha_julian; /* julian date of Pascha Sunday */
70 static char *holiday_name; /* strdup'd yacc_string */
71
72 struct holiday holiday[366]; /* info for each day, separate for */
73 struct holiday sm_holiday[366]; /* full-line texts under, and small */
74 /* texts next to day number */
75 extern short monthlen[12];
76 extern short monthbegin[12];
77
78
yyerror(char * msg)79 int yyerror(char *msg)
80 {
81 fprintf(stderr, _("%s: %s in line %d of %s\n"), progname,
82 msg, yylineno+1, filename);
83 if (!*errormsg)
84 sprintf(errormsg,
85 _("Problem with holiday file %s:\n%.80s in line %d"),
86 filename, msg, yylineno+1);
87 return(0);
88 }
89
90
91 /*
92 * clear holidays. This is done before parsing starts, or if a "reset"
93 * command is found in a holiday file. This lets users override system
94 * holidays because system holidays are read first and can be reset.
95 */
96
reset_holidays(struct holiday * hp)97 void reset_holidays(
98 struct holiday *hp)
99 {
100 int d;
101
102 for (d=0; d < 366; d++, hp++)
103 if (hp->string) {
104 if (!hp->dup)
105 free(hp->string);
106 hp->string = 0;
107 hp->stringcolor = 0;
108 hp->daycolor = 0;
109 hp->dup = FALSE;
110 }
111 }
112
113
114 static BOOL did_reset; /* if .holiday resets, skip sys file */
115
reset_all_hdays(void)116 void reset_all_hdays(void) /* this is for parser.y only */
117 {
118 did_reset = TRUE;
119 reset_holidays(holiday);
120 reset_holidays(sm_holiday);
121 }
122
123
124 /*
125 * parse the holiday text file, and set up the holiday arrays for a year.
126 * First read the system file (HOLIDAY_PATH), the the personal file; the
127 * latter may use the "reset" keyword to get rid of all previous settings.
128 * If year is -1, re-parse the last year parsed (this is used when the
129 * holiday file changes). If there is a CPP_PATH, check if the executable
130 * really exists, and if so, pipe the holioday files through it.
131 * Return an error message if an error occurred, 0 otherwise.
132 */
133
parse_holidays(int year,BOOL force)134 char *parse_holidays(
135 int year, /* year to parse for, 0=1970 */
136 BOOL force) /* file has changed, re-read */
137 {
138 int n;
139 BOOL piped = FALSE;
140 char buf[200];
141 #ifdef CPP_PATH
142 char *p, cpp[200];
143 #endif
144
145 if (year == parse_year && !force)
146 return(0);
147 if (year < 0)
148 year = parse_year;
149 parse_year = year;
150 easter_julian = calc_easter(year + 1900);
151 pascha_julian = calc_pascha(year + 1900);
152
153 reset_holidays(holiday);
154 reset_holidays(sm_holiday);
155
156 did_reset = FALSE;
157 for (n=0; n < 2; n++) {
158 sprintf(buf, "%s/%s", LIB, HOLIDAY_NAME);
159 filename = resolve_tilde(n ? buf : HOLIDAY_PATH);
160 if (access(filename, R_OK))
161 continue;
162 #ifdef CPP_PATH
163 strncpy(cpp, CPP_PATH, sizeof(cpp));
164 cpp[sizeof(cpp)-1] = 0;
165 if ((p = strchr(cpp, ' ')))
166 *p = 0;
167 if ((piped = !access(cpp, X_OK))) {
168 char cmd[200];
169 struct stat status;
170 if (!stat(filename, &status) && !status.st_size)
171 continue;
172 sprintf(cmd, "%s %s", CPP_PATH, filename);
173 yyin = popen(cmd, "r");
174 } else
175 #endif
176 yyin = fopen(filename, "r");
177 if (!yyin)
178 continue;
179 *errormsg = 0;
180 yylineno = 0;
181 yyparse();
182 if (piped)
183 pclose(yyin);
184 else
185 fclose(yyin);
186 if (*errormsg)
187 return(errormsg);
188 if (did_reset) /* found reset command in ~/.holiday,*/
189 break; /* don't read system-wide holidays */
190 }
191 return(0);
192 }
193
194
195 /*--------------------- yacc callbacks --------------------------------------*/
196 /*
197 * set holiday by weekday (monday..sunday). The expression is
198 * "every <num>-th <wday> of <month> plus <off> days". num and month
199 * can be ANY or LAST.
200 */
201
setwday(int num,int wday,int month,int off,int length)202 void setwday(
203 int num, /* which, 1st..5th, ANY, or LAST */
204 int wday, /* 1=monday..7=sunday, -1=workday */
205 int month, /* month, 1..12, ANY, or LAST */
206 int off, /* offset in days */
207 int length) /* length in days */
208 {
209 int min_month = 0, max_month = 11;
210 int min_num = 0, max_num = 4;
211 int m, n, d, l, mlen, wday1;
212 int dup = 0;
213
214 if (month != ANY)
215 min_month = max_month = month-1;
216 if (month == LAST)
217 min_month = max_month = 11;
218 if (num != ANY)
219 min_num = max_num = num-1;
220
221 holiday_name = yacc_string;
222 for (m=min_month; m <= max_month; m++) {
223 (void)date_to_time(1, m, parse_year, &wday1, 0, 0);
224 d = (wday-1 - (wday1-1) +7) % 7 + 1;
225 mlen = monthlen[m] + (m==1 && LEAPYEAR(parse_year));
226 if (num == LAST)
227 for (l=0; l < length; l++)
228 setliteraldate(m, d+28<=mlen ? d+28 : d+21,
229 off+l, &dup);
230 else
231 for (d+=min_num*7, n=min_num; n <= max_num; n++, d+=7)
232 if (d >= 1 && d <= mlen)
233 for (l=0; l < length; l++)
234 setliteraldate(m,d,off+l,&dup);
235 }
236 }
237
238
239 /*
240 * set holiday by weekday (monday..sunday) date offset. The expression is
241 * "every <wday> before/after <date> plus <off> days".
242 * (This routine contributed by Peter Littlefield <plittle@sofkin.ca>)
243 */
244
setdoff(int wday,int rel,int month,int day,int year,int off,int length)245 void setdoff(
246 int wday, /* 1=monday..7=sunday */
247 int rel, /* -1=before, -2=after */
248 int month, /* month, 1..12, ANY, or LAST */
249 int day, /* 1..31, ANY, or LAST */
250 int year, /* 1..2069, or ANY */
251 int off, /* offset in days */
252 int length) /* length in days */
253 {
254 int min_month = 0, max_month = 11;
255 int min_day = 1, max_day = 31;
256 int m, d, nd, l, wday1;
257 int dup = 0;
258
259 if (year != ANY) {
260 year %= 100;
261 if (year < 70) year += 100;
262 if (year != parse_year)
263 return;
264 }
265 if (month != ANY)
266 min_month = max_month = month-1;
267 if (month == LAST)
268 min_month = max_month = 11;
269 if (day != ANY)
270 min_day = max_day = day;
271
272 holiday_name = yacc_string;
273 for (m=min_month; m <= max_month; m++)
274 if (day == LAST) {
275 (void)date_to_time(monthlen[m], m, parse_year,
276 &wday1, 0, 0);
277 if (wday < 0) {
278 if (wday1 > 0 && wday1 < 6)
279 wday = wday1;
280 else
281 wday = (rel == BEFORE) ? 5 : 1;
282 }
283 nd = (((wday - wday1 + 7) % 7) -
284 ((rel == BEFORE) ? 7 : 0)) % 7;
285 for (l=0; l < length; l++)
286 setliteraldate(m,monthlen[m]+nd, off+l, &dup);
287 } else
288 for (d=min_day; d <= max_day; d++) {
289 (void)date_to_time(d, m, parse_year,
290 &wday1, 0, 0);
291 if (wday < 0) {
292 if (wday1 > 0 && wday1 < 6)
293 wday = wday1;
294 else
295 wday = (rel == BEFORE) ? 5 : 1;
296 }
297 nd = (((wday - wday1 + 7) % 7) -
298 ((rel == BEFORE) ? 7 : 0)) % 7;
299 for (l=0; l < length; l++)
300 setliteraldate(m, d+nd, off+l, &dup);
301 }
302 }
303
304
305 /*
306 * set holiday by date. Ignore holidays in the wrong year. The code is
307 * complicated by expressions such as "any/last/any" (every last day of
308 * the month).
309 */
310
setdate(int month,int day,int year,int off,int length)311 void setdate(
312 int month, /* 1..12, ANY, or LAST */
313 int day, /* 1..31, ANY, or LAST */
314 int year, /* 1..2069, or ANY */
315 int off, /* offset in days */
316 int length) /* length in days */
317 {
318 int min_month = 0, max_month = 11;
319 int min_day = 1, max_day = 31;
320 int m, d, l;
321 int dup = 0;
322
323 if (year != ANY) {
324 year %= 100;
325 if (year < 70) year += 100;
326 if (year != parse_year)
327 return;
328 }
329 if (month != ANY)
330 min_month = max_month = month-1;
331 if (month == LAST)
332 min_month = max_month = 11;
333 if (day != ANY)
334 min_day = max_day = day;
335
336 holiday_name = yacc_string;
337 for (m=min_month; m <= max_month; m++)
338 if (day == LAST)
339 for (l=0; l < length; l++)
340 setliteraldate(m, monthlen[m], off+l, &dup);
341 else
342 for (d=min_day; d <= max_day; d++)
343 for (l=0; l < length; l++)
344 setliteraldate(m, d, off+l, &dup);
345 }
346
347
348 /*
349 * After the two routines above have removed ambiguities (ANY) and resolved
350 * weekday specifications, this routine registers the holiday in the holiday
351 * array. There are two of these, for full-line holidays (they take away one
352 * appointment line in the month calendar daybox) and "small" holidays, which
353 * appear next to the day number. If the day is already some other holiday,
354 * ignore the new one. <dup> is information stored for parse_holidays(), it
355 * will free() the holiday name only if its dup field is 0 (because many
356 * string fields can point to the same string, which was allocated only once
357 * by the lexer, and should therefore only be freed once).
358 */
359
360 static int colormap[10] = {
361 0, COL_HBLACK, COL_HRED, COL_HGREEN, COL_HYELLOW,
362 COL_HBLUE, COL_HMAGENTA, COL_HCYAN, COL_HWHITE, COL_WEEKEND };
363
setliteraldate(int month,int day,int off,int * dup)364 static void setliteraldate(
365 int month, /* 0..11 */
366 int day, /* 1..31 */
367 int off, /* offset in days */
368 int *dup) /* flag for later free() */
369 {
370 int julian = JULIAN(month, day) + off;
371 struct holiday *hp = yacc_small ? &sm_holiday[julian]
372 : &holiday[julian];
373
374 if (julian >= 0 && julian <= 365 && !hp->string) {
375 if (!*dup)
376 holiday_name = mystrdup(holiday_name);
377 hp->string = holiday_name;
378 hp->stringcolor = colormap[yacc_stringcolor];
379 hp->daycolor = colormap[yacc_daycolor];
380 hp->dup = (*dup)++;
381 }
382 }
383
384
385 /*
386 * set a holiday relative to Easter
387 */
388
seteaster(int off,int length,int pascha)389 void seteaster(
390 int off, /* offset in days */
391 int length, /* length in days */
392 int pascha) /* 0=Easter, 1=Christian Orthodox */
393 {
394 int dup = 0; /* flag for later free() */
395 int julian = (pascha ? pascha_julian : easter_julian) + off;
396 struct holiday *hp = yacc_small ? &sm_holiday[julian]
397 : &holiday[julian];
398
399 holiday_name = yacc_string;
400 while (length-- > 0) {
401 if (julian >= 0 && julian <= 365 && !hp->string) {
402 if (!dup)
403 holiday_name = mystrdup(holiday_name);
404 hp->string = holiday_name;
405 hp->stringcolor = colormap[yacc_stringcolor];
406 hp->daycolor = colormap[yacc_daycolor];
407 hp->dup = dup++;
408 }
409 julian++, hp++;
410 }
411 }
412
413
414 /*
415 * calculate Easter Sunday as a julian date. I got this from Armin Liebl
416 * <liebla@informatik.tu-muenchen.de>, who got it from Knuth. I hope I got
417 * all this right...
418 */
419
calc_easter(int year)420 static int calc_easter(
421 int year) /* Easter in which year? */
422 {
423 int golden, cent, grcor, clcor, extra, epact, easter;
424
425 golden = (year/19)*(-19);
426 golden += year+1;
427 cent = year/100+1;
428 grcor = (cent*3)/(-4)+12;
429 clcor = ((cent-18)/(-25)+cent-16)/3;
430 extra = (year*5)/4+grcor-10;
431 epact = golden*11+20+clcor+grcor;
432 epact += (epact/30)*(-30);
433 if (epact<=0)
434 epact += 30;
435 if (epact==25) {
436 if (golden>11)
437 epact += 1;
438 } else {
439 if (epact==24)
440 epact += 1;
441 }
442 easter = epact*(-1)+44;
443 if (easter<21)
444 easter += 30;
445 extra += easter;
446 extra += (extra/7)*(-7);
447 extra *= -1;
448 easter += extra+7;
449 easter += 31+28+!(year&3)-1;
450 return(easter);
451 }
452
453
454 /*
455 * set a holiday relative to Pascha, which is the Christian Orthodox Easter.
456 * Algorithm provided by Efthimios Mavrogeorgiadis <emav@enl.auth.gr>.
457 * Changed 12.9.99 by Efthimios Mavrogeorgiadis <emav@enl.auth.gr>.
458 */
459
calc_pascha(int year)460 static int calc_pascha(
461 int year) /* Pascha in which year? */
462 {
463 int a = year % 19;
464 int b = (19 * a + 15) % 30;
465 int c = (year + (year - (year % 4))/4 + b) % 7;
466 int d = b - c;
467 int e = d-3 - (2 - (year-(year%100))/100 + (year-(year%400))/400);
468 int f = (e - (e % 31))/31;
469 int day = e - 30 * f;
470 return(31 + 28+!(year&3) + 31 + (f ? 30 : 0) + day-1);
471 }
472
473
474 /*
475 * functions used for [] syntax: (Erwin Hugo Achermann <acherman@inf.ethz.ch>)
476 *
477 * day_from_name (str) gets day from symbolic name
478 * day_from_easter () gets day as easter sunday
479 * day_from_monthday (m, d) gets <day> from <month/day>
480 * day_from_wday (day, wday, num) gets num-th day (wday) after <day> day
481 * monthday_from_day (day, *m, *d, *y) gets month/day/cur_year from <day>
482 */
483
day_from_name(char * str)484 int day_from_name(
485 char *str)
486 {
487 int i;
488 char *name;
489
490 for (i=0; i < 366; i++) {
491 name = holiday[i].string;
492 if (name && !strcmp(str, name))
493 return(i);
494 }
495 return(-1);
496 }
497
498
day_from_easter(void)499 int day_from_easter(void)
500 {
501 return(easter_julian);
502 }
503
504
day_from_monthday(int m,int d)505 int day_from_monthday(
506 int m,
507 int d)
508 {
509 if (m == 13)
510 return(365 + LEAPYEAR(parse_year));
511 return(JULIAN(m - 1, d));
512 }
513
514
monthday_from_day(int day,int * m,int * d,int * y)515 void monthday_from_day(
516 int day,
517 int *m,
518 int *d,
519 int *y)
520 {
521 int i, len;
522
523 *y = parse_year;
524 *m = 0;
525 *d = 0;
526 if (day < 0)
527 return;
528 for (i=0; i < 12; i++) {
529 len = monthlen[i] + (i == 1 && LEAPYEAR(parse_year));
530 if (day < len) {
531 *m = i + 1;
532 *d = day + 1;
533 break;
534 }
535 day -= len;
536 }
537 }
538
539
day_from_wday(int day,int wday,int num)540 int day_from_wday(
541 int day,
542 int wday,
543 int num)
544 {
545 int wkday, yday, weeknum;
546
547 (void)date_to_time(1, 0, parse_year, &wkday, &yday, &weeknum);
548 day += (wday - wkday - day + 1001) % 7;
549 day += num * 7;
550 return (day);
551 }
552
553
554 /*
555 * This function implements the -H option. Its main purpose is for the web
556 * interface. Written by Michel Bourget, extended by thomas@bitrot.de.
557 */
558
julian_to_date(int julian,int year)559 static char *julian_to_date(
560 int julian,
561 int year)
562 {
563 static char str[32];
564 int total, i, d, m;
565 int nbdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
566
567 total=0;
568 for (i=1; i <= 12; i++) {
569 if (i==2 && (year%4) == 0)
570 nbdays[i-1] = 29;
571 total += nbdays[i-1];
572 if (total < julian)
573 continue;
574 m = i;
575 d = julian - total + nbdays[i-1];
576 sprintf(str, "%d.%d.%d", d, m, year+1900);
577 return(str);
578 }
579 return("ERROR");
580 }
581
582
dump_holiday(int year)583 void dump_holiday(
584 int year)
585 {
586 int i;
587
588 parse_holidays(year -= 1900, TRUE);
589 for (i=0; i < 366; i++) {
590 if (holiday[i].string)
591 printf("%d;%s;%s\n", i+1, julian_to_date(i+1, year),
592 holiday[i].string);
593 if (sm_holiday[i].string)
594 printf("%d;%s;%s\n", i+1, julian_to_date(i+1, year),
595 sm_holiday[i].string);
596 }
597 fflush(stdout);
598 }
599