- 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 }