]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
Merge branch 'cal-fourth' of git://github.com/kerolasa/lelux-utiliteetit
[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 char *p = *optarg == '=' ? optarg + 1 : optarg;
351 colormode = colormode_from_string(p);
352 if (colormode < 0)
353 errx(EXIT_FAILURE, _("unsupported color mode: '%s'"), p);
354 }
355 break;
356 case 'V':
357 printf(UTIL_LINUX_VERSION);
358 return EXIT_SUCCESS;
359 case 'h':
360 usage(stdout);
361 case '?':
362 default:
363 usage(stderr);
364 }
365 argc -= optind;
366 argv += optind;
367
368 time(&now);
369 local_time = localtime(&now);
370
371 switch(argc) {
372 case 3:
373 day = strtos32_or_err(*argv++, _("illegal day value"));
374 if (day < 1 || DAYS_IN_MONTH < day)
375 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH);
376 /* FALLTHROUGH */
377 case 2:
378 month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
379 if (month < 1 || MONTHS_IN_YEAR < month)
380 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
381 /* FALLTHROUGH */
382 case 1:
383 year = strtol_or_err(*argv++, _("illegal year value"));
384 if (year < SMALLEST_YEAR)
385 errx(EXIT_FAILURE, _("illegal year value: use positive integer"));
386 if (day) {
387 int dm = days_in_month[leap_year(year)][month];
388 if (day > dm)
389 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
390 day = day_in_year(day, month, year);
391 } else if ((long) (local_time->tm_year + 1900) == year) {
392 day = local_time->tm_yday + 1;
393 }
394 if (!month)
395 yflag=1;
396 break;
397 case 0:
398 day = local_time->tm_yday + 1;
399 year = local_time->tm_year + 1900;
400 month = local_time->tm_mon + 1;
401 break;
402 default:
403 usage(stderr);
404 }
405 headers_init(julian);
406
407 if (!colors_init(colormode))
408 day = 0;
409
410 if (yflag)
411 yearly(day, year, julian);
412 else if (num_months == 1)
413 monthly(day, month, year);
414 else if (num_months == 3)
415 monthly3(day, month, year);
416
417 return EXIT_SUCCESS;
418 }
419
420 /* leap year -- account for gregorian reformation in 1752 */
421 static int leap_year(long year)
422 {
423 if (year <= REFORMATION_YEAR)
424 return !(year % 4);
425 else
426 return ( !(year % 4) && (year % 100) ) || !(year % 400);
427 }
428
429 static void headers_init(int julian)
430 {
431 int i, wd, spaces = julian ? J_DAY_LEN - 1 : DAY_LEN - 1;
432 char *cur_dh = day_headings;
433
434 for (i = 0; i < DAYS_IN_WEEK; i++) {
435 ssize_t space_left;
436 wd = (i + weekstart) % DAYS_IN_WEEK;
437
438 if (i)
439 strcat(cur_dh++, " ");
440 space_left =
441 sizeof(day_headings) - (cur_dh - day_headings);
442 if (space_left <= spaces)
443 break;
444 cur_dh +=
445 center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
446 space_left, spaces);
447 }
448
449 for (i = 0; i < MONTHS_IN_YEAR; i++)
450 full_month[i] = nl_langinfo(MON_1 + i);
451 }
452
453 static int
454 do_monthly(int day, int month, long year, struct fmt_st *out, int header_hint) {
455 int col, row, days[MAXDAYS];
456 char *p, lineout[FMT_ST_CHARS];
457 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
458 int pos = 0;
459
460 day_array(day, month, year, days);
461
462 /*
463 * %s is the month name, %d the year number.
464 * you can change the order and/or add something here; eg for
465 * Basque the translation should be: "%2$dko %1$s", and
466 * the Vietnamese should be "%s na(m %d", etc.
467 */
468 if (header_hint < 0)
469 header_hint = two_header_lines(month, year);
470 if (header_hint) {
471 snprintf(lineout, sizeof(lineout), _("%s"), full_month[month - 1]);
472 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
473 pos++;
474 snprintf(lineout, sizeof(lineout), _("%lu"), year);
475 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
476 pos++;
477 } else {
478 snprintf(lineout, sizeof(lineout), _("%s %lu"),
479 full_month[month - 1], year);
480 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
481 pos++;
482 }
483
484 snprintf(out->s[pos++], FMT_ST_CHARS, "%s", day_headings);
485 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
486 int has_hl = 0;
487 for (col = 0, p = lineout; col < DAYS_IN_WEEK; col++) {
488 int xd = days[row * DAYS_IN_WEEK + col];
489 if (xd != SPACE && (xd & TODAY_FLAG))
490 has_hl = 1;
491 p = ascii_day(p, xd);
492 }
493 *p = '\0';
494 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
495 if (has_hl)
496 Hrow = out->s[row+pos];
497 }
498 pos += row;
499 return pos;
500 }
501
502 static void
503 monthly(int day, int month, long year) {
504 int i, rows;
505 struct fmt_st out;
506
507 rows = do_monthly(day, month, year, &out, -1);
508 for (i = 0; i < rows; i++) {
509 my_putstring(out.s[i]);
510 my_putstring("\n");
511 }
512 }
513
514 static int
515 two_header_lines(int month, long year)
516 {
517 char lineout[FMT_ST_CHARS];
518 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
519 size_t len;
520 snprintf(lineout, sizeof(lineout), "%lu", year);
521 len = strlen(lineout);
522 len += strlen(full_month[month - 1]) + 1;
523 if (width < len)
524 return 1;
525 return 0;
526 }
527
528 static void
529 monthly3(int day, int month, long year) {
530 char lineout[FMT_ST_CHARS];
531 int i;
532 int width, rows, two_lines;
533 struct fmt_st out_prev;
534 struct fmt_st out_curm;
535 struct fmt_st out_next;
536 int prev_month, next_month;
537 long prev_year, next_year;
538
539 memset(&out_prev, 0, sizeof(struct fmt_st));
540 memset(&out_curm, 0, sizeof(struct fmt_st));
541 memset(&out_next, 0, sizeof(struct fmt_st));
542 if (month == 1) {
543 prev_month = MONTHS_IN_YEAR;
544 prev_year = year - 1;
545 } else {
546 prev_month = month - 1;
547 prev_year = year;
548 }
549 if (month == MONTHS_IN_YEAR) {
550 next_month = 1;
551 next_year = year + 1;
552 } else {
553 next_month = month + 1;
554 next_year = year;
555 }
556 two_lines = two_header_lines(prev_month, prev_year);
557 two_lines += two_header_lines(month, year);
558 two_lines += two_header_lines(next_month, next_year);
559 if (0 < two_lines)
560 rows = FMT_ST_LINES;
561 else
562 rows = FMT_ST_LINES - 1;
563 do_monthly(day, prev_month, prev_year, &out_prev, two_lines);
564 do_monthly(day, month, year, &out_curm, two_lines);
565 do_monthly(day, next_month, next_year, &out_next, two_lines);
566
567 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
568 for (i = 0; i < (two_lines ? 3 : 2); i++) {
569 snprintf(lineout, sizeof(lineout),
570 "%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
571 my_putstring(lineout);
572 }
573 for (i = two_lines ? 3 : 2; i < rows; i++) {
574 int w1, w2, w3;
575 w1 = w2 = w3 = width;
576
577 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
578 /* adjust width to allow for non printable characters */
579 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
580 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
581 w3 += (out_next.s[i] == Hrow ? Slen : 0);
582 #endif
583 snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n",
584 w1, out_prev.s[i],
585 w2, out_curm.s[i],
586 w3, out_next.s[i]);
587
588 my_putstring(lineout);
589 }
590 }
591
592 static void
593 yearly(int day, long year, int julian) {
594 int col, *dp, i, month, row, which_cal;
595 int maxrow, sep_len, week_len;
596 int days[MONTHS_IN_YEAR][MAXDAYS];
597 char *p, lineout[100];
598
599 if (julian) {
600 maxrow = J_MONTH_COLS;
601 sep_len = J_HEAD_SEP;
602 week_len = J_WEEK_LEN;
603 } else {
604 maxrow = MONTH_COLS;
605 sep_len = HEAD_SEP;
606 week_len = WEEK_LEN;
607 }
608 snprintf(lineout, sizeof(lineout), "%lu", year);
609 /* 2013-04-28: The -1 near sep_len makes year header to be
610 * aligned exactly how it has been aligned for long time, but it
611 * is unexplainable. */
612 center(lineout, (week_len + sep_len) * maxrow - sep_len - 1, 0);
613 my_putstring("\n\n");
614
615 for (i = 0; i < MONTHS_IN_YEAR; i++)
616 day_array(day, i + 1, year, days[i]);
617 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
618 center(full_month[month], week_len - 1, sep_len + 1);
619 if (julian) {
620 center(full_month[month + 1], week_len - 1, 0);
621 } else {
622 center(full_month[month + 1], week_len - 1, sep_len + 1);
623 center(full_month[month + 2], week_len - 1, 0);
624 }
625 if (julian)
626 snprintf(lineout, sizeof(lineout),
627 "\n%s%*s %s\n", day_headings, sep_len, "", day_headings);
628 else
629 snprintf(lineout, sizeof(lineout),
630 "\n%s%*s %s%*s %s\n", day_headings, sep_len,
631 "", day_headings, sep_len, "", day_headings);
632 my_putstring(lineout);
633 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
634 p = lineout;
635 for (which_cal = 0; which_cal < maxrow; which_cal++) {
636 dp = &days[month + which_cal][row * DAYS_IN_WEEK];
637 for (col = 0; col < DAYS_IN_WEEK; col++)
638 p = ascii_day(p, *dp++);
639 p += sprintf(p, " ");
640 }
641 *p = '\0';
642 my_putstring(lineout);
643 my_putstring("\n");
644 }
645 }
646 my_putstring("\n");
647 }
648
649 /*
650 * day_array --
651 * Fill in an array of 42 integers with a calendar. Assume for a moment
652 * that you took the (maximum) 6 rows in a calendar and stretched them
653 * out end to end. You would have 42 numbers or spaces. This routine
654 * builds that array for any month from Jan. 1 through Dec. 9999.
655 */
656 static void
657 day_array(int day, int month, long year, int *days) {
658 int julday, daynum, dw, dm;
659 int *sep1752;
660
661 memcpy(days, empty, MAXDAYS * sizeof(int));
662 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
663 sep1752 = julian ? j_sep1752 : d_sep1752;
664 memcpy(days, sep1752 + weekstart,
665 ((MAXDAYS / 2) - weekstart) * sizeof(int));
666 for (dm = 0; dm < MAXDAYS / 2; dm++)
667 if (j_sep1752[dm] == day)
668 days[dm] |= TODAY_FLAG;
669 return;
670 }
671 dm = days_in_month[leap_year(year)][month];
672 dw = (day_in_week(1, month, year) - weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
673 julday = day_in_year(1, month, year);
674 daynum = julian ? julday : 1;
675 while (dm--) {
676 days[dw] = daynum++;
677 if (julday++ == day)
678 days[dw] |= TODAY_FLAG;
679 dw++;
680 }
681 }
682
683 /*
684 * day_in_year --
685 * return the 1 based day number within the year
686 */
687 static int
688 day_in_year(int day, int month, long year) {
689 int i, leap;
690
691 leap = leap_year(year);
692 for (i = 1; i < month; i++)
693 day += days_in_month[leap][i];
694 return day;
695 }
696
697 /*
698 * day_in_week
699 * return the 0 based day number for any date from 1 Jan. 1 to
700 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
701 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
702 * during the period of 11 days.
703 */
704 static int
705 day_in_week(int d, int m, int y)
706 {
707 static const int reform[] = {
708 SUNDAY, WEDNESDAY, TUESDAY, FRIDAY, SUNDAY, WEDNESDAY,
709 FRIDAY, MONDAY, THURSDAY, SATURDAY, TUESDAY, THURSDAY
710 };
711 static const int old[] = {
712 FRIDAY, MONDAY, SUNDAY, WEDNESDAY, FRIDAY, MONDAY,
713 WEDNESDAY, SATURDAY, TUESDAY, THURSDAY, SUNDAY, TUESDAY
714 };
715 if (y != 1753)
716 y -= m < 3;
717 else
718 y -= (m < 3) + 14;
719 if (1752 < y || (y == 1752 && 9 < m) || (y == 1752 && m == 9 && 13 < d))
720 return (y + (y / 4) - (y / 100) + (y / 400) + reform[m - 1] +
721 d) % 7;
722 if (y < 1752 || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3))
723 return (y + y / 4 + old[m - 1] + d) % 7;
724 return NONEDAY;
725 }
726
727 static char *
728 ascii_day(char *p, int day) {
729 int display, val;
730 int highlight = 0;
731 static char *aday[] = {
732 "",
733 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
734 " 8", " 9", "10", "11", "12", "13", "14",
735 "15", "16", "17", "18", "19", "20", "21",
736 "22", "23", "24", "25", "26", "27", "28",
737 "29", "30", "31",
738 };
739
740 if (day == SPACE) {
741 int len = julian ? J_DAY_LEN : DAY_LEN;
742 memset(p, ' ', len);
743 return p+len;
744 }
745 if (day & TODAY_FLAG) {
746 day &= ~TODAY_FLAG;
747 p += sprintf(p, "%s", Senter);
748 highlight = 1;
749 }
750 if (julian) {
751 if ((val = day / 100)) {
752 day %= 100;
753 *p++ = val + '0';
754 display = 1;
755 } else {
756 *p++ = ' ';
757 display = 0;
758 }
759 val = day / 10;
760 if (val || display)
761 *p++ = val + '0';
762 else
763 *p++ = ' ';
764 *p++ = day % 10 + '0';
765 } else {
766 *p++ = aday[day][0];
767 *p++ = aday[day][1];
768 }
769 if (highlight)
770 p += sprintf(p, "%s", Sexit);
771 *p++ = ' ';
772 return p;
773 }
774
775 /*
776 * Center string, handling multibyte characters appropriately.
777 * In addition if the string is too large for the width it's truncated.
778 * The number of trailing spaces may be 1 less than the number of leading spaces.
779 */
780 static int
781 center_str(const char* src, char* dest, size_t dest_size, size_t width)
782 {
783 return mbsalign(src, dest, dest_size, &width,
784 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
785 }
786
787 static void
788 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 fputs(USAGE_OPTIONS, out);
806 fputs(_(" -1, --one show only current month (default)\n"), out);
807 fputs(_(" -3, --three show previous, current and next month\n"), out);
808 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
809 fputs(_(" -m, --monday Monday as first day of week\n"), out);
810 fputs(_(" -j, --julian output Julian dates\n"), out);
811 fputs(_(" -y, --year show whole current year\n"), out);
812 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
813 fputs(USAGE_SEPARATOR, out);
814 fputs(USAGE_HELP, out);
815 fputs(USAGE_VERSION, out);
816 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
817 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
818 }