]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/cal.c
cal: simplify ascii_weeknum() function
[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
7bd3336f 110static void my_setupterm(const char *term, int fildes __attribute__((__unused__)), int *errret)
4aefe5e8
SK
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 137
a814f0bb 138#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
d4be073d 139static const char *term="";
d4be073d 140static int Slen; /* strlen of Senter+Sexit */
a814f0bb
KZ
141#endif
142
143static const char *Senter="", *Sexit="";/* enter and exit standout mode */
d4be073d 144static char *Hrow; /* pointer to highlighted row in month */
d162fcb5 145
e8f26419 146#include "widechar.h"
95f1bdee 147
66ee8158
KZ
148/* allow compile-time define to over-ride default */
149#ifndef NUM_MONTHS
d4be073d 150# define NUM_MONTHS 1
66ee8158
KZ
151#endif
152
153#if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
d4be073d 154# error NUM_MONTHS must be 1 or 3
66ee8158
KZ
155#endif
156
5f845cb7
SK
157enum {
158 SUNDAY = 0,
159 MONDAY,
160 TUESDAY,
161 WEDNESDAY,
162 THURSDAY,
163 FRIDAY,
164 SATURDAY,
d68f076f
SK
165 DAYS_IN_WEEK,
166 NONEDAY
5f845cb7 167};
6dbe3af9 168
5f845cb7 169#define FIRST_WEEKDAY SATURDAY /* Jan 1st, 1 was a Saturday */
5f845cb7
SK
170#define REFORMATION_YEAR 1752 /* Signed-off-by: Lord Chesterfield */
171#define REFORMATION_MONTH 9 /* September */
d162fcb5
KZ
172#define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
173#define NUMBER_MISSING_DAYS 11 /* 11 day correction */
c36c4a4e 174#define YDAY_AFTER_MISSING 258 /* 14th in Sep 1752 */
6dbe3af9 175
5f845cb7
SK
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
f06602a4 179#define MAXDAYS 42 /* slots in a month array */
6dbe3af9
KZ
180#define SPACE -1 /* used in day array */
181
5f845cb7 182#define SMALLEST_YEAR 1
5f845cb7
SK
183
184#define DAY_LEN 3 /* 3 spaces per day */
185#define WEEK_LEN (DAYS_IN_WEEK * DAY_LEN)
186#define HEAD_SEP 2
b283141c 187#define MONTH_COLS 3 /* month columns in year view */
aae4f87e 188#define WNUM_LEN 3
5f845cb7
SK
189
190#define J_DAY_LEN 4 /* 4 spaces per day */
191#define J_WEEK_LEN (DAYS_IN_WEEK * J_DAY_LEN)
192#define J_HEAD_SEP 2
b283141c 193#define J_MONTH_COLS 2
5f845cb7
SK
194
195#define TODAY_FLAG 0x400 /* flag day for highlighting */
196
e44fe471 197#define FMT_ST_LINES 9
5f845cb7
SK
198#define FMT_ST_CHARS 300 /* 90 suffices in most locales */
199struct fmt_st
200{
d4be073d 201 char s[FMT_ST_LINES][FMT_ST_CHARS];
5f845cb7
SK
202};
203
1beb933e 204static const int days_in_month[2][13] = {
6dbe3af9
KZ
205 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
206 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
207};
208
e8c58289
SK
209/* September 1752 is special, and has static assignments for both date
210 * and Julian representations. */
1beb933e 211static const int d_sep1752[MAXDAYS / 2] = {
6dbe3af9
KZ
212 SPACE, SPACE, 1, 2, 14, 15, 16,
213 17, 18, 19, 20, 21, 22, 23,
e8c58289
SK
214 24, 25, 26, 27, 28, 29, 30
215}, j_sep1752[MAXDAYS / 2] = {
6dbe3af9
KZ
216 SPACE, SPACE, 245, 246, 258, 259, 260,
217 261, 262, 263, 264, 265, 266, 267,
e8c58289 218 268, 269, 270, 271, 272, 273, 274
6dbe3af9
KZ
219}, empty[MAXDAYS] = {
220 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
221 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
222 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
223 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
224 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
f06602a4 225 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE
6dbe3af9
KZ
226};
227
c36c4a4e
TK
228enum {
229 WEEK_NUM_DISABLED = 0,
aae4f87e
TK
230 WEEK_NUM_MASK=0xff,
231 WEEK_NUM_ISO=0x100,
232 WEEK_NUM_US=0x200,
c36c4a4e 233};
e8f26419
KZ
234
235/* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
d4be073d 236static char day_headings[J_WEEK_LEN * 6 + 1];
6dbe3af9 237
b549058b
SK
238struct cal_control {
239 const char *full_month[MONTHS_IN_YEAR]; /* month names */
240 int colormode; /* day and week number highlight */
241 int num_months; /* number of months horizontally in print out */
242 int weekstart; /* day the week starts, often Sun or Mon */
243 int weektype; /* WEEK_TYPE_{NONE,ISO,US} */
244 int weeknum; /* requested --week=<number> */
245 unsigned int julian:1, /* julian output */
246 yflag:1; /* print whole year */
247};
6dbe3af9 248
5f845cb7 249/* function prototypes */
e44fe471 250static int leap_year(long year);
b549058b 251static void headers_init(struct cal_control *ctl);
0106c9e2
SK
252static int do_monthly(int day, int month, long year, struct fmt_st *out, int header_hint,
253 const struct cal_control *ctl);
254static void monthly(int day, int month, long year, const struct cal_control *ctl);
255static int two_header_lines(int month, long year, const struct cal_control *ctl);
256static void monthly3(int day, int month, long year, const struct cal_control *ctl);
257static char *append_weeknum(char *p, int *dp, int month, long year, int cal, int row,
258 const struct cal_control *ctl);
259static void yearly(int day, long year, const struct cal_control *ctl);
260static void day_array(int day, int month, long year, int *days, const struct cal_control *ctl);
261static int day_in_year(int day, int month, long year);
ff90b006 262static int day_in_week(int day, int month, long year);
0106c9e2
SK
263static int week_number(int day, int month, long year, const struct cal_control *ctl);
264static int week_to_day(long year, const struct cal_control *ctl);
265static char *ascii_day(char *p, int day, const struct cal_control *ctl);
266static char *ascii_weeknum(char *p, int weeknum, const struct cal_control *ctl);
267static int center_str(const char *src, char *dest, size_t dest_size, size_t width);
268static void center(const char *str, size_t len, int separate);
269static void __attribute__((__noreturn__)) usage(FILE *out);
6dbe3af9 270
d4be073d
KZ
271int main(int argc, char **argv)
272{
6dbe3af9
KZ
273 struct tm *local_time;
274 time_t now;
b549058b 275 int ch, day = 0, month = 0;
e44fe471 276 long year;
b549058b
SK
277 static struct cal_control ctl = {
278 .weekstart = SUNDAY,
279 .num_months = NUM_MONTHS,
280 .colormode = UL_COLORMODE_AUTO,
281 .weektype = WEEK_NUM_DISABLED
282 };
7b353df2
SK
283
284 enum {
285 OPT_COLOR = CHAR_MAX + 1
286 };
eb63b9b8 287
022df321
SK
288 static const struct option longopts[] = {
289 {"one", no_argument, NULL, '1'},
290 {"three", no_argument, NULL, '3'},
291 {"sunday", no_argument, NULL, 's'},
292 {"monday", no_argument, NULL, 'm'},
293 {"julian", no_argument, NULL, 'j'},
294 {"year", no_argument, NULL, 'y'},
aae4f87e 295 {"week", optional_argument, NULL, 'w'},
7b353df2 296 {"color", optional_argument, NULL, OPT_COLOR},
022df321
SK
297 {"version", no_argument, NULL, 'V'},
298 {"help", no_argument, NULL, 'h'},
299 {NULL, 0, NULL, 0}
300 };
301
7eda085c
KZ
302 setlocale(LC_ALL, "");
303 bindtextdomain(PACKAGE, LOCALEDIR);
304 textdomain(PACKAGE);
c05a80ca 305 atexit(close_stdout);
e8f26419 306
310170b8 307#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
d162fcb5
KZ
308 if ((term = getenv("TERM"))) {
309 int ret;
5f845cb7 310 my_setupterm(term, STDOUT_FILENO, &ret);
d162fcb5
KZ
311 if (ret > 0) {
312 Senter = my_tgetstr("so","smso");
313 Sexit = my_tgetstr("se","rmso");
94b26919 314 Slen = strlen(Senter) + strlen(Sexit);
d162fcb5
KZ
315 }
316 }
317#endif
318
612721db 319/*
0a9bead0
PB
320 * The traditional Unix cal utility starts the week at Sunday,
321 * while ISO 8601 starts at Monday. We read the start day from
322 * the locale database, which can be overridden with the
323 * -s (Sunday) or -m (Monday) options.
612721db 324 */
69cabd72 325#if HAVE_DECL__NL_TIME_WEEK_1STDAY
0a9bead0
PB
326 /*
327 * You need to use 2 locale variables to get the first day of the week.
328 * This is needed to support first_weekday=2 and first_workday=1 for
329 * the rare case where working days span across 2 weeks.
330 * This shell script shows the combinations and calculations involved:
33c17354
SK
331 *
332 * for LANG in en_US ru_RU fr_FR csb_PL POSIX; do
333 * printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday)
334 * date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w
335 * done
336 *
337 * en_US: 19971130 + 1 -1 = 0 #0 = sunday
338 * ru_RU: 19971130 + 2 -1 = 1
339 * fr_FR: 19971201 + 1 -1 = 1
340 * csb_PL: 19971201 + 2 -1 = 2
341 * POSIX: 19971201 + 7 -1 = 0
0a9bead0
PB
342 */
343 {
aebb9522
KZ
344 int wfd;
345 union { unsigned int word; char *string; } val;
346 val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY);
347
348 wfd = val.word;
0a9bead0 349 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
b549058b 350 ctl.weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % DAYS_IN_WEEK;
0a9bead0 351 }
e8f26419 352#endif
d162fcb5 353
af7c483e 354 while ((ch = getopt_long(argc, argv, "13mjsywVh", longopts, NULL)) != -1)
6dbe3af9 355 switch(ch) {
d162fcb5 356 case '1':
b549058b 357 ctl.num_months = 1; /* default */
d162fcb5
KZ
358 break;
359 case '3':
b549058b 360 ctl.num_months = 3;
d162fcb5 361 break;
ffc43748 362 case 's':
b549058b 363 ctl.weekstart = SUNDAY; /* default */
ffc43748 364 break;
5c36a0eb 365 case 'm':
b549058b 366 ctl.weekstart = MONDAY;
5c36a0eb 367 break;
6dbe3af9 368 case 'j':
b549058b 369 ctl.julian = 1;
6dbe3af9
KZ
370 break;
371 case 'y':
b549058b 372 ctl.yflag = 1;
6dbe3af9 373 break;
c36c4a4e 374 case 'w':
aae4f87e 375 if (optarg) {
b549058b 376 ctl.weeknum = strtos32_or_err(optarg,
aae4f87e 377 _("invalid week argument"));
b549058b 378 if (ctl.weeknum < 1 || 53 < ctl.weeknum)
aae4f87e
TK
379 errx(EXIT_FAILURE,_("illegal week value: use 1-53"));
380 }
b549058b 381 ctl.weektype = WEEK_NUM_US; /* default per weekstart */
c36c4a4e 382 break;
7b353df2 383 case OPT_COLOR:
201b39f0 384 if (optarg)
b549058b 385 ctl.colormode = colormode_or_err(optarg,
201b39f0 386 _("unsupported color mode"));
7b353df2 387 break;
eb63b9b8 388 case 'V':
e421313d 389 printf(UTIL_LINUX_VERSION);
37d6897f 390 return EXIT_SUCCESS;
022df321
SK
391 case 'h':
392 usage(stdout);
6dbe3af9
KZ
393 case '?':
394 default:
022df321 395 usage(stderr);
6dbe3af9
KZ
396 }
397 argc -= optind;
398 argv += optind;
399
b549058b
SK
400 if (ctl.weektype) {
401 ctl.weektype = ctl.weeknum & WEEK_NUM_MASK;
402 ctl.weektype |= (ctl.weekstart == MONDAY ? WEEK_NUM_ISO : WEEK_NUM_US);
aae4f87e 403 }
c36c4a4e 404
a43145e1
RP
405 time(&now);
406 local_time = localtime(&now);
407
6dbe3af9 408 switch(argc) {
d7a92b89 409 case 3:
db41a429 410 day = strtos32_or_err(*argv++, _("illegal day value"));
5f845cb7
SK
411 if (day < 1 || DAYS_IN_MONTH < day)
412 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH);
d7a92b89 413 /* FALLTHROUGH */
6dbe3af9 414 case 2:
db41a429 415 month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
5f845cb7 416 if (month < 1 || MONTHS_IN_YEAR < month)
022df321 417 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
6dbe3af9
KZ
418 /* FALLTHROUGH */
419 case 1:
e44fe471
SK
420 year = strtol_or_err(*argv++, _("illegal year value"));
421 if (year < SMALLEST_YEAR)
422 errx(EXIT_FAILURE, _("illegal year value: use positive integer"));
d7a92b89
PB
423 if (day) {
424 int dm = days_in_month[leap_year(year)][month];
425 if (day > dm)
022df321 426 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
d7a92b89 427 day = day_in_year(day, month, year);
e44fe471 428 } else if ((long) (local_time->tm_year + 1900) == year) {
a43145e1 429 day = local_time->tm_yday + 1;
d7a92b89 430 }
b549058b
SK
431 if (!month && !ctl.weeknum)
432 ctl.yflag = 1;
6dbe3af9
KZ
433 break;
434 case 0:
d7a92b89 435 day = local_time->tm_yday + 1;
6dbe3af9 436 year = local_time->tm_year + 1900;
d7a92b89 437 month = local_time->tm_mon + 1;
6dbe3af9
KZ
438 break;
439 default:
022df321 440 usage(stderr);
6dbe3af9 441 }
aae4f87e 442
b549058b
SK
443 if (0 < ctl.weeknum) {
444 int yday = week_to_day(year, &ctl);
c1732e62
KZ
445 int leap = leap_year(year);
446
aae4f87e 447 if (yday < 1)
c1732e62
KZ
448 errx(EXIT_FAILURE, _("illegal week value: year %ld "
449 "doesn't have week %d"),
b549058b 450 year, ctl.weeknum);
aae4f87e 451 month = 1;
aae4f87e
TK
452 while (month <= 12 && yday > days_in_month[leap][month])
453 yday -= days_in_month[leap][month++];
454 if (month > 12) {
c1732e62
KZ
455 /* In some years (e.g. 2010 in ISO mode) it's possible
456 * to have a remnant of week 53 starting the year yet
457 * the year in question ends during 52, in this case
458 * we're assuming that early remnant is being referred
459 * to if 53 is given as argument. */
b549058b 460 if (ctl.weeknum == week_number(31, 12, year - 1, &ctl))
aae4f87e 461 month = 1;
c1732e62
KZ
462 else
463 errx(EXIT_FAILURE,
464 _("illegal week value: year %ld "
465 "doesn't have week %d"),
b549058b 466 year, ctl.weeknum);
aae4f87e 467 }
aae4f87e
TK
468 }
469
b549058b 470 headers_init(&ctl);
6dbe3af9 471
b549058b 472 if (!colors_init(ctl.colormode)) {
7b353df2 473 day = 0;
b549058b 474 ctl.weektype &= ~WEEK_NUM_MASK;
aae4f87e 475 }
d7a92b89 476
b549058b
SK
477 if (ctl.yflag)
478 yearly(day, year, &ctl);
479 else if (ctl.num_months == 1)
480 monthly(day, month, year, &ctl);
481 else if (ctl.num_months == 3)
482 monthly3(day, month, year, &ctl);
37d6897f
KZ
483
484 return EXIT_SUCCESS;
6dbe3af9
KZ
485}
486
3b66dfd6 487/* leap year -- account for gregorian reformation in 1752 */
e44fe471 488static int leap_year(long year)
3b66dfd6 489{
5f845cb7 490 if (year <= REFORMATION_YEAR)
3b66dfd6
SK
491 return !(year % 4);
492 else
493 return ( !(year % 4) && (year % 100) ) || !(year % 400);
494}
495
b549058b 496static void headers_init(struct cal_control *ctl)
6dbe3af9 497{
b549058b 498 size_t i, wd, spaces = ctl->julian ? J_DAY_LEN - 1 : DAY_LEN - 1;
b283141c 499 char *cur_dh = day_headings;
33c17354 500
5f845cb7 501 for (i = 0; i < DAYS_IN_WEEK; i++) {
add1924d 502 size_t space_left;
b549058b 503 wd = (i + ctl->weekstart) % DAYS_IN_WEEK;
33c17354
SK
504
505 if (i)
506 strcat(cur_dh++, " ");
add1924d
KZ
507 space_left = sizeof(day_headings) - (cur_dh - day_headings);
508
b283141c 509 if (space_left <= spaces)
33c17354 510 break;
add1924d
KZ
511 cur_dh += center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
512 space_left, spaces);
33c17354
SK
513 }
514
5f845cb7 515 for (i = 0; i < MONTHS_IN_YEAR; i++)
b549058b 516 ctl->full_month[i] = nl_langinfo(MON_1 + i);
6dbe3af9
KZ
517}
518
b549058b
SK
519static int do_monthly(int day, int month, long year, struct fmt_st *out,
520 int header_hint, const struct cal_control *ctl)
d4be073d 521{
ff87defc 522 int col, row, days[MAXDAYS];
d162fcb5 523 char *p, lineout[FMT_ST_CHARS];
b549058b
SK
524 size_t width = (ctl->julian ? J_WEEK_LEN : WEEK_LEN) - 1
525 + (ctl->weektype ? WNUM_LEN : 0);
e44fe471 526 int pos = 0;
d162fcb5 527
b549058b 528 day_array(day, month, year, days, ctl);
d162fcb5 529
e44fe471 530 if (header_hint < 0)
b549058b 531 header_hint = two_header_lines(month, year, ctl);
e44fe471 532 if (header_hint) {
b549058b 533 snprintf(lineout, sizeof(lineout), _("%s"), ctl->full_month[month - 1]);
e44fe471
SK
534 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
535 pos++;
536dd325 536 snprintf(lineout, sizeof(lineout), _("%ld"), year);
e44fe471
SK
537 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
538 pos++;
539 } else {
119ed9bf
BS
540 /* TRANSLATORS: %s is the month name, %ld the year number.
541 * You can change the order and/or add something here;
2ec8187e
KZ
542 * e.g. for Basque the translation should be "%2$ldko %1$s".
543 */
536dd325 544 snprintf(lineout, sizeof(lineout), _("%s %ld"),
b549058b 545 ctl->full_month[month - 1], year);
e44fe471
SK
546 center_str(lineout, out->s[pos], ARRAY_SIZE(out->s[pos]), width);
547 pos++;
548 }
d162fcb5 549
c1732e62 550 snprintf(out->s[pos++], FMT_ST_CHARS, "%s%s",
b549058b 551 (ctl->weektype ? " " : ""),
c1732e62
KZ
552 day_headings);
553
5f845cb7 554 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
94b26919 555 int has_hl = 0;
c36c4a4e 556 p = lineout;
b549058b 557 if (ctl->weektype)
c36c4a4e
TK
558 for (col = 0; col < DAYS_IN_WEEK; col++) {
559 int xd = days[row * DAYS_IN_WEEK + col];
560 if (xd != SPACE) {
b549058b
SK
561 int wn = week_number(xd & ~TODAY_FLAG,
562 month, year, ctl);
76c4bbcc 563 p = ascii_weeknum(p, wn, ctl);
c36c4a4e
TK
564 break;
565 } else if (col+1 == DAYS_IN_WEEK)
566 p += sprintf(p," ");
567 }
568 for (col = 0; col < DAYS_IN_WEEK; col++) {
5f845cb7 569 int xd = days[row * DAYS_IN_WEEK + col];
94b26919
KZ
570 if (xd != SPACE && (xd & TODAY_FLAG))
571 has_hl = 1;
b549058b 572 p = ascii_day(p, xd, ctl);
94b26919 573 }
6dbe3af9 574 *p = '\0';
e44fe471 575 snprintf(out->s[row+pos], FMT_ST_CHARS, "%s", lineout);
94b26919 576 if (has_hl)
e44fe471 577 Hrow = out->s[row+pos];
66ee8158 578 }
e44fe471
SK
579 pos += row;
580 return pos;
66ee8158
KZ
581}
582
b549058b 583static void monthly(int day, int month, long year, const struct cal_control *ctl)
d4be073d 584{
e44fe471 585 int i, rows;
66ee8158
KZ
586 struct fmt_st out;
587
b549058b 588 rows = do_monthly(day, month, year, &out, -1, ctl);
e44fe471 589 for (i = 0; i < rows; i++) {
f4061938 590 my_putstring(out.s[i]);
e66ba1bf 591 my_putstring("\n");
66ee8158
KZ
592 }
593}
594
b549058b 595static int two_header_lines(int month, long year, const struct cal_control *ctl)
e44fe471
SK
596{
597 char lineout[FMT_ST_CHARS];
b549058b 598 size_t width = (ctl->julian ? J_WEEK_LEN : WEEK_LEN) - 1;
e44fe471 599 size_t len;
536dd325 600 snprintf(lineout, sizeof(lineout), "%ld", year);
e44fe471 601 len = strlen(lineout);
b549058b 602 len += strlen(ctl->full_month[month - 1]) + 1;
e44fe471
SK
603 if (width < len)
604 return 1;
605 return 0;
606}
607
b549058b 608static void monthly3(int day, int month, long year, const struct cal_control *ctl)
d4be073d 609{
d162fcb5 610 char lineout[FMT_ST_CHARS];
66ee8158 611 int i;
e44fe471 612 int width, rows, two_lines;
66ee8158
KZ
613 struct fmt_st out_prev;
614 struct fmt_st out_curm;
615 struct fmt_st out_next;
e44fe471
SK
616 int prev_month, next_month;
617 long prev_year, next_year;
66ee8158 618
e44fe471
SK
619 memset(&out_prev, 0, sizeof(struct fmt_st));
620 memset(&out_curm, 0, sizeof(struct fmt_st));
621 memset(&out_next, 0, sizeof(struct fmt_st));
d162fcb5 622 if (month == 1) {
5f845cb7 623 prev_month = MONTHS_IN_YEAR;
d162fcb5
KZ
624 prev_year = year - 1;
625 } else {
626 prev_month = month - 1;
627 prev_year = year;
66ee8158 628 }
5f845cb7 629 if (month == MONTHS_IN_YEAR) {
d162fcb5
KZ
630 next_month = 1;
631 next_year = year + 1;
632 } else {
633 next_month = month + 1;
634 next_year = year;
66ee8158 635 }
b549058b
SK
636 two_lines = two_header_lines(prev_month, prev_year, ctl);
637 two_lines += two_header_lines(month, year, ctl);
638 two_lines += two_header_lines(next_month, next_year, ctl);
e44fe471
SK
639 if (0 < two_lines)
640 rows = FMT_ST_LINES;
641 else
642 rows = FMT_ST_LINES - 1;
b549058b
SK
643 do_monthly(day, prev_month, prev_year, &out_prev, two_lines, ctl);
644 do_monthly(day, month, year, &out_curm, two_lines, ctl);
645 do_monthly(day, next_month, next_year, &out_next, two_lines, ctl);
94b26919 646
b549058b 647 width = (ctl->julian ? J_WEEK_LEN : WEEK_LEN) -1;
e44fe471 648 for (i = 0; i < (two_lines ? 3 : 2); i++) {
e66ba1bf
KZ
649 snprintf(lineout, sizeof(lineout),
650 "%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
651 my_putstring(lineout);
652 }
e44fe471 653 for (i = two_lines ? 3 : 2; i < rows; i++) {
94b26919
KZ
654 int w1, w2, w3;
655 w1 = w2 = w3 = width;
656
310170b8 657#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
33c17354
SK
658 /* adjust width to allow for non printable characters */
659 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
660 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
661 w3 += (out_next.s[i] == Hrow ? Slen : 0);
94b26919 662#endif
22468708 663 snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n",
94b26919
KZ
664 w1, out_prev.s[i],
665 w2, out_curm.s[i],
666 w3, out_next.s[i]);
667
d162fcb5 668 my_putstring(lineout);
6dbe3af9
KZ
669 }
670}
671
b549058b 672static char *append_weeknum(char *p, int *dp,
c1732e62 673 int month, long year, int cal,
b549058b 674 int row, const struct cal_control *ctl)
c1732e62
KZ
675{
676 int col;
677
678 for (col = 0; col < DAYS_IN_WEEK; col++) {
679 int xd = dp[row * DAYS_IN_WEEK + col];
680
681 if (xd != SPACE) {
b549058b
SK
682 int weeknum = week_number(xd & ~TODAY_FLAG,
683 month + cal + 1, year, ctl);
76c4bbcc 684 p = ascii_weeknum(p, weeknum, ctl);
c1732e62
KZ
685 break;
686 } else if (col+1 == DAYS_IN_WEEK)
687 p += sprintf(p," ");
688 }
689 return p;
690}
691
b549058b 692static void yearly(int day, long year, const struct cal_control *ctl)
d4be073d 693{
c1732e62 694 int col, i, month, row, which_cal;
b283141c 695 int maxrow, sep_len, week_len;
5f845cb7 696 int days[MONTHS_IN_YEAR][MAXDAYS];
add1924d
KZ
697 char *p;
698 /* three weeks + separators + \0 */
b549058b
SK
699 int weeknumlen = (ctl->weektype ? WNUM_LEN : 0);
700 char lineout[ weeknumlen + sizeof(day_headings) + 2 +
701 weeknumlen + sizeof(day_headings) + 2 +
702 weeknumlen + sizeof(day_headings) + 1 ];
703 if (ctl->julian) {
b283141c
SK
704 maxrow = J_MONTH_COLS;
705 sep_len = J_HEAD_SEP;
b549058b 706 week_len = J_WEEK_LEN + weeknumlen;
b283141c
SK
707 } else {
708 maxrow = MONTH_COLS;
709 sep_len = HEAD_SEP;
b549058b 710 week_len = WEEK_LEN + weeknumlen;
b283141c 711 }
536dd325 712 snprintf(lineout, sizeof(lineout), "%ld", year);
c1732e62
KZ
713
714 /* 2013-04-28: The -1 near sep_len makes year header to be aligned
715 * exactly how it has been aligned for long time, but it is
716 * unexplainable. */
b283141c 717 center(lineout, (week_len + sep_len) * maxrow - sep_len - 1, 0);
e66ba1bf 718 my_putstring("\n\n");
d162fcb5 719
5f845cb7 720 for (i = 0; i < MONTHS_IN_YEAR; i++)
b549058b 721 day_array(day, i + 1, year, days[i], ctl);
d4be073d 722
b283141c 723 for (month = 0; month < MONTHS_IN_YEAR; month += maxrow) {
b549058b
SK
724 center(ctl->full_month[month], week_len - 1, sep_len + 1);
725 if (ctl->julian) {
726 center(ctl->full_month[month + 1], week_len - 1, 0);
b283141c 727 } else {
b549058b
SK
728 center(ctl->full_month[month + 1], week_len - 1, sep_len + 1);
729 center(ctl->full_month[month + 2], week_len - 1, 0);
b283141c 730 }
b549058b 731 if (ctl->julian)
b283141c 732 snprintf(lineout, sizeof(lineout),
c36c4a4e 733 "\n%*s%s%*s %*s%s\n",
b549058b
SK
734 weeknumlen,"", day_headings, sep_len, "",
735 weeknumlen,"", day_headings);
b283141c
SK
736 else
737 snprintf(lineout, sizeof(lineout),
c36c4a4e 738 "\n%*s%s%*s %*s%s%*s %*s%s\n",
b549058b
SK
739 weeknumlen,"", day_headings, sep_len, "",
740 weeknumlen,"", day_headings, sep_len, "",
741 weeknumlen,"", day_headings);
add1924d 742
e66ba1bf 743 my_putstring(lineout);
5f845cb7 744 for (row = 0; row < DAYS_IN_WEEK - 1; row++) {
d162fcb5 745 p = lineout;
b283141c 746 for (which_cal = 0; which_cal < maxrow; which_cal++) {
c1732e62
KZ
747 int *dp = &days[month + which_cal][row * DAYS_IN_WEEK];
748
b549058b
SK
749 if (ctl->weektype)
750 p = append_weeknum(p, days[month + which_cal],
c1732e62 751 month, year, which_cal,
b549058b 752 row, ctl);
c1732e62 753
5f845cb7 754 for (col = 0; col < DAYS_IN_WEEK; col++)
b549058b 755 p = ascii_day(p, *dp++, ctl);
d162fcb5 756 p += sprintf(p, " ");
6dbe3af9
KZ
757 }
758 *p = '\0';
f4061938 759 my_putstring(lineout);
e66ba1bf 760 my_putstring("\n");
6dbe3af9
KZ
761 }
762 }
e66ba1bf 763 my_putstring("\n");
6dbe3af9
KZ
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 */
b549058b 773static void day_array(int day, int month, long year, int *days, const struct cal_control *ctl)
d4be073d 774{
d162fcb5 775 int julday, daynum, dw, dm;
1beb933e 776 const int *sep1752;
e8c58289
SK
777
778 memcpy(days, empty, MAXDAYS * sizeof(int));
779 if (year == REFORMATION_YEAR && month == REFORMATION_MONTH) {
b549058b
SK
780 sep1752 = ctl->julian ? j_sep1752 : d_sep1752;
781 memcpy(days, sep1752 + ctl->weekstart,
782 ((MAXDAYS / 2) - ctl->weekstart) * sizeof(int));
e8c58289
SK
783 for (dm = 0; dm < MAXDAYS / 2; dm++)
784 if (j_sep1752[dm] == day)
d7a92b89 785 days[dm] |= TODAY_FLAG;
6dbe3af9
KZ
786 return;
787 }
6dbe3af9 788 dm = days_in_month[leap_year(year)][month];
b549058b 789 dw = (day_in_week(1, month, year) - ctl->weekstart + DAYS_IN_WEEK) % DAYS_IN_WEEK;
d162fcb5 790 julday = day_in_year(1, month, year);
b549058b 791 daynum = ctl->julian ? julday : 1;
c1732e62 792
d162fcb5
KZ
793 while (dm--) {
794 days[dw] = daynum++;
795 if (julday++ == day)
796 days[dw] |= TODAY_FLAG;
797 dw++;
798 }
6dbe3af9
KZ
799}
800
801/*
802 * day_in_year --
803 * return the 1 based day number within the year
804 */
d4be073d
KZ
805static int day_in_year(int day, int month, long year)
806{
6dbe3af9
KZ
807 int i, leap;
808
809 leap = leap_year(year);
810 for (i = 1; i < month; i++)
811 day += days_in_month[leap][i];
d162fcb5 812 return day;
6dbe3af9
KZ
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
d68f076f
SK
819 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
820 * during the period of 11 days.
6dbe3af9 821 */
ff90b006 822static int day_in_week(int day, int month, long year)
91ac9ef5
SK
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 };
ff90b006
SK
832 if (year != 1753)
833 year -= month < 3;
91ac9ef5 834 else
ff90b006
SK
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;
91ac9ef5 845 return NONEDAY;
6dbe3af9
KZ
846}
847
c36c4a4e
TK
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 */
b549058b 855static int week_number(int day, int month, long year, const struct cal_control *ctl)
c1732e62
KZ
856{
857 int fday = 0, yday;
858 int wday = day_in_week(1, 1, year);
859
b549058b 860 if (ctl->weektype & WEEK_NUM_ISO)
c1732e62
KZ
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
c36c4a4e 875 yday = day_in_year(day,month,year);
c1732e62 876 if (year == REFORMATION_YEAR) {
c36c4a4e
TK
877 if (yday >= YDAY_AFTER_MISSING)
878 fday -= NUMBER_MISSING_DAYS;
c36c4a4e 879 }
c1732e62
KZ
880
881 /* Last year is last year */
882 if (yday + fday < 7)
b549058b 883 return week_number(31, 12, year - 1, ctl);
c1732e62
KZ
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. */
b549058b 888 if (ctl->weektype == WEEK_NUM_ISO && yday >= 363
c1732e62
KZ
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)
b549058b 893 return week_number(1, 1, year + 1, ctl);
c1732e62
KZ
894
895 return (yday + fday) / 7;
c36c4a4e
TK
896}
897
aae4f87e
TK
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 */
b549058b 905static int week_to_day(long year, const struct cal_control *ctl)
c1732e62 906{
aae4f87e 907 int yday, wday;
c1732e62
KZ
908
909 wday = day_in_week(1, 1, year);
b549058b 910 yday = ctl->weeknum * 7 - wday;
c1732e62 911
b549058b 912 if (ctl->weektype & WEEK_NUM_ISO)
c1732e62
KZ
913 yday -= (wday >= FRIDAY ? -2 : 5);
914 else
915 yday -= (wday == SUNDAY ? 6 : -1); /* WEEK_NUM_US */
916 if (yday <= 0)
aae4f87e
TK
917 return 1;
918
919 return yday;
920}
921
b549058b 922static char *ascii_day(char *p, int day, const struct cal_control *ctl)
d4be073d 923{
6dbe3af9 924 int display, val;
d162fcb5 925 int highlight = 0;
6dbe3af9
KZ
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) {
b549058b 936 int len = ctl->julian ? J_DAY_LEN : DAY_LEN;
d162fcb5
KZ
937 memset(p, ' ', len);
938 return p+len;
939 }
940 if (day & TODAY_FLAG) {
941 day &= ~TODAY_FLAG;
8c6c72bf 942 p += sprintf(p, "%s", Senter);
d162fcb5 943 highlight = 1;
6dbe3af9 944 }
b549058b 945 if (ctl->julian) {
726f69e2 946 if ((val = day / 100)) {
6dbe3af9
KZ
947 day %= 100;
948 *p++ = val + '0';
949 display = 1;
950 } else {
951 *p++ = ' ';
952 display = 0;
953 }
954 val = day / 10;
955 if (val || display)
956 *p++ = val + '0';
957 else
958 *p++ = ' ';
959 *p++ = day % 10 + '0';
960 } else {
961 *p++ = aday[day][0];
962 *p++ = aday[day][1];
963 }
d162fcb5 964 if (highlight)
8c6c72bf 965 p += sprintf(p, "%s", Sexit);
d162fcb5
KZ
966 *p++ = ' ';
967 return p;
6dbe3af9
KZ
968}
969
76c4bbcc 970static char *ascii_weeknum(char *p, int weeknum, const struct cal_control *ctl)
aae4f87e 971{
b549058b 972 if ((ctl->weektype & WEEK_NUM_MASK) == weeknum)
44df9bd4 973 p += sprintf(p, "%s%2d%s ", Senter, weeknum, Sexit);
aae4f87e 974 else
44df9bd4 975 p += sprintf(p, "%2d ", weeknum);
aae4f87e
TK
976 return p;
977}
978
d162fcb5
KZ
979/*
980 * Center string, handling multibyte characters appropriately.
981 * In addition if the string is too large for the width it's truncated.
ff87defc 982 * The number of trailing spaces may be 1 less than the number of leading spaces.
d162fcb5 983 */
d4be073d
KZ
984static int center_str(const char* src, char* dest,
985 size_t dest_size, size_t width)
d162fcb5 986{
104b92f8
PB
987 return mbsalign(src, dest, dest_size, &width,
988 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
d162fcb5
KZ
989}
990
d4be073d 991static void center(const char *str, size_t len, int separate)
6dbe3af9 992{
d162fcb5 993 char lineout[FMT_ST_CHARS];
e66ba1bf 994
17282538 995 center_str(str, lineout, ARRAY_SIZE(lineout), len);
e66ba1bf
KZ
996 my_putstring(lineout);
997
998 if (separate) {
999 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
1000 my_putstring(lineout);
1001 }
6dbe3af9
KZ
1002}
1003
022df321 1004static void __attribute__ ((__noreturn__)) usage(FILE * out)
6dbe3af9 1005{
201d43fd
SK
1006 fputs(USAGE_HEADER, out);
1007 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
d4be073d 1008
233ad1fa
PB
1009 fputs(USAGE_SEPARATOR, out);
1010 fputs(_("Display a calendar, or some part of it.\n"), out);
1011 fputs(_("Without any arguments, display the current month.\n"), out);
1012
201d43fd 1013 fputs(USAGE_OPTIONS, out);
233ad1fa
PB
1014 fputs(_(" -1, --one show only a single month (default)\n"), out);
1015 fputs(_(" -3, --three show three months spanning the date\n"), out);
201d43fd
SK
1016 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
1017 fputs(_(" -m, --monday Monday as first day of week\n"), out);
1018 fputs(_(" -j, --julian output Julian dates\n"), out);
233ad1fa 1019 fputs(_(" -y, --year show the whole year\n"), out);
af7c483e 1020 fputs(_(" -w, --week[=<num>] show US or ISO-8601 week numbers\n"), out);
201d43fd 1021 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
d4be073d 1022
201d43fd
SK
1023 fputs(USAGE_SEPARATOR, out);
1024 fputs(USAGE_HELP, out);
1025 fputs(USAGE_VERSION, out);
1026 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
d4be073d 1027
022df321 1028 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9 1029}