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