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