You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/libkholidays/parseholiday.y

716 lines
20 KiB

%{
/*
* 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()
{
struct holiday *hp;
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)
{
struct holiday *hp;
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);
}