]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
37aa1b1de9cf230195189d692cd4ec6d70fdf224
[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@pld.ORG.PL>
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 * 2001-05-07 Pablo Saratxaga <pablo@mandrakesoft.com>
50 * Fixed the bugs with multi-byte charset (zg: cjk, utf-8)
51 * displaying. made the 'month year' ("%s %d") header translatable
52 * so it can be adapted to conventions used by different languages
53 * added support to read "first_weekday" locale information
54 * still to do: support for 'cal_direction' (will require a major
55 * rewrite of the displaying) and proper handling of RTL scripts
56 */
57
58 #include <sys/types.h>
59
60 #include <ctype.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <time.h>
65 #include <unistd.h>
66 #include <locale.h>
67 #include "errs.h"
68 #include "nls.h"
69 #include "../defines.h"
70
71 #ifdef HAVE_langinfo_h
72 # include <langinfo.h>
73 #else
74 # include <localeinfo.h> /* libc4 only */
75 #endif
76
77 #include "widechar.h"
78
79 /* allow compile-time define to over-ride default */
80 #ifndef NUM_MONTHS
81 #define NUM_MONTHS 1
82 #endif
83
84 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
85 #error NUM_MONTHS must be 1 or 3
86 #endif
87
88 #define THURSDAY 4 /* for reformation */
89 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
90
91 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
92 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
93
94 #define MAXDAYS 43 /* max slots in a month array */
95 #define SPACE -1 /* used in day array */
96
97 static int days_in_month[2][13] = {
98 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
99 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
100 };
101
102 int sep1752[MAXDAYS] = {
103 SPACE, SPACE, 1, 2, 14, 15, 16,
104 17, 18, 19, 20, 21, 22, 23,
105 24, 25, 26, 27, 28, 29, 30,
106 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
107 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
108 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
109 SPACE
110 }, j_sep1752[MAXDAYS] = {
111 SPACE, SPACE, 245, 246, 258, 259, 260,
112 261, 262, 263, 264, 265, 266, 267,
113 268, 269, 270, 271, 272, 273, 274,
114 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
115 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
116 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
117 SPACE
118 }, empty[MAXDAYS] = {
119 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
120 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
121 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
122 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
123 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
124 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
125 SPACE
126 };
127
128 #define DAY_LEN 3 /* 3 spaces per day */
129 #define J_DAY_LEN 4 /* 4 spaces per day */
130 #define WEEK_LEN 21 /* 7 days * 3 characters */
131 #define J_WEEK_LEN 28 /* 7 days * 4 characters */
132 #define HEAD_SEP 2 /* spaces between day headings */
133 #define J_HEAD_SEP 2
134
135 /* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
136 char day_headings[WEEK_LEN*6+1];
137 /* week1stday = 1 => " M Tu W Th F S S " */
138 char j_day_headings[J_WEEK_LEN*6+1];
139 /* week1stday = 1 => " M Tu W Th F S S " */
140 const char *full_month[12];
141
142 /* leap year -- account for gregorian reformation in 1752 */
143 #define leap_year(yr) \
144 ((yr) <= 1752 ? !((yr) % 4) : \
145 (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400))
146
147 /* number of centuries since 1700, not inclusive */
148 #define centuries_since_1700(yr) \
149 ((yr) > 1700 ? (yr) / 100 - 17 : 0)
150
151 /* number of centuries since 1700 whose modulo of 400 is 0 */
152 #define quad_centuries_since_1700(yr) \
153 ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
154
155 /* number of leap years between year 1 and this year, not inclusive */
156 #define leap_years_since_year_1(yr) \
157 ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
158
159 /* 0 => sunday (default), 1 => monday */
160 int week1stday=0;
161 int julian;
162
163 #define FMT_ST_LINES 8
164 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
165 struct fmt_st
166 {
167 char s[FMT_ST_LINES][FMT_ST_CHARS];
168 };
169
170 void ascii_day __P((char *, int));
171 void center __P((const char *, int, int));
172 void day_array __P((int, int, int *));
173 int day_in_week __P((int, int, int));
174 int day_in_year __P((int, int, int));
175 void j_yearly __P((int));
176 void do_monthly __P((int, int, struct fmt_st*));
177 void monthly __P((int, int));
178 void monthly3 __P((int, int));
179 void trim_trailing_spaces __P((char *));
180 void usage __P((void));
181 void yearly __P((int));
182 void headers_init(void);
183 extern char *__progname;
184
185 int
186 main(int argc, char **argv) {
187 struct tm *local_time;
188 time_t now;
189 int ch, month, year, yflag;
190 char *progname, *p;
191 int num_months = NUM_MONTHS;
192
193 progname = argv[0];
194 if ((p = strrchr(progname, '/')) != NULL)
195 progname = p+1;
196 __progname = progname;
197
198 setlocale(LC_ALL, "");
199 bindtextdomain(PACKAGE, LOCALEDIR);
200 textdomain(PACKAGE);
201
202 #if 0 /* setting week1stday is against man page */
203 /*
204 * What *is* the first day of the week? Note that glibc does not
205 * provide any information today, it (almost) always answers Monday.
206 * Sunday is the Jewish and Christian tradition.
207 * Sometimes an answer is built into the language:
208 * German calls Wednesday "Mittwoch", so starts at Sunday.
209 * Portuguese calls Monday "segunda-feira", so starts at Sunday.
210 * Russian calls Friday "pyatnitsa", so starts at Monday.
211 * ISO 8601 decided to start at Monday.
212 *
213 * The traditional Unix cal utility starts at Sunday.
214 * We start at Sunday and have an option -m for starting at Monday.
215 *
216 * At some future time this may become -s for Sunday, -m for Monday,
217 * no option for glibc-determined locale-dependent version.
218 */
219 #ifdef HAVE_langinfo_h
220 week1stday = (int)(nl_langinfo(_NL_TIME_FIRST_WEEKDAY))[0];
221 #endif
222 #endif
223
224 yflag = 0;
225 while ((ch = getopt(argc, argv, "13mjyV")) != EOF)
226 switch(ch) {
227 case '1':
228 num_months = 1;
229 break;
230 case '3':
231 num_months = 3;
232 break;
233 case 'm':
234 week1stday = 1;
235 break;
236 case 'j':
237 julian = 1;
238 break;
239 case 'y':
240 yflag = 1;
241 break;
242 case 'V':
243 printf(_("%s from %s\n"),
244 progname, util_linux_version);
245 return 0;
246 case '?':
247 default:
248 usage();
249 }
250 argc -= optind;
251 argv += optind;
252
253 month = year = 0;
254 switch(argc) {
255 case 2:
256 if ((month = atoi(*argv++)) < 1 || month > 12)
257 errx(1, _("illegal month value: use 1-12"));
258 /* FALLTHROUGH */
259 case 1:
260 if ((year = atoi(*argv)) < 1 || year > 9999)
261 errx(1, _("illegal year value: use 1-9999"));
262 break;
263 case 0:
264 (void)time(&now);
265 local_time = localtime(&now);
266 year = local_time->tm_year + 1900;
267 if (!yflag)
268 month = local_time->tm_mon + 1;
269 break;
270 default:
271 usage();
272 }
273 headers_init();
274
275 if (month && num_months == 1)
276 monthly(month, year);
277 else if (month && num_months == 3)
278 monthly3(month, year);
279 else if (julian)
280 j_yearly(year);
281 else
282 yearly(year);
283 exit(0);
284 }
285
286 #ifndef ENABLE_WIDECHAR
287 static char *eos(char *s) {
288 while (s && *s)
289 s++;
290 return s;
291 }
292 #endif
293
294 void headers_init(void)
295 {
296 int i, wd;
297 #ifdef ENABLE_WIDECHAR
298 wchar_t day_headings_wc[22],j_day_headings_wc[29];
299 wchar_t wd_wc[10];
300 #endif
301
302 strcpy(day_headings,"");
303 strcpy(j_day_headings,"");
304 #ifdef ENABLE_WIDECHAR
305 wcscpy(day_headings_wc,L"");
306 wcscpy(j_day_headings_wc,L"");
307 #endif
308
309 #ifdef HAVE_langinfo_h
310 # define weekday(wd) nl_langinfo(ABDAY_1+wd)
311 #else
312 # define weekday(wd) _time_info->abbrev_wkday[wd]
313 #endif
314
315 for(i = 0 ; i < 7 ; i++ ) {
316 wd = (i + week1stday) % 7;
317 #ifdef ENABLE_WIDECHAR
318 mbstowcs(wd_wc,weekday(wd),10);
319 if (wcswidth(wd_wc,10) < 3)
320 wcscat(j_day_headings_wc,L" ");
321 if (wcswidth(wd_wc,10) < 2) {
322 wcscat(day_headings_wc, L" ");
323 wcscat(j_day_headings_wc, L" ");
324 }
325 wcsncat(day_headings_wc,wd_wc,2);
326 wcsncat(j_day_headings_wc,wd_wc,3);
327 wcscat(day_headings_wc, L" ");
328 wcscat(j_day_headings_wc, L" ");
329 #else
330 sprintf(eos(day_headings), "%2.2s ", weekday(wd));
331 sprintf(eos(j_day_headings), "%3.3s ", weekday(wd));
332 #endif
333 }
334
335 #ifdef ENABLE_WIDECHAR
336 wcstombs(day_headings,day_headings_wc,sizeof(day_headings));
337 wcstombs(j_day_headings,j_day_headings_wc,sizeof(j_day_headings));
338 #endif
339
340 #undef weekday
341
342 for (i = 0; i < 12; i++) {
343 #ifdef HAVE_langinfo_h
344 full_month[i] = nl_langinfo(MON_1+i);
345 #else
346 full_month[i] = _time_info->full_month[i];
347 #endif
348 }
349 }
350
351 void
352 do_monthly(month, year, out)
353 int month, year;
354 struct fmt_st* out;
355 {
356 int col, row, len, days[MAXDAYS];
357 char *p, lineout[300];
358 #ifdef ENABLE_WIDECHAR
359 wchar_t lineout_wc[300];
360 #endif
361
362 day_array(month, year, days);
363 /* %s is the month name, %d the year number.
364 * you can change the order and/or add something her; eg for
365 * Basque the translation should be: "%2$dko %1$s", and
366 * the Vietnamese should be "%s na(m %d", etc.
367 */
368 len = sprintf(lineout, _("%s %d"), full_month[month - 1], year);
369 #ifdef ENABLE_WIDECHAR
370 if (mbstowcs(lineout_wc,lineout,len) > 0) {
371 len = wcswidth(lineout_wc,len);
372 } else {
373 len = strlen(lineout);
374 }
375 #endif
376 (void)sprintf(out->s[0],"%*s%s",
377 ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "", lineout );
378 (void)sprintf(out->s[1],"%s",
379 julian ? j_day_headings : day_headings);
380 for (row = 0; row < 6; row++) {
381 for (col = 0, p = lineout; col < 7; col++,
382 p += julian ? J_DAY_LEN : DAY_LEN)
383 ascii_day(p, days[row * 7 + col]);
384 *p = '\0';
385 trim_trailing_spaces(lineout);
386 (void)sprintf(out->s[row+2],"%s", lineout);
387 }
388 }
389
390 void
391 monthly(month, year)
392 int month, year;
393 {
394 int i;
395 struct fmt_st out;
396
397 do_monthly(month, year, &out);
398 for ( i = 0; i < FMT_ST_LINES; i++ )
399 {
400 printf("%s\n", out.s[i]);
401 }
402 }
403
404 void
405 monthly3(month, year)
406 int month, year;
407 {
408 int i;
409 int width;
410 struct fmt_st out_prev;
411 struct fmt_st out_curm;
412 struct fmt_st out_next;
413 int prev_month, prev_year;
414 int next_month, next_year;
415
416 if ( month == 1 )
417 {
418 prev_month = 12;
419 prev_year = year - 1;
420 }
421 else
422 {
423 prev_month = month - 1;
424 prev_year = year;
425 }
426 if ( month == 12 )
427 {
428 next_month = 1;
429 next_year = year + 1;
430 }
431 else
432 {
433 next_month = month + 1;
434 next_year = year;
435 }
436
437 do_monthly(prev_month, prev_year, &out_prev);
438 do_monthly(month, year, &out_curm);
439 do_monthly(next_month, next_year, &out_next);
440 width = (julian ? J_WEEK_LEN : WEEK_LEN);
441 for ( i = 0; i < FMT_ST_LINES; i++ )
442 {
443 printf("%-*.*s %-*.*s %-*.*s\n",
444 width, width, out_prev.s[i],
445 width, width, out_curm.s[i],
446 width, width, out_next.s[i] );
447 }
448 }
449
450 void
451 j_yearly(year)
452 int year;
453 {
454 int col, *dp, i, month, row, which_cal;
455 int days[12][MAXDAYS];
456 char *p, lineout[80];
457
458 (void)sprintf(lineout, "%d", year);
459 center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0);
460 (void)printf("\n\n");
461 for (i = 0; i < 12; i++)
462 day_array(i + 1, year, days[i]);
463 (void)memset(lineout, ' ', sizeof(lineout) - 1);
464 lineout[sizeof(lineout) - 1] = '\0';
465 for (month = 0; month < 12; month += 2) {
466 center(full_month[month], J_WEEK_LEN, J_HEAD_SEP);
467 center(full_month[month + 1], J_WEEK_LEN, 0);
468 (void)printf("\n%s%*s%s\n", j_day_headings, J_HEAD_SEP, "",
469 j_day_headings);
470 for (row = 0; row < 6; row++) {
471 for (which_cal = 0; which_cal < 2; which_cal++) {
472 p = lineout + which_cal * (J_WEEK_LEN + 2);
473 dp = &days[month + which_cal][row * 7];
474 for (col = 0; col < 7; col++, p += J_DAY_LEN)
475 ascii_day(p, *dp++);
476 }
477 *p = '\0';
478 trim_trailing_spaces(lineout);
479 (void)printf("%s\n", lineout);
480 }
481 }
482 (void)printf("\n");
483 }
484
485 void
486 yearly(year)
487 int year;
488 {
489 int col, *dp, i, month, row, which_cal;
490 int days[12][MAXDAYS];
491 char *p, lineout[80];
492
493 (void)sprintf(lineout, "%d", year);
494 center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0);
495 (void)printf("\n\n");
496 for (i = 0; i < 12; i++)
497 day_array(i + 1, year, days[i]);
498 (void)memset(lineout, ' ', sizeof(lineout) - 1);
499 lineout[sizeof(lineout) - 1] = '\0';
500 for (month = 0; month < 12; month += 3) {
501 center(full_month[month], WEEK_LEN, HEAD_SEP);
502 center(full_month[month + 1], WEEK_LEN, HEAD_SEP);
503 center(full_month[month + 2], WEEK_LEN, 0);
504 (void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP,
505 "", day_headings, HEAD_SEP, "", day_headings);
506 for (row = 0; row < 6; row++) {
507 for (which_cal = 0; which_cal < 3; which_cal++) {
508 p = lineout + which_cal * (WEEK_LEN + 2);
509 dp = &days[month + which_cal][row * 7];
510 for (col = 0; col < 7; col++, p += DAY_LEN)
511 ascii_day(p, *dp++);
512 }
513 *p = '\0';
514 trim_trailing_spaces(lineout);
515 (void)printf("%s\n", lineout);
516 }
517 }
518 (void)printf("\n");
519 }
520
521 /*
522 * day_array --
523 * Fill in an array of 42 integers with a calendar. Assume for a moment
524 * that you took the (maximum) 6 rows in a calendar and stretched them
525 * out end to end. You would have 42 numbers or spaces. This routine
526 * builds that array for any month from Jan. 1 through Dec. 9999.
527 */
528 void
529 day_array(month, year, days)
530 int month, year;
531 int *days;
532 {
533 int day, dw, dm;
534 int *d_sep1752;
535
536 if (month == 9 && year == 1752) {
537 d_sep1752 = julian ? j_sep1752 : sep1752;
538 memcpy(days, d_sep1752 + week1stday, MAXDAYS * sizeof(int));
539 return;
540 }
541 memcpy(days, empty, MAXDAYS * sizeof(int));
542 dm = days_in_month[leap_year(year)][month];
543 dw = (day_in_week(1, month, year) - week1stday + 7) % 7;
544 day = julian ? day_in_year(1, month, year) : 1;
545 while (dm--)
546 days[dw++] = day++;
547 }
548
549 /*
550 * day_in_year --
551 * return the 1 based day number within the year
552 */
553 int
554 day_in_year(day, month, year)
555 int day, month, year;
556 {
557 int i, leap;
558
559 leap = leap_year(year);
560 for (i = 1; i < month; i++)
561 day += days_in_month[leap][i];
562 return (day);
563 }
564
565 /*
566 * day_in_week
567 * return the 0 based day number for any date from 1 Jan. 1 to
568 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
569 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
570 * missing days.
571 */
572 int
573 day_in_week(day, month, year)
574 int day, month, year;
575 {
576 long temp;
577
578 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
579 + day_in_year(day, month, year);
580 if (temp < FIRST_MISSING_DAY)
581 return ((temp - 1 + SATURDAY) % 7);
582 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
583 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
584 return (THURSDAY);
585 }
586
587 void
588 ascii_day(p, day)
589 char *p;
590 int day;
591 {
592 int display, val;
593 static char *aday[] = {
594 "",
595 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
596 " 8", " 9", "10", "11", "12", "13", "14",
597 "15", "16", "17", "18", "19", "20", "21",
598 "22", "23", "24", "25", "26", "27", "28",
599 "29", "30", "31",
600 };
601
602 if (day == SPACE) {
603 memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
604 return;
605 }
606 if (julian) {
607 if ((val = day / 100)) {
608 day %= 100;
609 *p++ = val + '0';
610 display = 1;
611 } else {
612 *p++ = ' ';
613 display = 0;
614 }
615 val = day / 10;
616 if (val || display)
617 *p++ = val + '0';
618 else
619 *p++ = ' ';
620 *p++ = day % 10 + '0';
621 } else {
622 *p++ = aday[day][0];
623 *p++ = aday[day][1];
624 }
625 *p = ' ';
626 }
627
628 void
629 trim_trailing_spaces(s)
630 char *s;
631 {
632 char *p;
633
634 for (p = s; *p; ++p)
635 continue;
636 while (p > s && isspace(*--p))
637 continue;
638 if (p > s)
639 ++p;
640 *p = '\0';
641 }
642
643 void
644 center(str, len, separate)
645 const char *str;
646 int len;
647 int separate;
648 {
649 #ifdef ENABLE_WIDECHAR
650 wchar_t str_wc[300];
651 int str_len;
652
653 if (mbstowcs(str_wc,str,300) > 0) {
654 str_len = wcswidth(str_wc,300);
655 } else {
656 str_len = strlen(str);
657 }
658 len -= str_len;
659 #else
660 len -= strlen(str);
661 #endif
662 (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
663 if (separate)
664 (void)printf("%*s", separate, "");
665 }
666
667 void
668 usage()
669 {
670
671 (void)fprintf(stderr, _("usage: cal [-mjyV] [[month] year]\n"));
672 exit(1);
673 }