xref: /original-bsd/usr.bin/cal/cal.c (revision 7e5c8007)
1 /*
2  * Copyright (c) 1989, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kim Letkeman.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 static char copyright[] =
13 "@(#) Copyright (c) 1989, 1993, 1994\n\
14 	The Regents of the University of California.  All rights reserved.\n";
15 #endif /* not lint */
16 
17 #ifndef lint
18 static char sccsid[] = "@(#)cal.c	8.4 (Berkeley) 04/02/94";
19 #endif /* not lint */
20 
21 #include <sys/types.h>
22 
23 #include <ctype.h>
24 #include <err.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #define	THURSDAY		4		/* for reformation */
32 #define	SATURDAY 		6		/* 1 Jan 1 was a Saturday */
33 
34 #define	FIRST_MISSING_DAY 	639787		/* 3 Sep 1752 */
35 #define	NUMBER_MISSING_DAYS 	11		/* 11 day correction */
36 
37 #define	MAXDAYS			42		/* max slots in a month array */
38 #define	SPACE			-1		/* used in day array */
39 
40 static int days_in_month[2][13] = {
41 	{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
42 	{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
43 };
44 
45 int sep1752[MAXDAYS] = {
46 	SPACE,	SPACE,	1,	2,	14,	15,	16,
47 	17,	18,	19,	20,	21,	22,	23,
48 	24,	25,	26,	27,	28,	29,	30,
49 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
50 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
51 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
52 }, j_sep1752[MAXDAYS] = {
53 	SPACE,	SPACE,	245,	246,	258,	259,	260,
54 	261,	262,	263,	264,	265,	266,	267,
55 	268,	269,	270,	271,	272,	273,	274,
56 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
57 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
58 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
59 }, empty[MAXDAYS] = {
60 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
61 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
62 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
63 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
64 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
65 	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,	SPACE,
66 };
67 
68 char *month_names[12] = {
69 	"January", "February", "March", "April", "May", "June",
70 	"July", "August", "September", "October", "November", "December",
71 };
72 
73 char *day_headings = " S  M Tu  W Th  F  S";
74 char *j_day_headings = "  S   M  Tu   W  Th   F   S";
75 
76 /* leap year -- account for gregorian reformation in 1752 */
77 #define	leap_year(yr) \
78 	((yr) <= 1752 ? !((yr) % 4) : \
79 	!((yr) % 4) && ((yr) % 100) || !((yr) % 400))
80 
81 /* number of centuries since 1700, not inclusive */
82 #define	centuries_since_1700(yr) \
83 	((yr) > 1700 ? (yr) / 100 - 17 : 0)
84 
85 /* number of centuries since 1700 whose modulo of 400 is 0 */
86 #define	quad_centuries_since_1700(yr) \
87 	((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
88 
89 /* number of leap years between year 1 and this year, not inclusive */
90 #define	leap_years_since_year_1(yr) \
91 	((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
92 
93 int julian;
94 
95 void	ascii_day __P((char *, int));
96 void	center __P((char *, int, int));
97 void	day_array __P((int, int, int *));
98 int	day_in_week __P((int, int, int));
99 int	day_in_year __P((int, int, int));
100 void	j_yearly __P((int));
101 void	monthly __P((int, int));
102 void	trim_trailing_spaces __P((char *));
103 void	usage __P((void));
104 void	yearly __P((int));
105 
106 int
107 main(argc, argv)
108 	int argc;
109 	char **argv;
110 {
111 	struct tm *local_time;
112 	time_t now;
113 	int ch, month, year, yflag;
114 
115 	yflag = 0;
116 	while ((ch = getopt(argc, argv, "jy")) != EOF)
117 		switch(ch) {
118 		case 'j':
119 			julian = 1;
120 			break;
121 		case 'y':
122 			yflag = 1;
123 			break;
124 		case '?':
125 		default:
126 			usage();
127 		}
128 	argc -= optind;
129 	argv += optind;
130 
131 	month = 0;
132 	switch(argc) {
133 	case 2:
134 		if ((month = atoi(*argv++)) < 1 || month > 12)
135 			errx(1, "illegal month value: use 1-12");
136 		/* FALLTHROUGH */
137 	case 1:
138 		if ((year = atoi(*argv)) < 1 || year > 9999)
139 			errx(1, "illegal year value: use 1-9999");
140 		break;
141 	case 0:
142 		(void)time(&now);
143 		local_time = localtime(&now);
144 		year = local_time->tm_year + 1900;
145 		if (!yflag)
146 			month = local_time->tm_mon + 1;
147 		break;
148 	default:
149 		usage();
150 	}
151 
152 	if (month)
153 		monthly(month, year);
154 	else if (julian)
155 		j_yearly(year);
156 	else
157 		yearly(year);
158 	exit(0);
159 }
160 
161 #define	DAY_LEN		3		/* 3 spaces per day */
162 #define	J_DAY_LEN	4		/* 4 spaces per day */
163 #define	WEEK_LEN	20		/* 7 * 3 - one space at the end */
164 #define	J_WEEK_LEN	27		/* 7 * 4 - one space at the end */
165 #define	HEAD_SEP	2		/* spaces between day headings */
166 #define	J_HEAD_SEP	2
167 
168 void
169 monthly(month, year)
170 	int month, year;
171 {
172 	int col, row, len, days[MAXDAYS];
173 	char *p, lineout[30];
174 
175 	day_array(month, year, days);
176 	len = sprintf(lineout, "%s %d", month_names[month - 1], year);
177 	(void)printf("%*s%s\n%s\n",
178 	    ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "",
179 	    lineout, julian ? j_day_headings : day_headings);
180 	for (row = 0; row < 6; row++) {
181 		for (col = 0, p = lineout; col < 7; col++,
182 		    p += julian ? J_DAY_LEN : DAY_LEN)
183 			ascii_day(p, days[row * 7 + col]);
184 		*p = '\0';
185 		trim_trailing_spaces(lineout);
186 		(void)printf("%s\n", lineout);
187 	}
188 }
189 
190 void
191 j_yearly(year)
192 	int year;
193 {
194 	int col, *dp, i, month, row, which_cal;
195 	int days[12][MAXDAYS];
196 	char *p, lineout[80];
197 
198 	(void)sprintf(lineout, "%d", year);
199 	center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0);
200 	(void)printf("\n\n");
201 	for (i = 0; i < 12; i++)
202 		day_array(i + 1, year, days[i]);
203 	(void)memset(lineout, ' ', sizeof(lineout) - 1);
204 	lineout[sizeof(lineout) - 1] = '\0';
205 	for (month = 0; month < 12; month += 2) {
206 		center(month_names[month], J_WEEK_LEN, J_HEAD_SEP);
207 		center(month_names[month + 1], J_WEEK_LEN, 0);
208 		(void)printf("\n%s%*s%s\n", j_day_headings, J_HEAD_SEP, "",
209 		    j_day_headings);
210 		for (row = 0; row < 6; row++) {
211 			for (which_cal = 0; which_cal < 2; which_cal++) {
212 				p = lineout + which_cal * (J_WEEK_LEN + 2);
213 				dp = &days[month + which_cal][row * 7];
214 				for (col = 0; col < 7; col++, p += J_DAY_LEN)
215 					ascii_day(p, *dp++);
216 			}
217 			*p = '\0';
218 			trim_trailing_spaces(lineout);
219 			(void)printf("%s\n", lineout);
220 		}
221 	}
222 	(void)printf("\n");
223 }
224 
225 void
226 yearly(year)
227 	int year;
228 {
229 	int col, *dp, i, month, row, which_cal;
230 	int days[12][MAXDAYS];
231 	char *p, lineout[80];
232 
233 	(void)sprintf(lineout, "%d", year);
234 	center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0);
235 	(void)printf("\n\n");
236 	for (i = 0; i < 12; i++)
237 		day_array(i + 1, year, days[i]);
238 	(void)memset(lineout, ' ', sizeof(lineout) - 1);
239 	lineout[sizeof(lineout) - 1] = '\0';
240 	for (month = 0; month < 12; month += 3) {
241 		center(month_names[month], WEEK_LEN, HEAD_SEP);
242 		center(month_names[month + 1], WEEK_LEN, HEAD_SEP);
243 		center(month_names[month + 2], WEEK_LEN, 0);
244 		(void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP,
245 		    "", day_headings, HEAD_SEP, "", day_headings);
246 		for (row = 0; row < 6; row++) {
247 			for (which_cal = 0; which_cal < 3; which_cal++) {
248 				p = lineout + which_cal * (WEEK_LEN + 2);
249 				dp = &days[month + which_cal][row * 7];
250 				for (col = 0; col < 7; col++, p += DAY_LEN)
251 					ascii_day(p, *dp++);
252 			}
253 			*p = '\0';
254 			trim_trailing_spaces(lineout);
255 			(void)printf("%s\n", lineout);
256 		}
257 	}
258 	(void)printf("\n");
259 }
260 
261 /*
262  * day_array --
263  *	Fill in an array of 42 integers with a calendar.  Assume for a moment
264  *	that you took the (maximum) 6 rows in a calendar and stretched them
265  *	out end to end.  You would have 42 numbers or spaces.  This routine
266  *	builds that array for any month from Jan. 1 through Dec. 9999.
267  */
268 void
269 day_array(month, year, days)
270 	int month, year;
271 	int *days;
272 {
273 	int day, dw, dm;
274 
275 	if (month == 9 && year == 1752) {
276 		memmove(days,
277 			julian ? j_sep1752 : sep1752, MAXDAYS * sizeof(int));
278 		return;
279 	}
280 	memmove(days, empty, MAXDAYS * sizeof(int));
281 	dm = days_in_month[leap_year(year)][month];
282 	dw = day_in_week(1, month, year);
283 	day = julian ? day_in_year(1, month, year) : 1;
284 	while (dm--)
285 		days[dw++] = day++;
286 }
287 
288 /*
289  * day_in_year --
290  *	return the 1 based day number within the year
291  */
292 int
293 day_in_year(day, month, year)
294 	int day, month, year;
295 {
296 	int i, leap;
297 
298 	leap = leap_year(year);
299 	for (i = 1; i < month; i++)
300 		day += days_in_month[leap][i];
301 	return (day);
302 }
303 
304 /*
305  * day_in_week
306  *	return the 0 based day number for any date from 1 Jan. 1 to
307  *	31 Dec. 9999.  Assumes the Gregorian reformation eliminates
308  *	3 Sep. 1752 through 13 Sep. 1752.  Returns Thursday for all
309  *	missing days.
310  */
311 int
312 day_in_week(day, month, year)
313 	int day, month, year;
314 {
315 	long temp;
316 
317 	temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
318 	    + day_in_year(day, month, year);
319 	if (temp < FIRST_MISSING_DAY)
320 		return ((temp - 1 + SATURDAY) % 7);
321 	if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
322 		return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
323 	return (THURSDAY);
324 }
325 
326 void
327 ascii_day(p, day)
328 	char *p;
329 	int day;
330 {
331 	int display, val;
332 	static char *aday[] = {
333 		"",
334 		" 1", " 2", " 3", " 4", " 5", " 6", " 7",
335 		" 8", " 9", "10", "11", "12", "13", "14",
336 		"15", "16", "17", "18", "19", "20", "21",
337 		"22", "23", "24", "25", "26", "27", "28",
338 		"29", "30", "31",
339 	};
340 
341 	if (day == SPACE) {
342 		memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
343 		return;
344 	}
345 	if (julian) {
346 		if (val = day / 100) {
347 			day %= 100;
348 			*p++ = val + '0';
349 			display = 1;
350 		} else {
351 			*p++ = ' ';
352 			display = 0;
353 		}
354 		val = day / 10;
355 		if (val || display)
356 			*p++ = val + '0';
357 		else
358 			*p++ = ' ';
359 		*p++ = day % 10 + '0';
360 	} else {
361 		*p++ = aday[day][0];
362 		*p++ = aday[day][1];
363 	}
364 	*p = ' ';
365 }
366 
367 void
368 trim_trailing_spaces(s)
369 	char *s;
370 {
371 	char *p;
372 
373 	for (p = s; *p; ++p)
374 		continue;
375 	while (p > s && isspace(*--p))
376 		continue;
377 	if (p > s)
378 		++p;
379 	*p = '\0';
380 }
381 
382 void
383 center(str, len, separate)
384 	char *str;
385 	int len;
386 	int separate;
387 {
388 
389 	len -= strlen(str);
390 	(void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
391 	if (separate)
392 		(void)printf("%*s", separate, "");
393 }
394 
395 void
396 usage()
397 {
398 
399 	(void)fprintf(stderr, "usage: cal [-jy] [[month] year]\n");
400 	exit(1);
401 }
402