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