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