]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
cal: simplify colormode parsing
[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 else
100 return ret;
101 }
102
103 #elif defined(HAVE_LIBTERMCAP)
104 # include <termcap.h>
105
106 char termbuffer[4096];
107 char tcbuffer[4096];
108 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 else
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 const char *term="";
140 const char *Senter="", *Sexit="";/* enter and exit standout mode */
141 int Slen; /* strlen of Senter+Sexit */
142 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 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 char day_headings[J_WEEK_LEN * 6 + 1];
227 /* weekstart = 1 => " M Tu W Th F S S " */
228 const char *full_month[MONTHS_IN_YEAR];
229
230 /* 0 => sunday, 1 => monday */
231 int weekstart = SUNDAY;
232 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, int);
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
251 main(int argc, char **argv) {
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
451 do_monthly(int day, int month, long year, struct fmt_st *out, int header_hint) {
452 int col, row, days[MAXDAYS];
453 char *p, lineout[FMT_ST_CHARS];
454 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
455 int pos = 0;
456
457 day_array(day, month, year, days);
458
459 /*
460 * %s is the month name, %d the year number.
461 * you can change the order and/or add something here; eg for
462 * Basque the translation should be: "%2$dko %1$s", and
463 * the Vietnamese should be "%s na(m %d", etc.
464 */
465 if (header_hint < 0)
466 header_hint = two_header_lines(month, year);
467 if (header_hint) {
468 snprintf(lineout, sizeof(lineout), _("%s"), full_month[month - 1]);
469 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
470 pos++;
471 snprintf(lineout, sizeof(lineout), _("%lu"), year);
472 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
473 pos++;
474 } else {
475 snprintf(lineout, sizeof(lineout), _("%s %lu"),
476 full_month[month - 1], year);
477 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
478 pos++;
479 }
480
481 snprintf(out->s[pos++], FMT_ST_CHARS, "%s", day_headings);
482 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
483 int has_hl = 0;
484 for (col = 0, p = lineout; col < DAYS_IN_WEEK; col++) {
485 int xd = days[row * DAYS_IN_WEEK + col];
486 if (xd != SPACE && (xd & TODAY_FLAG))
487 has_hl = 1;
488 p = ascii_day(p, xd);
489 }
490 *p = '\0';
491 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
492 if (has_hl)
493 Hrow = out->s[row+pos];
494 }
495 pos += row;
496 return pos;
497 }
498
499 static void
500 monthly(int day, int month, long year) {
501 int i, rows;
502 struct fmt_st out;
503
504 rows = do_monthly(day, month, year, &out, -1);
505 for (i = 0; i < rows; i++) {
506 my_putstring(out.s[i]);
507 my_putstring("\n");
508 }
509 }
510
511 static int
512 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), "%lu", 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
526 monthly3(int day, int month, long year) {
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
590 yearly(int day, long year, int julian) {
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), "%lu", 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 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
615 center(full_month[month], week_len - 1, sep_len + 1);
616 if (julian) {
617 center(full_month[month + 1], week_len - 1, 0);
618 } else {
619 center(full_month[month + 1], week_len - 1, sep_len + 1);
620 center(full_month[month + 2], week_len - 1, 0);
621 }
622 if (julian)
623 snprintf(lineout, sizeof(lineout),
624 "\n%s%*s %s\n", day_headings, sep_len, "", day_headings);
625 else
626 snprintf(lineout, sizeof(lineout),
627 "\n%s%*s %s%*s %s\n", day_headings, sep_len,
628 "", day_headings, sep_len, "", day_headings);
629 my_putstring(lineout);
630 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
631 p = lineout;
632 for (which_cal = 0; which_cal < maxrow; which_cal++) {
633 dp = &days[month + which_cal][row * DAYS_IN_WEEK];
634 for (col = 0; col < DAYS_IN_WEEK; col++)
635 p = ascii_day(p, *dp++);
636 p += sprintf(p, " ");
637 }
638 *p = '\0';
639 my_putstring(lineout);
640 my_putstring("\n");
641 }
642 }
643 my_putstring("\n");
644 }
645
646 /*
647 * day_array --
648 * Fill in an array of 42 integers with a calendar. Assume for a moment
649 * that you took the (maximum) 6 rows in a calendar and stretched them
650 * out end to end. You would have 42 numbers or spaces. This routine
651 * builds that array for any month from Jan. 1 through Dec. 9999.
652 */
653 static void
654 day_array(int day, int month, long year, int *days) {
655 int julday, daynum, dw, dm;
656 int *sep1752;
657
658 memcpy(days, empty, MAXDAYS * sizeof(int));
659 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
660 sep1752 = julian ? j_sep1752 : d_sep1752;
661 memcpy(days, sep1752 + weekstart,
662 ((MAXDAYS / 2) - weekstart) * sizeof(int));
663 for (dm = 0; dm < MAXDAYS / 2; dm++)
664 if (j_sep1752[dm] == day)
665 days[dm] |= TODAY_FLAG;
666 return;
667 }
668 dm = days_in_month[leap_year(year)][month];
669 dw = (day_in_week(1, month, year) - weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
670 julday = day_in_year(1, month, year);
671 daynum = julian ? julday : 1;
672 while (dm--) {
673 days[dw] = daynum++;
674 if (julday++ == day)
675 days[dw] |= TODAY_FLAG;
676 dw++;
677 }
678 }
679
680 /*
681 * day_in_year --
682 * return the 1 based day number within the year
683 */
684 static int
685 day_in_year(int day, int month, long year) {
686 int i, leap;
687
688 leap = leap_year(year);
689 for (i = 1; i < month; i++)
690 day += days_in_month[leap][i];
691 return day;
692 }
693
694 /*
695 * day_in_week
696 * return the 0 based day number for any date from 1 Jan. 1 to
697 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
698 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
699 * during the period of 11 days.
700 */
701 static int
702 day_in_week(int d, int m, int 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 (1752 < y || (y == 1752 && 9 < m) || (y == 1752 && m == 9 && 13 < d))
717 return (y + (y / 4) - (y / 100) + (y / 400) + reform[m - 1] +
718 d) % 7;
719 if (y < 1752 || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3))
720 return (y + y / 4 + old[m - 1] + d) % 7;
721 return NONEDAY;
722 }
723
724 static char *
725 ascii_day(char *p, int day) {
726 int display, val;
727 int highlight = 0;
728 static char *aday[] = {
729 "",
730 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
731 " 8", " 9", "10", "11", "12", "13", "14",
732 "15", "16", "17", "18", "19", "20", "21",
733 "22", "23", "24", "25", "26", "27", "28",
734 "29", "30", "31",
735 };
736
737 if (day == SPACE) {
738 int len = julian ? J_DAY_LEN : DAY_LEN;
739 memset(p, ' ', len);
740 return p+len;
741 }
742 if (day & TODAY_FLAG) {
743 day &= ~TODAY_FLAG;
744 p += sprintf(p, "%s", Senter);
745 highlight = 1;
746 }
747 if (julian) {
748 if ((val = day / 100)) {
749 day %= 100;
750 *p++ = val + '0';
751 display = 1;
752 } else {
753 *p++ = ' ';
754 display = 0;
755 }
756 val = day / 10;
757 if (val || display)
758 *p++ = val + '0';
759 else
760 *p++ = ' ';
761 *p++ = day % 10 + '0';
762 } else {
763 *p++ = aday[day][0];
764 *p++ = aday[day][1];
765 }
766 if (highlight)
767 p += sprintf(p, "%s", Sexit);
768 *p++ = ' ';
769 return p;
770 }
771
772 /*
773 * Center string, handling multibyte characters appropriately.
774 * In addition if the string is too large for the width it's truncated.
775 * The number of trailing spaces may be 1 less than the number of leading spaces.
776 */
777 static int
778 center_str(const char* src, char* dest, size_t dest_size, size_t width)
779 {
780 return mbsalign(src, dest, dest_size, &width,
781 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
782 }
783
784 static void
785 center(const char *str, size_t len, int separate)
786 {
787 char lineout[FMT_ST_CHARS];
788
789 center_str(str, lineout, ARRAY_SIZE(lineout), len);
790 my_putstring(lineout);
791
792 if (separate) {
793 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
794 my_putstring(lineout);
795 }
796 }
797
798 static void __attribute__ ((__noreturn__)) usage(FILE * out)
799 {
800 fputs(USAGE_HEADER, out);
801 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
802 fputs(USAGE_OPTIONS, out);
803 fputs(_(" -1, --one show only current month (default)\n"), out);
804 fputs(_(" -3, --three show previous, current and next month\n"), out);
805 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
806 fputs(_(" -m, --monday Monday as first day of week\n"), out);
807 fputs(_(" -j, --julian output Julian dates\n"), out);
808 fputs(_(" -y, --year show whole current year\n"), out);
809 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
810 fputs(USAGE_SEPARATOR, out);
811 fputs(USAGE_HELP, out);
812 fputs(USAGE_VERSION, out);
813 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
814 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
815 }