diff options
Diffstat (limited to 'libkholidays/parseholiday.y')
-rw-r--r-- | libkholidays/parseholiday.y | 715 |
1 files changed, 715 insertions, 0 deletions
diff --git a/libkholidays/parseholiday.y b/libkholidays/parseholiday.y new file mode 100644 index 000000000..fd03b11d2 --- /dev/null +++ b/libkholidays/parseholiday.y @@ -0,0 +1,715 @@ +%{ +/* + * deals with the holiday file. A yacc parser is used to parse the file. + * All the holidays of the specified year are calculated at once and stored + * in two arrays that have one entry for each day of the year. The day + * drawing routines just use the julian date to index into these arrays. + * There are two arrays because holidays can be printed either on a full + * line under the day number, or as a small line to the right of the day + * number. It's convenient to have both. + * + * parse_holidays(year, force) read the holiday file and evaluate + * all the holiday definitions for + * <year>. Sets holiday and sm_holiday + * arrays. If force is set, re-eval even + * if year is the same as last time. + * + * Taken from plan by Thomas Driemeyer <thomas@bitrot.de> + * Adapted for use in KOrganizer by Preston Brown <pbrown@kde.org> and + * Reinhold Kainhofer <reinhold@kainhofer.com> + */ + +#include <config.h> + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <stdlib.h> +#include <pwd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> + +#include <limits.h> + +/*** Macro definitions and constants ***/ +/* + * Before you mail and complain that the following macro is incorrect, + * please consider that this is one of the main battlegrounds of the + * Annual Usenet Flame Wars. 2000 is a leap year. Just trust me on this :-) + */ + +#define ISLEAPYEAR(y) !((y)&3) +#define JULIAN(m,d) (monthbegin[m] + (d)-1+((m)>1 && ISLEAPYEAR(parse_year))) +#define LAST 999 +#define ANY 0 +#define BEFORE -1 +#define AFTER -2 +/**** Public forward declarations ****/ +char *parse_holidays(const char *holidays, int year, short force); + +/**** Private forward declarations ****/ +extern int kcallex(void); /* external lexical analyzer */ +static void kcalerror(const char *s); +static time_t date_to_time(int day, int month, int year, + int *wkday, int *julian, int *weeknum); +static time_t tm_to_time(struct tm *tm); +static int day_from_name(char *str); +static int day_from_easter(void); +static int day_from_monthday(int month, int day); +static int day_from_wday(int day, int wday, int num); +static void monthday_from_day(int day, int *m, int *d, int *y); +static int calc_easter(int year); +static int calc_pascha(int year); +static void setliteraldate(int month, int day, int off, int *ddup); +static void seteaster(int off, int length, int pascha); +static void setdate(int month, int day, int year, int off, int conditionaloff, int length); +static void setwday(int num, int wday, int month, int off, int length); +static void setdoff(int wday, int rel, int month, int day, + int year, int off, int length); +/*** Variables and structures ***/ +static int m, d, y; +int kcallineno; /* current line # being parsed */ +FILE *kcalin; /* file currently being processed */ +int yacc_small; /* small string or on its own line? */ +int yacc_stringcolor; /* color of holiday name text, 1..8 */ +char *yacc_string; /* holiday name text */ +int yacc_daycolor; /* color of day number, 1..8 */ +char *progname; /* argv[0] */ +int parse_year = -1; /* year being parsed, 0=1970..99=2069*/ +static const char *filename; /* holiday filename */ +static char errormsg[PATH_MAX+200];/* error message if any, or "" */ +static int easter_julian; /* julian date of Easter Sunday */ +static int pascha_julian; /* julian date of Pascha Sunday */ +static char *holiday_name; /* strdup'd yacc_string */ +short monthlen[12] = { 31, 28, 31, 30, + 31, 30, 31, 31, + 30, 31, 30, 31 }; +short monthbegin[12] = { 0, 31, 59, 90, + 120, 151, 181, + 212, 243, 273, + 304, 334 }; + +/* struct holiday;*/ +struct holiday { + char *string; /* name of holiday, 0=not a holiday */ + int color; + unsigned short dup; /* reference count */ + struct holiday *next; +}; + +struct holiday holidays[366]; /* info for each day, separate for */ +/*struct holiday sm_holiday[366];*/ /* full-line texts under, and small */ + /* texts next to day number */ +static int initialized=0; +%} + +%union { int ival; char *sval; } +%type <ival> color offset conditionaloffset length expr pexpr number month reldate wdaycondition +%token <ival> NUMBER MONTH WDAY COLOR +%token <sval> STRING +%token IN PLUS MINUS SMALL CYEAR LEAPYEAR SHIFT IF +%token LENGTH EASTER EQ NE LE GE LT GT PASCHA + +%left OR +%left AND +%right EQ NE LE GE LT GT +%left '-' '+' +%left '*' '/' '%' +%nonassoc '!' +%nonassoc UMINUS +%left '?' ':' + +%start list + +%% +list : + | list small color STRING color { yacc_stringcolor = $3; + yacc_string = $4; + yacc_daycolor = $5; } + entry { free(yacc_string); } + ; + +small : { yacc_small = 0; } + | SMALL { yacc_small = 1; } + ; + + color : { $$ = 0; } + | COLOR { $$ = $1; } + ; + +entry : EASTER offset length { seteaster($2, $3, 0); } + | PASCHA offset length { seteaster($2, $3, 1); } + | date offset conditionaloffset length { setdate( m, d, y, $2, $3, $4);} + | WDAY offset length { setwday( 0, $1, 0, $2, $3);} + | pexpr WDAY offset length { setwday($1, $2, 0, $3, $4);} + | pexpr WDAY IN month offset length { setwday($1, $2, $4, $5, $6);} + | WDAY pexpr date offset length { setdoff($1, $2,m,d,y,$4,$5);} + ; + + offset : { $$ = 0; } + | PLUS expr { $$ = $2; } + | MINUS expr { $$ = -$2; } + ; + + conditionaloffset : { $$ = 0; } + | SHIFT wdaycondition IF wdaycondition { $$ = ($2<<8) | $4;printf("Shift to %i if %i\n", $2, $4); } + ; + + wdaycondition : { $$ = 0; } + | WDAY { $$ = (1<<$1); } + | WDAY OR wdaycondition { $$ = (1<<$1) | $3; } + ; + + length : { $$ = 1; } + | LENGTH expr { $$ = $2; } + ; + + date : pexpr '.' month { m = $3; d = $1; y = 0; } + | pexpr '.' month '.' { m = $3; d = $1; y = 0; } + | pexpr '.' month '.' expr { m = $3; d = $1; y = $5; } + | month '/' pexpr { m = $1; d = $3; y = 0; } + | month '/' pexpr '/' pexpr { m = $1; d = $3; y = $5; } + | MONTH pexpr { m = $1; d = $2; y = 0; } + | MONTH pexpr pexpr { m = $1; d = $2; y = $3; } + | pexpr MONTH { m = $2; d = $1; y = 0; } + | pexpr MONTH pexpr { m = $2; d = $1; y = $3; } + | pexpr '.' MONTH pexpr { m = $3; d = $1; y = $4; } + | pexpr { monthday_from_day($1, + &m, &d, &y); } + ; + + reldate : STRING { $$ = day_from_name($1); } + | EASTER { $$ = day_from_easter(); } + | pexpr '.' month { $$ = day_from_monthday + ($3, $1); } + | pexpr '.' month '.' { $$ = day_from_monthday + ($3, $1); } + | month '/' pexpr { $$ = day_from_monthday + ($1, $3); } + | pexpr MONTH { $$ = day_from_monthday + ($2, $1); } + | MONTH pexpr { $$ = day_from_monthday + ($1, $2); } + | WDAY pexpr pexpr { $$ = day_from_wday($3, $1, + $2 == -1 ? -1 : 0); } + | pexpr WDAY IN month { int day=day_from_monthday($4,1); + $$ = $1 == 999 + ? day_from_wday(day+1,$2,-1) + : day_from_wday(day,$2,$1-1);} + ; + + month : MONTH | pexpr; + + expr : pexpr { $$ = $1; } + | expr OR expr { $$ = $1 || $3; } + | expr AND expr { $$ = $1 && $3; } + | expr EQ expr { $$ = $1 == $3; } + | expr NE expr { $$ = $1 != $3; } + | expr LE expr { $$ = $1 <= $3; } + | expr GE expr { $$ = $1 >= $3; } + | expr LT expr { $$ = $1 < $3; } + | expr GT expr { $$ = $1 > $3; } + | expr '+' expr { $$ = $1 + $3; } + | expr '-' expr { $$ = $1 - $3; } + | expr '*' expr { $$ = $1 * $3; } + | expr '/' expr { $$ = $3 ? $1 / $3 : 0; } + | expr '%' expr { $$ = $3 ? $1 % $3 : 0; } + | expr '?' expr ':' expr { $$ = $1 ? $3 : $5; } + | '!' expr { $$ = !$2; } + | '[' reldate ']' { $$ = $2; } + ; + + pexpr : '(' expr ')' { $$ = $2; } + | number { $$ = $1; } + ; + + number : NUMBER + | '-' NUMBER %prec UMINUS { $$ = -$2; } + | CYEAR { $$ = parse_year; } + | LEAPYEAR pexpr { $$ = !(($2) & 3); } + ; +%% + +/*** Private Yacc callbacks and helper functions ***/ +static void kcalerror(const char *msg) +{ + fprintf(stderr, "%s: %s in line %d of %s\n", progname, + msg, kcallineno+1, filename); + if (!*errormsg) + snprintf(errormsg,sizeof(errormsg), + "Problem with holiday file %s:\n%.80s in line %d", + filename, msg, kcallineno+1); +} + +static time_t date_to_time(int day, int month, int year, + int *wkday, int *julian, int *weeknum) +{ + struct tm tm; + time_t ttime; + + tm.tm_sec = 0; + tm.tm_min = 0; + tm.tm_hour = 0; + tm.tm_mday = day; + tm.tm_mon = month; + tm.tm_year = year; + ttime = tm_to_time(&tm); + if (wkday) + *wkday = tm.tm_wday; + if (julian) + *julian = tm.tm_yday; + if (weeknum) + *weeknum = 0 + ? tm.tm_yday / 7 + : tm.tm_yday ? ((tm.tm_yday - 1) /7) + 1 : 0; + return(ttime == -1 || day != tm.tm_mday ? 0 : ttime); +} + +static time_t tm_to_time(struct tm *tm) +{ + time_t t; /* return value */ + + t = monthbegin[tm->tm_mon] /* full months */ + + tm->tm_mday-1 /* full days */ + + (!(tm->tm_year & 3) && tm->tm_mon > 1); /* leap day this year*/ + tm->tm_yday = t; + t += 365 * (tm->tm_year - 70) /* full years */ + + (tm->tm_year - 69)/4; /* past leap days */ + tm->tm_wday = (t + 4) % 7; + + t = t*86400 + tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec; + if (tm->tm_mday > monthlen[tm->tm_mon] + + (!(tm->tm_year & 3) && tm->tm_mon == 1)) + return((time_t)-1); + return(t); +} + +/* + * set holiday by weekday (monday..sunday). The expression is + * "every <num>-th <wday> of <month> plus <off> days". num and month + * can be ANY or LAST. + */ + +static void setwday(int num, int wday, int month, int off, int length) +{ + int min_month = 0, max_month = 11; + int min_num = 0, max_num = 4; + int mn, n, dy, l, mlen, wday1; + int ddup = 0; + + if (month != ANY) + min_month = max_month = month-1; + if (month == LAST) + min_month = max_month = 11; + if (num != ANY) + min_num = max_num = num-1; + + holiday_name = yacc_string; + for (mn=min_month; mn <= max_month; mn++) { + (void)date_to_time(1, mn, parse_year, &wday1, 0, 0); + dy = (wday-1 - (wday1-1) +7) % 7 + 1; + mlen = monthlen[mn] + (mn==1 && ISLEAPYEAR(parse_year)); + if (num == LAST) + for (l=0; l < length; l++) + setliteraldate(mn, dy+28<=mlen ? dy+28 : dy+21, + off+l, &ddup); + else + for (dy+=min_num*7, n=min_num; n <= max_num; n++, dy+=7) + if (dy >= 1 && dy <= mlen) + for (l=0; l < length; l++) + setliteraldate(mn,dy,off+l,&ddup); + } +} + +/* + * set holiday by weekday (monday..sunday) date offset. The expression is + * "every <wday> before/after <date> plus <off> days". + * (This routine contributed by Peter Littlefield <plittle@sofkin.ca>) + */ + +static void setdoff(int wday, int rel, int month, int day, + int year, int off, int length) +{ + int min_month = 0, max_month = 11; + int min_day = 1, max_day = 31; + int mn, dy, nd, l, wday1; + int ddup = 0; + + if (year != ANY) { + year %= 100; + if (year < 70) year += 100; + if (year != parse_year) + return; + } + if (month != ANY) + min_month = max_month = month-1; + if (month == LAST) + min_month = max_month = 11; + if (day != ANY) + min_day = max_day = day; + + holiday_name = yacc_string; + for (mn=min_month; mn <= max_month; mn++) + if (day == LAST) { + (void)date_to_time(monthlen[mn], mn, parse_year, + &wday1, 0, 0); + nd = (((wday - wday1 + 7) % 7) - + ((rel == BEFORE) ? 7 : 0)) % 7; + for (l=0; l < length; l++) + setliteraldate(mn,monthlen[mn]+nd, off+l, &ddup); + } else + for (dy=min_day; dy <= max_day; dy++) { + (void)date_to_time(dy, mn, parse_year, + &wday1, 0, 0); + nd = (((wday - wday1 + 7) % 7) - + ((rel == BEFORE) ? 7 : 0)) % 7; + for (l=0; l < length; l++) + setliteraldate(mn, dy+nd, off+l, &ddup); + } +} + +static int conditionalOffset( int day, int month, int year, int cond ) +{ + int off = 0; + int wday = 0; + (void)date_to_time( day, month, year, &wday, 0, 0); + if ( wday == 0 ) { wday = 7; } /* sunday is 7, not 0 */ + if ( cond & (1<<wday) ) { + /* condition matches -> higher 8 bits contain the possible days to shift to */ + int to = (cond >> 8); + while ( !(to & (1<<((wday+off)%7))) && (off < 8) ) { + ++off; + } + } + if ( off >= 8 ) return 0; + else return off; +} + +/* + * set holiday by date. Ignore holidays in the wrong year. The code is + * complicated by expressions such as "any/last/any" (every last day of + * the month). + */ + +static void setdate(int month, int day, int year, int off, int conditionaloff, int length) +{ + int min_month = 0, max_month = 11; + int min_day = 1, max_day = 31; + int mn, dy, l; + int ddup = 0; + + if (year != ANY) { + year %= 100; + if (year < 70) year += 100; + if (year != parse_year) + return; + } + if (month != ANY) + min_month = max_month = month-1; + if (month == LAST) + min_month = max_month = 11; + if (day != ANY) + min_day = max_day = day; + + holiday_name = yacc_string; + /** TODO: Include the conditionaloff variable. */ + /** The encoding of the conditional offset is: + 8 lower bits: conditions to shift (bit-register, bit 1=mon, ..., bit 7=sun) + 8 higher bits: weekday to shift to (bit-register, bit 1=mon, ..., bit 7=sun) + */ + for (mn=min_month; mn <= max_month; mn++) { + if (day == LAST) { + int newoff = off + conditionalOffset( monthlen[mn], mn, parse_year, conditionaloff ); + for (l=0; l < length; l++) + setliteraldate(mn, monthlen[mn], newoff+l, &ddup); + } else { + for (dy=min_day; dy <= max_day; dy++) { + int newoff = off + conditionalOffset( dy, mn, parse_year, conditionaloff ); + for (l=0; l < length; l++) + setliteraldate(mn, dy, newoff+l, &ddup); + } + } + } +} + + +/* + * After the two routines above have removed ambiguities (ANY) and resolved + * weekday specifications, this routine registers the holiday in the holiday + * array. There are two of these, for full-line holidays (they take away one + * appointment line in the month calendar daybox) and "small" holidays, which + * appear next to the day number. If the day is already some other holiday, + * add a new item to the singly-linked list and insert the holiday there. + * <ddup> is information stored for parse_holidays(), it + * will free() the holiday name only if its dup field is 0 (because many + * string fields can point to the same string, which was allocated only once + * by the lexer, and should therefore only be freed once). + */ + +static void setliteraldate(int month, int day, int off, int *ddup) +{ + int julian = JULIAN(month, day) + off; + /* struct holiday *hp = yacc_small ? &sm_holiday[julian] + : &holiday[julian]; */ + struct holiday *hp = 0; + + if (julian >= 0 && julian <= 365 ) { + hp = &holidays[julian]; + if ( hp->string ) { + while (hp->next) { hp = hp->next; } + hp->next = malloc( sizeof(struct holiday)*2 ); + hp = hp->next; + hp->next = 0; + } + if (!*ddup) + holiday_name = strdup(holiday_name); + hp->string = holiday_name; + hp->color = (yacc_stringcolor == 0) ? yacc_daycolor : yacc_stringcolor; + hp->dup = (*ddup)++; + + } +} + + +/* + * set a holiday relative to Easter + */ + +static void seteaster(int off, int length, int pascha /*0=Easter, 1=Pascha*/) +{ + int ddup = 0; /* flag for later free() */ + int julian = (pascha ? pascha_julian : easter_julian) + off; + /* struct holiday *hp = yacc_small ? &sm_holiday[julian] + : &holidays[julian];*/ + struct holiday *hp = 0; + + holiday_name = yacc_string; + while (length-- > 0) { + if (julian >= 0 && julian <= 365 ) { + hp = &holidays[julian]; + if ( hp->string ) { + while (hp->next) { hp = hp->next; } + hp->next = malloc( sizeof(struct holiday)*2 ); + hp = hp->next; + hp->next = 0; + } + if (!ddup) + holiday_name = strdup(holiday_name); + hp->string = holiday_name; + hp->color = (yacc_stringcolor == 0) ? yacc_daycolor : yacc_stringcolor; + hp->dup = ddup++; + } + julian++; + } +} + + +/* + * calculate Easter Sunday as a julian date. I got this from Armin Liebl + * <liebla@informatik.tu-muenchen.de>, who got it from Knuth. I hope I got + * all this right... + */ + +static int calc_easter(int year) +{ + int golden, cent, grcor, clcor, extra, epact, easter; + + golden = (year/19)*(-19); + golden += year+1; + cent = year/100+1; + grcor = (cent*3)/(-4)+12; + clcor = ((cent-18)/(-25)+cent-16)/3; + extra = (year*5)/4+grcor-10; + epact = golden*11+20+clcor+grcor; + epact += (epact/30)*(-30); + if (epact<=0) + epact += 30; + if (epact==25) { + if (golden>11) + epact += 1; + } else { + if (epact==24) + epact += 1; + } + easter = epact*(-1)+44; + if (easter<21) + easter += 30; + extra += easter; + extra += (extra/7)*(-7); + extra *= -1; + easter += extra+7; + easter += 31+28+!(year&3)-1; + return(easter); +} + + +/* + * set a holiday relative to Pascha, which is the Christian Orthodox Easter. + * Algorithm provided by Efthimios Mavrogeorgiadis <emav@enl.auth.gr>. + * Changed 12.9.99 by Efthimios Mavrogeorgiadis <emav@enl.auth.gr>. + */ + +static int calc_pascha(int year) /* Pascha in which year? */ +{ + int a = year % 19; + int b = (19 * a + 15) % 30; + int c = (year + (year - (year % 4))/4 + b) % 7; + int dd = b - c; + int e = dd-3 - (2 - (year-(year%100))/100 + (year-(year%400))/400); + int f = (e - (e % 31))/31; + int day = e - 30 * f; + return(31 + 28+!(year&3) + 31 + (f ? 30 : 0) + day-1); +} + + +/* + * functions used for [] syntax: (Erwin Hugo Achermann <acherman@inf.ethz.ch>) + * + * day_from_name (str) gets day from symbolic name + * day_from_easter () gets day as easter sunday + * day_from_monthday (month, day) gets <day> from <month/day> + * day_from_wday (day, wday, num) gets num-th day (wday) after <day> day + * monthday_from_day (day, *m, *d, *y) gets month/day/cur_year from <day> + */ + +static int day_from_name(char *str) +{ + int i; + char *name; + + for (i=0; i < 366; i++) { + name = holidays[i].string; + if (name && !strcmp(str, name)) + return(i); + } + return(-1); +} + + +static int day_from_easter(void) +{ + return(easter_julian); +} + + +static int day_from_monthday(int month, int day) +{ + if (month == 13) + return(365 + ISLEAPYEAR(parse_year)); + return(JULIAN(month - 1, day)); +} + + +static void monthday_from_day(int day, int *mn, int *dy, int *yr) +{ + int i, len; + + *yr = parse_year; + *mn = 0; + *dy = 0; + if (day < 0) + return; + for (i=0; i < 12; i++) { + len = monthlen[i] + (i == 1 && ISLEAPYEAR(parse_year)); + if (day < len) { + *mn = i + 1; + *dy = day + 1; + break; + } + day -= len; + } +} + + +static int day_from_wday(int day, int wday, int num) +{ + int wkday, yday, weeknum; + + (void)date_to_time(1, 0, parse_year, &wkday, &yday, &weeknum); + day += (wday - wkday - day + 1001) % 7; + day += num * 7; + return (day); +} + +static void initialize() +{ + register struct holiday *hp; + register int dy; + initialized = 1; + for (hp=holidays, dy=0; dy < 366; dy++, hp++) + { + hp->color = 0; + hp->dup = 0; + hp->string = 0; + hp->next = 0; + } +} + +/*** Public Functions ***/ +/* + * parse the holiday text file, and set up the holiday arrays for a year. + * If year is -1, re-parse the last year parsed (this is used when the + * holiday file changes). If there is a CPP_PATH, check if the executable + * really exists, and if so, pipe the holioday files through it. + * Return an error message if an error occurred, 0 otherwise. + */ + +char *parse_holidays(const char *holidayfile, int year, short force) +{ + register struct holiday *hp; + register int dy; + short piped = 0; + if (!initialized) + initialize(); + + if (year == parse_year && !force) + return(0); + if (year < 0) + year = parse_year; + parse_year = year; + easter_julian = calc_easter(year + 1900); + pascha_julian = calc_pascha(year + 1900); + + for (hp=holidays, dy=0; dy < 366; dy++, hp++) + { + hp->color = 0; + if (hp->string) { + if (!hp->dup ) + free(hp->string); + hp->string = 0; + } + { + struct holiday *nx = hp->next; + hp->next = 0; + while (nx) { + struct holiday *nxtmp; + if ( nx->string && !nx->dup ) { + free( nx->string ); + } + nxtmp=nx; + nx = nxtmp->next; + free( nxtmp ); + } + } + } + /* for (hp=sm_holiday, d=0; d < 366; d++, hp++) + if (hp->string) { + if (!hp->dup) + free(hp->string); + hp->string = 0; + }*/ + + filename = holidayfile; + if (access(filename, R_OK)) return(0); + kcalin = fopen(filename, "r"); + if (!kcalin) return(0); + *errormsg = 0; + kcallineno = 0; + kcalparse(); + if (piped) pclose(kcalin); + else fclose(kcalin); + if (*errormsg) return(errormsg); + + return(0); +} |