]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
aba1b6f061fd53638ce77814d2ec18bf433788eb
[thirdparty/util-linux.git] / misc-utils / cal.c
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 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 /* 1999-02-01 Jean-Francois Bignolles: added option '-m' to display
38 * monday as the first day of the week.
39 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org>
40 * - added Native Language Support
41 *
42 * 2000-09-01 Michael Charles Pruznick <dummy@netwiz.net>
43 * Added "-3" option to print prev/next month with current.
44 * Added over-ridable default NUM_MONTHS and "-1" option to
45 * get traditional output when -3 is the default. I hope that
46 * enough people will like -3 as the default that one day the
47 * product can be shipped that way.
48 */
49
50 #include <sys/types.h>
51
52 #include <ctype.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <time.h>
57 #include <unistd.h>
58 #include <locale.h>
59 #include "errs.h"
60 #include "nls.h"
61 #include "../defines.h"
62
63 #ifdef HAVE_langinfo_h
64 # include <langinfo.h>
65 #else
66 # include <localeinfo.h> /* libc4 only */
67 #endif
68
69 /* allow compile-time define to over-ride default */
70 #ifndef NUM_MONTHS
71 #define NUM_MONTHS 1
72 #endif
73
74 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
75 #error NUM_MONTHS must be 1 or 3
76 #endif
77
78 #define THURSDAY 4 /* for reformation */
79 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
80
81 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
82 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
83
84 #define MAXDAYS 43 /* max slots in a month array */
85 #define SPACE -1 /* used in day array */
86
87 static int days_in_month[2][13] = {
88 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
89 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
90 };
91
92 int sep1752[MAXDAYS] = {
93 SPACE, SPACE, 1, 2, 14, 15, 16,
94 17, 18, 19, 20, 21, 22, 23,
95 24, 25, 26, 27, 28, 29, 30,
96 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
97 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
98 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
99 SPACE
100 }, j_sep1752[MAXDAYS] = {
101 SPACE, SPACE, 245, 246, 258, 259, 260,
102 261, 262, 263, 264, 265, 266, 267,
103 268, 269, 270, 271, 272, 273, 274,
104 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
105 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
106 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
107 SPACE
108 }, empty[MAXDAYS] = {
109 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
110 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
111 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
112 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
113 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
114 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
115 SPACE
116 };
117
118 char day_headings[] = " S M Tu W Th F S ";
119 /* week1stday = 1 => " M Tu W Th F S S " */
120 char j_day_headings[] = "Sun Mon Tue Wed Thu Fri Sat ";
121 /* week1stday = 1 => " M Tu W Th F S S " */
122 const char *full_month[12];
123
124 /* leap year -- account for gregorian reformation in 1752 */
125 #define leap_year(yr) \
126 ((yr) <= 1752 ? !((yr) % 4) : \
127 (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400))
128
129 /* number of centuries since 1700, not inclusive */
130 #define centuries_since_1700(yr) \
131 ((yr) > 1700 ? (yr) / 100 - 17 : 0)
132
133 /* number of centuries since 1700 whose modulo of 400 is 0 */
134 #define quad_centuries_since_1700(yr) \
135 ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
136
137 /* number of leap years between year 1 and this year, not inclusive */
138 #define leap_years_since_year_1(yr) \
139 ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
140
141 /* 0 => sunday (default), 1 => monday */
142 int week1stday;
143 int julian;
144
145 #define FMT_ST_LINES 8
146 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
147 struct fmt_st
148 {
149 char s[FMT_ST_LINES][FMT_ST_CHARS];
150 };
151
152 void ascii_day __P((char *, int));
153 void center __P((const char *, int, int));
154 void day_array __P((int, int, int *));
155 int day_in_week __P((int, int, int));
156 int day_in_year __P((int, int, int));
157 void j_yearly __P((int));
158 void do_monthly __P((int, int, struct fmt_st*));
159 void monthly __P((int, int));
160 void monthly3 __P((int, int));
161 void trim_trailing_spaces __P((char *));
162 void usage __P((void));
163 void yearly __P((int));
164 void headers_init(void);
165 extern char *__progname;
166
167 int
168 main(int argc, char **argv) {
169 struct tm *local_time;
170 time_t now;
171 int ch, month, year, yflag;
172 char *progname, *p;
173 int num_months = NUM_MONTHS;
174
175 progname = argv[0];
176 if ((p = strrchr(progname, '/')) != NULL)
177 progname = p+1;
178 __progname = progname;
179
180 setlocale(LC_ALL, "");
181 bindtextdomain(PACKAGE, LOCALEDIR);
182 textdomain(PACKAGE);
183
184 yflag = 0;
185 while ((ch = getopt(argc, argv, "13mjyV")) != EOF)
186 switch(ch) {
187 case '1':
188 num_months = 1;
189 break;
190 case '3':
191 num_months = 3;
192 break;
193 case 'm':
194 week1stday = 1;
195 break;
196 case 'j':
197 julian = 1;
198 break;
199 case 'y':
200 yflag = 1;
201 break;
202 case 'V':
203 printf(_("%s from %s\n"),
204 progname, util_linux_version);
205 return 0;
206 case '?':
207 default:
208 usage();
209 }
210 argc -= optind;
211 argv += optind;
212
213 month = year = 0;
214 switch(argc) {
215 case 2:
216 if ((month = atoi(*argv++)) < 1 || month > 12)
217 errx(1, _("illegal month value: use 1-12"));
218 /* FALLTHROUGH */
219 case 1:
220 if ((year = atoi(*argv)) < 1 || year > 9999)
221 errx(1, _("illegal year value: use 1-9999"));
222 break;
223 case 0:
224 (void)time(&now);
225 local_time = localtime(&now);
226 year = local_time->tm_year + 1900;
227 if (!yflag)
228 month = local_time->tm_mon + 1;
229 break;
230 default:
231 usage();
232 }
233 headers_init();
234
235 if (month && num_months == 1)
236 monthly(month, year);
237 else if (month && num_months == 3)
238 monthly3(month, year);
239 else if (julian)
240 j_yearly(year);
241 else
242 yearly(year);
243 exit(0);
244 }
245
246 #define DAY_LEN 3 /* 3 spaces per day */
247 #define J_DAY_LEN 4 /* 4 spaces per day */
248 #define WEEK_LEN 21 /* 7 days * 3 characters */
249 #define J_WEEK_LEN 28 /* 7 days * 4 characters */
250 #define HEAD_SEP 2 /* spaces between day headings */
251 #define J_HEAD_SEP 2
252
253 void headers_init(void)
254 {
255 int i, wd;
256
257 strcpy(day_headings,"");
258 strcpy(j_day_headings,"");
259
260 #ifdef HAVE_langinfo_h
261 # define weekday(wd) nl_langinfo(ABDAY_1+wd)
262 #else
263 # define weekday(wd) _time_info->abbrev_wkday[wd]
264 #endif
265
266 for(i = 0 ; i < 7 ; i++ ) {
267 wd = (i + week1stday) % 7;
268 strncat(day_headings,weekday(wd),2);
269 strncat(j_day_headings,weekday(wd),3);
270 if (strlen(weekday(wd)) == 2)
271 strcat(j_day_headings," ");
272 strcat(day_headings," ");
273 strcat(j_day_headings," ");
274 }
275
276 #undef weekday
277
278 for (i = 0; i < 12; i++) {
279 #ifdef HAVE_langinfo_h
280 full_month[i] = nl_langinfo(MON_1+i);
281 #else
282 full_month[i] = _time_info->full_month[i];
283 #endif
284 }
285 }
286
287 void
288 do_monthly(month, year, out)
289 int month, year;
290 struct fmt_st* out;
291 {
292 int col, row, len, days[MAXDAYS];
293 char *p, lineout[300];
294
295 day_array(month, year, days);
296 len = sprintf(lineout, "%s %d", full_month[month - 1], year);
297 (void)sprintf(out->s[0],"%*s%s",
298 ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "", lineout );
299 (void)sprintf(out->s[1],"%s",
300 julian ? j_day_headings : day_headings);
301 for (row = 0; row < 6; row++) {
302 for (col = 0, p = lineout; col < 7; col++,
303 p += julian ? J_DAY_LEN : DAY_LEN)
304 ascii_day(p, days[row * 7 + col]);
305 *p = '\0';
306 trim_trailing_spaces(lineout);
307 (void)sprintf(out->s[row+2],"%s", lineout);
308 }
309 }
310
311 void
312 monthly(month, year)
313 int month, year;
314 {
315 int i;
316 struct fmt_st out;
317
318 do_monthly(month, year, &out);
319 for ( i = 0; i < FMT_ST_LINES; i++ )
320 {
321 printf("%s\n", out.s[i]);
322 }
323 }
324
325 void
326 monthly3(month, year)
327 int month, year;
328 {
329 int i;
330 int width;
331 struct fmt_st out_prev;
332 struct fmt_st out_curm;
333 struct fmt_st out_next;
334 int prev_month, prev_year;
335 int next_month, next_year;
336
337 if ( month == 1 )
338 {
339 prev_month = 12;
340 prev_year = year - 1;
341 }
342 else
343 {
344 prev_month = month - 1;
345 prev_year = year;
346 }
347 if ( month == 12 )
348 {
349 next_month = 1;
350 next_year = year + 1;
351 }
352 else
353 {
354 next_month = month + 1;
355 next_year = year;
356 }
357
358 do_monthly(prev_month, prev_year, &out_prev);
359 do_monthly(month, year, &out_curm);
360 do_monthly(next_month, next_year, &out_next);
361 width = (julian ? J_WEEK_LEN : WEEK_LEN);
362 for ( i = 0; i < FMT_ST_LINES; i++ )
363 {
364 printf("%-*.*s %-*.*s %-*.*s\n",
365 width, width, out_prev.s[i],
366 width, width, out_curm.s[i],
367 width, width, out_next.s[i] );
368 }
369 }
370
371 void
372 j_yearly(year)
373 int year;
374 {
375 int col, *dp, i, month, row, which_cal;
376 int days[12][MAXDAYS];
377 char *p, lineout[80];
378
379 (void)sprintf(lineout, "%d", year);
380 center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0);
381 (void)printf("\n\n");
382 for (i = 0; i < 12; i++)
383 day_array(i + 1, year, days[i]);
384 (void)memset(lineout, ' ', sizeof(lineout) - 1);
385 lineout[sizeof(lineout) - 1] = '\0';
386 for (month = 0; month < 12; month += 2) {
387 center(full_month[month], J_WEEK_LEN, J_HEAD_SEP);
388 center(full_month[month + 1], J_WEEK_LEN, 0);
389 (void)printf("\n%s%*s%s\n", j_day_headings, J_HEAD_SEP, "",
390 j_day_headings);
391 for (row = 0; row < 6; row++) {
392 for (which_cal = 0; which_cal < 2; which_cal++) {
393 p = lineout + which_cal * (J_WEEK_LEN + 2);
394 dp = &days[month + which_cal][row * 7];
395 for (col = 0; col < 7; col++, p += J_DAY_LEN)
396 ascii_day(p, *dp++);
397 }
398 *p = '\0';
399 trim_trailing_spaces(lineout);
400 (void)printf("%s\n", lineout);
401 }
402 }
403 (void)printf("\n");
404 }
405
406 void
407 yearly(year)
408 int year;
409 {
410 int col, *dp, i, month, row, which_cal;
411 int days[12][MAXDAYS];
412 char *p, lineout[80];
413
414 (void)sprintf(lineout, "%d", year);
415 center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0);
416 (void)printf("\n\n");
417 for (i = 0; i < 12; i++)
418 day_array(i + 1, year, days[i]);
419 (void)memset(lineout, ' ', sizeof(lineout) - 1);
420 lineout[sizeof(lineout) - 1] = '\0';
421 for (month = 0; month < 12; month += 3) {
422 center(full_month[month], WEEK_LEN, HEAD_SEP);
423 center(full_month[month + 1], WEEK_LEN, HEAD_SEP);
424 center(full_month[month + 2], WEEK_LEN, 0);
425 (void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP,
426 "", day_headings, HEAD_SEP, "", day_headings);
427 for (row = 0; row < 6; row++) {
428 for (which_cal = 0; which_cal < 3; which_cal++) {
429 p = lineout + which_cal * (WEEK_LEN + 2);
430 dp = &days[month + which_cal][row * 7];
431 for (col = 0; col < 7; col++, p += DAY_LEN)
432 ascii_day(p, *dp++);
433 }
434 *p = '\0';
435 trim_trailing_spaces(lineout);
436 (void)printf("%s\n", lineout);
437 }
438 }
439 (void)printf("\n");
440 }
441
442 /*
443 * day_array --
444 * Fill in an array of 42 integers with a calendar. Assume for a moment
445 * that you took the (maximum) 6 rows in a calendar and stretched them
446 * out end to end. You would have 42 numbers or spaces. This routine
447 * builds that array for any month from Jan. 1 through Dec. 9999.
448 */
449 void
450 day_array(month, year, days)
451 int month, year;
452 int *days;
453 {
454 int day, dw, dm;
455 int *d_sep1752;
456
457 if (month == 9 && year == 1752) {
458 d_sep1752 = julian ? j_sep1752 : sep1752;
459 memcpy(days, d_sep1752 + week1stday, MAXDAYS * sizeof(int));
460 return;
461 }
462 memcpy(days, empty, MAXDAYS * sizeof(int));
463 dm = days_in_month[leap_year(year)][month];
464 dw = (day_in_week(1, month, year) - week1stday + 7) % 7;
465 day = julian ? day_in_year(1, month, year) : 1;
466 while (dm--)
467 days[dw++] = day++;
468 }
469
470 /*
471 * day_in_year --
472 * return the 1 based day number within the year
473 */
474 int
475 day_in_year(day, month, year)
476 int day, month, year;
477 {
478 int i, leap;
479
480 leap = leap_year(year);
481 for (i = 1; i < month; i++)
482 day += days_in_month[leap][i];
483 return (day);
484 }
485
486 /*
487 * day_in_week
488 * return the 0 based day number for any date from 1 Jan. 1 to
489 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
490 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
491 * missing days.
492 */
493 int
494 day_in_week(day, month, year)
495 int day, month, year;
496 {
497 long temp;
498
499 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
500 + day_in_year(day, month, year);
501 if (temp < FIRST_MISSING_DAY)
502 return ((temp - 1 + SATURDAY) % 7);
503 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
504 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
505 return (THURSDAY);
506 }
507
508 void
509 ascii_day(p, day)
510 char *p;
511 int day;
512 {
513 int display, val;
514 static char *aday[] = {
515 "",
516 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
517 " 8", " 9", "10", "11", "12", "13", "14",
518 "15", "16", "17", "18", "19", "20", "21",
519 "22", "23", "24", "25", "26", "27", "28",
520 "29", "30", "31",
521 };
522
523 if (day == SPACE) {
524 memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
525 return;
526 }
527 if (julian) {
528 if ((val = day / 100)) {
529 day %= 100;
530 *p++ = val + '0';
531 display = 1;
532 } else {
533 *p++ = ' ';
534 display = 0;
535 }
536 val = day / 10;
537 if (val || display)
538 *p++ = val + '0';
539 else
540 *p++ = ' ';
541 *p++ = day % 10 + '0';
542 } else {
543 *p++ = aday[day][0];
544 *p++ = aday[day][1];
545 }
546 *p = ' ';
547 }
548
549 void
550 trim_trailing_spaces(s)
551 char *s;
552 {
553 char *p;
554
555 for (p = s; *p; ++p)
556 continue;
557 while (p > s && isspace(*--p))
558 continue;
559 if (p > s)
560 ++p;
561 *p = '\0';
562 }
563
564 void
565 center(str, len, separate)
566 const char *str;
567 int len;
568 int separate;
569 {
570
571 len -= strlen(str);
572 (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
573 if (separate)
574 (void)printf("%*s", separate, "");
575 }
576
577 void
578 usage()
579 {
580
581 (void)fprintf(stderr, _("usage: cal [-mjyV] [[month] year]\n"));
582 exit(1);
583 }