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