/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include "util.h" enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; enum caltype { JULIAN, GREGORIAN }; enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 }; static struct tm *ltime; static int isleap(size_t year, enum caltype cal) { if (cal == GREGORIAN) { if (year % 400 == 0) return 1; if (year % 100 == 0) return 0; return (year % 4 == 0); } else { /* cal == Julian */ return (year % 4 == 0); } } static int monthlength(size_t year, int month, enum caltype cal) { int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return (month == FEB && isleap(year, cal)) ? 29 : mdays[month]; } /* From http://www.tondering.dk/claus/cal/chrweek.php#calcdow */ static int dayofweek(size_t year, int month, int dom, enum caltype cal) { size_t y; int m, a; a = (13 - month) / 12; y = year - a; m = month + 12 * a - 1; if (cal == GREGORIAN) return (dom + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12) % 7; else /* cal == Julian */ return (5 + dom + y + y / 4 + (31 * m) / 12) % 7; } static void printgrid(size_t year, int month, int fday, int line) { enum caltype cal; int offset, dom, d = 0, trans; /* are we in the transition from Julian to Gregorian? */ int today = 0; cal = (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH)) ? JULIAN : GREGORIAN; trans = (year == TRANS_YEAR && month == TRANS_MONTH); offset = dayofweek(year, month, 1, cal) - fday; if (offset < 0) offset += 7; if (line == 1) { for (; d < offset; ++d) printf(" "); dom = 1; } else { dom = 8 - offset + (line - 2) * 7; if (trans && !(line == 2 && fday == 3)) dom += 11; } if (ltime && year == ltime->tm_year + 1900 && month == ltime->tm_mon) today = ltime->tm_mday; for (; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) { if (dom == today) printf("\x1b[7m%2d\x1b[0m ", dom); /* highlight today's date */ else printf("%2d ", dom); if (trans && dom == TRANS_DAY) dom += 11; } for (; d < 7; ++d) printf(" "); } static void drawcal(size_t year, int month, size_t ncols, size_t nmons, int fday) { char *smon[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; char *days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", }; size_t m, n, col, cur_year, cur_month, dow; int line, pad; char month_year[sizeof("Su Mo Tu We Th Fr Sa")]; for (m = 0; m < nmons; ) { n = m; for (col = 0; m < nmons && col < ncols; ++col, ++m) { cur_year = year + m / 12; cur_month = month + m % 12; if (cur_month > 11) { cur_month -= 12; cur_year += 1; } snprintf(month_year, sizeof(month_year), "%s %zu", smon[cur_month], cur_year); pad = sizeof(month_year) - 1 - strlen(month_year); printf("%*s%s%*s ", pad / 2 + pad % 2, "", month_year, pad / 2, ""); } putchar('\n'); for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { for (dow = fday; dow < (fday + 7); ++dow) printf("%s ", days[dow % 7]); printf(" "); } putchar('\n'); for (line = 1; line <= 6; ++line) { for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { cur_year = year + m / 12; cur_month = month + m % 12; if (cur_month > 11) { cur_month -= 12; cur_year += 1; } printgrid(cur_year, cur_month, fday, line); printf(" "); } putchar('\n'); } } } static void usage(void) { eprintf("usage: %s [-1 | -3 | -y | -n num] " "[-s | -m | -f num] [-c num] [[month] year]\n", argv0); } int main(int argc, char *argv[]) { time_t now; size_t year, ncols, nmons; int fday, month; now = time(NULL); ltime = localtime(&now); year = ltime->tm_year + 1900; month = ltime->tm_mon + 1; fday = 0; if (!isatty(STDOUT_FILENO)) ltime = NULL; /* don't highlight today's date */ ncols = 3; nmons = 0; ARGBEGIN { case '1': nmons = 1; break; case '3': nmons = 3; if (--month == 0) { month = 12; year--; } break; case 'c': ncols = estrtonum(EARGF(usage()), 0, MIN(SIZE_MAX, LLONG_MAX)); break; case 'f': fday = estrtonum(EARGF(usage()), 0, 6); break; case 'm': /* Monday */ fday = 1; break; case 'n': nmons = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); break; case 's': /* Sunday */ fday = 0; break; case 'y': month = 1; nmons = 12; break; default: usage(); } ARGEND if (nmons == 0) { if (argc == 1) { month = 1; nmons = 12; } else { nmons = 1; } } switch (argc) { case 2: month = estrtonum(argv[0], 1, 12); argv++; case 1: /* fallthrough */ year = estrtonum(argv[0], 0, INT_MAX); break; case 0: break; default: usage(); } drawcal(year, month - 1, ncols, nmons, fday); return fshut(stdout, ""); }