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