calendar

BSD calendar reimplementation
git clone https://git.ce9e.org/calendar.git

commit
132581f1b3d4d9d84707da59febef424e7984e16
parent
0c16ddeae8b3fb885aeaac878d2a36c48f35ada0
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2018-11-18 18:28
add mostly compatible c implementation

Diffstat

A calendar.c 400 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1 files changed, 400 insertions, 0 deletions


diff --git a/calendar.c b/calendar.c

@@ -0,0 +1,400 @@
   -1     1 /**
   -1     2  * BSD calendar reimplementation
   -1     3  *
   -1     4  * The calendar utility reads events from plain text files and outputs today's
   -1     5  * events. The files look something like this::
   -1     6  *
   -1     7  *     #include <holiday>
   -1     8  *     #include <birthday>
   -1     9  *
   -1    10  *     06/15	June 15
   -1    11  *     Thu	Every Thursday.
   -1    12  *     15	15th of every month.
   -1    13  *
   -1    14  *     05/Sun+2	second Sunday in May (Muttertag)
   -1    15  *     04/Sun-1	last Sunday in April
   -1    16  *
   -1    17  *     Easter-2	Good Friday (2 days before Easter)
   -1    18  *     Paskha	Orthodox Easter
   -1    19  *
   -1    20  * This implementation adds some new features while dropping others, most
   -1    21  * notably, the date formats are not completely compatible::
   -1    22  *
   -1    23  *     # added formats
   -1    24  *     1999/06/15	June 15 1999
   -1    25  *     1999/06/15*	June 15 (usefull for birthdays)
   -1    26  *     1999/05/Sun+2	second Sunday in May 1999
   -1    27  *     1999/06/15+2	June 15 1999 and every second week since then
   -1    28  *
   -1    29  *     # removed formats (mostly alternative formats)
   -1    30  *     Jun. 15	06/15
   -1    31  *     15 June	06/15
   -1    32  *     Thursday	Thu
   -1    33  *     June	06/01
   -1    34  *     15 *	15
   -1    35  *
   -1    36  *     May Sun+2	05/Sun+2
   -1    37  *     04/SunLast	04/Sun-1
   -1    38  */
   -1    39 
   -1    40 #define _XOPEN_SOURCE 500
   -1    41 
   -1    42 #include <stdio.h>
   -1    43 #include <stdlib.h>
   -1    44 #include <stdbool.h>
   -1    45 #include <libgen.h>
   -1    46 #include <string.h>
   -1    47 #include <time.h>
   -1    48 #include <unistd.h>
   -1    49 
   -1    50 struct tpl {
   -1    51 	int year;
   -1    52 	int month;
   -1    53 	int day;
   -1    54 	int repeat;
   -1    55 	int weekday;  /* starting with sunday */
   -1    56 	int nth_of_month;
   -1    57 	int from_easter;
   -1    58 	int from_paskha;
   -1    59 };
   -1    60 
   -1    61 struct line {
   -1    62 	struct line *next;
   -1    63 	struct tpl tpl;
   -1    64 	char *desc;
   -1    65 };
   -1    66 
   -1    67 struct tm easter(int year, bool paskha) {
   -1    68 	/* https://de.wikipedia.org/wiki/Gau%C3%9Fsche_Osterformel#Eine_erg.C3.A4nzte_Osterformel */
   -1    69 	int k = year / 100;
   -1    70 	int s = 0;
   -1    71 	int m = 15;
   -1    72 	if (!paskha) {
   -1    73 		s = 2 - (3 * k + 3) / 4;
   -1    74 		m = 17 - s - (8 * k + 13) / 25;
   -1    75 	}
   -1    76 	int a = year % 19;
   -1    77 	int d = (19 * a + m) % 30;
   -1    78 	int r = (d + a / 11) / 29;
   -1    79 	int og = 21 + d - r;
   -1    80 	int sz = 7 - (year + year / 4 + s) % 7;
   -1    81 	int oe = 7 - (og - sz) % 7;
   -1    82 	int os = og + oe;
   -1    83 
   -1    84 	if (paskha) {
   -1    85 		os += k - year / 400 - 2;
   -1    86 	}
   -1    87 
   -1    88 	struct tm t = {
   -1    89 		.tm_year=year - 1900,
   -1    90 		.tm_mon=2,
   -1    91 		.tm_mday=os,
   -1    92 	};
   -1    93 	mktime(&t);
   -1    94 
   -1    95 	return t;
   -1    96 }
   -1    97 
   -1    98 struct tm add_days(struct tm date, int days) {
   -1    99 	struct tm d = {
   -1   100 		.tm_year=date.tm_year,
   -1   101 		.tm_mon=date.tm_mon,
   -1   102 		.tm_mday=date.tm_mday + days,
   -1   103 	};
   -1   104 	mktime(&d);
   -1   105 	return d;
   -1   106 }
   -1   107 
   -1   108 bool date_comp(struct tm a, struct tm b) {
   -1   109 	return \
   -1   110 		a.tm_year == b.tm_year && \
   -1   111 		a.tm_mon == b.tm_mon && \
   -1   112 		a.tm_mday == b.tm_mday;
   -1   113 }
   -1   114 
   -1   115 bool is_match(struct tpl tpl, struct tm date) {
   -1   116 	if (tpl.repeat) {
   -1   117 		if (tpl.day && tpl.month && tpl.year) {
   -1   118 			struct tm reference = {
   -1   119 				.tm_year=tpl.year - 1900,
   -1   120 				.tm_mon=tpl.month - 1,
   -1   121 				.tm_mday=tpl.day,
   -1   122 			};
   -1   123 			time_t delta = difftime(mktime(&reference), mktime(&date));
   -1   124 			int delta_days = delta / 60 / 60 / 24;
   -1   125 			if (delta_days % (7 * tpl.repeat) != 0) {
   -1   126 				return false;
   -1   127 			}
   -1   128 		} else {
   -1   129 			fprintf(stderr, "Error: repeat used without day, month and year\n");
   -1   130 			exit(1);
   -1   131 		}
   -1   132 	} else {
   -1   133 		if (tpl.day && date.tm_mday != tpl.day) {
   -1   134 			return false;
   -1   135 		} if (tpl.month && date.tm_mon != tpl.month - 1) {
   -1   136 			return false;
   -1   137 		} if (tpl.year && date.tm_year != tpl.year - 1900) {
   -1   138 			return false;
   -1   139 		}
   -1   140 	}
   -1   141 
   -1   142 	if (tpl.weekday && date.tm_wday != tpl.weekday - 1) {
   -1   143 		return false;
   -1   144 	}
   -1   145 
   -1   146 	if (tpl.nth_of_month) {
   -1   147 		int a = tpl.nth_of_month;
   -1   148 		struct tm outside = add_days(date, -a * 7);
   -1   149 		if (outside.tm_mon == date.tm_mon) {
   -1   150 			return false;
   -1   151 		}
   -1   152 
   -1   153 		int b = a < 0 ? a + 1 : a - 1;
   -1   154 		struct tm inside = add_days(date, -b * 7);
   -1   155 		if (inside.tm_mon != date.tm_mon) {
   -1   156 			return false;
   -1   157 		}
   -1   158 	}
   -1   159 
   -1   160 	if (tpl.from_easter) {
   -1   161 		struct tm d = add_days(date, -tpl.from_easter);
   -1   162 		if (!date_comp(easter(d.tm_year, false), d)) {
   -1   163 			return false;
   -1   164 		}
   -1   165 	}
   -1   166 
   -1   167 	if (tpl.from_paskha) {
   -1   168 		struct tm d = add_days(date, -tpl.from_paskha);
   -1   169 		if (!date_comp(easter(d.tm_year, true), d)) {
   -1   170 			return false;
   -1   171 		}
   -1   172 	}
   -1   173 
   -1   174 	return true;
   -1   175 }
   -1   176 
   -1   177 struct tpl parse_date(char *s) {
   -1   178 	time_t t = time(NULL);
   -1   179 	struct tm *TODAY = localtime(&t);
   -1   180 
   -1   181 	struct tpl tpl = {
   -1   182 		.repeat=0,
   -1   183 		.day=0,
   -1   184 		.month=0,
   -1   185 		.year=0,
   -1   186 		.weekday=0,
   -1   187 		.nth_of_month=0,
   -1   188 		.from_easter=0,
   -1   189 		.from_paskha=0,
   -1   190 	};
   -1   191 
   -1   192 	int n = 0;
   -1   193 	if (strchr(s, '+')) {
   -1   194 		strtok(s, "+");
   -1   195 		n = atoi(strtok(NULL, ""));
   -1   196 	} else if (strchr(s, '-')) {
   -1   197 		strtok(s, "-");
   -1   198 		n = atoi(strtok(NULL, ""));
   -1   199 	}
   -1   200 
   -1   201 	/* easter */
   -1   202 	if (strcmp(s, "Easter") == 0) {
   -1   203 		tpl.from_easter = n;
   -1   204 		return tpl;
   -1   205 	}
   -1   206 	if (strcmp(s, "Paskha") == 0) {
   -1   207 		tpl.from_paskha = n;
   -1   208 		return tpl;
   -1   209 	}
   -1   210 
   -1   211 	bool star = false;
   -1   212 	if (strcmp(s + strlen(s) - strlen("*"), "*") == 0) {
   -1   213 		strcpy(s + strlen(s) - strlen("*"), "");
   -1   214 		star = true;
   -1   215 	}
   -1   216 
   -1   217 	struct tm match;
   -1   218 
   -1   219 	/* weekday */
   -1   220 	if (strptime(s, "%Y/%m/%a", &match)) {
   -1   221 		tpl.month = match.tm_mon + 1;
   -1   222 		tpl.weekday = match.tm_wday + 1;
   -1   223 		if (!star) {
   -1   224 			tpl.year = match.tm_mday + 2000;
   -1   225 		}
   -1   226 		tpl.nth_of_month = n;
   -1   227 		return tpl;
   -1   228 	} else if (strptime(s, "%m/%a", &match)) {
   -1   229 		tpl.month = match.tm_mon + 1;
   -1   230 		tpl.weekday = match.tm_wday + 1;
   -1   231 		if (!star) {
   -1   232 			tpl.year = TODAY->tm_mday + 2000;
   -1   233 		}
   -1   234 		tpl.nth_of_month = n;
   -1   235 		return tpl;
   -1   236 	} else if (strptime(s, "%a", &match)) {
   -1   237 		tpl.weekday = match.tm_wday + 1;
   -1   238 		if (!star) {
   -1   239 			tpl.year = TODAY->tm_mday + 2000;
   -1   240 		}
   -1   241 		tpl.nth_of_month = n;
   -1   242 		return tpl;
   -1   243 	}
   -1   244 
   -1   245 	/* date */
   -1   246 	if (strptime(s, "%Y/%m/%d", &match)) {
   -1   247 		tpl.month = match.tm_mon + 1;
   -1   248 		tpl.day = match.tm_mday;
   -1   249 		if (!star) {
   -1   250 			tpl.year = match.tm_mday + 2000;
   -1   251 		}
   -1   252 		tpl.repeat = n;
   -1   253 		return tpl;
   -1   254 	} else if (strptime(s, "%m/%d", &match)) {
   -1   255 		tpl.month = match.tm_mon + 1;
   -1   256 		tpl.day = match.tm_mday;
   -1   257 		if (!star) {
   -1   258 			tpl.year = TODAY->tm_mday + 2000;
   -1   259 			if (match.tm_mon < TODAY->tm_mon) {
   -1   260 				tpl.year += 1;
   -1   261 			}
   -1   262 		}
   -1   263 		return tpl;
   -1   264 	} else if (strptime(s, "%d", &match)) {
   -1   265 		tpl.day = match.tm_mday;
   -1   266 		if (!star) {
   -1   267 			tpl.year = TODAY->tm_mday + 2000;
   -1   268 		}
   -1   269 		return tpl;
   -1   270 	}
   -1   271 
   -1   272 	fprintf(stderr, "Error: Invalid date template: %s\n", s);
   -1   273 	exit(1);
   -1   274 }
   -1   275 
   -1   276 struct line *parse_line(char *s_line) {
   -1   277 	char *s_date = strtok(s_line, "\t");
   -1   278 	const char *desc = strtok(NULL, "\n");
   -1   279 	struct line *line = (struct line *)malloc(sizeof(struct line));
   -1   280 	line->tpl = parse_date(s_date);
   -1   281 	line->desc = strdup(desc);
   -1   282 	line->next = NULL;
   -1   283 	return line;
   -1   284 }
   -1   285 
   -1   286 char *get_cmd(const char *path) {
   -1   287 	char *cmd_tpl = "cpp -P -traditional -I %s -I %s -I %s -I %s %s";
   -1   288 	char *tmp = strdup(path);
   -1   289 	char *dir = dirname(tmp);
   -1   290 
   -1   291 	int length = snprintf(NULL, 0, cmd_tpl, dir, "~/.calendar", "/etc/calendar", "/usr/share/calendar", path);
   -1   292 	char* cmd = malloc(length + 1);
   -1   293 	snprintf(cmd, length + 1, cmd_tpl, dir, "~/.calendar", "/etc/calendar", "/usr/share/calendar", path);
   -1   294 	free(tmp);
   -1   295 
   -1   296 	return cmd;
   -1   297 }
   -1   298 
   -1   299 struct line *get_lines(char *path) {
   -1   300 	char s_line[265];
   -1   301 	struct line *first = NULL;
   -1   302 	struct line *line = NULL;
   -1   303 
   -1   304 	char *cmd = get_cmd(path);
   -1   305 	FILE *fp = popen(cmd, "r");
   -1   306 	free(cmd);
   -1   307 	while (fgets(s_line, sizeof(s_line) - 1, fp) != NULL) {
   -1   308 		if (strlen(s_line) > 1) {
   -1   309 			if (!first) {
   -1   310 				line = parse_line(s_line);
   -1   311 				first = line;
   -1   312 			} else {
   -1   313 				line->next = parse_line(s_line);
   -1   314 				line = line->next;
   -1   315 			}
   -1   316 		}
   -1   317 	}
   -1   318 	pclose(fp);
   -1   319 
   -1   320 	return first;
   -1   321 }
   -1   322 
   -1   323 void free_lines(struct line *line) {
   -1   324 	if (line) {
   -1   325 		free_lines(line->next);
   -1   326 		free(line->desc);
   -1   327 		free(line);
   -1   328 	}
   -1   329 }
   -1   330 
   -1   331 void print_matches(struct tm date, struct line *first) {
   -1   332 	char ds[11];
   -1   333 	strftime(ds, 11, "%a %b %d", &date);
   -1   334 
   -1   335 #define byday
   -1   336 #ifdef byday
   -1   337 	printf("%s\t", ds);
   -1   338 	struct line *line = first;
   -1   339 	bool is_first = true;
   -1   340 	while (line) {
   -1   341 		if (is_match(line->tpl, date)) {
   -1   342 			if (!is_first) printf("; ");
   -1   343 			is_first = false;
   -1   344 			printf(line->desc);
   -1   345 		}
   -1   346 		line = line->next;
   -1   347 	}
   -1   348 	printf("\n");
   -1   349 #else
   -1   350 	struct line *line = first;
   -1   351 	while (line) {
   -1   352 		if (is_match(line->tpl, date)) {
   -1   353 			printf(ds);
   -1   354 			if (!line->tpl.year) {
   -1   355 				printf("*");
   -1   356 			}
   -1   357 			printf("\t%s\n", line->desc);
   -1   358 		}
   -1   359 		line = line->next;
   -1   360 	}
   -1   361 #endif
   -1   362 }
   -1   363 
   -1   364 void help() {
   -1   365 	printf(
   -1   366 		"BSD calendar reimplementation\n"
   -1   367 		"usage: calendar [-h] [-A DAYS_AFTER]\n"
   -1   368 	);
   -1   369 }
   -1   370 
   -1   371 int main(int argc, char *argv[]) {
   -1   372 	int days = 3;
   -1   373 
   -1   374 	int c;
   -1   375 	while ((c = getopt(argc, argv, "hA:")) != -1) {
   -1   376 		switch (c) {
   -1   377 			case 'h':
   -1   378 				help();
   -1   379 				return 0;
   -1   380 			case 'A':
   -1   381 				days = 1 + atoi(optarg);
   -1   382 				break;
   -1   383 			case '?':
   -1   384 				exit(EXIT_FAILURE);
   -1   385 		}
   -1   386 	}
   -1   387 
   -1   388 	struct line *first = get_lines("~/DATES");
   -1   389 
   -1   390 	time_t t = time(NULL);
   -1   391 	struct tm *today = localtime(&t);
   -1   392 
   -1   393 	struct tm day;
   -1   394 	for (int i = 0; i < days; i++) {
   -1   395 		day = add_days(*today, i);
   -1   396 		print_matches(day, first);
   -1   397 	}
   -1   398 
   -1   399 	free_lines(first);
   -1   400 }