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