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