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}