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