This is a simple implementation of the calendar(1) utility included in some UNIX systems (all BSDs have it, GNU has not). I do not have much experience with <sys/time.h>
and <time.h>
Manual:
CALENDAR(1) General Commands Manual CALENDAR(1)
NAME
calendar - print upcoming events
SYNOPSIS
calendar [-l] [-A num] [-B num] [-t [[yyyy]mm]dd] [file...]
DESCRIPTION
calendar reads files for events, one event per line; and writes to
standard output those events beginning with either today's date or
tomorrow's. On Fridays and Saturdays, events through Monday are
printed. If a hyphen (-) is provided as argument or the argument is
absent, calendar reads from the standard input.
The options are as follows:
-A num Print lines from today and next num days (forward, future).
-B num Print lines from today and previous num days (backward, past).
-l Rather than print the date on the same line of each event, print
the date alone in a line and followed by each event indented
with a tab.
-t[[yyyy]mm]dd
Act like the specified value is “today” instead of using the
current date.
Each event should begin with a date pattern in the format
[[YYYY-[MM]]-DDWW[+N|-N]. The hyphen (-) that separates the values can
be replaced by a slash (/) or a period (.). Several date patterns can
be supplied separated by a comma (,).
YYYY should be any year number. MM should be a month number or a month
name (either complete or abbreviate, such as "April" or "Apr"). DD
should be the number of a day in the month. WW should be the name of a
day in the week (either complete or abbreviate). Either DD or WW (or
both) must be supplied.
The date pattern can be followed by +N or -N to specify the week on the
month (for example Sun+2 is the second Sunday in the month, Mon-3 is
the third from last Monday in the month).
EXAMPLES
Consider the following input.
# holidays
01/01 New Year's day
05/01 Labor Day
07/25 Generic holiday
12/25 Christmas
May/Sun+2 Mother's day
13Fri Friday the 13th
# classes
Mon,Wed Java Class
Tue,Thu Algebra Class
Tue,Thu Operating Systems Class
Tue,Thu Computer Network Class
If today were 09 March 2021, then running calendar with the options -l
and -A7 on this input would print the following:
Sunday 09 May 2021
Mother's day
Monday 10 May 2021
Java Class
Tuesday 11 May 2021
Algebra Class
Computer Network Class
Operating Systems Class
Wednesday 12 May 2021
Java Class
Thursday 13 May 2021
Algebra Class
Computer Network Class
Operating Systems Class
Friday 14 May 2021
Saturday 15 May 2021
SEE ALSO
at(1), cal(1), cron(1), todo(1)
STANDARDS
The calendar program previously selected lines which had the correct
date anywhere in the line. This is no longer true: the date is only
recognized when it occurs at the beginning of a line.
The calendar program previously could interpret only one date for each
event. This is no longer true: each event can occur in more than one
date (see the examples for the classes above).
The calendar program previously could not read events from the standard
input. This is no longer true: this version of calendar is an actual
filter, which can read from the standard input or from named files.
The calendar program previously had an option to send a mail to all
users. This is no longer true: to have your calendar mailed every day,
use cron(8).
HISTORY
A calendar command appeared in Version 7 AT&T UNIX.
CALENDAR(1)
calendar.c:
#include <sys/time.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "calendar.h"
#define SECS_PER_DAY (24 * 60 * 60)
#define DAYS_PER_WEEK 7
#define MIDDAY 12
#define EPOCH 1970
#define isleap(y) ((!((y) % 4) && ((y) % 100)) || !((y) % 400))
static const int days_in_month[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};
/* show usage and exit */
static void
usage(void)
{
(void)fprintf(stderr, "usage: calendar [-d] [-A num] [-B num] -t [yyyymmdd] [file ...]\n");
exit(1);
}
/* call calloc checking for error */
static void *
ecalloc(size_t nmemb, size_t size)
{
void *p;
if ((p = calloc(nmemb, size)) == NULL)
err(1, "malloc");
return p;
}
/* call strdup checking for error */
static char *
estrdup(const char *s)
{
char *t;
if ((t = strdup(s)) == NULL)
err(1, "strdup");
return t;
}
/* convert string value to int */
static int
strtonum(const char *s)
{
long n;
char *ep;
errno = 0;
n = strtol(s, &ep, 10);
if (s[0] == '\0' || *ep != '\0')
goto error;
if ((errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)) || (n > INT_MAX || n < 0))
goto error;
return (int)n;
error:
errno = EINVAL;
err(1, "%s", s);
return -1;
}
/* convert YYYYMMDD to time */
static time_t
strtotime(const char *s)
{
struct tm *tmorig;
struct tm tm;
size_t len;
time_t t;
char *ep;
t = time(NULL);
tmorig = localtime(&t);
tm = *tmorig;
len = strlen(s);
if (len == 2 || len == 1)
ep = strptime(s, "%d", &tm);
else if (len == 4)
ep = strptime(s, "%m%d", &tm);
else
ep = strptime(s, "%Y%m%d", &tm);
if (s[0] == '\0' || ep == NULL || ep[0] != '\0')
goto error;
tm.tm_hour = MIDDAY;
tm.tm_min = 0;
tm.tm_sec = 0;
t = mktime(&tm);
if (t == -1)
goto error;
return t;
error:
errno = EINVAL;
err(1, "%s", s);
return (time_t)-1;
}
/* set today time for 12:00; also set number of days after today */
static void
settoday(time_t *today, int *after)
{
struct tm *tmorig;
struct tm tm;
time_t t;
t = time(NULL);
tmorig = localtime(&t);
tm = *tmorig;
tm.tm_hour = MIDDAY;
tm.tm_min = 0;
tm.tm_sec = 0;
*today = mktime(&tm);
switch (tm.tm_wday) {
case 5:
*after = 3;
break;
case 6:
*after = 2;
break;
default:
*after = 1;
break;
}
}
/* check if c is separator */
static int
isseparator(int c)
{
return c == '-' || c == '.' || c == '/';
}
/* get patterns for event s; also return its name */
static struct Day *
getpatterns(char *s, char **name)
{
struct tm tm;
struct Day *patt, *oldpatt;
struct Day d;
size_t len;
int n;
char *t, *end;
patt = NULL;
for (;;) {
memset(&d, 0, sizeof(d));
while (isspace(*s)) {
s++;
}
n = strtol(s, &end, 10);
if (n > 0 && isseparator(*end)) {
/* got numeric year or month */
d.month = n;
s = end + 1;
n = strtol(s, &end, 10);
if (n > 0 && isseparator(*end)) {
/* got numeric month after year */
d.year = d.month;
d.month = n;
s = end + 1;
} else if ((t = strptime(s, "%b", &tm)) != NULL && isseparator(*t)){
/* got month name after year */
d.year = d.month;
d.month = tm.tm_mon + 1;
s = t + 1;
}
} else if ((t = strptime(s, "%b", &tm)) != NULL && isseparator(*t)) {
/* got month name */
d.month = tm.tm_mon + 1;
s = t + 1;
}
n = strtol(s, &end, 10);
if (n > 0 && *end != '\0') {
/* got month day */
d.monthday = n;
s = end;
}
if ((t = strptime(s, "%a", &tm)) != NULL) {
/* got week day */
d.weekday = tm.tm_wday + 1;
s = t;
}
if (d.monthday == 0 && d.weekday == 0)
break;
n = strtol(s, &end, 10);
if (n >= -5 && n <= 5 && *end != '\0') {
d.monthweek = n;
s = end;
}
oldpatt = patt;
patt = ecalloc(1, sizeof(*patt));
*patt = d;
patt->next = oldpatt;
while (isspace(*s)) {
s++;
}
if (*s == ',') {
s++;
while (isspace(*s)) {
s++;
}
} else {
break;
}
}
*name = s;
len = strlen(*name);
if ((*name)[len - 1] == '\n')
(*name)[len - 1] = '\0';
return patt;
}
/* get events for file fp */
static struct Event *
getevents(FILE *fp, struct Event *evs)
{
struct Event *p;
struct Day *patt;
char buf[BUFSIZ];
char *name;
while (fgets(buf, BUFSIZ, fp) != NULL) {
if ((patt = getpatterns(buf, &name)) != NULL) {
p = ecalloc(1, sizeof(*p));
p->next = evs;
p->days = patt;
p->name = estrdup(name);
evs = p;
}
}
return evs;
}
/* get week of year */
static int
getwofy(int yday, int wday)
{
return (yday + DAYS_PER_WEEK - (wday ? (wday - 1) : (DAYS_PER_WEEK - 1))) / DAYS_PER_WEEK;
}
/* check if event occurs today */
static int
occurstoday(struct Event *ev, struct tm *tm, int thiswofm, int lastwofm)
{
struct Day *d;
for (d = ev->days; d != NULL; d = d->next) {
if ((d->year == 0 || d->year == tm->tm_year + EPOCH) &&
(d->month == 0 || d->month == tm->tm_mon + 1) &&
(d->monthday == 0 || d->monthday == tm->tm_mday) &&
(d->weekday == 0 || d->weekday == tm->tm_wday + 1) &&
(d->monthweek == 0 ||
(d->monthweek < 0 && d->monthweek == -1 * (lastwofm - thiswofm - 1)) ||
(d->monthweek == thiswofm))) {
return 1;
}
}
return 0;
}
/* print events for today and after days */
static void
printevents(struct Event *evs, time_t today, int after, int lflag)
{
struct tm *tmorig;
struct tm tm;
struct Event *ev;
int wofy; /* week of year of first day of month */
int thiswofm; /* this week of current month */
int lastwofm; /* last week of current month */
int n, a, b;
char buf1[BUFSIZ];
char buf2[BUFSIZ];
buf1[0] = buf2[0] = '\0';
while (after-- > 0) {
tmorig = localtime(&today);
tm = *tmorig;
n = days_in_month[isleap(tm.tm_year + EPOCH)][tm.tm_mon + 1];
a = (tm.tm_wday - tm.tm_mday + 1) % DAYS_PER_WEEK;
if (a < 0)
a += DAYS_PER_WEEK;
b = (tm.tm_wday + n - tm.tm_mday + 1) % DAYS_PER_WEEK;
if (b < 0)
b += DAYS_PER_WEEK;
wofy = getwofy(tm.tm_yday - tm.tm_mday + 1, a);
thiswofm = getwofy(tm.tm_yday, tm.tm_wday) - wofy + 1;
lastwofm = getwofy(tm.tm_yday + n - tm.tm_mday + 1, b) - wofy + 1;
if (lflag) {
strftime(buf1, sizeof(buf1), "%A", &tm);
strftime(buf2, sizeof(buf2), "%d %B %Y", &tm);
printf("%-10s %s\n", buf1, buf2);
} else {
strftime(buf1, sizeof(buf1), "%b %d", &tm);
}
for (ev = evs; ev != NULL; ev = ev->next) {
if (occurstoday(ev, &tm, thiswofm, lastwofm)) {
if (lflag) {
printf("\t");
} else {
printf("%-8s ", buf1);
}
printf("%s\n", ev->name);
}
}
today += SECS_PER_DAY;
}
}
/* calendar: print upcoming events */
int
main(int argc, char *argv[])
{
static struct Event *evs;
FILE *fp;
time_t today; /* seconds of 12:00:00 of today since epoch*/
int before; /* number of days before today */
int after; /* number of days after today */
int lflag; /* whether to print in long format */
int exitval;
int ch;
before = lflag = 0;
settoday(&today, &after);
while ((ch = getopt(argc, argv, "A:B:lt:")) != -1) {
switch (ch) {
case 'A':
after = strtonum(optarg);
break;
case 'B':
before = strtonum(optarg);
break;
case 'l':
lflag = 1;
break;
case 't':
today = strtotime(optarg);
break;
default:
usage();
break;
}
}
argc -= optind;
argv += optind;
today -= before * SECS_PER_DAY;
evs = NULL;
exitval = 0;
if (argc == 0) {
evs = getevents(stdin, evs);
} else {
for (; *argv != NULL; argv++) {
if (strcmp(*argv, "-") == 0) {
evs = getevents(stdin, evs);
continue;
}
if ((fp = fopen(*argv, "r")) == NULL) {
warn("%s", *argv);
exitval = 1;
continue;
}
evs = getevents(fp, evs);
fclose(fp);
}
}
printevents(evs, today, after, lflag);
return exitval;
}
calendar.h:
/* day or day pattern */
struct Day {
/*
* This structure express both a day and a day pattern. For
* convenience, let's express a Day entry as YYYY-MM-DD-m-w,
* where:
* - year is YYYY (1 to INT_MAX)
* - month is MM (1 to 12)
* - monthday is DD (1 to 31)
* - monthweek is m (-5 to 5)
* - weekday is w (1-Monday to 7-Sunday)
*
* A day is expressed with all values nonzero. For example,
* 2020-03-11-2-3 represents 11 March 2020, which was a
* Wednesday (3) on the second week of March.
*
* A day pattern can have any value as zero. A zero value
* matches anything. For example:
* - 0000-12-25-0-0 matches 25 December of every year.
* - 0000-05-00-2-7 matches the second Sunday of May.
* - 2020-03-11-2-3 matches 11 March 2020.
*/
struct Day *next;
int year;
int month;
int monthday;
int monthweek;
int weekday;
};
/* event */
struct Event {
struct Event *next;
struct Day *days; /* list of day patterns */
char *name; /* event name */
};