cal.c revision 1.7 1 1.7 lukem /* $NetBSD: cal.c,v 1.7 1997/10/18 12:24:59 lukem Exp $ */
2 1.6 glass
3 1.1 cgd /*
4 1.6 glass * Copyright (c) 1989, 1993, 1994
5 1.6 glass * The Regents of the University of California. All rights reserved.
6 1.1 cgd *
7 1.1 cgd * This code is derived from software contributed to Berkeley by
8 1.1 cgd * Kim Letkeman.
9 1.1 cgd *
10 1.1 cgd * Redistribution and use in source and binary forms, with or without
11 1.1 cgd * modification, are permitted provided that the following conditions
12 1.1 cgd * are met:
13 1.1 cgd * 1. Redistributions of source code must retain the above copyright
14 1.1 cgd * notice, this list of conditions and the following disclaimer.
15 1.1 cgd * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 cgd * notice, this list of conditions and the following disclaimer in the
17 1.1 cgd * documentation and/or other materials provided with the distribution.
18 1.1 cgd * 3. All advertising materials mentioning features or use of this software
19 1.1 cgd * must display the following acknowledgement:
20 1.1 cgd * This product includes software developed by the University of
21 1.1 cgd * California, Berkeley and its contributors.
22 1.1 cgd * 4. Neither the name of the University nor the names of its contributors
23 1.1 cgd * may be used to endorse or promote products derived from this software
24 1.1 cgd * without specific prior written permission.
25 1.1 cgd *
26 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 1.1 cgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 1.1 cgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 1.1 cgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 1.1 cgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 1.1 cgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 1.1 cgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 1.1 cgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 1.1 cgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 1.1 cgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 1.1 cgd * SUCH DAMAGE.
37 1.1 cgd */
38 1.1 cgd
39 1.7 lukem #include <sys/cdefs.h>
40 1.1 cgd #ifndef lint
41 1.7 lukem __COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\n\
42 1.7 lukem The Regents of the University of California. All rights reserved.\n");
43 1.1 cgd #endif /* not lint */
44 1.1 cgd
45 1.1 cgd #ifndef lint
46 1.6 glass #if 0
47 1.6 glass static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94";
48 1.6 glass #else
49 1.7 lukem __RCSID("$NetBSD: cal.c,v 1.7 1997/10/18 12:24:59 lukem Exp $");
50 1.6 glass #endif
51 1.1 cgd #endif /* not lint */
52 1.1 cgd
53 1.1 cgd #include <sys/types.h>
54 1.6 glass
55 1.6 glass #include <ctype.h>
56 1.6 glass #include <err.h>
57 1.1 cgd #include <stdio.h>
58 1.6 glass #include <stdlib.h>
59 1.4 cgd #include <string.h>
60 1.6 glass #include <time.h>
61 1.6 glass #include <unistd.h>
62 1.1 cgd
63 1.1 cgd #define THURSDAY 4 /* for reformation */
64 1.1 cgd #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
65 1.1 cgd
66 1.5 ws #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
67 1.1 cgd #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
68 1.1 cgd
69 1.1 cgd #define MAXDAYS 42 /* max slots in a month array */
70 1.1 cgd #define SPACE -1 /* used in day array */
71 1.1 cgd
72 1.1 cgd static int days_in_month[2][13] = {
73 1.1 cgd {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
74 1.1 cgd {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
75 1.1 cgd };
76 1.1 cgd
77 1.1 cgd int sep1752[MAXDAYS] = {
78 1.1 cgd SPACE, SPACE, 1, 2, 14, 15, 16,
79 1.1 cgd 17, 18, 19, 20, 21, 22, 23,
80 1.1 cgd 24, 25, 26, 27, 28, 29, 30,
81 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
82 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
83 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
84 1.1 cgd }, j_sep1752[MAXDAYS] = {
85 1.1 cgd SPACE, SPACE, 245, 246, 258, 259, 260,
86 1.1 cgd 261, 262, 263, 264, 265, 266, 267,
87 1.1 cgd 268, 269, 270, 271, 272, 273, 274,
88 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
89 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
90 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
91 1.1 cgd }, empty[MAXDAYS] = {
92 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
93 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
94 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
95 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
96 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
97 1.1 cgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
98 1.1 cgd };
99 1.1 cgd
100 1.1 cgd char *month_names[12] = {
101 1.1 cgd "January", "February", "March", "April", "May", "June",
102 1.1 cgd "July", "August", "September", "October", "November", "December",
103 1.1 cgd };
104 1.1 cgd
105 1.1 cgd char *day_headings = " S M Tu W Th F S";
106 1.1 cgd char *j_day_headings = " S M Tu W Th F S";
107 1.1 cgd
108 1.1 cgd /* leap year -- account for gregorian reformation in 1752 */
109 1.1 cgd #define leap_year(yr) \
110 1.1 cgd ((yr) <= 1752 ? !((yr) % 4) : \
111 1.7 lukem !(((yr) % 4) && ((yr) % 100)) || !((yr) % 400))
112 1.1 cgd
113 1.1 cgd /* number of centuries since 1700, not inclusive */
114 1.1 cgd #define centuries_since_1700(yr) \
115 1.1 cgd ((yr) > 1700 ? (yr) / 100 - 17 : 0)
116 1.1 cgd
117 1.1 cgd /* number of centuries since 1700 whose modulo of 400 is 0 */
118 1.1 cgd #define quad_centuries_since_1700(yr) \
119 1.1 cgd ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
120 1.1 cgd
121 1.1 cgd /* number of leap years between year 1 and this year, not inclusive */
122 1.1 cgd #define leap_years_since_year_1(yr) \
123 1.1 cgd ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
124 1.1 cgd
125 1.1 cgd int julian;
126 1.1 cgd
127 1.6 glass void ascii_day __P((char *, int));
128 1.6 glass void center __P((char *, int, int));
129 1.6 glass void day_array __P((int, int, int *));
130 1.6 glass int day_in_week __P((int, int, int));
131 1.6 glass int day_in_year __P((int, int, int));
132 1.6 glass void j_yearly __P((int));
133 1.6 glass void monthly __P((int, int));
134 1.7 lukem int main __P((int, char **));
135 1.6 glass void trim_trailing_spaces __P((char *));
136 1.6 glass void usage __P((void));
137 1.6 glass void yearly __P((int));
138 1.6 glass
139 1.6 glass int
140 1.1 cgd main(argc, argv)
141 1.1 cgd int argc;
142 1.1 cgd char **argv;
143 1.1 cgd {
144 1.1 cgd struct tm *local_time;
145 1.6 glass time_t now;
146 1.1 cgd int ch, month, year, yflag;
147 1.1 cgd
148 1.7 lukem yflag = year = 0;
149 1.7 lukem while ((ch = getopt(argc, argv, "jy")) != -1)
150 1.1 cgd switch(ch) {
151 1.1 cgd case 'j':
152 1.1 cgd julian = 1;
153 1.1 cgd break;
154 1.1 cgd case 'y':
155 1.1 cgd yflag = 1;
156 1.1 cgd break;
157 1.1 cgd case '?':
158 1.1 cgd default:
159 1.1 cgd usage();
160 1.1 cgd }
161 1.1 cgd argc -= optind;
162 1.1 cgd argv += optind;
163 1.1 cgd
164 1.1 cgd month = 0;
165 1.1 cgd switch(argc) {
166 1.1 cgd case 2:
167 1.6 glass if ((month = atoi(*argv++)) < 1 || month > 12)
168 1.6 glass errx(1, "illegal month value: use 1-12");
169 1.1 cgd /* FALLTHROUGH */
170 1.1 cgd case 1:
171 1.6 glass if ((year = atoi(*argv)) < 1 || year > 9999)
172 1.6 glass errx(1, "illegal year value: use 1-9999");
173 1.1 cgd break;
174 1.1 cgd case 0:
175 1.1 cgd (void)time(&now);
176 1.1 cgd local_time = localtime(&now);
177 1.1 cgd year = local_time->tm_year + 1900;
178 1.1 cgd if (!yflag)
179 1.1 cgd month = local_time->tm_mon + 1;
180 1.1 cgd break;
181 1.1 cgd default:
182 1.1 cgd usage();
183 1.1 cgd }
184 1.1 cgd
185 1.1 cgd if (month)
186 1.1 cgd monthly(month, year);
187 1.1 cgd else if (julian)
188 1.1 cgd j_yearly(year);
189 1.1 cgd else
190 1.1 cgd yearly(year);
191 1.1 cgd exit(0);
192 1.1 cgd }
193 1.1 cgd
194 1.1 cgd #define DAY_LEN 3 /* 3 spaces per day */
195 1.1 cgd #define J_DAY_LEN 4 /* 4 spaces per day */
196 1.1 cgd #define WEEK_LEN 20 /* 7 * 3 - one space at the end */
197 1.1 cgd #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */
198 1.1 cgd #define HEAD_SEP 2 /* spaces between day headings */
199 1.1 cgd #define J_HEAD_SEP 2
200 1.1 cgd
201 1.6 glass void
202 1.1 cgd monthly(month, year)
203 1.1 cgd int month, year;
204 1.1 cgd {
205 1.6 glass int col, row, len, days[MAXDAYS];
206 1.6 glass char *p, lineout[30];
207 1.1 cgd
208 1.1 cgd day_array(month, year, days);
209 1.1 cgd len = sprintf(lineout, "%s %d", month_names[month - 1], year);
210 1.1 cgd (void)printf("%*s%s\n%s\n",
211 1.1 cgd ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "",
212 1.1 cgd lineout, julian ? j_day_headings : day_headings);
213 1.1 cgd for (row = 0; row < 6; row++) {
214 1.1 cgd for (col = 0, p = lineout; col < 7; col++,
215 1.1 cgd p += julian ? J_DAY_LEN : DAY_LEN)
216 1.1 cgd ascii_day(p, days[row * 7 + col]);
217 1.3 cgd *p = '\0';
218 1.1 cgd trim_trailing_spaces(lineout);
219 1.1 cgd (void)printf("%s\n", lineout);
220 1.1 cgd }
221 1.1 cgd }
222 1.1 cgd
223 1.6 glass void
224 1.1 cgd j_yearly(year)
225 1.1 cgd int year;
226 1.1 cgd {
227 1.6 glass int col, *dp, i, month, row, which_cal;
228 1.1 cgd int days[12][MAXDAYS];
229 1.6 glass char *p, lineout[80];
230 1.1 cgd
231 1.1 cgd (void)sprintf(lineout, "%d", year);
232 1.1 cgd center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0);
233 1.1 cgd (void)printf("\n\n");
234 1.1 cgd for (i = 0; i < 12; i++)
235 1.1 cgd day_array(i + 1, year, days[i]);
236 1.1 cgd (void)memset(lineout, ' ', sizeof(lineout) - 1);
237 1.1 cgd lineout[sizeof(lineout) - 1] = '\0';
238 1.1 cgd for (month = 0; month < 12; month += 2) {
239 1.1 cgd center(month_names[month], J_WEEK_LEN, J_HEAD_SEP);
240 1.1 cgd center(month_names[month + 1], J_WEEK_LEN, 0);
241 1.1 cgd (void)printf("\n%s%*s%s\n", j_day_headings, J_HEAD_SEP, "",
242 1.1 cgd j_day_headings);
243 1.1 cgd for (row = 0; row < 6; row++) {
244 1.1 cgd for (which_cal = 0; which_cal < 2; which_cal++) {
245 1.1 cgd p = lineout + which_cal * (J_WEEK_LEN + 2);
246 1.1 cgd dp = &days[month + which_cal][row * 7];
247 1.1 cgd for (col = 0; col < 7; col++, p += J_DAY_LEN)
248 1.1 cgd ascii_day(p, *dp++);
249 1.1 cgd }
250 1.3 cgd *p = '\0';
251 1.1 cgd trim_trailing_spaces(lineout);
252 1.1 cgd (void)printf("%s\n", lineout);
253 1.1 cgd }
254 1.1 cgd }
255 1.1 cgd (void)printf("\n");
256 1.1 cgd }
257 1.1 cgd
258 1.6 glass void
259 1.1 cgd yearly(year)
260 1.1 cgd int year;
261 1.1 cgd {
262 1.6 glass int col, *dp, i, month, row, which_cal;
263 1.1 cgd int days[12][MAXDAYS];
264 1.6 glass char *p, lineout[80];
265 1.1 cgd
266 1.1 cgd (void)sprintf(lineout, "%d", year);
267 1.1 cgd center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0);
268 1.1 cgd (void)printf("\n\n");
269 1.1 cgd for (i = 0; i < 12; i++)
270 1.1 cgd day_array(i + 1, year, days[i]);
271 1.1 cgd (void)memset(lineout, ' ', sizeof(lineout) - 1);
272 1.1 cgd lineout[sizeof(lineout) - 1] = '\0';
273 1.1 cgd for (month = 0; month < 12; month += 3) {
274 1.1 cgd center(month_names[month], WEEK_LEN, HEAD_SEP);
275 1.1 cgd center(month_names[month + 1], WEEK_LEN, HEAD_SEP);
276 1.1 cgd center(month_names[month + 2], WEEK_LEN, 0);
277 1.1 cgd (void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP,
278 1.1 cgd "", day_headings, HEAD_SEP, "", day_headings);
279 1.1 cgd for (row = 0; row < 6; row++) {
280 1.1 cgd for (which_cal = 0; which_cal < 3; which_cal++) {
281 1.1 cgd p = lineout + which_cal * (WEEK_LEN + 2);
282 1.1 cgd dp = &days[month + which_cal][row * 7];
283 1.1 cgd for (col = 0; col < 7; col++, p += DAY_LEN)
284 1.1 cgd ascii_day(p, *dp++);
285 1.1 cgd }
286 1.3 cgd *p = '\0';
287 1.1 cgd trim_trailing_spaces(lineout);
288 1.1 cgd (void)printf("%s\n", lineout);
289 1.1 cgd }
290 1.1 cgd }
291 1.1 cgd (void)printf("\n");
292 1.1 cgd }
293 1.1 cgd
294 1.1 cgd /*
295 1.1 cgd * day_array --
296 1.1 cgd * Fill in an array of 42 integers with a calendar. Assume for a moment
297 1.1 cgd * that you took the (maximum) 6 rows in a calendar and stretched them
298 1.1 cgd * out end to end. You would have 42 numbers or spaces. This routine
299 1.1 cgd * builds that array for any month from Jan. 1 through Dec. 9999.
300 1.1 cgd */
301 1.6 glass void
302 1.1 cgd day_array(month, year, days)
303 1.1 cgd int month, year;
304 1.6 glass int *days;
305 1.1 cgd {
306 1.6 glass int day, dw, dm;
307 1.1 cgd
308 1.1 cgd if (month == 9 && year == 1752) {
309 1.6 glass memmove(days,
310 1.6 glass julian ? j_sep1752 : sep1752, MAXDAYS * sizeof(int));
311 1.1 cgd return;
312 1.1 cgd }
313 1.6 glass memmove(days, empty, MAXDAYS * sizeof(int));
314 1.1 cgd dm = days_in_month[leap_year(year)][month];
315 1.1 cgd dw = day_in_week(1, month, year);
316 1.1 cgd day = julian ? day_in_year(1, month, year) : 1;
317 1.1 cgd while (dm--)
318 1.1 cgd days[dw++] = day++;
319 1.1 cgd }
320 1.1 cgd
321 1.1 cgd /*
322 1.1 cgd * day_in_year --
323 1.1 cgd * return the 1 based day number within the year
324 1.1 cgd */
325 1.6 glass int
326 1.1 cgd day_in_year(day, month, year)
327 1.6 glass int day, month, year;
328 1.1 cgd {
329 1.6 glass int i, leap;
330 1.1 cgd
331 1.1 cgd leap = leap_year(year);
332 1.1 cgd for (i = 1; i < month; i++)
333 1.1 cgd day += days_in_month[leap][i];
334 1.6 glass return (day);
335 1.1 cgd }
336 1.1 cgd
337 1.1 cgd /*
338 1.1 cgd * day_in_week
339 1.1 cgd * return the 0 based day number for any date from 1 Jan. 1 to
340 1.1 cgd * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
341 1.1 cgd * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
342 1.1 cgd * missing days.
343 1.1 cgd */
344 1.6 glass int
345 1.1 cgd day_in_week(day, month, year)
346 1.1 cgd int day, month, year;
347 1.1 cgd {
348 1.1 cgd long temp;
349 1.1 cgd
350 1.1 cgd temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
351 1.1 cgd + day_in_year(day, month, year);
352 1.1 cgd if (temp < FIRST_MISSING_DAY)
353 1.6 glass return ((temp - 1 + SATURDAY) % 7);
354 1.1 cgd if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
355 1.6 glass return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
356 1.6 glass return (THURSDAY);
357 1.1 cgd }
358 1.1 cgd
359 1.6 glass void
360 1.1 cgd ascii_day(p, day)
361 1.6 glass char *p;
362 1.6 glass int day;
363 1.1 cgd {
364 1.6 glass int display, val;
365 1.1 cgd static char *aday[] = {
366 1.1 cgd "",
367 1.1 cgd " 1", " 2", " 3", " 4", " 5", " 6", " 7",
368 1.1 cgd " 8", " 9", "10", "11", "12", "13", "14",
369 1.1 cgd "15", "16", "17", "18", "19", "20", "21",
370 1.1 cgd "22", "23", "24", "25", "26", "27", "28",
371 1.1 cgd "29", "30", "31",
372 1.1 cgd };
373 1.1 cgd
374 1.1 cgd if (day == SPACE) {
375 1.1 cgd memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
376 1.1 cgd return;
377 1.1 cgd }
378 1.1 cgd if (julian) {
379 1.7 lukem if ((val = day / 100) != 0) {
380 1.1 cgd day %= 100;
381 1.1 cgd *p++ = val + '0';
382 1.1 cgd display = 1;
383 1.1 cgd } else {
384 1.1 cgd *p++ = ' ';
385 1.1 cgd display = 0;
386 1.1 cgd }
387 1.1 cgd val = day / 10;
388 1.1 cgd if (val || display)
389 1.1 cgd *p++ = val + '0';
390 1.1 cgd else
391 1.1 cgd *p++ = ' ';
392 1.1 cgd *p++ = day % 10 + '0';
393 1.1 cgd } else {
394 1.1 cgd *p++ = aday[day][0];
395 1.1 cgd *p++ = aday[day][1];
396 1.1 cgd }
397 1.1 cgd *p = ' ';
398 1.1 cgd }
399 1.1 cgd
400 1.6 glass void
401 1.1 cgd trim_trailing_spaces(s)
402 1.6 glass char *s;
403 1.1 cgd {
404 1.6 glass char *p;
405 1.1 cgd
406 1.6 glass for (p = s; *p; ++p)
407 1.6 glass continue;
408 1.6 glass while (p > s && isspace(*--p))
409 1.6 glass continue;
410 1.1 cgd if (p > s)
411 1.1 cgd ++p;
412 1.1 cgd *p = '\0';
413 1.1 cgd }
414 1.1 cgd
415 1.6 glass void
416 1.1 cgd center(str, len, separate)
417 1.1 cgd char *str;
418 1.6 glass int len;
419 1.1 cgd int separate;
420 1.1 cgd {
421 1.6 glass
422 1.1 cgd len -= strlen(str);
423 1.1 cgd (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
424 1.1 cgd if (separate)
425 1.1 cgd (void)printf("%*s", separate, "");
426 1.1 cgd }
427 1.1 cgd
428 1.6 glass void
429 1.1 cgd usage()
430 1.1 cgd {
431 1.6 glass
432 1.1 cgd (void)fprintf(stderr, "usage: cal [-jy] [[month] year]\n");
433 1.1 cgd exit(1);
434 1.1 cgd }
435