master xplshn/aruu / cmd / posix / cal.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include <limits.h>
  5#include <stdint.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9#include <time.h>
 10#include <unistd.h>
 11
 12#include "util.h"
 13
 14enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
 15enum caltype { JULIAN, GREGORIAN };
 16enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 };
 17
 18static struct tm *ltime;
 19
 20static int
 21isleap(size_t year, enum caltype cal)
 22{
 23	if (cal == GREGORIAN) {
 24		if (year % 400 == 0)
 25			return 1;
 26		if (year % 100 == 0)
 27			return 0;
 28		return (year % 4 == 0);
 29	}
 30	else { /* cal == Julian */
 31		return (year % 4 == 0);
 32	}
 33}
 34
 35static int
 36monthlength(size_t year, int month, enum caltype cal)
 37{
 38	int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 39
 40	return (month == FEB && isleap(year, cal)) ? 29 : mdays[month];
 41}
 42
 43/* From http://www.tondering.dk/claus/cal/chrweek.php#calcdow */
 44static int
 45dayofweek(size_t year, int month, int dom, enum caltype cal)
 46{
 47	size_t y;
 48	int m, a;
 49
 50	a = (13 - month) / 12;
 51	y = year - a;
 52	m = month + 12 * a - 1;
 53
 54	if (cal == GREGORIAN)
 55		return (dom + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12) % 7;
 56	else  /* cal == Julian */
 57		return (5 + dom + y + y / 4 + (31 * m) / 12) % 7;
 58}
 59
 60static void
 61printgrid(size_t year, int month, int fday, int line)
 62{
 63	enum caltype cal;
 64	int offset, dom, d = 0, trans; /* are we in the transition from Julian to Gregorian? */
 65	int today = 0;
 66
 67	cal = (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH)) ? JULIAN : GREGORIAN;
 68	trans = (year == TRANS_YEAR && month == TRANS_MONTH);
 69	offset = dayofweek(year, month, 1, cal) - fday;
 70
 71	if (offset < 0)
 72		offset += 7;
 73	if (line == 1) {
 74		for (; d < offset; ++d)
 75			printf("   ");
 76		dom = 1;
 77	} else {
 78		dom = 8 - offset + (line - 2) * 7;
 79		if (trans && !(line == 2 && fday == 3))
 80			dom += 11;
 81	}
 82	if (ltime && year == (size_t)(ltime->tm_year + 1900) && month == ltime->tm_mon)
 83		today = ltime->tm_mday;
 84	for (; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) {
 85		if (dom == today)
 86			printf("\x1b[7m%2d\x1b[0m ", dom); /* highlight today's date */
 87		else
 88			printf("%2d ", dom);
 89		if (trans && dom == TRANS_DAY)
 90			dom += 11;
 91	}
 92	for (; d < 7; ++d)
 93		printf("   ");
 94}
 95
 96static void
 97drawcal(size_t year, int month, size_t ncols, size_t nmons, int fday)
 98{
 99	char *smon[] = { "January", "February", "March", "April",
100	                 "May", "June", "July", "August",
101	                 "September", "October", "November", "December" };
102	char *days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", };
103	size_t m, n, col, cur_year, cur_month;
104	int line, pad, dow;
105	char month_year[sizeof("Su Mo Tu We Th Fr Sa")];
106
107	for (m = 0; m < nmons; ) {
108		n = m;
109		for (col = 0; m < nmons && col < ncols; ++col, ++m) {
110			cur_year = year + m / 12;
111			cur_month = month + m % 12;
112			if (cur_month > 11) {
113				cur_month -= 12;
114				cur_year += 1;
115			}
116			snprintf(month_year, sizeof(month_year), "%s %zu", smon[cur_month], cur_year);
117			pad = sizeof(month_year) - 1 - strlen(month_year);
118			printf("%*s%s%*s   ", pad / 2 + pad % 2, "", month_year, pad / 2, "");
119		}
120		putchar('\n');
121		for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
122			for (dow = fday; dow < (fday + 7); ++dow)
123				printf("%s ", days[dow % 7]);
124			printf("  ");
125		}
126		putchar('\n');
127		for (line = 1; line <= 6; ++line) {
128			for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
129				cur_year = year + m / 12;
130				cur_month = month + m % 12;
131				if (cur_month > 11) {
132					cur_month -= 12;
133					cur_year += 1;
134				}
135				printgrid(cur_year, cur_month, fday, line);
136				printf("  ");
137			}
138			putchar('\n');
139		}
140	}
141}
142
143static void
144usage(void)
145{
146#ifdef FEATURE_CAL_EXT
147	eprintf("usage: %s [-1 | -3 | -y | -n num] "
148	        "[-s | -m | -f num] [-c num] [[month] year]\n", argv0);
149#else
150	eprintf("usage: %s [-y] [[month] year]\n", argv0);
151#endif
152}
153
154// ?man cal: display a calendar
155// ?man print a formatted calendar of the specified month or year
156int
157main(int argc, char *argv[])
158{
159	time_t now;
160	size_t year, ncols, nmons;
161	int fday, month;
162
163	now   = time(NULL);
164	ltime = localtime(&now);
165	year  = ltime->tm_year + 1900;
166	month = ltime->tm_mon + 1;
167	fday  = 0;
168
169	if (!isatty(STDOUT_FILENO))
170		ltime = NULL; /* don't highlight today's date */
171
172#ifdef FEATURE_CAL_EXT
173	ncols = 3;
174	nmons = 0;
175#else
176	ncols = 1;
177	nmons = 1;
178#endif
179
180	ARGBEGIN {
181#ifdef FEATURE_CAL_EXT
182	// ?man -1: specify option flag
183	case '1':
184		nmons = 1;
185		break;
186	// ?man -3: specify option flag
187	case '3':
188		nmons = 3;
189		if (--month == 0) {
190			month = 12;
191			year--;
192		}
193		break;
194	// ?man -c:num: print count or perform stdout action
195	case 'c':
196		ncols = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
197		break;
198	// ?man -f:num: force the operation
199	case 'f':
200		fday = estrtonum(EARGF(usage()), 0, 6);
201		break;
202	// ?man -m: specify mode or limit
203	case 'm': /* Monday */
204		fday = 1;
205		break;
206	// ?man -n:num: print line numbers or counts
207	case 'n':
208		nmons = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
209		break;
210	// ?man -s: silent mode or print summary
211	case 's': /* Sunday */
212		fday = 0;
213		break;
214#endif
215	// ?man -y: specify option flag
216	case 'y':
217		month = 1;
218		nmons = 12;
219		break;
220	default:
221		usage();
222	} ARGEND
223
224#ifdef FEATURE_CAL_EXT
225	if (nmons == 0) {
226		if (argc == 1) {
227			month = 1;
228			nmons = 12;
229		} else {
230			nmons = 1;
231		}
232	}
233#endif
234
235	switch (argc) {
236	case 2:
237		month = estrtonum(argv[0], 1, 12);
238		argv++;
239		/* fallthrough */
240	case 1:
241		year = estrtonum(argv[0], 0, INT_MAX);
242		break;
243	case 0:
244		break;
245	default:
246		usage();
247	}
248
249	drawcal(year, month - 1, ncols, nmons, fday);
250
251	return fshut(stdout, "<stdout>");
252}