]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
cal: fix few type mismatches
[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 <getopt.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <time.h>
66 #include <unistd.h>
67 #include <errno.h>
68
69 #include "c.h"
70 #include "closestream.h"
71 #include "colors.h"
72 #include "nls.h"
73 #include "mbsalign.h"
74 #include "strutils.h"
75
76 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
77 # ifdef HAVE_NCURSES_H
78 # include <ncurses.h>
79 # elif defined(HAVE_NCURSES_NCURSES_H)
80 # include <ncurses/ncurses.h>
81 # endif
82 # include <term.h>
83
84 static void my_setupterm(const char *term, int fildes, int *errret)
85 {
86 setupterm((char *)term, fildes, errret);
87 }
88
89 static void my_putstring(char *s)
90 {
91 putp(s);
92 }
93
94 static const char *my_tgetstr(char *s __attribute__((__unused__)), char *ss)
95 {
96 const char *ret = tigetstr(ss);
97 if (!ret || ret == (char *)-1)
98 return "";
99
100 return ret;
101 }
102
103 #elif defined(HAVE_LIBTERMCAP)
104 # include <termcap.h>
105
106 static char termbuffer[4096];
107 static char tcbuffer[4096];
108 static char *strbuf = termbuffer;
109
110 static void my_setupterm(const char *term, int fildes, int *errret)
111 {
112 *errret = tgetent(tcbuffer, term);
113 }
114
115 static void my_putstring(char *s)
116 {
117 tputs(s, 1, putchar);
118 }
119
120 static const char *my_tgetstr(char *s, char *ss __attribute__((__unused__)))
121 {
122 const char *ret = tgetstr(s, &strbuf);
123 if (!ret)
124 return "";
125
126 return ret;
127 }
128
129 #else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
130
131 static void my_putstring(char *s)
132 {
133 fputs(s, stdout);
134 }
135
136 #endif /* end of LIBTERMCAP / NCURSES */
137
138
139 static const char *term="";
140 static const char *Senter="", *Sexit="";/* enter and exit standout mode */
141 static int Slen; /* strlen of Senter+Sexit */
142 static char *Hrow; /* pointer to highlighted row in month */
143
144 #include "widechar.h"
145
146 /* allow compile-time define to over-ride default */
147 #ifndef NUM_MONTHS
148 # define NUM_MONTHS 1
149 #endif
150
151 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
152 # error NUM_MONTHS must be 1 or 3
153 #endif
154
155 enum {
156 SUNDAY = 0,
157 MONDAY,
158 TUESDAY,
159 WEDNESDAY,
160 THURSDAY,
161 FRIDAY,
162 SATURDAY,
163 DAYS_IN_WEEK,
164 NONEDAY
165 };
166
167 #define FIRST_WEEKDAY SATURDAY /* Jan 1st, 1 was a Saturday */
168 #define REFORMATION_YEAR 1752 /* Signed-off-by: Lord Chesterfield */
169 #define REFORMATION_MONTH 9 /* September */
170 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
171 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
172
173 #define DAYS_IN_YEAR 365 /* the common case, leap years are calculated */
174 #define MONTHS_IN_YEAR 12
175 #define DAYS_IN_MONTH 31
176 #define MAXDAYS 42 /* slots in a month array */
177 #define SPACE -1 /* used in day array */
178
179 #define SMALLEST_YEAR 1
180
181 #define DAY_LEN 3 /* 3 spaces per day */
182 #define WEEK_LEN (DAYS_IN_WEEK * DAY_LEN)
183 #define HEAD_SEP 2
184 #define MONTH_COLS 3 /* month columns in year view */
185
186 #define J_DAY_LEN 4 /* 4 spaces per day */
187 #define J_WEEK_LEN (DAYS_IN_WEEK * J_DAY_LEN)
188 #define J_HEAD_SEP 2
189 #define J_MONTH_COLS 2
190
191 #define TODAY_FLAG 0x400 /* flag day for highlighting */
192
193 #define FMT_ST_LINES 9
194 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
195 struct fmt_st
196 {
197 char s[FMT_ST_LINES][FMT_ST_CHARS];
198 };
199
200 static int days_in_month[2][13] = {
201 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
202 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
203 };
204
205 /* September 1752 is special, and has static assignments for both date
206 * and Julian representations. */
207 static int d_sep1752[MAXDAYS / 2] = {
208 SPACE, SPACE, 1, 2, 14, 15, 16,
209 17, 18, 19, 20, 21, 22, 23,
210 24, 25, 26, 27, 28, 29, 30
211 }, j_sep1752[MAXDAYS / 2] = {
212 SPACE, SPACE, 245, 246, 258, 259, 260,
213 261, 262, 263, 264, 265, 266, 267,
214 268, 269, 270, 271, 272, 273, 274
215 }, empty[MAXDAYS] = {
216 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
217 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
218 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
219 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
220 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
221 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE
222 };
223
224
225 /* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
226 static char day_headings[J_WEEK_LEN * 6 + 1];
227 /* weekstart = 1 => " M Tu W Th F S S " */
228 static const char *full_month[MONTHS_IN_YEAR];
229
230 /* 0 => sunday, 1 => monday */
231 static int weekstart = SUNDAY;
232 static int julian;
233
234 /* function prototypes */
235 static int leap_year(long year);
236 static char * ascii_day(char *, int);
237 static int center_str(const char* src, char* dest, size_t dest_size, size_t width);
238 static void center(const char *, size_t, int);
239 static void day_array(int, int, long, int *);
240 static int day_in_week(int, int, long);
241 static int day_in_year(int, int, long);
242 static void yearly(int, long, int);
243 static int do_monthly(int, int, long, struct fmt_st*, int);
244 static void monthly(int, int, long);
245 static int two_header_lines(int month, long year);
246 static void monthly3(int, int, long);
247 static void __attribute__ ((__noreturn__)) usage(FILE * out);
248 static void headers_init(int);
249
250 int main(int argc, char **argv)
251 {
252 struct tm *local_time;
253 time_t now;
254 int ch, day = 0, month = 0, yflag = 0;
255 long year;
256 int num_months = NUM_MONTHS;
257 int colormode = UL_COLORMODE_AUTO;
258
259 enum {
260 OPT_COLOR = CHAR_MAX + 1
261 };
262
263 static const struct option longopts[] = {
264 {"one", no_argument, NULL, '1'},
265 {"three", no_argument, NULL, '3'},
266 {"sunday", no_argument, NULL, 's'},
267 {"monday", no_argument, NULL, 'm'},
268 {"julian", no_argument, NULL, 'j'},
269 {"year", no_argument, NULL, 'y'},
270 {"color", optional_argument, NULL, OPT_COLOR},
271 {"version", no_argument, NULL, 'V'},
272 {"help", no_argument, NULL, 'h'},
273 {NULL, 0, NULL, 0}
274 };
275
276 setlocale(LC_ALL, "");
277 bindtextdomain(PACKAGE, LOCALEDIR);
278 textdomain(PACKAGE);
279 atexit(close_stdout);
280
281 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
282 if ((term = getenv("TERM"))) {
283 int ret;
284 my_setupterm(term, STDOUT_FILENO, &ret);
285 if (ret > 0) {
286 Senter = my_tgetstr("so","smso");
287 Sexit = my_tgetstr("se","rmso");
288 Slen = strlen(Senter) + strlen(Sexit);
289 }
290 }
291 #endif
292
293 /*
294 * The traditional Unix cal utility starts the week at Sunday,
295 * while ISO 8601 starts at Monday. We read the start day from
296 * the locale database, which can be overridden with the
297 * -s (Sunday) or -m (Monday) options.
298 */
299 #if HAVE_DECL__NL_TIME_WEEK_1STDAY
300 /*
301 * You need to use 2 locale variables to get the first day of the week.
302 * This is needed to support first_weekday=2 and first_workday=1 for
303 * the rare case where working days span across 2 weeks.
304 * This shell script shows the combinations and calculations involved:
305 *
306 * for LANG in en_US ru_RU fr_FR csb_PL POSIX; do
307 * printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday)
308 * date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w
309 * done
310 *
311 * en_US: 19971130 + 1 -1 = 0 #0 = sunday
312 * ru_RU: 19971130 + 2 -1 = 1
313 * fr_FR: 19971201 + 1 -1 = 1
314 * csb_PL: 19971201 + 2 -1 = 2
315 * POSIX: 19971201 + 7 -1 = 0
316 */
317 {
318 int wfd;
319 union { unsigned int word; char *string; } val;
320 val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY);
321
322 wfd = val.word;
323 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
324 weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % DAYS_IN_WEEK;
325 }
326 #endif
327
328 while ((ch = getopt_long(argc, argv, "13mjsyVh", longopts, NULL)) != -1)
329 switch(ch) {
330 case '1':
331 num_months = 1; /* default */
332 break;
333 case '3':
334 num_months = 3;
335 break;
336 case 's':
337 weekstart = SUNDAY; /* default */
338 break;
339 case 'm':
340 weekstart = MONDAY;
341 break;
342 case 'j':
343 julian = 1;
344 break;
345 case 'y':
346 yflag = 1;
347 break;
348 case OPT_COLOR:
349 if (optarg)
350 colormode = colormode_or_err(optarg,
351 _("unsupported color mode"));
352 break;
353 case 'V':
354 printf(UTIL_LINUX_VERSION);
355 return EXIT_SUCCESS;
356 case 'h':
357 usage(stdout);
358 case '?':
359 default:
360 usage(stderr);
361 }
362 argc -= optind;
363 argv += optind;
364
365 time(&now);
366 local_time = localtime(&now);
367
368 switch(argc) {
369 case 3:
370 day = strtos32_or_err(*argv++, _("illegal day value"));
371 if (day < 1 || DAYS_IN_MONTH < day)
372 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH);
373 /* FALLTHROUGH */
374 case 2:
375 month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
376 if (month < 1 || MONTHS_IN_YEAR < month)
377 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
378 /* FALLTHROUGH */
379 case 1:
380 year = strtol_or_err(*argv++, _("illegal year value"));
381 if (year < SMALLEST_YEAR)
382 errx(EXIT_FAILURE, _("illegal year value: use positive integer"));
383 if (day) {
384 int dm = days_in_month[leap_year(year)][month];
385 if (day > dm)
386 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
387 day = day_in_year(day, month, year);
388 } else if ((long) (local_time->tm_year + 1900) == year) {
389 day = local_time->tm_yday + 1;
390 }
391 if (!month)
392 yflag=1;
393 break;
394 case 0:
395 day = local_time->tm_yday + 1;
396 year = local_time->tm_year + 1900;
397 month = local_time->tm_mon + 1;
398 break;
399 default:
400 usage(stderr);
401 }
402 headers_init(julian);
403
404 if (!colors_init(colormode))
405 day = 0;
406
407 if (yflag)
408 yearly(day, year, julian);
409 else if (num_months == 1)
410 monthly(day, month, year);
411 else if (num_months == 3)
412 monthly3(day, month, year);
413
414 return EXIT_SUCCESS;
415 }
416
417 /* leap year -- account for gregorian reformation in 1752 */
418 static int leap_year(long year)
419 {
420 if (year <= REFORMATION_YEAR)
421 return !(year % 4);
422 else
423 return ( !(year % 4) && (year % 100) ) || !(year % 400);
424 }
425
426 static void headers_init(int julian)
427 {
428 int i, wd, spaces = julian ? J_DAY_LEN - 1 : DAY_LEN - 1;
429 char *cur_dh = day_headings;
430
431 for (i = 0; i < DAYS_IN_WEEK; i++) {
432 ssize_t space_left;
433 wd = (i + weekstart) % DAYS_IN_WEEK;
434
435 if (i)
436 strcat(cur_dh++, " ");
437 space_left =
438 sizeof(day_headings) - (cur_dh - day_headings);
439 if (space_left <= spaces)
440 break;
441 cur_dh +=
442 center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
443 space_left, spaces);
444 }
445
446 for (i = 0; i < MONTHS_IN_YEAR; i++)
447 full_month[i] = nl_langinfo(MON_1 + i);
448 }
449
450 static int do_monthly(int day, int month, long year,
451 struct fmt_st *out, int header_hint)
452 {
453 int col, row, days[MAXDAYS];
454 char *p, lineout[FMT_ST_CHARS];
455 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
456 int pos = 0;
457
458 day_array(day, month, year, days);
459
460 /*
461 * %s is the month name, %d the year number.
462 * you can change the order and/or add something here; eg for
463 * Basque the translation should be: "%2$dko %1$s", and
464 * the Vietnamese should be "%s na(m %d", etc.
465 */
466 if (header_hint < 0)
467 header_hint = two_header_lines(month, year);
468 if (header_hint) {
469 snprintf(lineout, sizeof(lineout), _("%s"), full_month[month - 1]);
470 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
471 pos++;
472 snprintf(lineout, sizeof(lineout), _("%ld"), year);
473 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
474 pos++;
475 } else {
476 snprintf(lineout, sizeof(lineout), _("%s %ld"),
477 full_month[month - 1], year);
478 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
479 pos++;
480 }
481
482 snprintf(out->s[pos++], FMT_ST_CHARS, "%s", day_headings);
483 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
484 int has_hl = 0;
485 for (col = 0, p = lineout; col < DAYS_IN_WEEK; col++) {
486 int xd = days[row * DAYS_IN_WEEK + col];
487 if (xd != SPACE && (xd & TODAY_FLAG))
488 has_hl = 1;
489 p = ascii_day(p, xd);
490 }
491 *p = '\0';
492 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
493 if (has_hl)
494 Hrow = out->s[row+pos];
495 }
496 pos += row;
497 return pos;
498 }
499
500 static void monthly(int day, int month, long year)
501 {
502 int i, rows;
503 struct fmt_st out;
504
505 rows = do_monthly(day, month, year, &out, -1);
506 for (i = 0; i < rows; i++) {
507 my_putstring(out.s[i]);
508 my_putstring("\n");
509 }
510 }
511
512 static int two_header_lines(int month, long year)
513 {
514 char lineout[FMT_ST_CHARS];
515 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
516 size_t len;
517 snprintf(lineout, sizeof(lineout), "%ld", year);
518 len = strlen(lineout);
519 len += strlen(full_month[month - 1]) + 1;
520 if (width < len)
521 return 1;
522 return 0;
523 }
524
525 static void monthly3(int day, int month, long year)
526 {
527 char lineout[FMT_ST_CHARS];
528 int i;
529 int width, rows, two_lines;
530 struct fmt_st out_prev;
531 struct fmt_st out_curm;
532 struct fmt_st out_next;
533 int prev_month, next_month;
534 long prev_year, next_year;
535
536 memset(&out_prev, 0, sizeof(struct fmt_st));
537 memset(&out_curm, 0, sizeof(struct fmt_st));
538 memset(&out_next, 0, sizeof(struct fmt_st));
539 if (month == 1) {
540 prev_month = MONTHS_IN_YEAR;
541 prev_year = year - 1;
542 } else {
543 prev_month = month - 1;
544 prev_year = year;
545 }
546 if (month == MONTHS_IN_YEAR) {
547 next_month = 1;
548 next_year = year + 1;
549 } else {
550 next_month = month + 1;
551 next_year = year;
552 }
553 two_lines = two_header_lines(prev_month, prev_year);
554 two_lines += two_header_lines(month, year);
555 two_lines += two_header_lines(next_month, next_year);
556 if (0 < two_lines)
557 rows = FMT_ST_LINES;
558 else
559 rows = FMT_ST_LINES - 1;
560 do_monthly(day, prev_month, prev_year, &out_prev, two_lines);
561 do_monthly(day, month, year, &out_curm, two_lines);
562 do_monthly(day, next_month, next_year, &out_next, two_lines);
563
564 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
565 for (i = 0; i < (two_lines ? 3 : 2); i++) {
566 snprintf(lineout, sizeof(lineout),
567 "%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
568 my_putstring(lineout);
569 }
570 for (i = two_lines ? 3 : 2; i < rows; i++) {
571 int w1, w2, w3;
572 w1 = w2 = w3 = width;
573
574 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
575 /* adjust width to allow for non printable characters */
576 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
577 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
578 w3 += (out_next.s[i] == Hrow ? Slen : 0);
579 #endif
580 snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n",
581 w1, out_prev.s[i],
582 w2, out_curm.s[i],
583 w3, out_next.s[i]);
584
585 my_putstring(lineout);
586 }
587 }
588
589 static void yearly(int day, long year, int julian)
590 {
591 int col, *dp, i, month, row, which_cal;
592 int maxrow, sep_len, week_len;
593 int days[MONTHS_IN_YEAR][MAXDAYS];
594 char *p, lineout[100];
595
596 if (julian) {
597 maxrow = J_MONTH_COLS;
598 sep_len = J_HEAD_SEP;
599 week_len = J_WEEK_LEN;
600 } else {
601 maxrow = MONTH_COLS;
602 sep_len = HEAD_SEP;
603 week_len = WEEK_LEN;
604 }
605 snprintf(lineout, sizeof(lineout), "%ld", year);
606 /* 2013-04-28: The -1 near sep_len makes year header to be
607 * aligned exactly how it has been aligned for long time, but it
608 * is unexplainable. */
609 center(lineout, (week_len + sep_len) * maxrow - sep_len - 1, 0);
610 my_putstring("\n\n");
611
612 for (i = 0; i < MONTHS_IN_YEAR; i++)
613 day_array(day, i + 1, year, days[i]);
614
615 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
616 center(full_month[month], week_len - 1, sep_len + 1);
617 if (julian) {
618 center(full_month[month + 1], week_len - 1, 0);
619 } else {
620 center(full_month[month + 1], week_len - 1, sep_len + 1);
621 center(full_month[month + 2], week_len - 1, 0);
622 }
623 if (julian)
624 snprintf(lineout, sizeof(lineout),
625 "\n%s%*s %s\n", day_headings, sep_len, "", day_headings);
626 else
627 snprintf(lineout, sizeof(lineout),
628 "\n%s%*s %s%*s %s\n", day_headings, sep_len,
629 "", day_headings, sep_len, "", day_headings);
630 my_putstring(lineout);
631 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
632 p = lineout;
633 for (which_cal = 0; which_cal < maxrow; which_cal++) {
634 dp = &days[month + which_cal][row * DAYS_IN_WEEK];
635 for (col = 0; col < DAYS_IN_WEEK; col++)
636 p = ascii_day(p, *dp++);
637 p += sprintf(p, " ");
638 }
639 *p = '\0';
640 my_putstring(lineout);
641 my_putstring("\n");
642 }
643 }
644 my_putstring("\n");
645 }
646
647 /*
648 * day_array --
649 * Fill in an array of 42 integers with a calendar. Assume for a moment
650 * that you took the (maximum) 6 rows in a calendar and stretched them
651 * out end to end. You would have 42 numbers or spaces. This routine
652 * builds that array for any month from Jan. 1 through Dec. 9999.
653 */
654 static void day_array(int day, int month, long year, int *days)
655 {
656 int julday, daynum, dw, dm;
657 int *sep1752;
658
659 memcpy(days, empty, MAXDAYS * sizeof(int));
660 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
661 sep1752 = julian ? j_sep1752 : d_sep1752;
662 memcpy(days, sep1752 + weekstart,
663 ((MAXDAYS / 2) - weekstart) * sizeof(int));
664 for (dm = 0; dm < MAXDAYS / 2; dm++)
665 if (j_sep1752[dm] == day)
666 days[dm] |= TODAY_FLAG;
667 return;
668 }
669 dm = days_in_month[leap_year(year)][month];
670 dw = (day_in_week(1, month, year) - weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
671 julday = day_in_year(1, month, year);
672 daynum = julian ? julday : 1;
673 while (dm--) {
674 days[dw] = daynum++;
675 if (julday++ == day)
676 days[dw] |= TODAY_FLAG;
677 dw++;
678 }
679 }
680
681 /*
682 * day_in_year --
683 * return the 1 based day number within the year
684 */
685 static int day_in_year(int day, int month, long year)
686 {
687 int i, leap;
688
689 leap = leap_year(year);
690 for (i = 1; i < month; i++)
691 day += days_in_month[leap][i];
692 return day;
693 }
694
695 /*
696 * day_in_week
697 * return the 0 based day number for any date from 1 Jan. 1 to
698 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
699 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
700 * during the period of 11 days.
701 */
702 static int day_in_week(int d, int m, long y)
703 {
704 static const int reform[] = {
705 SUNDAY, WEDNESDAY, TUESDAY, FRIDAY, SUNDAY, WEDNESDAY,
706 FRIDAY, MONDAY, THURSDAY, SATURDAY, TUESDAY, THURSDAY
707 };
708 static const int old[] = {
709 FRIDAY, MONDAY, SUNDAY, WEDNESDAY, FRIDAY, MONDAY,
710 WEDNESDAY, SATURDAY, TUESDAY, THURSDAY, SUNDAY, TUESDAY
711 };
712 if (y != 1753)
713 y -= m < 3;
714 else
715 y -= (m < 3) + 14;
716 if (REFORMATION_YEAR < y
717 || (y == REFORMATION_YEAR && 9 < m)
718 || (y == REFORMATION_YEAR && m == 9 && 13 < d))
719 return (y + (y / 4) - (y / 100) + (y / 400) + reform[m - 1] +
720 d) % 7;
721 if (y < REFORMATION_YEAR
722 || (y == REFORMATION_YEAR && m < 9)
723 || (y == REFORMATION_YEAR && m == 9 && d < 3))
724 return (y + y / 4 + old[m - 1] + d) % 7;
725 return NONEDAY;
726 }
727
728 static char *ascii_day(char *p, int day)
729 {
730 int display, val;
731 int highlight = 0;
732 static char *aday[] = {
733 "",
734 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
735 " 8", " 9", "10", "11", "12", "13", "14",
736 "15", "16", "17", "18", "19", "20", "21",
737 "22", "23", "24", "25", "26", "27", "28",
738 "29", "30", "31",
739 };
740
741 if (day == SPACE) {
742 int len = julian ? J_DAY_LEN : DAY_LEN;
743 memset(p, ' ', len);
744 return p+len;
745 }
746 if (day & TODAY_FLAG) {
747 day &= ~TODAY_FLAG;
748 p += sprintf(p, "%s", Senter);
749 highlight = 1;
750 }
751 if (julian) {
752 if ((val = day / 100)) {
753 day %= 100;
754 *p++ = val + '0';
755 display = 1;
756 } else {
757 *p++ = ' ';
758 display = 0;
759 }
760 val = day / 10;
761 if (val || display)
762 *p++ = val + '0';
763 else
764 *p++ = ' ';
765 *p++ = day % 10 + '0';
766 } else {
767 *p++ = aday[day][0];
768 *p++ = aday[day][1];
769 }
770 if (highlight)
771 p += sprintf(p, "%s", Sexit);
772 *p++ = ' ';
773 return p;
774 }
775
776 /*
777 * Center string, handling multibyte characters appropriately.
778 * In addition if the string is too large for the width it's truncated.
779 * The number of trailing spaces may be 1 less than the number of leading spaces.
780 */
781 static int center_str(const char* src, char* dest,
782 size_t dest_size, size_t width)
783 {
784 return mbsalign(src, dest, dest_size, &width,
785 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
786 }
787
788 static void center(const char *str, size_t len, int separate)
789 {
790 char lineout[FMT_ST_CHARS];
791
792 center_str(str, lineout, ARRAY_SIZE(lineout), len);
793 my_putstring(lineout);
794
795 if (separate) {
796 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
797 my_putstring(lineout);
798 }
799 }
800
801 static void __attribute__ ((__noreturn__)) usage(FILE * out)
802 {
803 fputs(USAGE_HEADER, out);
804 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
805
806 fputs(USAGE_OPTIONS, out);
807 fputs(_(" -1, --one show only current month (default)\n"), out);
808 fputs(_(" -3, --three show previous, current and next month\n"), out);
809 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
810 fputs(_(" -m, --monday Monday as first day of week\n"), out);
811 fputs(_(" -j, --julian output Julian dates\n"), out);
812 fputs(_(" -y, --year show whole current year\n"), out);
813 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
814
815 fputs(USAGE_SEPARATOR, out);
816 fputs(USAGE_HELP, out);
817 fputs(USAGE_VERSION, out);
818 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
819
820 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
821 }