]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/cal.c
rev: reduce stream checking when closing read-only file descriptor
[thirdparty/util-linux.git] / misc-utils / cal.c
CommitLineData
6dbe3af9
KZ
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
5c36a0eb 37/* 1999-02-01 Jean-Francois Bignolles: added option '-m' to display
d162fcb5 38 * monday as the first day of the week.
b50945d4 39 * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
7eda085c
KZ
40 * - added Native Language Support
41 *
d162fcb5 42 * 2000-09-01 Michael Charles Pruznick <dummy@netwiz.net>
66ee8158
KZ
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.
e8f26419
KZ
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
5c36a0eb
KZ
56 */
57
6dbe3af9
KZ
58#include <sys/types.h>
59
60#include <ctype.h>
022df321 61#include <getopt.h>
6dbe3af9
KZ
62#include <stdio.h>
63#include <stdlib.h>
64#include <string.h>
65#include <time.h>
66#include <unistd.h>
37d6897f 67#include <errno.h>
17282538
KZ
68
69#include "c.h"
c05a80ca 70#include "closestream.h"
7b353df2 71#include "colors.h"
7eda085c 72#include "nls.h"
104b92f8 73#include "mbsalign.h"
022df321 74#include "strutils.h"
726f69e2 75
310170b8 76#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
4aefe5e8
SK
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
84static void my_setupterm(const char *term, int fildes, int *errret)
85{
86 setupterm((char *)term, fildes, errret);
d162fcb5
KZ
87}
88
4aefe5e8
SK
89static void my_putstring(char *s)
90{
91 putp(s);
d162fcb5
KZ
92}
93
4aefe5e8
SK
94static 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 "";
d4be073d
KZ
99
100 return ret;
d162fcb5
KZ
101}
102
48d7b13a 103#elif defined(HAVE_LIBTERMCAP)
4aefe5e8 104# include <termcap.h>
d162fcb5 105
d4be073d
KZ
106static char termbuffer[4096];
107static char tcbuffer[4096];
108static char *strbuf = termbuffer;
d162fcb5 109
4aefe5e8
SK
110static void my_setupterm(const char *term, int fildes, int *errret)
111{
112 *errret = tgetent(tcbuffer, term);
d162fcb5
KZ
113}
114
4aefe5e8
SK
115static void my_putstring(char *s)
116{
117 tputs(s, 1, putchar);
d162fcb5
KZ
118}
119
4aefe5e8
SK
120static const char *my_tgetstr(char *s, char *ss __attribute__((__unused__)))
121{
122 const char *ret = tgetstr(s, &strbuf);
123 if (!ret)
124 return "";
d4be073d
KZ
125
126 return ret;
d162fcb5
KZ
127}
128
4aefe5e8 129#else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
f4061938 130
4aefe5e8
SK
131static void my_putstring(char *s)
132{
133 fputs(s, stdout);
f4061938
KZ
134}
135
4aefe5e8 136#endif /* end of LIBTERMCAP / NCURSES */
f4061938
KZ
137
138
d4be073d
KZ
139static const char *term="";
140static const char *Senter="", *Sexit="";/* enter and exit standout mode */
141static int Slen; /* strlen of Senter+Sexit */
142static char *Hrow; /* pointer to highlighted row in month */
d162fcb5 143
e8f26419 144#include "widechar.h"
95f1bdee 145
66ee8158
KZ
146/* allow compile-time define to over-ride default */
147#ifndef NUM_MONTHS
d4be073d 148# define NUM_MONTHS 1
66ee8158
KZ
149#endif
150
151#if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
d4be073d 152# error NUM_MONTHS must be 1 or 3
66ee8158
KZ
153#endif
154
5f845cb7
SK
155enum {
156 SUNDAY = 0,
157 MONDAY,
158 TUESDAY,
159 WEDNESDAY,
160 THURSDAY,
161 FRIDAY,
162 SATURDAY,
d68f076f
SK
163 DAYS_IN_WEEK,
164 NONEDAY
5f845cb7 165};
6dbe3af9 166
5f845cb7 167#define FIRST_WEEKDAY SATURDAY /* Jan 1st, 1 was a Saturday */
5f845cb7
SK
168#define REFORMATION_YEAR 1752 /* Signed-off-by: Lord Chesterfield */
169#define REFORMATION_MONTH 9 /* September */
d162fcb5
KZ
170#define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
171#define NUMBER_MISSING_DAYS 11 /* 11 day correction */
6dbe3af9 172
5f845cb7
SK
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
f06602a4 176#define MAXDAYS 42 /* slots in a month array */
6dbe3af9
KZ
177#define SPACE -1 /* used in day array */
178
5f845cb7 179#define SMALLEST_YEAR 1
5f845cb7
SK
180
181#define DAY_LEN 3 /* 3 spaces per day */
182#define WEEK_LEN (DAYS_IN_WEEK * DAY_LEN)
183#define HEAD_SEP 2
b283141c 184#define MONTH_COLS 3 /* month columns in year view */
5f845cb7
SK
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
b283141c 189#define J_MONTH_COLS 2
5f845cb7
SK
190
191#define TODAY_FLAG 0x400 /* flag day for highlighting */
192
e44fe471 193#define FMT_ST_LINES 9
5f845cb7
SK
194#define FMT_ST_CHARS 300 /* 90 suffices in most locales */
195struct fmt_st
196{
d4be073d 197 char s[FMT_ST_LINES][FMT_ST_CHARS];
5f845cb7
SK
198};
199
6dbe3af9
KZ
200static 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
e8c58289
SK
205/* September 1752 is special, and has static assignments for both date
206 * and Julian representations. */
d4be073d 207static int d_sep1752[MAXDAYS / 2] = {
6dbe3af9
KZ
208 SPACE, SPACE, 1, 2, 14, 15, 16,
209 17, 18, 19, 20, 21, 22, 23,
e8c58289
SK
210 24, 25, 26, 27, 28, 29, 30
211}, j_sep1752[MAXDAYS / 2] = {
6dbe3af9
KZ
212 SPACE, SPACE, 245, 246, 258, 259, 260,
213 261, 262, 263, 264, 265, 266, 267,
e8c58289 214 268, 269, 270, 271, 272, 273, 274
6dbe3af9
KZ
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,
f06602a4 221 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE
6dbe3af9
KZ
222};
223
e8f26419
KZ
224
225/* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
d4be073d 226static char day_headings[J_WEEK_LEN * 6 + 1];
0a9bead0 227/* weekstart = 1 => " M Tu W Th F S S " */
d4be073d 228static const char *full_month[MONTHS_IN_YEAR];
6dbe3af9 229
0a9bead0 230/* 0 => sunday, 1 => monday */
d4be073d
KZ
231static int weekstart = SUNDAY;
232static int julian;
6dbe3af9 233
5f845cb7 234/* function prototypes */
e44fe471 235static int leap_year(long year);
8ea0caa2
SK
236static char * ascii_day(char *, int);
237static int center_str(const char* src, char* dest, size_t dest_size, size_t width);
238static void center(const char *, size_t, int);
e44fe471 239static void day_array(int, int, long, int *);
8ea0caa2 240static int day_in_week(int, int, int);
e44fe471
SK
241static int day_in_year(int, int, long);
242static void yearly(int, long, int);
243static int do_monthly(int, int, long, struct fmt_st*, int);
244static void monthly(int, int, long);
245static int two_header_lines(int month, long year);
246static void monthly3(int, int, long);
022df321 247static void __attribute__ ((__noreturn__)) usage(FILE * out);
8ea0caa2 248static void headers_init(int);
6dbe3af9 249
d4be073d
KZ
250int main(int argc, char **argv)
251{
6dbe3af9
KZ
252 struct tm *local_time;
253 time_t now;
e44fe471
SK
254 int ch, day = 0, month = 0, yflag = 0;
255 long year;
66ee8158 256 int num_months = NUM_MONTHS;
7b353df2
SK
257 int colormode = UL_COLORMODE_AUTO;
258
259 enum {
260 OPT_COLOR = CHAR_MAX + 1
261 };
eb63b9b8 262
022df321
SK
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'},
7b353df2 270 {"color", optional_argument, NULL, OPT_COLOR},
022df321
SK
271 {"version", no_argument, NULL, 'V'},
272 {"help", no_argument, NULL, 'h'},
273 {NULL, 0, NULL, 0}
274 };
275
7eda085c
KZ
276 setlocale(LC_ALL, "");
277 bindtextdomain(PACKAGE, LOCALEDIR);
278 textdomain(PACKAGE);
c05a80ca 279 atexit(close_stdout);
e8f26419 280
310170b8 281#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
d162fcb5
KZ
282 if ((term = getenv("TERM"))) {
283 int ret;
5f845cb7 284 my_setupterm(term, STDOUT_FILENO, &ret);
d162fcb5
KZ
285 if (ret > 0) {
286 Senter = my_tgetstr("so","smso");
287 Sexit = my_tgetstr("se","rmso");
94b26919 288 Slen = strlen(Senter) + strlen(Sexit);
d162fcb5
KZ
289 }
290 }
291#endif
292
612721db 293/*
0a9bead0
PB
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.
612721db 298 */
69cabd72 299#if HAVE_DECL__NL_TIME_WEEK_1STDAY
0a9bead0
PB
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:
33c17354
SK
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
0a9bead0
PB
316 */
317 {
aebb9522
KZ
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;
0a9bead0 323 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
5f845cb7 324 weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % DAYS_IN_WEEK;
0a9bead0 325 }
e8f26419 326#endif
d162fcb5 327
022df321 328 while ((ch = getopt_long(argc, argv, "13mjsyVh", longopts, NULL)) != -1)
6dbe3af9 329 switch(ch) {
d162fcb5
KZ
330 case '1':
331 num_months = 1; /* default */
332 break;
333 case '3':
334 num_months = 3;
335 break;
ffc43748 336 case 's':
5f845cb7 337 weekstart = SUNDAY; /* default */
ffc43748 338 break;
5c36a0eb 339 case 'm':
5f845cb7 340 weekstart = MONDAY;
5c36a0eb 341 break;
6dbe3af9
KZ
342 case 'j':
343 julian = 1;
344 break;
345 case 'y':
346 yflag = 1;
347 break;
7b353df2 348 case OPT_COLOR:
201b39f0
KZ
349 if (optarg)
350 colormode = colormode_or_err(optarg,
351 _("unsupported color mode"));
7b353df2 352 break;
eb63b9b8 353 case 'V':
e421313d 354 printf(UTIL_LINUX_VERSION);
37d6897f 355 return EXIT_SUCCESS;
022df321
SK
356 case 'h':
357 usage(stdout);
6dbe3af9
KZ
358 case '?':
359 default:
022df321 360 usage(stderr);
6dbe3af9
KZ
361 }
362 argc -= optind;
363 argv += optind;
364
a43145e1
RP
365 time(&now);
366 local_time = localtime(&now);
367
6dbe3af9 368 switch(argc) {
d7a92b89 369 case 3:
db41a429 370 day = strtos32_or_err(*argv++, _("illegal day value"));
5f845cb7
SK
371 if (day < 1 || DAYS_IN_MONTH < day)
372 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH);
d7a92b89 373 /* FALLTHROUGH */
6dbe3af9 374 case 2:
db41a429 375 month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
5f845cb7 376 if (month < 1 || MONTHS_IN_YEAR < month)
022df321 377 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
6dbe3af9
KZ
378 /* FALLTHROUGH */
379 case 1:
e44fe471
SK
380 year = strtol_or_err(*argv++, _("illegal year value"));
381 if (year < SMALLEST_YEAR)
382 errx(EXIT_FAILURE, _("illegal year value: use positive integer"));
d7a92b89
PB
383 if (day) {
384 int dm = days_in_month[leap_year(year)][month];
385 if (day > dm)
022df321 386 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
d7a92b89 387 day = day_in_year(day, month, year);
e44fe471 388 } else if ((long) (local_time->tm_year + 1900) == year) {
a43145e1 389 day = local_time->tm_yday + 1;
d7a92b89
PB
390 }
391 if (!month)
392 yflag=1;
6dbe3af9
KZ
393 break;
394 case 0:
d7a92b89 395 day = local_time->tm_yday + 1;
6dbe3af9 396 year = local_time->tm_year + 1900;
d7a92b89 397 month = local_time->tm_mon + 1;
6dbe3af9
KZ
398 break;
399 default:
022df321 400 usage(stderr);
6dbe3af9 401 }
b283141c 402 headers_init(julian);
6dbe3af9 403
7b353df2
SK
404 if (!colors_init(colormode))
405 day = 0;
d7a92b89 406
b283141c
SK
407 if (yflag)
408 yearly(day, year, julian);
d7a92b89
PB
409 else if (num_months == 1)
410 monthly(day, month, year);
411 else if (num_months == 3)
412 monthly3(day, month, year);
37d6897f
KZ
413
414 return EXIT_SUCCESS;
6dbe3af9
KZ
415}
416
3b66dfd6 417/* leap year -- account for gregorian reformation in 1752 */
e44fe471 418static int leap_year(long year)
3b66dfd6 419{
5f845cb7 420 if (year <= REFORMATION_YEAR)
3b66dfd6
SK
421 return !(year % 4);
422 else
423 return ( !(year % 4) && (year % 100) ) || !(year % 400);
424}
425
8ea0caa2 426static void headers_init(int julian)
6dbe3af9 427{
b283141c
SK
428 int i, wd, spaces = julian ? J_DAY_LEN - 1 : DAY_LEN - 1;
429 char *cur_dh = day_headings;
33c17354 430
5f845cb7 431 for (i = 0; i < DAYS_IN_WEEK; i++) {
33c17354 432 ssize_t space_left;
5f845cb7 433 wd = (i + weekstart) % DAYS_IN_WEEK;
33c17354
SK
434
435 if (i)
436 strcat(cur_dh++, " ");
437 space_left =
438 sizeof(day_headings) - (cur_dh - day_headings);
b283141c 439 if (space_left <= spaces)
33c17354
SK
440 break;
441 cur_dh +=
442 center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
b283141c 443 space_left, spaces);
33c17354
SK
444 }
445
5f845cb7 446 for (i = 0; i < MONTHS_IN_YEAR; i++)
33c17354 447 full_month[i] = nl_langinfo(MON_1 + i);
6dbe3af9
KZ
448}
449
d4be073d
KZ
450static int do_monthly(int day, int month, long year,
451 struct fmt_st *out, int header_hint)
452{
ff87defc 453 int col, row, days[MAXDAYS];
d162fcb5 454 char *p, lineout[FMT_ST_CHARS];
e44fe471
SK
455 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
456 int pos = 0;
d162fcb5
KZ
457
458 day_array(day, month, year, days);
459
460 /*
461 * %s is the month name, %d the year number.
0e6f4a20 462 * you can change the order and/or add something here; eg for
e8f26419
KZ
463 * Basque the translation should be: "%2$dko %1$s", and
464 * the Vietnamese should be "%s na(m %d", etc.
465 */
e44fe471
SK
466 if (header_hint < 0)
467 header_hint = two_header_lines(month, year);
468 if (header_hint) {
469 snprintf(lineout, sizeof(lineout), _("%s"), full_month[month - 1]);
470 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
471 pos++;
472 snprintf(lineout, sizeof(lineout), _("%lu"), year);
473 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
474 pos++;
475 } else {
476 snprintf(lineout, sizeof(lineout), _("%s %lu"),
ff87defc 477 full_month[month - 1], year);
e44fe471
SK
478 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
479 pos++;
480 }
d162fcb5 481
e44fe471 482 snprintf(out->s[pos++], FMT_ST_CHARS, "%s", day_headings);
5f845cb7 483 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
94b26919 484 int has_hl = 0;
5f845cb7
SK
485 for (col = 0, p = lineout; col < DAYS_IN_WEEK; col++) {
486 int xd = days[row * DAYS_IN_WEEK + col];
94b26919
KZ
487 if (xd != SPACE && (xd & TODAY_FLAG))
488 has_hl = 1;
489 p = ascii_day(p, xd);
490 }
6dbe3af9 491 *p = '\0';
e44fe471 492 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
94b26919 493 if (has_hl)
e44fe471 494 Hrow = out->s[row+pos];
66ee8158 495 }
e44fe471
SK
496 pos += row;
497 return pos;
66ee8158
KZ
498}
499
d4be073d
KZ
500static void monthly(int day, int month, long year)
501{
e44fe471 502 int i, rows;
66ee8158
KZ
503 struct fmt_st out;
504
e44fe471
SK
505 rows = do_monthly(day, month, year, &out, -1);
506 for (i = 0; i < rows; i++) {
f4061938 507 my_putstring(out.s[i]);
e66ba1bf 508 my_putstring("\n");
66ee8158
KZ
509 }
510}
511
d4be073d 512static int two_header_lines(int month, long year)
e44fe471
SK
513{
514 char lineout[FMT_ST_CHARS];
515 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
516 size_t len;
517 snprintf(lineout, sizeof(lineout), "%lu", year);
518 len = strlen(lineout);
519 len += strlen(full_month[month - 1]) + 1;
520 if (width < len)
521 return 1;
522 return 0;
523}
524
d4be073d
KZ
525static void monthly3(int day, int month, long year)
526{
d162fcb5 527 char lineout[FMT_ST_CHARS];
66ee8158 528 int i;
e44fe471 529 int width, rows, two_lines;
66ee8158
KZ
530 struct fmt_st out_prev;
531 struct fmt_st out_curm;
532 struct fmt_st out_next;
e44fe471
SK
533 int prev_month, next_month;
534 long prev_year, next_year;
66ee8158 535
e44fe471
SK
536 memset(&out_prev, 0, sizeof(struct fmt_st));
537 memset(&out_curm, 0, sizeof(struct fmt_st));
538 memset(&out_next, 0, sizeof(struct fmt_st));
d162fcb5 539 if (month == 1) {
5f845cb7 540 prev_month = MONTHS_IN_YEAR;
d162fcb5
KZ
541 prev_year = year - 1;
542 } else {
543 prev_month = month - 1;
544 prev_year = year;
66ee8158 545 }
5f845cb7 546 if (month == MONTHS_IN_YEAR) {
d162fcb5
KZ
547 next_month = 1;
548 next_year = year + 1;
549 } else {
550 next_month = month + 1;
551 next_year = year;
66ee8158 552 }
e44fe471
SK
553 two_lines = two_header_lines(prev_month, prev_year);
554 two_lines += two_header_lines(month, year);
555 two_lines += two_header_lines(next_month, next_year);
556 if (0 < two_lines)
557 rows = FMT_ST_LINES;
558 else
559 rows = FMT_ST_LINES - 1;
560 do_monthly(day, prev_month, prev_year, &out_prev, two_lines);
561 do_monthly(day, month, year, &out_curm, two_lines);
562 do_monthly(day, next_month, next_year, &out_next, two_lines);
94b26919 563
33c17354 564 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
e44fe471 565 for (i = 0; i < (two_lines ? 3 : 2); i++) {
e66ba1bf
KZ
566 snprintf(lineout, sizeof(lineout),
567 "%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
568 my_putstring(lineout);
569 }
e44fe471 570 for (i = two_lines ? 3 : 2; i < rows; i++) {
94b26919
KZ
571 int w1, w2, w3;
572 w1 = w2 = w3 = width;
573
310170b8 574#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
33c17354
SK
575 /* adjust width to allow for non printable characters */
576 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
577 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
578 w3 += (out_next.s[i] == Hrow ? Slen : 0);
94b26919 579#endif
22468708 580 snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n",
94b26919
KZ
581 w1, out_prev.s[i],
582 w2, out_curm.s[i],
583 w3, out_next.s[i]);
584
d162fcb5 585 my_putstring(lineout);
6dbe3af9
KZ
586 }
587}
588
d4be073d
KZ
589static void yearly(int day, long year, int julian)
590{
6dbe3af9 591 int col, *dp, i, month, row, which_cal;
b283141c 592 int maxrow, sep_len, week_len;
5f845cb7 593 int days[MONTHS_IN_YEAR][MAXDAYS];
d162fcb5 594 char *p, lineout[100];
6dbe3af9 595
b283141c
SK
596 if (julian) {
597 maxrow = J_MONTH_COLS;
598 sep_len = J_HEAD_SEP;
599 week_len = J_WEEK_LEN;
600 } else {
601 maxrow = MONTH_COLS;
602 sep_len = HEAD_SEP;
603 week_len = WEEK_LEN;
604 }
e44fe471 605 snprintf(lineout, sizeof(lineout), "%lu", year);
b283141c
SK
606 /* 2013-04-28: The -1 near sep_len makes year header to be
607 * aligned exactly how it has been aligned for long time, but it
608 * is unexplainable. */
609 center(lineout, (week_len + sep_len) * maxrow - sep_len - 1, 0);
e66ba1bf 610 my_putstring("\n\n");
d162fcb5 611
5f845cb7 612 for (i = 0; i < MONTHS_IN_YEAR; i++)
d162fcb5 613 day_array(day, i + 1, year, days[i]);
d4be073d 614
b283141c
SK
615 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
616 center(full_month[month], week_len - 1, sep_len + 1);
617 if (julian) {
618 center(full_month[month + 1], week_len - 1, 0);
619 } else {
620 center(full_month[month + 1], week_len - 1, sep_len + 1);
621 center(full_month[month + 2], week_len - 1, 0);
622 }
623 if (julian)
624 snprintf(lineout, sizeof(lineout),
625 "\n%s%*s %s\n", day_headings, sep_len, "", day_headings);
626 else
627 snprintf(lineout, sizeof(lineout),
628 "\n%s%*s %s%*s %s\n", day_headings, sep_len,
629 "", day_headings, sep_len, "", day_headings);
e66ba1bf 630 my_putstring(lineout);
5f845cb7 631 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
d162fcb5 632 p = lineout;
b283141c 633 for (which_cal = 0; which_cal < maxrow; which_cal++) {
5f845cb7
SK
634 dp = &days[month + which_cal][row * DAYS_IN_WEEK];
635 for (col = 0; col < DAYS_IN_WEEK; col++)
d162fcb5
KZ
636 p = ascii_day(p, *dp++);
637 p += sprintf(p, " ");
6dbe3af9
KZ
638 }
639 *p = '\0';
f4061938 640 my_putstring(lineout);
e66ba1bf 641 my_putstring("\n");
6dbe3af9
KZ
642 }
643 }
e66ba1bf 644 my_putstring("\n");
6dbe3af9
KZ
645}
646
647/*
648 * day_array --
649 * Fill in an array of 42 integers with a calendar. Assume for a moment
650 * that you took the (maximum) 6 rows in a calendar and stretched them
651 * out end to end. You would have 42 numbers or spaces. This routine
652 * builds that array for any month from Jan. 1 through Dec. 9999.
653 */
d4be073d
KZ
654static void day_array(int day, int month, long year, int *days)
655{
d162fcb5 656 int julday, daynum, dw, dm;
e8c58289
SK
657 int *sep1752;
658
659 memcpy(days, empty, MAXDAYS * sizeof(int));
660 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
661 sep1752 = julian ? j_sep1752 : d_sep1752;
662 memcpy(days, sep1752 + weekstart,
663 ((MAXDAYS / 2) - weekstart) * sizeof(int));
664 for (dm = 0; dm < MAXDAYS / 2; dm++)
665 if (j_sep1752[dm] == day)
d7a92b89 666 days[dm] |= TODAY_FLAG;
6dbe3af9
KZ
667 return;
668 }
6dbe3af9 669 dm = days_in_month[leap_year(year)][month];
5f845cb7 670 dw = (day_in_week(1, month, year) - weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
d162fcb5
KZ
671 julday = day_in_year(1, month, year);
672 daynum = julian ? julday : 1;
673 while (dm--) {
674 days[dw] = daynum++;
675 if (julday++ == day)
676 days[dw] |= TODAY_FLAG;
677 dw++;
678 }
6dbe3af9
KZ
679}
680
681/*
682 * day_in_year --
683 * return the 1 based day number within the year
684 */
d4be073d
KZ
685static int day_in_year(int day, int month, long year)
686{
6dbe3af9
KZ
687 int i, leap;
688
689 leap = leap_year(year);
690 for (i = 1; i < month; i++)
691 day += days_in_month[leap][i];
d162fcb5 692 return day;
6dbe3af9
KZ
693}
694
695/*
696 * day_in_week
697 * return the 0 based day number for any date from 1 Jan. 1 to
698 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
d68f076f
SK
699 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
700 * during the period of 11 days.
6dbe3af9 701 */
d4be073d 702static int day_in_week(int d, int m, int y)
91ac9ef5
SK
703{
704 static const int reform[] = {
705 SUNDAY, WEDNESDAY, TUESDAY, FRIDAY, SUNDAY, WEDNESDAY,
706 FRIDAY, MONDAY, THURSDAY, SATURDAY, TUESDAY, THURSDAY
707 };
708 static const int old[] = {
709 FRIDAY, MONDAY, SUNDAY, WEDNESDAY, FRIDAY, MONDAY,
710 WEDNESDAY, SATURDAY, TUESDAY, THURSDAY, SUNDAY, TUESDAY
711 };
712 if (y != 1753)
713 y -= m < 3;
714 else
715 y -= (m < 3) + 14;
d4be073d
KZ
716 if (REFORMATION_YEAR < y
717 || (y == REFORMATION_YEAR && 9 < m)
718 || (y == REFORMATION_YEAR && m == 9 && 13 < d))
91ac9ef5
SK
719 return (y + (y / 4) - (y / 100) + (y / 400) + reform[m - 1] +
720 d) % 7;
d4be073d
KZ
721 if (y < REFORMATION_YEAR
722 || (y == REFORMATION_YEAR && m < 9)
723 || (y == REFORMATION_YEAR && m == 9 && d < 3))
91ac9ef5
SK
724 return (y + y / 4 + old[m - 1] + d) % 7;
725 return NONEDAY;
6dbe3af9
KZ
726}
727
d4be073d
KZ
728static char *ascii_day(char *p, int day)
729{
6dbe3af9 730 int display, val;
d162fcb5 731 int highlight = 0;
6dbe3af9
KZ
732 static char *aday[] = {
733 "",
734 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
735 " 8", " 9", "10", "11", "12", "13", "14",
736 "15", "16", "17", "18", "19", "20", "21",
737 "22", "23", "24", "25", "26", "27", "28",
738 "29", "30", "31",
739 };
740
741 if (day == SPACE) {
d162fcb5
KZ
742 int len = julian ? J_DAY_LEN : DAY_LEN;
743 memset(p, ' ', len);
744 return p+len;
745 }
746 if (day & TODAY_FLAG) {
747 day &= ~TODAY_FLAG;
8c6c72bf 748 p += sprintf(p, "%s", Senter);
d162fcb5 749 highlight = 1;
6dbe3af9
KZ
750 }
751 if (julian) {
726f69e2 752 if ((val = day / 100)) {
6dbe3af9
KZ
753 day %= 100;
754 *p++ = val + '0';
755 display = 1;
756 } else {
757 *p++ = ' ';
758 display = 0;
759 }
760 val = day / 10;
761 if (val || display)
762 *p++ = val + '0';
763 else
764 *p++ = ' ';
765 *p++ = day % 10 + '0';
766 } else {
767 *p++ = aday[day][0];
768 *p++ = aday[day][1];
769 }
d162fcb5 770 if (highlight)
8c6c72bf 771 p += sprintf(p, "%s", Sexit);
d162fcb5
KZ
772 *p++ = ' ';
773 return p;
6dbe3af9
KZ
774}
775
d162fcb5
KZ
776/*
777 * Center string, handling multibyte characters appropriately.
778 * In addition if the string is too large for the width it's truncated.
ff87defc 779 * The number of trailing spaces may be 1 less than the number of leading spaces.
d162fcb5 780 */
d4be073d
KZ
781static int center_str(const char* src, char* dest,
782 size_t dest_size, size_t width)
d162fcb5 783{
104b92f8
PB
784 return mbsalign(src, dest, dest_size, &width,
785 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
d162fcb5
KZ
786}
787
d4be073d 788static void center(const char *str, size_t len, int separate)
6dbe3af9 789{
d162fcb5 790 char lineout[FMT_ST_CHARS];
e66ba1bf 791
17282538 792 center_str(str, lineout, ARRAY_SIZE(lineout), len);
e66ba1bf
KZ
793 my_putstring(lineout);
794
795 if (separate) {
796 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
797 my_putstring(lineout);
798 }
6dbe3af9
KZ
799}
800
022df321 801static void __attribute__ ((__noreturn__)) usage(FILE * out)
6dbe3af9 802{
201d43fd
SK
803 fputs(USAGE_HEADER, out);
804 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
d4be073d 805
201d43fd
SK
806 fputs(USAGE_OPTIONS, out);
807 fputs(_(" -1, --one show only current month (default)\n"), out);
808 fputs(_(" -3, --three show previous, current and next month\n"), out);
809 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
810 fputs(_(" -m, --monday Monday as first day of week\n"), out);
811 fputs(_(" -j, --julian output Julian dates\n"), out);
812 fputs(_(" -y, --year show whole current year\n"), out);
813 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
d4be073d 814
201d43fd
SK
815 fputs(USAGE_SEPARATOR, out);
816 fputs(USAGE_HELP, out);
817 fputs(USAGE_VERSION, out);
818 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
d4be073d 819
022df321 820 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9 821}