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