]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/cal.c
cal: simplify colormode parsing
[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 "";
99 else
100 return ret;
d162fcb5
KZ
101}
102
48d7b13a 103#elif defined(HAVE_LIBTERMCAP)
4aefe5e8 104# include <termcap.h>
d162fcb5
KZ
105
106char termbuffer[4096];
107char tcbuffer[4096];
108char *strbuf = termbuffer;
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 "";
125 else
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
d162fcb5
KZ
139const char *term="";
140const char *Senter="", *Sexit="";/* enter and exit standout mode */
94b26919
KZ
141int Slen; /* strlen of Senter+Sexit */
142char *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
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
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{
197 char s[FMT_ST_LINES][FMT_ST_CHARS];
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. */
207 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 */
b283141c 226char day_headings[J_WEEK_LEN * 6 + 1];
0a9bead0 227/* weekstart = 1 => " M Tu W Th F S S " */
5f845cb7 228const char *full_month[MONTHS_IN_YEAR];
6dbe3af9 229
0a9bead0 230/* 0 => sunday, 1 => monday */
5f845cb7 231int weekstart = SUNDAY;
6dbe3af9
KZ
232int julian;
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
KZ
249
250int
22853e4a 251main(int argc, char **argv) {
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
e44fe471
SK
450static int
451do_monthly(int day, int month, long year, struct fmt_st *out, int header_hint) {
ff87defc 452 int col, row, days[MAXDAYS];
d162fcb5 453 char *p, lineout[FMT_ST_CHARS];
e44fe471
SK
454 size_t width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
455 int pos = 0;
d162fcb5
KZ
456
457 day_array(day, month, year, days);
458
459 /*
460 * %s is the month name, %d the year number.
0e6f4a20 461 * you can change the order and/or add something here; eg for
e8f26419
KZ
462 * Basque the translation should be: "%2$dko %1$s", and
463 * the Vietnamese should be "%s na(m %d", etc.
464 */
e44fe471
SK
465 if (header_hint < 0)
466 header_hint = two_header_lines(month, year);
467 if (header_hint) {
468 snprintf(lineout, sizeof(lineout), _("%s"), full_month[month - 1]);
469 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
470 pos++;
471 snprintf(lineout, sizeof(lineout), _("%lu"), year);
472 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
473 pos++;
474 } else {
475 snprintf(lineout, sizeof(lineout), _("%s %lu"),
ff87defc 476 full_month[month - 1], year);
e44fe471
SK
477 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
478 pos++;
479 }
d162fcb5 480
e44fe471 481 snprintf(out->s[pos++], FMT_ST_CHARS, "%s", day_headings);
5f845cb7 482 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
94b26919 483 int has_hl = 0;
5f845cb7
SK
484 for (col = 0, p = lineout; col < DAYS_IN_WEEK; col++) {
485 int xd = days[row * DAYS_IN_WEEK + col];
94b26919
KZ
486 if (xd != SPACE && (xd & TODAY_FLAG))
487 has_hl = 1;
488 p = ascii_day(p, xd);
489 }
6dbe3af9 490 *p = '\0';
e44fe471 491 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
94b26919 492 if (has_hl)
e44fe471 493 Hrow = out->s[row+pos];
66ee8158 494 }
e44fe471
SK
495 pos += row;
496 return pos;
66ee8158
KZ
497}
498
8ea0caa2 499static void
e44fe471
SK
500monthly(int day, int month, long year) {
501 int i, rows;
66ee8158
KZ
502 struct fmt_st out;
503
e44fe471
SK
504 rows = do_monthly(day, month, year, &out, -1);
505 for (i = 0; i < rows; i++) {
f4061938 506 my_putstring(out.s[i]);
e66ba1bf 507 my_putstring("\n");
66ee8158
KZ
508 }
509}
510
e44fe471
SK
511static int
512two_header_lines(int month, long year)
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
8ea0caa2 525static void
e44fe471 526monthly3(int day, int month, long year) {
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
8ea0caa2 589static void
e44fe471 590yearly(int day, long year, int julian) {
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]);
b283141c
SK
614 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
615 center(full_month[month], week_len - 1, sep_len + 1);
616 if (julian) {
617 center(full_month[month + 1], week_len - 1, 0);
618 } else {
619 center(full_month[month + 1], week_len - 1, sep_len + 1);
620 center(full_month[month + 2], week_len - 1, 0);
621 }
622 if (julian)
623 snprintf(lineout, sizeof(lineout),
624 "\n%s%*s %s\n", day_headings, sep_len, "", day_headings);
625 else
626 snprintf(lineout, sizeof(lineout),
627 "\n%s%*s %s%*s %s\n", day_headings, sep_len,
628 "", day_headings, sep_len, "", day_headings);
e66ba1bf 629 my_putstring(lineout);
5f845cb7 630 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
d162fcb5 631 p = lineout;
b283141c 632 for (which_cal = 0; which_cal < maxrow; which_cal++) {
5f845cb7
SK
633 dp = &days[month + which_cal][row * DAYS_IN_WEEK];
634 for (col = 0; col < DAYS_IN_WEEK; col++)
d162fcb5
KZ
635 p = ascii_day(p, *dp++);
636 p += sprintf(p, " ");
6dbe3af9
KZ
637 }
638 *p = '\0';
f4061938 639 my_putstring(lineout);
e66ba1bf 640 my_putstring("\n");
6dbe3af9
KZ
641 }
642 }
e66ba1bf 643 my_putstring("\n");
6dbe3af9
KZ
644}
645
646/*
647 * day_array --
648 * Fill in an array of 42 integers with a calendar. Assume for a moment
649 * that you took the (maximum) 6 rows in a calendar and stretched them
650 * out end to end. You would have 42 numbers or spaces. This routine
651 * builds that array for any month from Jan. 1 through Dec. 9999.
652 */
8ea0caa2 653static void
e44fe471 654day_array(int day, int month, long year, int *days) {
d162fcb5 655 int julday, daynum, dw, dm;
e8c58289
SK
656 int *sep1752;
657
658 memcpy(days, empty, MAXDAYS * sizeof(int));
659 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
660 sep1752 = julian ? j_sep1752 : d_sep1752;
661 memcpy(days, sep1752 + weekstart,
662 ((MAXDAYS / 2) - weekstart) * sizeof(int));
663 for (dm = 0; dm < MAXDAYS / 2; dm++)
664 if (j_sep1752[dm] == day)
d7a92b89 665 days[dm] |= TODAY_FLAG;
6dbe3af9
KZ
666 return;
667 }
6dbe3af9 668 dm = days_in_month[leap_year(year)][month];
5f845cb7 669 dw = (day_in_week(1, month, year) - weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
d162fcb5
KZ
670 julday = day_in_year(1, month, year);
671 daynum = julian ? julday : 1;
672 while (dm--) {
673 days[dw] = daynum++;
674 if (julday++ == day)
675 days[dw] |= TODAY_FLAG;
676 dw++;
677 }
6dbe3af9
KZ
678}
679
680/*
681 * day_in_year --
682 * return the 1 based day number within the year
683 */
8ea0caa2 684static int
e44fe471 685day_in_year(int day, int month, long year) {
6dbe3af9
KZ
686 int i, leap;
687
688 leap = leap_year(year);
689 for (i = 1; i < month; i++)
690 day += days_in_month[leap][i];
d162fcb5 691 return day;
6dbe3af9
KZ
692}
693
694/*
695 * day_in_week
696 * return the 0 based day number for any date from 1 Jan. 1 to
697 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
d68f076f
SK
698 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
699 * during the period of 11 days.
6dbe3af9 700 */
8ea0caa2 701static int
91ac9ef5
SK
702day_in_week(int d, int m, int y)
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;
716 if (1752 < y || (y == 1752 && 9 < m) || (y == 1752 && m == 9 && 13 < d))
717 return (y + (y / 4) - (y / 100) + (y / 400) + reform[m - 1] +
718 d) % 7;
719 if (y < 1752 || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3))
720 return (y + y / 4 + old[m - 1] + d) % 7;
721 return NONEDAY;
6dbe3af9
KZ
722}
723
8ea0caa2 724static char *
d162fcb5 725ascii_day(char *p, int day) {
6dbe3af9 726 int display, val;
d162fcb5 727 int highlight = 0;
6dbe3af9
KZ
728 static char *aday[] = {
729 "",
730 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
731 " 8", " 9", "10", "11", "12", "13", "14",
732 "15", "16", "17", "18", "19", "20", "21",
733 "22", "23", "24", "25", "26", "27", "28",
734 "29", "30", "31",
735 };
736
737 if (day == SPACE) {
d162fcb5
KZ
738 int len = julian ? J_DAY_LEN : DAY_LEN;
739 memset(p, ' ', len);
740 return p+len;
741 }
742 if (day & TODAY_FLAG) {
743 day &= ~TODAY_FLAG;
8c6c72bf 744 p += sprintf(p, "%s", Senter);
d162fcb5 745 highlight = 1;
6dbe3af9
KZ
746 }
747 if (julian) {
726f69e2 748 if ((val = day / 100)) {
6dbe3af9
KZ
749 day %= 100;
750 *p++ = val + '0';
751 display = 1;
752 } else {
753 *p++ = ' ';
754 display = 0;
755 }
756 val = day / 10;
757 if (val || display)
758 *p++ = val + '0';
759 else
760 *p++ = ' ';
761 *p++ = day % 10 + '0';
762 } else {
763 *p++ = aday[day][0];
764 *p++ = aday[day][1];
765 }
d162fcb5 766 if (highlight)
8c6c72bf 767 p += sprintf(p, "%s", Sexit);
d162fcb5
KZ
768 *p++ = ' ';
769 return p;
6dbe3af9
KZ
770}
771
d162fcb5
KZ
772/*
773 * Center string, handling multibyte characters appropriately.
774 * In addition if the string is too large for the width it's truncated.
ff87defc 775 * The number of trailing spaces may be 1 less than the number of leading spaces.
d162fcb5 776 */
8ea0caa2 777static int
104b92f8 778center_str(const char* src, char* dest, size_t dest_size, size_t width)
d162fcb5 779{
104b92f8
PB
780 return mbsalign(src, dest, dest_size, &width,
781 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
d162fcb5
KZ
782}
783
8ea0caa2 784static void
2985e383 785center(const char *str, size_t len, int separate)
6dbe3af9 786{
d162fcb5 787 char lineout[FMT_ST_CHARS];
e66ba1bf 788
17282538 789 center_str(str, lineout, ARRAY_SIZE(lineout), len);
e66ba1bf
KZ
790 my_putstring(lineout);
791
792 if (separate) {
793 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
794 my_putstring(lineout);
795 }
6dbe3af9
KZ
796}
797
022df321 798static void __attribute__ ((__noreturn__)) usage(FILE * out)
6dbe3af9 799{
201d43fd
SK
800 fputs(USAGE_HEADER, out);
801 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
802 fputs(USAGE_OPTIONS, out);
803 fputs(_(" -1, --one show only current month (default)\n"), out);
804 fputs(_(" -3, --three show previous, current and next month\n"), out);
805 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
806 fputs(_(" -m, --monday Monday as first day of week\n"), out);
807 fputs(_(" -j, --julian output Julian dates\n"), out);
808 fputs(_(" -y, --year show whole current year\n"), out);
809 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
810 fputs(USAGE_SEPARATOR, out);
811 fputs(USAGE_HELP, out);
812 fputs(USAGE_VERSION, out);
813 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
022df321 814 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9 815}