cal.c revision 1.12 1 1.12 perry /* $NetBSD: cal.c,v 1.12 2002/06/21 19:58:48 perry 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.12 perry __RCSID("$NetBSD: cal.c,v 1.12 2002/06/21 19:58:48 perry 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.8 christos #include <tzfile.h>
62 1.6 glass #include <unistd.h>
63 1.1 cgd
64 1.1 cgd #define THURSDAY 4 /* for reformation */
65 1.1 cgd #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
66 1.1 cgd
67 1.5 ws #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
68 1.1 cgd #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
69 1.1 cgd
70 1.1 cgd #define MAXDAYS 42 /* max slots in a month array */
71 1.1 cgd #define SPACE -1 /* used in day array */
72 1.1 cgd
73 1.1 cgd static int days_in_month[2][13] = {
74 1.1 cgd {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
75 1.1 cgd {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
76 1.1 cgd };
77 1.1 cgd
78 1.1 cgd int sep1752[MAXDAYS] = {
79 1.1 cgd SPACE, SPACE, 1, 2, 14, 15, 16,
80 1.1 cgd 17, 18, 19, 20, 21, 22, 23,
81 1.1 cgd 24, 25, 26, 27, 28, 29, 30,
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 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
85 1.1 cgd }, j_sep1752[MAXDAYS] = {
86 1.1 cgd SPACE, SPACE, 245, 246, 258, 259, 260,
87 1.1 cgd 261, 262, 263, 264, 265, 266, 267,
88 1.1 cgd 268, 269, 270, 271, 272, 273, 274,
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 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
92 1.1 cgd }, empty[MAXDAYS] = {
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 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
99 1.1 cgd };
100 1.1 cgd
101 1.1 cgd char *month_names[12] = {
102 1.1 cgd "January", "February", "March", "April", "May", "June",
103 1.1 cgd "July", "August", "September", "October", "November", "December",
104 1.1 cgd };
105 1.1 cgd
106 1.1 cgd char *day_headings = " S M Tu W Th F S";
107 1.1 cgd char *j_day_headings = " S M Tu W Th F S";
108 1.1 cgd
109 1.1 cgd /* leap year -- account for gregorian reformation in 1752 */
110 1.1 cgd #define leap_year(yr) \
111 1.1 cgd ((yr) <= 1752 ? !((yr) % 4) : \
112 1.9 ws (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400))
113 1.1 cgd
114 1.1 cgd /* number of centuries since 1700, not inclusive */
115 1.1 cgd #define centuries_since_1700(yr) \
116 1.1 cgd ((yr) > 1700 ? (yr) / 100 - 17 : 0)
117 1.1 cgd
118 1.1 cgd /* number of centuries since 1700 whose modulo of 400 is 0 */
119 1.1 cgd #define quad_centuries_since_1700(yr) \
120 1.1 cgd ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
121 1.1 cgd
122 1.1 cgd /* number of leap years between year 1 and this year, not inclusive */
123 1.1 cgd #define leap_years_since_year_1(yr) \
124 1.1 cgd ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
125 1.1 cgd
126 1.1 cgd int julian;
127 1.1 cgd
128 1.12 perry void ascii_day(char *, int);
129 1.12 perry void center(char *, int, int);
130 1.12 perry void day_array(int, int, int *);
131 1.12 perry int day_in_week(int, int, int);
132 1.12 perry int day_in_year(int, int, int);
133 1.12 perry void j_yearly(int);
134 1.12 perry void monthly(int, int);
135 1.12 perry int main(int, char **);
136 1.12 perry void trim_trailing_spaces(char *);
137 1.12 perry void usage(void);
138 1.12 perry void yearly(int);
139 1.6 glass
140 1.6 glass int
141 1.12 perry main(int argc, char **argv)
142 1.1 cgd {
143 1.1 cgd struct tm *local_time;
144 1.6 glass time_t now;
145 1.1 cgd int ch, month, year, yflag;
146 1.1 cgd
147 1.7 lukem yflag = year = 0;
148 1.12 perry while ((ch = getopt(argc, argv, "jy")) != -1) {
149 1.12 perry switch (ch) {
150 1.1 cgd case 'j':
151 1.1 cgd julian = 1;
152 1.1 cgd break;
153 1.1 cgd case 'y':
154 1.1 cgd yflag = 1;
155 1.1 cgd break;
156 1.1 cgd case '?':
157 1.1 cgd default:
158 1.1 cgd usage();
159 1.12 perry /* NOTREACHED */
160 1.1 cgd }
161 1.12 perry }
162 1.1 cgd argc -= optind;
163 1.1 cgd argv += optind;
164 1.1 cgd
165 1.1 cgd month = 0;
166 1.12 perry switch (argc) {
167 1.1 cgd case 2:
168 1.6 glass if ((month = atoi(*argv++)) < 1 || month > 12)
169 1.6 glass errx(1, "illegal month value: use 1-12");
170 1.1 cgd /* FALLTHROUGH */
171 1.1 cgd case 1:
172 1.6 glass if ((year = atoi(*argv)) < 1 || year > 9999)
173 1.6 glass errx(1, "illegal year value: use 1-9999");
174 1.1 cgd break;
175 1.1 cgd case 0:
176 1.1 cgd (void)time(&now);
177 1.1 cgd local_time = localtime(&now);
178 1.8 christos year = local_time->tm_year + TM_YEAR_BASE;
179 1.1 cgd if (!yflag)
180 1.1 cgd month = local_time->tm_mon + 1;
181 1.1 cgd break;
182 1.1 cgd default:
183 1.1 cgd usage();
184 1.1 cgd }
185 1.1 cgd
186 1.1 cgd if (month)
187 1.1 cgd monthly(month, year);
188 1.1 cgd else if (julian)
189 1.1 cgd j_yearly(year);
190 1.1 cgd else
191 1.1 cgd yearly(year);
192 1.1 cgd exit(0);
193 1.1 cgd }
194 1.1 cgd
195 1.1 cgd #define DAY_LEN 3 /* 3 spaces per day */
196 1.1 cgd #define J_DAY_LEN 4 /* 4 spaces per day */
197 1.1 cgd #define WEEK_LEN 20 /* 7 * 3 - one space at the end */
198 1.1 cgd #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */
199 1.1 cgd #define HEAD_SEP 2 /* spaces between day headings */
200 1.1 cgd #define J_HEAD_SEP 2
201 1.1 cgd
202 1.6 glass void
203 1.12 perry monthly(int month, int 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.10 mycroft len = snprintf(lineout, sizeof(lineout), "%s %d",
210 1.10 mycroft month_names[month - 1], year);
211 1.1 cgd (void)printf("%*s%s\n%s\n",
212 1.1 cgd ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "",
213 1.1 cgd lineout, julian ? j_day_headings : day_headings);
214 1.1 cgd for (row = 0; row < 6; row++) {
215 1.1 cgd for (col = 0, p = lineout; col < 7; col++,
216 1.1 cgd p += julian ? J_DAY_LEN : DAY_LEN)
217 1.1 cgd ascii_day(p, days[row * 7 + col]);
218 1.3 cgd *p = '\0';
219 1.1 cgd trim_trailing_spaces(lineout);
220 1.1 cgd (void)printf("%s\n", lineout);
221 1.1 cgd }
222 1.1 cgd }
223 1.1 cgd
224 1.6 glass void
225 1.12 perry j_yearly(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.10 mycroft (void)snprintf(lineout, sizeof(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.12 perry yearly(int year)
260 1.1 cgd {
261 1.6 glass int col, *dp, i, month, row, which_cal;
262 1.1 cgd int days[12][MAXDAYS];
263 1.6 glass char *p, lineout[80];
264 1.1 cgd
265 1.10 mycroft (void)snprintf(lineout, sizeof(lineout), "%d", year);
266 1.1 cgd center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0);
267 1.1 cgd (void)printf("\n\n");
268 1.1 cgd for (i = 0; i < 12; i++)
269 1.1 cgd day_array(i + 1, year, days[i]);
270 1.1 cgd (void)memset(lineout, ' ', sizeof(lineout) - 1);
271 1.1 cgd lineout[sizeof(lineout) - 1] = '\0';
272 1.1 cgd for (month = 0; month < 12; month += 3) {
273 1.1 cgd center(month_names[month], WEEK_LEN, HEAD_SEP);
274 1.1 cgd center(month_names[month + 1], WEEK_LEN, HEAD_SEP);
275 1.1 cgd center(month_names[month + 2], WEEK_LEN, 0);
276 1.1 cgd (void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP,
277 1.1 cgd "", day_headings, HEAD_SEP, "", day_headings);
278 1.1 cgd for (row = 0; row < 6; row++) {
279 1.1 cgd for (which_cal = 0; which_cal < 3; which_cal++) {
280 1.1 cgd p = lineout + which_cal * (WEEK_LEN + 2);
281 1.1 cgd dp = &days[month + which_cal][row * 7];
282 1.1 cgd for (col = 0; col < 7; col++, p += DAY_LEN)
283 1.1 cgd ascii_day(p, *dp++);
284 1.1 cgd }
285 1.3 cgd *p = '\0';
286 1.1 cgd trim_trailing_spaces(lineout);
287 1.1 cgd (void)printf("%s\n", lineout);
288 1.1 cgd }
289 1.1 cgd }
290 1.1 cgd (void)printf("\n");
291 1.1 cgd }
292 1.1 cgd
293 1.1 cgd /*
294 1.1 cgd * day_array --
295 1.1 cgd * Fill in an array of 42 integers with a calendar. Assume for a moment
296 1.1 cgd * that you took the (maximum) 6 rows in a calendar and stretched them
297 1.1 cgd * out end to end. You would have 42 numbers or spaces. This routine
298 1.1 cgd * builds that array for any month from Jan. 1 through Dec. 9999.
299 1.1 cgd */
300 1.6 glass void
301 1.12 perry day_array(int month, int year, int *days)
302 1.1 cgd {
303 1.6 glass int day, dw, dm;
304 1.1 cgd
305 1.1 cgd if (month == 9 && year == 1752) {
306 1.6 glass memmove(days,
307 1.6 glass julian ? j_sep1752 : sep1752, MAXDAYS * sizeof(int));
308 1.1 cgd return;
309 1.1 cgd }
310 1.6 glass memmove(days, empty, MAXDAYS * sizeof(int));
311 1.1 cgd dm = days_in_month[leap_year(year)][month];
312 1.1 cgd dw = day_in_week(1, month, year);
313 1.1 cgd day = julian ? day_in_year(1, month, year) : 1;
314 1.1 cgd while (dm--)
315 1.1 cgd days[dw++] = day++;
316 1.1 cgd }
317 1.1 cgd
318 1.1 cgd /*
319 1.1 cgd * day_in_year --
320 1.1 cgd * return the 1 based day number within the year
321 1.1 cgd */
322 1.6 glass int
323 1.12 perry day_in_year(int day, int month, int year)
324 1.1 cgd {
325 1.6 glass int i, leap;
326 1.1 cgd
327 1.1 cgd leap = leap_year(year);
328 1.1 cgd for (i = 1; i < month; i++)
329 1.1 cgd day += days_in_month[leap][i];
330 1.6 glass return (day);
331 1.1 cgd }
332 1.1 cgd
333 1.1 cgd /*
334 1.1 cgd * day_in_week
335 1.1 cgd * return the 0 based day number for any date from 1 Jan. 1 to
336 1.1 cgd * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
337 1.1 cgd * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
338 1.1 cgd * missing days.
339 1.1 cgd */
340 1.6 glass int
341 1.12 perry day_in_week(int day, int month, int year)
342 1.1 cgd {
343 1.1 cgd long temp;
344 1.1 cgd
345 1.1 cgd temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
346 1.1 cgd + day_in_year(day, month, year);
347 1.1 cgd if (temp < FIRST_MISSING_DAY)
348 1.6 glass return ((temp - 1 + SATURDAY) % 7);
349 1.1 cgd if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
350 1.6 glass return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
351 1.6 glass return (THURSDAY);
352 1.1 cgd }
353 1.1 cgd
354 1.6 glass void
355 1.12 perry ascii_day(char *p, int day)
356 1.1 cgd {
357 1.6 glass int display, val;
358 1.1 cgd static char *aday[] = {
359 1.1 cgd "",
360 1.1 cgd " 1", " 2", " 3", " 4", " 5", " 6", " 7",
361 1.1 cgd " 8", " 9", "10", "11", "12", "13", "14",
362 1.1 cgd "15", "16", "17", "18", "19", "20", "21",
363 1.1 cgd "22", "23", "24", "25", "26", "27", "28",
364 1.1 cgd "29", "30", "31",
365 1.1 cgd };
366 1.1 cgd
367 1.1 cgd if (day == SPACE) {
368 1.1 cgd memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
369 1.1 cgd return;
370 1.1 cgd }
371 1.1 cgd if (julian) {
372 1.7 lukem if ((val = day / 100) != 0) {
373 1.1 cgd day %= 100;
374 1.1 cgd *p++ = val + '0';
375 1.1 cgd display = 1;
376 1.1 cgd } else {
377 1.1 cgd *p++ = ' ';
378 1.1 cgd display = 0;
379 1.1 cgd }
380 1.1 cgd val = day / 10;
381 1.1 cgd if (val || display)
382 1.1 cgd *p++ = val + '0';
383 1.1 cgd else
384 1.1 cgd *p++ = ' ';
385 1.1 cgd *p++ = day % 10 + '0';
386 1.1 cgd } else {
387 1.1 cgd *p++ = aday[day][0];
388 1.1 cgd *p++ = aday[day][1];
389 1.1 cgd }
390 1.1 cgd *p = ' ';
391 1.1 cgd }
392 1.1 cgd
393 1.6 glass void
394 1.12 perry trim_trailing_spaces(char *s)
395 1.1 cgd {
396 1.6 glass char *p;
397 1.1 cgd
398 1.6 glass for (p = s; *p; ++p)
399 1.6 glass continue;
400 1.11 christos while (p > s && isspace((unsigned char)*--p))
401 1.6 glass continue;
402 1.1 cgd if (p > s)
403 1.1 cgd ++p;
404 1.1 cgd *p = '\0';
405 1.1 cgd }
406 1.1 cgd
407 1.6 glass void
408 1.12 perry center(char *str, int len, int separate)
409 1.1 cgd {
410 1.6 glass
411 1.1 cgd len -= strlen(str);
412 1.1 cgd (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
413 1.1 cgd if (separate)
414 1.1 cgd (void)printf("%*s", separate, "");
415 1.1 cgd }
416 1.1 cgd
417 1.6 glass void
418 1.12 perry usage(void)
419 1.1 cgd {
420 1.6 glass
421 1.1 cgd (void)fprintf(stderr, "usage: cal [-jy] [[month] year]\n");
422 1.1 cgd exit(1);
423 1.1 cgd }
424