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